1
0
mirror of https://github.com/BookStackApp/BookStack.git synced 2025-01-31 20:21:36 +01:00

Apply fixes from StyleCI

This commit is contained in:
Dan Brown 2021-06-26 15:23:15 +00:00 committed by StyleCI Bot
parent 3a402f6adc
commit 934a833818
349 changed files with 3655 additions and 2625 deletions

View File

@ -20,7 +20,6 @@ use Illuminate\Support\Str;
*/ */
class Activity extends Model class Activity extends Model
{ {
/** /**
* Get the entity for this activity. * Get the entity for this activity.
*/ */
@ -29,6 +28,7 @@ class Activity extends Model
if ($this->entity_type === '') { if ($this->entity_type === '') {
$this->entity_type = null; $this->entity_type = null;
} }
return $this->morphTo('entity'); return $this->morphTo('entity');
} }
@ -54,7 +54,7 @@ class Activity extends Model
public function isForEntity(): bool public function isForEntity(): bool
{ {
return Str::startsWith($this->type, [ return Str::startsWith($this->type, [
'page_', 'chapter_', 'book_', 'bookshelf_' 'page_', 'chapter_', 'book_', 'bookshelf_',
]); ]);
} }

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Actions; <?php
namespace BookStack\Actions;
use BookStack\Auth\Permissions\PermissionService; use BookStack\Auth\Permissions\PermissionService;
use BookStack\Auth\User; use BookStack\Auth\User;
@ -33,6 +35,7 @@ class ActivityService
/** /**
* Add a generic activity event to the database. * Add a generic activity event to the database.
*
* @param string|Loggable $detail * @param string|Loggable $detail
*/ */
public function add(string $type, $detail = '') public function add(string $type, $detail = '')
@ -98,10 +101,10 @@ class ActivityService
$queryIds = [$entity->getMorphClass() => [$entity->id]]; $queryIds = [$entity->getMorphClass() => [$entity->id]];
if ($entity->isA('book')) { if ($entity->isA('book')) {
$queryIds[(new Chapter)->getMorphClass()] = $entity->chapters()->visible()->pluck('id'); $queryIds[(new Chapter())->getMorphClass()] = $entity->chapters()->visible()->pluck('id');
} }
if ($entity->isA('book') || $entity->isA('chapter')) { if ($entity->isA('book') || $entity->isA('chapter')) {
$queryIds[(new Page)->getMorphClass()] = $entity->pages()->visible()->pluck('id'); $queryIds[(new Page())->getMorphClass()] = $entity->pages()->visible()->pluck('id');
} }
$query = $this->activity->newQuery(); $query = $this->activity->newQuery();
@ -143,7 +146,9 @@ class ActivityService
/** /**
* Filters out similar activity. * Filters out similar activity.
*
* @param Activity[] $activities * @param Activity[] $activities
*
* @return array * @return array
*/ */
protected function filterSimilar(iterable $activities): array protected function filterSimilar(iterable $activities): array
@ -185,7 +190,7 @@ class ActivityService
return; return;
} }
$message = str_replace("%u", $username, $message); $message = str_replace('%u', $username, $message);
$channel = config('logging.failed_login.channel'); $channel = config('logging.failed_login.channel');
Log::channel($channel)->warning($message); Log::channel($channel)->warning($message);
} }

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Actions; <?php
namespace BookStack\Actions;
class ActivityType class ActivityType
{ {

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Actions; <?php
namespace BookStack\Actions;
use BookStack\Model; use BookStack\Model;
use BookStack\Traits\HasCreatorAndUpdater; use BookStack\Traits\HasCreatorAndUpdater;
@ -18,7 +20,7 @@ class Comment extends Model
protected $appends = ['created', 'updated']; protected $appends = ['created', 'updated'];
/** /**
* Get the entity that this comment belongs to * Get the entity that this comment belongs to.
*/ */
public function entity(): MorphTo public function entity(): MorphTo
{ {
@ -35,6 +37,7 @@ class Comment extends Model
/** /**
* Get created date as a relative diff. * Get created date as a relative diff.
*
* @return mixed * @return mixed
*/ */
public function getCreatedAttribute() public function getCreatedAttribute()
@ -44,6 +47,7 @@ class Comment extends Model
/** /**
* Get updated date as a relative diff. * Get updated date as a relative diff.
*
* @return mixed * @return mixed
*/ */
public function getUpdatedAttribute() public function getUpdatedAttribute()

View File

@ -1,21 +1,21 @@
<?php namespace BookStack\Actions; <?php
namespace BookStack\Actions;
use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\Entity;
use League\CommonMark\CommonMarkConverter;
use BookStack\Facades\Activity as ActivityService; use BookStack\Facades\Activity as ActivityService;
use League\CommonMark\CommonMarkConverter;
/** /**
* Class CommentRepo * Class CommentRepo.
*/ */
class CommentRepo class CommentRepo
{ {
/** /**
* @var Comment $comment * @var Comment
*/ */
protected $comment; protected $comment;
public function __construct(Comment $comment) public function __construct(Comment $comment)
{ {
$this->comment = $comment; $this->comment = $comment;
@ -46,6 +46,7 @@ class CommentRepo
$entity->comments()->save($comment); $entity->comments()->save($comment);
ActivityService::addForEntity($entity, ActivityType::COMMENTED_ON); ActivityService::addForEntity($entity, ActivityType::COMMENTED_ON);
return $comment; return $comment;
} }
@ -58,6 +59,7 @@ class CommentRepo
$comment->text = $text; $comment->text = $text;
$comment->html = $this->commentToHtml($text); $comment->html = $this->commentToHtml($text);
$comment->save(); $comment->save();
return $comment; return $comment;
} }
@ -89,6 +91,7 @@ class CommentRepo
protected function getNextLocalId(Entity $entity): int protected function getNextLocalId(Entity $entity): int
{ {
$comments = $entity->comments(false)->orderBy('local_id', 'desc')->first(); $comments = $entity->comments(false)->orderBy('local_id', 'desc')->first();
return ($comments->local_id ?? 0) + 1; return ($comments->local_id ?? 0) + 1;
} }
} }

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Actions; <?php
namespace BookStack\Actions;
use BookStack\Model; use BookStack\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo; use Illuminate\Database\Eloquent\Relations\MorphTo;

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Actions; <?php
namespace BookStack\Actions;
use BookStack\Model; use BookStack\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo; use Illuminate\Database\Eloquent\Relations\MorphTo;
@ -9,7 +11,7 @@ class Tag extends Model
protected $hidden = ['id', 'entity_id', 'entity_type', 'created_at', 'updated_at']; protected $hidden = ['id', 'entity_id', 'entity_type', 'created_at', 'updated_at'];
/** /**
* Get the entity that this tag belongs to * Get the entity that this tag belongs to.
*/ */
public function entity(): MorphTo public function entity(): MorphTo
{ {

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Actions; <?php
namespace BookStack\Actions;
use BookStack\Auth\Permissions\PermissionService; use BookStack\Auth\Permissions\PermissionService;
use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\Entity;
@ -7,7 +9,6 @@ use Illuminate\Support\Collection;
class TagRepo class TagRepo
{ {
protected $tag; protected $tag;
protected $permissionService; protected $permissionService;
@ -37,6 +38,7 @@ class TagRepo
} }
$query = $this->permissionService->filterRestrictedEntityRelations($query, 'tags', 'entity_id', 'entity_type'); $query = $this->permissionService->filterRestrictedEntityRelations($query, 'tags', 'entity_id', 'entity_type');
return $query->get(['name'])->pluck('name'); return $query->get(['name'])->pluck('name');
} }
@ -62,11 +64,12 @@ class TagRepo
} }
$query = $this->permissionService->filterRestrictedEntityRelations($query, 'tags', 'entity_id', 'entity_type'); $query = $this->permissionService->filterRestrictedEntityRelations($query, 'tags', 'entity_id', 'entity_type');
return $query->get(['value'])->pluck('value'); return $query->get(['value'])->pluck('value');
} }
/** /**
* Save an array of tags to an entity * Save an array of tags to an entity.
*/ */
public function saveTagsToEntity(Entity $entity, array $tags = []): iterable public function saveTagsToEntity(Entity $entity, array $tags = []): iterable
{ {
@ -89,6 +92,7 @@ class TagRepo
{ {
$name = trim($input['name']); $name = trim($input['name']);
$value = isset($input['value']) ? trim($input['value']) : ''; $value = isset($input['value']) ? trim($input['value']) : '';
return $this->tag->newInstance(['name' => $name, 'value' => $value]); return $this->tag->newInstance(['name' => $name, 'value' => $value]);
} }
} }

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Actions; <?php
namespace BookStack\Actions;
use BookStack\Interfaces\Viewable; use BookStack\Interfaces\Viewable;
use BookStack\Model; use BookStack\Model;
@ -16,7 +18,6 @@ use Illuminate\Database\Eloquent\Relations\MorphTo;
*/ */
class View extends Model class View extends Model
{ {
protected $fillable = ['user_id', 'views']; protected $fillable = ['user_id', 'views'];
/** /**

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Api; <?php
namespace BookStack\Api;
use BookStack\Http\Controllers\Api\ApiController; use BookStack\Http\Controllers\Api\ApiController;
use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Contracts\Container\BindingResolutionException;
@ -12,7 +14,6 @@ use ReflectionMethod;
class ApiDocsGenerator class ApiDocsGenerator
{ {
protected $reflectionClasses = []; protected $reflectionClasses = [];
protected $controllerClasses = []; protected $controllerClasses = [];
@ -30,6 +31,7 @@ class ApiDocsGenerator
$docs = (new static())->generate(); $docs = (new static())->generate();
Cache::put($cacheKey, $docs, 60 * 24); Cache::put($cacheKey, $docs, 60 * 24);
} }
return $docs; return $docs;
} }
@ -42,6 +44,7 @@ class ApiDocsGenerator
$apiRoutes = $this->loadDetailsFromControllers($apiRoutes); $apiRoutes = $this->loadDetailsFromControllers($apiRoutes);
$apiRoutes = $this->loadDetailsFromFiles($apiRoutes); $apiRoutes = $this->loadDetailsFromFiles($apiRoutes);
$apiRoutes = $apiRoutes->groupBy('base_model'); $apiRoutes = $apiRoutes->groupBy('base_model');
return $apiRoutes; return $apiRoutes;
} }
@ -57,6 +60,7 @@ class ApiDocsGenerator
$exampleContent = file_exists($exampleFile) ? file_get_contents($exampleFile) : null; $exampleContent = file_exists($exampleFile) ? file_get_contents($exampleFile) : null;
$route["example_{$exampleType}"] = $exampleContent; $route["example_{$exampleType}"] = $exampleContent;
} }
return $route; return $route;
}); });
} }
@ -71,12 +75,14 @@ class ApiDocsGenerator
$comment = $method->getDocComment(); $comment = $method->getDocComment();
$route['description'] = $comment ? $this->parseDescriptionFromMethodComment($comment) : null; $route['description'] = $comment ? $this->parseDescriptionFromMethodComment($comment) : null;
$route['body_params'] = $this->getBodyParamsFromClass($route['controller'], $route['controller_method']); $route['body_params'] = $this->getBodyParamsFromClass($route['controller'], $route['controller_method']);
return $route; return $route;
}); });
} }
/** /**
* Load body params and their rules by inspecting the given class and method name. * Load body params and their rules by inspecting the given class and method name.
*
* @throws BindingResolutionException * @throws BindingResolutionException
*/ */
protected function getBodyParamsFromClass(string $className, string $methodName): ?array protected function getBodyParamsFromClass(string $className, string $methodName): ?array
@ -92,6 +98,7 @@ class ApiDocsGenerator
foreach ($rules as $param => $ruleString) { foreach ($rules as $param => $ruleString) {
$rules[$param] = explode('|', $ruleString); $rules[$param] = explode('|', $ruleString);
} }
return count($rules) > 0 ? $rules : null; return count($rules) > 0 ? $rules : null;
} }
@ -102,11 +109,13 @@ class ApiDocsGenerator
{ {
$matches = []; $matches = [];
preg_match_all('/^\s*?\*\s((?![@\s]).*?)$/m', $comment, $matches); preg_match_all('/^\s*?\*\s((?![@\s]).*?)$/m', $comment, $matches);
return implode(' ', $matches[1] ?? []); return implode(' ', $matches[1] ?? []);
} }
/** /**
* Get a reflection method from the given class name and method name. * Get a reflection method from the given class name and method name.
*
* @throws ReflectionException * @throws ReflectionException
*/ */
protected function getReflectionMethod(string $className, string $methodName): ReflectionMethod protected function getReflectionMethod(string $className, string $methodName): ReflectionMethod
@ -131,6 +140,7 @@ class ApiDocsGenerator
[$controller, $controllerMethod] = explode('@', $route->action['uses']); [$controller, $controllerMethod] = explode('@', $route->action['uses']);
$baseModelName = explode('.', explode('/', $route->uri)[1])[0]; $baseModelName = explode('.', explode('/', $route->uri)[1])[0];
$shortName = $baseModelName . '-' . $controllerMethod; $shortName = $baseModelName . '-' . $controllerMethod;
return [ return [
'name' => $shortName, 'name' => $shortName,
'uri' => $route->uri, 'uri' => $route->uri,

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Api; <?php
namespace BookStack\Api;
use BookStack\Auth\User; use BookStack\Auth\User;
use BookStack\Interfaces\Loggable; use BookStack\Interfaces\Loggable;
@ -7,7 +9,8 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
/** /**
* Class ApiToken * Class ApiToken.
*
* @property int $id * @property int $id
* @property string $token_id * @property string $token_id
* @property string $secret * @property string $secret
@ -19,7 +22,7 @@ class ApiToken extends Model implements Loggable
{ {
protected $fillable = ['name', 'expires_at']; protected $fillable = ['name', 'expires_at'];
protected $casts = [ protected $casts = [
'expires_at' => 'date:Y-m-d' 'expires_at' => 'date:Y-m-d',
]; ];
/** /**

View File

@ -12,7 +12,6 @@ use Symfony\Component\HttpFoundation\Request;
class ApiTokenGuard implements Guard class ApiTokenGuard implements Guard
{ {
use GuardHelpers; use GuardHelpers;
/** /**
@ -20,9 +19,9 @@ class ApiTokenGuard implements Guard
*/ */
protected $request; protected $request;
/** /**
* The last auth exception thrown in this request. * The last auth exception thrown in this request.
*
* @var ApiAuthException * @var ApiAuthException
*/ */
protected $lastAuthException; protected $lastAuthException;
@ -47,6 +46,7 @@ class ApiTokenGuard implements Guard
} }
$user = null; $user = null;
try { try {
$user = $this->getAuthorisedUserFromRequest(); $user = $this->getAuthorisedUserFromRequest();
} catch (ApiAuthException $exception) { } catch (ApiAuthException $exception) {
@ -54,15 +54,16 @@ class ApiTokenGuard implements Guard
} }
$this->user = $user; $this->user = $user;
return $user; return $user;
} }
/** /**
* Determine if current user is authenticated. If not, throw an exception. * Determine if current user is authenticated. If not, throw an exception.
* *
* @return \Illuminate\Contracts\Auth\Authenticatable
*
* @throws ApiAuthException * @throws ApiAuthException
*
* @return \Illuminate\Contracts\Auth\Authenticatable
*/ */
public function authenticate() public function authenticate()
{ {
@ -79,6 +80,7 @@ class ApiTokenGuard implements Guard
/** /**
* Check the API token in the request and fetch a valid authorised user. * Check the API token in the request and fetch a valid authorised user.
*
* @throws ApiAuthException * @throws ApiAuthException
*/ */
protected function getAuthorisedUserFromRequest(): Authenticatable protected function getAuthorisedUserFromRequest(): Authenticatable
@ -98,6 +100,7 @@ class ApiTokenGuard implements Guard
/** /**
* Validate the format of the token header value string. * Validate the format of the token header value string.
*
* @throws ApiAuthException * @throws ApiAuthException
*/ */
protected function validateTokenHeaderValue(string $authToken): void protected function validateTokenHeaderValue(string $authToken): void
@ -114,6 +117,7 @@ class ApiTokenGuard implements Guard
/** /**
* Validate the given secret against the given token and ensure the token * Validate the given secret against the given token and ensure the token
* currently has access to the instance API. * currently has access to the instance API.
*
* @throws ApiAuthException * @throws ApiAuthException
*/ */
protected function validateToken(?ApiToken $token, string $secret): void protected function validateToken(?ApiToken $token, string $secret): void

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Api; <?php
namespace BookStack\Api;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
@ -6,7 +8,6 @@ use Illuminate\Http\Request;
class ListingResponseBuilder class ListingResponseBuilder
{ {
protected $query; protected $query;
protected $request; protected $request;
protected $fields; protected $fields;
@ -18,7 +19,7 @@ class ListingResponseBuilder
'lt' => '<', 'lt' => '<',
'gte' => '>=', 'gte' => '>=',
'lte' => '<=', 'lte' => '<=',
'like' => 'like' 'like' => 'like',
]; ];
/** /**
@ -54,6 +55,7 @@ class ListingResponseBuilder
{ {
$query = $this->countAndOffsetQuery($query); $query = $this->countAndOffsetQuery($query);
$query = $this->sortQuery($query); $query = $this->sortQuery($query);
return $query->get($this->fields); return $query->get($this->fields);
} }
@ -95,6 +97,7 @@ class ListingResponseBuilder
} }
$queryOperator = $this->filterOperators[$filterOperator]; $queryOperator = $this->filterOperators[$filterOperator];
return [$field, $queryOperator, $value]; return [$field, $queryOperator, $value];
} }

View File

@ -4,11 +4,11 @@ namespace BookStack;
class Application extends \Illuminate\Foundation\Application class Application extends \Illuminate\Foundation\Application
{ {
/** /**
* Get the path to the application configuration files. * Get the path to the application configuration files.
* *
* @param string $path Optionally, a path to append to the config path * @param string $path Optionally, a path to append to the config path
*
* @return string * @return string
*/ */
public function configPath($path = '') public function configPath($path = '')

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Auth\Access; <?php
namespace BookStack\Auth\Access;
use BookStack\Auth\User; use BookStack\Auth\User;
use BookStack\Exceptions\ConfirmationEmailException; use BookStack\Exceptions\ConfirmationEmailException;
@ -12,7 +14,9 @@ class EmailConfirmationService extends UserTokenService
/** /**
* Create new confirmation for a user, * Create new confirmation for a user,
* Also removes any existing old ones. * Also removes any existing old ones.
*
* @param User $user * @param User $user
*
* @throws ConfirmationEmailException * @throws ConfirmationEmailException
*/ */
public function sendConfirmation(User $user) public function sendConfirmation(User $user)
@ -29,6 +33,7 @@ class EmailConfirmationService extends UserTokenService
/** /**
* Check if confirmation is required in this instance. * Check if confirmation is required in this instance.
*
* @return bool * @return bool
*/ */
public function confirmationRequired(): bool public function confirmationRequired(): bool

View File

@ -1,10 +1,10 @@
<?php namespace BookStack\Auth\Access; <?php
namespace BookStack\Auth\Access;
use BookStack\Auth\Role; use BookStack\Auth\Role;
use BookStack\Auth\User; use BookStack\Auth\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
class ExternalAuthService class ExternalAuthService
{ {
@ -19,6 +19,7 @@ class ExternalAuthService
} }
$roleName = str_replace(' ', '-', trim(strtolower($role->display_name))); $roleName = str_replace(' ', '-', trim(strtolower($role->display_name)));
return in_array($roleName, $groupNames); return in_array($roleName, $groupNames);
} }
@ -57,7 +58,7 @@ class ExternalAuthService
} }
/** /**
* Sync the groups to the user roles for the current user * Sync the groups to the user roles for the current user.
*/ */
public function syncWithGroups(User $user, array $userGroups): void public function syncWithGroups(User $user, array $userGroups): void
{ {

View File

@ -7,7 +7,6 @@ use Illuminate\Contracts\Auth\UserProvider;
class ExternalBaseUserProvider implements UserProvider class ExternalBaseUserProvider implements UserProvider
{ {
/** /**
* The user model. * The user model.
* *
@ -17,6 +16,7 @@ class ExternalBaseUserProvider implements UserProvider
/** /**
* LdapUserProvider constructor. * LdapUserProvider constructor.
*
* @param $model * @param $model
*/ */
public function __construct(string $model) public function __construct(string $model)
@ -32,13 +32,15 @@ class ExternalBaseUserProvider implements UserProvider
public function createModel() public function createModel()
{ {
$class = '\\' . ltrim($this->model, '\\'); $class = '\\' . ltrim($this->model, '\\');
return new $class;
return new $class();
} }
/** /**
* Retrieve a user by their unique identifier. * Retrieve a user by their unique identifier.
* *
* @param mixed $identifier * @param mixed $identifier
*
* @return \Illuminate\Contracts\Auth\Authenticatable|null * @return \Illuminate\Contracts\Auth\Authenticatable|null
*/ */
public function retrieveById($identifier) public function retrieveById($identifier)
@ -51,6 +53,7 @@ class ExternalBaseUserProvider implements UserProvider
* *
* @param mixed $identifier * @param mixed $identifier
* @param string $token * @param string $token
*
* @return \Illuminate\Contracts\Auth\Authenticatable|null * @return \Illuminate\Contracts\Auth\Authenticatable|null
*/ */
public function retrieveByToken($identifier, $token) public function retrieveByToken($identifier, $token)
@ -58,12 +61,12 @@ class ExternalBaseUserProvider implements UserProvider
return null; return null;
} }
/** /**
* Update the "remember me" token for the given user in storage. * Update the "remember me" token for the given user in storage.
* *
* @param \Illuminate\Contracts\Auth\Authenticatable $user * @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param string $token * @param string $token
*
* @return void * @return void
*/ */
public function updateRememberToken(Authenticatable $user, $token) public function updateRememberToken(Authenticatable $user, $token)
@ -75,12 +78,14 @@ class ExternalBaseUserProvider implements UserProvider
* Retrieve a user by the given credentials. * Retrieve a user by the given credentials.
* *
* @param array $credentials * @param array $credentials
*
* @return \Illuminate\Contracts\Auth\Authenticatable|null * @return \Illuminate\Contracts\Auth\Authenticatable|null
*/ */
public function retrieveByCredentials(array $credentials) public function retrieveByCredentials(array $credentials)
{ {
// Search current user base by looking up a uid // Search current user base by looking up a uid
$model = $this->createModel(); $model = $this->createModel();
return $model->newQuery() return $model->newQuery()
->where('external_auth_id', $credentials['external_auth_id']) ->where('external_auth_id', $credentials['external_auth_id'])
->first(); ->first();
@ -91,6 +96,7 @@ class ExternalBaseUserProvider implements UserProvider
* *
* @param \Illuminate\Contracts\Auth\Authenticatable $user * @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param array $credentials * @param array $credentials
*
* @return bool * @return bool
*/ */
public function validateCredentials(Authenticatable $user, array $credentials) public function validateCredentials(Authenticatable $user, array $credentials)

View File

@ -119,6 +119,7 @@ class ExternalBaseSessionGuard implements StatefulGuard
* Log a user into the application without sessions or cookies. * Log a user into the application without sessions or cookies.
* *
* @param array $credentials * @param array $credentials
*
* @return bool * @return bool
*/ */
public function once(array $credentials = []) public function once(array $credentials = [])
@ -136,6 +137,7 @@ class ExternalBaseSessionGuard implements StatefulGuard
* Log the given user ID into the application without sessions or cookies. * Log the given user ID into the application without sessions or cookies.
* *
* @param mixed $id * @param mixed $id
*
* @return \Illuminate\Contracts\Auth\Authenticatable|false * @return \Illuminate\Contracts\Auth\Authenticatable|false
*/ */
public function onceUsingId($id) public function onceUsingId($id)
@ -153,6 +155,7 @@ class ExternalBaseSessionGuard implements StatefulGuard
* Validate a user's credentials. * Validate a user's credentials.
* *
* @param array $credentials * @param array $credentials
*
* @return bool * @return bool
*/ */
public function validate(array $credentials = []) public function validate(array $credentials = [])
@ -160,12 +163,12 @@ class ExternalBaseSessionGuard implements StatefulGuard
return false; return false;
} }
/** /**
* Attempt to authenticate a user using the given credentials. * Attempt to authenticate a user using the given credentials.
* *
* @param array $credentials * @param array $credentials
* @param bool $remember * @param bool $remember
*
* @return bool * @return bool
*/ */
public function attempt(array $credentials = [], $remember = false) public function attempt(array $credentials = [], $remember = false)
@ -178,6 +181,7 @@ class ExternalBaseSessionGuard implements StatefulGuard
* *
* @param mixed $id * @param mixed $id
* @param bool $remember * @param bool $remember
*
* @return \Illuminate\Contracts\Auth\Authenticatable|false * @return \Illuminate\Contracts\Auth\Authenticatable|false
*/ */
public function loginUsingId($id, $remember = false) public function loginUsingId($id, $remember = false)
@ -196,6 +200,7 @@ class ExternalBaseSessionGuard implements StatefulGuard
* *
* @param \Illuminate\Contracts\Auth\Authenticatable $user * @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param bool $remember * @param bool $remember
*
* @return void * @return void
*/ */
public function login(AuthenticatableContract $user, $remember = false) public function login(AuthenticatableContract $user, $remember = false)
@ -209,6 +214,7 @@ class ExternalBaseSessionGuard implements StatefulGuard
* Update the session with the given ID. * Update the session with the given ID.
* *
* @param string $id * @param string $id
*
* @return void * @return void
*/ */
protected function updateSession($id) protected function updateSession($id)
@ -289,6 +295,7 @@ class ExternalBaseSessionGuard implements StatefulGuard
* Set the current user. * Set the current user.
* *
* @param \Illuminate\Contracts\Auth\Authenticatable $user * @param \Illuminate\Contracts\Auth\Authenticatable $user
*
* @return $this * @return $this
*/ */
public function setUser(AuthenticatableContract $user) public function setUser(AuthenticatableContract $user)

View File

@ -6,8 +6,8 @@ use BookStack\Auth\Access\LdapService;
use BookStack\Auth\Access\RegistrationService; use BookStack\Auth\Access\RegistrationService;
use BookStack\Auth\User; use BookStack\Auth\User;
use BookStack\Exceptions\LdapException; use BookStack\Exceptions\LdapException;
use BookStack\Exceptions\LoginAttemptException;
use BookStack\Exceptions\LoginAttemptEmailNeededException; use BookStack\Exceptions\LoginAttemptEmailNeededException;
use BookStack\Exceptions\LoginAttemptException;
use BookStack\Exceptions\UserRegistrationException; use BookStack\Exceptions\UserRegistrationException;
use Illuminate\Contracts\Auth\UserProvider; use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Contracts\Session\Session; use Illuminate\Contracts\Session\Session;
@ -15,7 +15,6 @@ use Illuminate\Support\Str;
class LdapSessionGuard extends ExternalBaseSessionGuard class LdapSessionGuard extends ExternalBaseSessionGuard
{ {
protected $ldapService; protected $ldapService;
/** /**
@ -36,8 +35,10 @@ class LdapSessionGuard extends ExternalBaseSessionGuard
* Validate a user's credentials. * Validate a user's credentials.
* *
* @param array $credentials * @param array $credentials
* @return bool *
* @throws LdapException * @throws LdapException
*
* @return bool
*/ */
public function validate(array $credentials = []) public function validate(array $credentials = [])
{ {
@ -45,7 +46,7 @@ class LdapSessionGuard extends ExternalBaseSessionGuard
if (isset($userDetails['uid'])) { if (isset($userDetails['uid'])) {
$this->lastAttempted = $this->provider->retrieveByCredentials([ $this->lastAttempted = $this->provider->retrieveByCredentials([
'external_auth_id' => $userDetails['uid'] 'external_auth_id' => $userDetails['uid'],
]); ]);
} }
@ -57,9 +58,11 @@ class LdapSessionGuard extends ExternalBaseSessionGuard
* *
* @param array $credentials * @param array $credentials
* @param bool $remember * @param bool $remember
* @return bool *
* @throws LoginAttemptException * @throws LoginAttemptException
* @throws LdapException * @throws LdapException
*
* @return bool
*/ */
public function attempt(array $credentials = [], $remember = false) public function attempt(array $credentials = [], $remember = false)
{ {
@ -69,7 +72,7 @@ class LdapSessionGuard extends ExternalBaseSessionGuard
$user = null; $user = null;
if (isset($userDetails['uid'])) { if (isset($userDetails['uid'])) {
$this->lastAttempted = $user = $this->provider->retrieveByCredentials([ $this->lastAttempted = $user = $this->provider->retrieveByCredentials([
'external_auth_id' => $userDetails['uid'] 'external_auth_id' => $userDetails['uid'],
]); ]);
} }
@ -96,11 +99,13 @@ class LdapSessionGuard extends ExternalBaseSessionGuard
} }
$this->login($user, $remember); $this->login($user, $remember);
return true; return true;
} }
/** /**
* Create a new user from the given ldap credentials and login credentials * Create a new user from the given ldap credentials and login credentials.
*
* @throws LoginAttemptEmailNeededException * @throws LoginAttemptEmailNeededException
* @throws LoginAttemptException * @throws LoginAttemptException
* @throws UserRegistrationException * @throws UserRegistrationException
@ -122,6 +127,7 @@ class LdapSessionGuard extends ExternalBaseSessionGuard
$user = $this->registrationService->registerUser($details, null, false); $user = $this->registrationService->registerUser($details, null, false);
$this->ldapService->saveAndAttachAvatar($user, $ldapUserDetails); $this->ldapService->saveAndAttachAvatar($user, $ldapUserDetails);
return $user; return $user;
} }
} }

View File

@ -3,7 +3,7 @@
namespace BookStack\Auth\Access\Guards; namespace BookStack\Auth\Access\Guards;
/** /**
* Saml2 Session Guard * Saml2 Session Guard.
* *
* The saml2 login process is async in nature meaning it does not fit very well * 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 * into the default laravel 'Guard' auth flow. Instead most of the logic is done
@ -16,6 +16,7 @@ class Saml2SessionGuard extends ExternalBaseSessionGuard
* Validate a user's credentials. * Validate a user's credentials.
* *
* @param array $credentials * @param array $credentials
*
* @return bool * @return bool
*/ */
public function validate(array $credentials = []) public function validate(array $credentials = [])
@ -28,6 +29,7 @@ class Saml2SessionGuard extends ExternalBaseSessionGuard
* *
* @param array $credentials * @param array $credentials
* @param bool $remember * @param bool $remember
*
* @return bool * @return bool
*/ */
public function attempt(array $credentials = [], $remember = false) public function attempt(array $credentials = [], $remember = false)

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Auth\Access; <?php
namespace BookStack\Auth\Access;
/** /**
* Class Ldap * Class Ldap
@ -7,11 +9,12 @@
*/ */
class Ldap class Ldap
{ {
/** /**
* Connect to a LDAP server. * Connect to a LDAP server.
*
* @param string $hostName * @param string $hostName
* @param int $port * @param int $port
*
* @return resource * @return resource
*/ */
public function connect($hostName, $port) public function connect($hostName, $port)
@ -21,9 +24,11 @@ class Ldap
/** /**
* Set the value of a LDAP option for the given connection. * Set the value of a LDAP option for the given connection.
*
* @param resource $ldapConnection * @param resource $ldapConnection
* @param int $option * @param int $option
* @param mixed $value * @param mixed $value
*
* @return bool * @return bool
*/ */
public function setOption($ldapConnection, $option, $value) public function setOption($ldapConnection, $option, $value)
@ -41,8 +46,10 @@ class Ldap
/** /**
* Set the version number for the given ldap connection. * Set the version number for the given ldap connection.
*
* @param $ldapConnection * @param $ldapConnection
* @param $version * @param $version
*
* @return bool * @return bool
*/ */
public function setVersion($ldapConnection, $version) public function setVersion($ldapConnection, $version)
@ -52,10 +59,12 @@ class Ldap
/** /**
* Search LDAP tree using the provided filter. * Search LDAP tree using the provided filter.
*
* @param resource $ldapConnection * @param resource $ldapConnection
* @param string $baseDn * @param string $baseDn
* @param string $filter * @param string $filter
* @param array|null $attributes * @param array|null $attributes
*
* @return resource * @return resource
*/ */
public function search($ldapConnection, $baseDn, $filter, array $attributes = null) public function search($ldapConnection, $baseDn, $filter, array $attributes = null)
@ -65,8 +74,10 @@ class Ldap
/** /**
* Get entries from an ldap search result. * Get entries from an ldap search result.
*
* @param resource $ldapConnection * @param resource $ldapConnection
* @param resource $ldapSearchResult * @param resource $ldapSearchResult
*
* @return array * @return array
*/ */
public function getEntries($ldapConnection, $ldapSearchResult) public function getEntries($ldapConnection, $ldapSearchResult)
@ -76,23 +87,28 @@ class Ldap
/** /**
* Search and get entries immediately. * Search and get entries immediately.
*
* @param resource $ldapConnection * @param resource $ldapConnection
* @param string $baseDn * @param string $baseDn
* @param string $filter * @param string $filter
* @param array|null $attributes * @param array|null $attributes
*
* @return resource * @return resource
*/ */
public function searchAndGetEntries($ldapConnection, $baseDn, $filter, array $attributes = null) public function searchAndGetEntries($ldapConnection, $baseDn, $filter, array $attributes = null)
{ {
$search = $this->search($ldapConnection, $baseDn, $filter, $attributes); $search = $this->search($ldapConnection, $baseDn, $filter, $attributes);
return $this->getEntries($ldapConnection, $search); return $this->getEntries($ldapConnection, $search);
} }
/** /**
* Bind to LDAP directory. * Bind to LDAP directory.
*
* @param resource $ldapConnection * @param resource $ldapConnection
* @param string $bindRdn * @param string $bindRdn
* @param string $bindPassword * @param string $bindPassword
*
* @return bool * @return bool
*/ */
public function bind($ldapConnection, $bindRdn = null, $bindPassword = null) public function bind($ldapConnection, $bindRdn = null, $bindPassword = null)
@ -102,8 +118,10 @@ class Ldap
/** /**
* Explode a LDAP dn string into an array of components. * Explode a LDAP dn string into an array of components.
*
* @param string $dn * @param string $dn
* @param int $withAttrib * @param int $withAttrib
*
* @return array * @return array
*/ */
public function explodeDn(string $dn, int $withAttrib) public function explodeDn(string $dn, int $withAttrib)
@ -113,12 +131,14 @@ class Ldap
/** /**
* Escape a string for use in an LDAP filter. * Escape a string for use in an LDAP filter.
*
* @param string $value * @param string $value
* @param string $ignore * @param string $ignore
* @param int $flags * @param int $flags
*
* @return string * @return string
*/ */
public function escape(string $value, string $ignore = "", int $flags = 0) public function escape(string $value, string $ignore = '', int $flags = 0)
{ {
return ldap_escape($value, $ignore, $flags); return ldap_escape($value, $ignore, $flags);
} }

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Auth\Access; <?php
namespace BookStack\Auth\Access;
use BookStack\Auth\User; use BookStack\Auth\User;
use BookStack\Exceptions\JsonDebugException; use BookStack\Exceptions\JsonDebugException;
@ -13,7 +15,6 @@ use Illuminate\Support\Facades\Log;
*/ */
class LdapService extends ExternalAuthService class LdapService extends ExternalAuthService
{ {
protected $ldap; protected $ldap;
protected $ldapConnection; protected $ldapConnection;
protected $userAvatars; protected $userAvatars;
@ -33,6 +34,7 @@ class LdapService extends ExternalAuthService
/** /**
* Check if groups should be synced. * Check if groups should be synced.
*
* @return bool * @return bool
*/ */
public function shouldSyncGroups() public function shouldSyncGroups()
@ -42,6 +44,7 @@ class LdapService extends ExternalAuthService
/** /**
* Search for attributes for a specific user on the ldap. * Search for attributes for a specific user on the ldap.
*
* @throws LdapException * @throws LdapException
*/ */
private function getUserWithAttributes(string $userName, array $attributes): ?array private function getUserWithAttributes(string $userName, array $attributes): ?array
@ -73,6 +76,7 @@ class LdapService extends ExternalAuthService
/** /**
* Get the details of a user from LDAP using the given username. * Get the details of a user from LDAP using the given username.
* User found via configurable user filter. * User found via configurable user filter.
*
* @throws LdapException * @throws LdapException
*/ */
public function getUserDetails(string $userName): ?array public function getUserDetails(string $userName): ?array
@ -137,6 +141,7 @@ class LdapService extends ExternalAuthService
/** /**
* Check if the given credentials are valid for the given user. * Check if the given credentials are valid for the given user.
*
* @throws LdapException * @throws LdapException
*/ */
public function validateUserCredentials(?array $ldapUserDetails, string $password): bool public function validateUserCredentials(?array $ldapUserDetails, string $password): bool
@ -146,6 +151,7 @@ class LdapService extends ExternalAuthService
} }
$ldapConnection = $this->getConnection(); $ldapConnection = $this->getConnection();
try { try {
$ldapBind = $this->ldap->bind($ldapConnection, $ldapUserDetails['dn'], $password); $ldapBind = $this->ldap->bind($ldapConnection, $ldapUserDetails['dn'], $password);
} catch (ErrorException $e) { } catch (ErrorException $e) {
@ -158,7 +164,9 @@ class LdapService extends ExternalAuthService
/** /**
* Bind the system user to the LDAP connection using the given credentials * Bind the system user to the LDAP connection using the given credentials
* otherwise anonymous access is attempted. * otherwise anonymous access is attempted.
*
* @param $connection * @param $connection
*
* @throws LdapException * @throws LdapException
*/ */
protected function bindSystemUser($connection) protected function bindSystemUser($connection)
@ -181,8 +189,10 @@ class LdapService extends ExternalAuthService
/** /**
* Get the connection to the LDAP server. * Get the connection to the LDAP server.
* Creates a new connection if one does not exist. * Creates a new connection if one does not exist.
* @return resource *
* @throws LdapException * @throws LdapException
*
* @return resource
*/ */
protected function getConnection() protected function getConnection()
{ {
@ -222,6 +232,7 @@ class LdapService extends ExternalAuthService
} }
$this->ldapConnection = $ldapConnection; $this->ldapConnection = $ldapConnection;
return $this->ldapConnection; return $this->ldapConnection;
} }
@ -241,6 +252,7 @@ class LdapService extends ExternalAuthService
// Otherwise, extract the port out // Otherwise, extract the port out
$hostName = $serverNameParts[0]; $hostName = $serverNameParts[0];
$ldapPort = (count($serverNameParts) > 1) ? intval($serverNameParts[1]) : 389; $ldapPort = (count($serverNameParts) > 1) ? intval($serverNameParts[1]) : 389;
return ['host' => $hostName, 'port' => $ldapPort]; return ['host' => $hostName, 'port' => $ldapPort];
} }
@ -254,11 +266,13 @@ class LdapService extends ExternalAuthService
$newKey = '${' . $key . '}'; $newKey = '${' . $key . '}';
$newAttrs[$newKey] = $this->ldap->escape($attrText); $newAttrs[$newKey] = $this->ldap->escape($attrText);
} }
return strtr($filterString, $newAttrs); return strtr($filterString, $newAttrs);
} }
/** /**
* Get the groups a user is a part of on ldap. * Get the groups a user is a part of on ldap.
*
* @throws LdapException * @throws LdapException
*/ */
public function getUserGroups(string $userName): array public function getUserGroups(string $userName): array
@ -272,11 +286,13 @@ class LdapService extends ExternalAuthService
$userGroups = $this->groupFilter($user); $userGroups = $this->groupFilter($user);
$userGroups = $this->getGroupsRecursive($userGroups, []); $userGroups = $this->getGroupsRecursive($userGroups, []);
return $userGroups; return $userGroups;
} }
/** /**
* Get the parent groups of an array of groups. * Get the parent groups of an array of groups.
*
* @throws LdapException * @throws LdapException
*/ */
private function getGroupsRecursive(array $groupsArray, array $checked): array private function getGroupsRecursive(array $groupsArray, array $checked): array
@ -303,6 +319,7 @@ class LdapService extends ExternalAuthService
/** /**
* Get the parent groups of a single group. * Get the parent groups of a single group.
*
* @throws LdapException * @throws LdapException
*/ */
private function getGroupGroups(string $groupName): array private function getGroupGroups(string $groupName): array
@ -351,6 +368,7 @@ class LdapService extends ExternalAuthService
/** /**
* Sync the LDAP groups to the user roles for the current user. * Sync the LDAP groups to the user roles for the current user.
*
* @throws LdapException * @throws LdapException
*/ */
public function syncGroups(User $user, string $username) public function syncGroups(User $user, string $username)

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Auth\Access; <?php
namespace BookStack\Auth\Access;
use BookStack\Actions\ActivityType; use BookStack\Actions\ActivityType;
use BookStack\Auth\SocialAccount; use BookStack\Auth\SocialAccount;
@ -12,7 +14,6 @@ use Exception;
class RegistrationService class RegistrationService
{ {
protected $userRepo; protected $userRepo;
protected $emailConfirmationService; protected $emailConfirmationService;
@ -27,6 +28,7 @@ class RegistrationService
/** /**
* Check whether or not registrations are allowed in the app settings. * Check whether or not registrations are allowed in the app settings.
*
* @throws UserRegistrationException * @throws UserRegistrationException
*/ */
public function ensureRegistrationAllowed() public function ensureRegistrationAllowed()
@ -44,11 +46,13 @@ class RegistrationService
{ {
$authMethod = config('auth.method'); $authMethod = config('auth.method');
$authMethodsWithRegistration = ['standard']; $authMethodsWithRegistration = ['standard'];
return in_array($authMethod, $authMethodsWithRegistration) && setting('registration-enabled'); return in_array($authMethod, $authMethodsWithRegistration) && setting('registration-enabled');
} }
/** /**
* The registrations flow for all users. * The registrations flow for all users.
*
* @throws UserRegistrationException * @throws UserRegistrationException
*/ */
public function registerUser(array $userData, ?SocialAccount $socialAccount = null, bool $emailConfirmed = false): User public function registerUser(array $userData, ?SocialAccount $socialAccount = null, bool $emailConfirmed = false): User
@ -84,6 +88,7 @@ class RegistrationService
session()->flash('sent-email-confirmation', true); session()->flash('sent-email-confirmation', true);
} catch (Exception $e) { } catch (Exception $e) {
$message = trans('auth.email_confirm_send_error'); $message = trans('auth.email_confirm_send_error');
throw new UserRegistrationException($message, '/register/confirm'); throw new UserRegistrationException($message, '/register/confirm');
} }
} }
@ -94,6 +99,7 @@ class RegistrationService
/** /**
* Ensure that the given email meets any active email domain registration restrictions. * Ensure that the given email meets any active email domain registration restrictions.
* Throws if restrictions are active and the email does not match an allowed domain. * Throws if restrictions are active and the email does not match an allowed domain.
*
* @throws UserRegistrationException * @throws UserRegistrationException
*/ */
protected function ensureEmailDomainAllowed(string $userEmail): void protected function ensureEmailDomainAllowed(string $userEmail): void
@ -105,9 +111,10 @@ class RegistrationService
} }
$restrictedEmailDomains = explode(',', str_replace(' ', '', $registrationRestrict)); $restrictedEmailDomains = explode(',', str_replace(' ', '', $registrationRestrict));
$userEmailDomain = $domain = mb_substr(mb_strrchr($userEmail, "@"), 1); $userEmailDomain = $domain = mb_substr(mb_strrchr($userEmail, '@'), 1);
if (!in_array($userEmailDomain, $restrictedEmailDomains)) { if (!in_array($userEmailDomain, $restrictedEmailDomains)) {
$redirect = $this->registrationAllowed() ? '/register' : '/login'; $redirect = $this->registrationAllowed() ? '/register' : '/login';
throw new UserRegistrationException(trans('auth.registration_email_domain_invalid'), $redirect); throw new UserRegistrationException(trans('auth.registration_email_domain_invalid'), $redirect);
} }
} }

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Auth\Access; <?php
namespace BookStack\Auth\Access;
use BookStack\Actions\ActivityType; use BookStack\Actions\ActivityType;
use BookStack\Auth\User; use BookStack\Auth\User;
@ -37,12 +39,14 @@ class Saml2Service extends ExternalAuthService
/** /**
* Initiate a login flow. * Initiate a login flow.
*
* @throws Error * @throws Error
*/ */
public function login(): array public function login(): array
{ {
$toolKit = $this->getToolkit(); $toolKit = $this->getToolkit();
$returnRoute = url('/saml2/acs'); $returnRoute = url('/saml2/acs');
return [ return [
'url' => $toolKit->login($returnRoute, [], false, false, true), 'url' => $toolKit->login($returnRoute, [], false, false, true),
'id' => $toolKit->getLastRequestID(), 'id' => $toolKit->getLastRequestID(),
@ -51,6 +55,7 @@ class Saml2Service extends ExternalAuthService
/** /**
* Initiate a logout flow. * Initiate a logout flow.
*
* @throws Error * @throws Error
*/ */
public function logout(): array public function logout(): array
@ -78,6 +83,7 @@ class Saml2Service extends ExternalAuthService
* Process the ACS response from the idp and return the * Process the ACS response from the idp and return the
* matching, or new if registration active, user matched to the idp. * matching, or new if registration active, user matched to the idp.
* Returns null if not authenticated. * Returns null if not authenticated.
*
* @throws Error * @throws Error
* @throws SamlException * @throws SamlException
* @throws ValidationError * @throws ValidationError
@ -108,6 +114,7 @@ class Saml2Service extends ExternalAuthService
/** /**
* Process a response for the single logout service. * Process a response for the single logout service.
*
* @throws Error * @throws Error
*/ */
public function processSlsResponse(?string $requestId): ?string public function processSlsResponse(?string $requestId): ?string
@ -124,6 +131,7 @@ class Saml2Service extends ExternalAuthService
} }
$this->actionLogout(); $this->actionLogout();
return $redirect; return $redirect;
} }
@ -138,6 +146,7 @@ class Saml2Service extends ExternalAuthService
/** /**
* Get the metadata for this service provider. * Get the metadata for this service provider.
*
* @throws Error * @throws Error
*/ */
public function metadata(): string public function metadata(): string
@ -159,6 +168,7 @@ class Saml2Service extends ExternalAuthService
/** /**
* Load the underlying Onelogin SAML2 toolkit. * Load the underlying Onelogin SAML2 toolkit.
*
* @throws Error * @throws Error
* @throws Exception * @throws Exception
*/ */
@ -178,6 +188,7 @@ class Saml2Service extends ExternalAuthService
$spSettings = $this->loadOneloginServiceProviderDetails(); $spSettings = $this->loadOneloginServiceProviderDetails();
$settings = array_replace_recursive($settings, $spSettings, $metaDataSettings, $overrides); $settings = array_replace_recursive($settings, $spSettings, $metaDataSettings, $overrides);
return new Auth($settings); return new Auth($settings);
} }
@ -192,13 +203,13 @@ class Saml2Service extends ExternalAuthService
'url' => url('/saml2/acs'), 'url' => url('/saml2/acs'),
], ],
'singleLogoutService' => [ 'singleLogoutService' => [
'url' => url('/saml2/sls') 'url' => url('/saml2/sls'),
], ],
]; ];
return [ return [
'baseurl' => url('/saml2'), 'baseurl' => url('/saml2'),
'sp' => $spDetails 'sp' => $spDetails,
]; ];
} }
@ -211,7 +222,7 @@ class Saml2Service extends ExternalAuthService
} }
/** /**
* Calculate the display name * Calculate the display name.
*/ */
protected function getUserDisplayName(array $samlAttributes, string $defaultValue): string protected function getUserDisplayName(array $samlAttributes, string $defaultValue): string
{ {
@ -297,6 +308,7 @@ class Saml2Service extends ExternalAuthService
$data = $data[0]; $data = $data[0];
break; break;
} }
return $data; return $data;
} }
@ -315,6 +327,7 @@ class Saml2Service extends ExternalAuthService
/** /**
* Get the user from the database for the specified details. * Get the user from the database for the specified details.
*
* @throws UserRegistrationException * @throws UserRegistrationException
*/ */
protected function getOrRegisterUser(array $userDetails): ?User protected function getOrRegisterUser(array $userDetails): ?User
@ -340,6 +353,7 @@ class Saml2Service extends ExternalAuthService
/** /**
* Process the SAML response for a user. Login the user when * Process the SAML response for a user. Login the user when
* they exist, optionally registering them automatically. * they exist, optionally registering them automatically.
*
* @throws SamlException * @throws SamlException
* @throws JsonDebugException * @throws JsonDebugException
* @throws UserRegistrationException * @throws UserRegistrationException
@ -378,6 +392,7 @@ class Saml2Service extends ExternalAuthService
auth()->login($user); auth()->login($user);
Activity::add(ActivityType::AUTH_LOGIN, "saml2; {$user->logDescriptor()}"); Activity::add(ActivityType::AUTH_LOGIN, "saml2; {$user->logDescriptor()}");
Theme::dispatch(ThemeEvents::AUTH_LOGIN, 'saml2', $user); Theme::dispatch(ThemeEvents::AUTH_LOGIN, 'saml2', $user);
return $user; return $user;
} }
} }

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Auth\Access; <?php
namespace BookStack\Auth\Access;
use BookStack\Actions\ActivityType; use BookStack\Actions\ActivityType;
use BookStack\Auth\SocialAccount; use BookStack\Auth\SocialAccount;
@ -21,12 +23,14 @@ class SocialAuthService
{ {
/** /**
* The core socialite library used. * The core socialite library used.
*
* @var Socialite * @var Socialite
*/ */
protected $socialite; protected $socialite;
/** /**
* The default built-in social drivers we support. * The default built-in social drivers we support.
*
* @var string[] * @var string[]
*/ */
protected $validSocialDrivers = [ protected $validSocialDrivers = [
@ -39,7 +43,7 @@ class SocialAuthService
'okta', 'okta',
'gitlab', 'gitlab',
'twitch', 'twitch',
'discord' 'discord',
]; ];
/** /**
@ -47,6 +51,7 @@ class SocialAuthService
* for an initial redirect action. * for an initial redirect action.
* Array is keyed by social driver name. * Array is keyed by social driver name.
* Callbacks are passed an instance of the driver. * Callbacks are passed an instance of the driver.
*
* @var array<string, callable> * @var array<string, callable>
*/ */
protected $configureForRedirectCallbacks = []; protected $configureForRedirectCallbacks = [];
@ -61,26 +66,31 @@ class SocialAuthService
/** /**
* Start the social login path. * Start the social login path.
*
* @throws SocialDriverNotConfigured * @throws SocialDriverNotConfigured
*/ */
public function startLogIn(string $socialDriver): RedirectResponse public function startLogIn(string $socialDriver): RedirectResponse
{ {
$driver = $this->validateDriver($socialDriver); $driver = $this->validateDriver($socialDriver);
return $this->getDriverForRedirect($driver)->redirect(); return $this->getDriverForRedirect($driver)->redirect();
} }
/** /**
* Start the social registration process * Start the social registration process.
*
* @throws SocialDriverNotConfigured * @throws SocialDriverNotConfigured
*/ */
public function startRegister(string $socialDriver): RedirectResponse public function startRegister(string $socialDriver): RedirectResponse
{ {
$driver = $this->validateDriver($socialDriver); $driver = $this->validateDriver($socialDriver);
return $this->getDriverForRedirect($driver)->redirect(); return $this->getDriverForRedirect($driver)->redirect();
} }
/** /**
* Handle the social registration process on callback. * Handle the social registration process on callback.
*
* @throws UserRegistrationException * @throws UserRegistrationException
*/ */
public function handleRegistrationCallback(string $socialDriver, SocialUser $socialUser): SocialUser public function handleRegistrationCallback(string $socialDriver, SocialUser $socialUser): SocialUser
@ -92,6 +102,7 @@ class SocialAuthService
if (User::query()->where('email', '=', $socialUser->getEmail())->exists()) { if (User::query()->where('email', '=', $socialUser->getEmail())->exists()) {
$email = $socialUser->getEmail(); $email = $socialUser->getEmail();
throw new UserRegistrationException(trans('errors.error_user_exists_different_creds', ['email' => $email]), '/login'); throw new UserRegistrationException(trans('errors.error_user_exists_different_creds', ['email' => $email]), '/login');
} }
@ -100,16 +111,19 @@ class SocialAuthService
/** /**
* Get the social user details via the social driver. * Get the social user details via the social driver.
*
* @throws SocialDriverNotConfigured * @throws SocialDriverNotConfigured
*/ */
public function getSocialUser(string $socialDriver): SocialUser public function getSocialUser(string $socialDriver): SocialUser
{ {
$driver = $this->validateDriver($socialDriver); $driver = $this->validateDriver($socialDriver);
return $this->socialite->driver($driver)->user(); return $this->socialite->driver($driver)->user();
} }
/** /**
* Handle the login process on a oAuth callback. * Handle the login process on a oAuth callback.
*
* @throws SocialSignInAccountNotUsed * @throws SocialSignInAccountNotUsed
*/ */
public function handleLoginCallback(string $socialDriver, SocialUser $socialUser) public function handleLoginCallback(string $socialDriver, SocialUser $socialUser)
@ -128,6 +142,7 @@ class SocialAuthService
auth()->login($socialAccount->user); auth()->login($socialAccount->user);
Activity::add(ActivityType::AUTH_LOGIN, $socialAccount); Activity::add(ActivityType::AUTH_LOGIN, $socialAccount);
Theme::dispatch(ThemeEvents::AUTH_LOGIN, $socialDriver, $socialAccount->user); Theme::dispatch(ThemeEvents::AUTH_LOGIN, $socialDriver, $socialAccount->user);
return redirect()->intended('/'); return redirect()->intended('/');
} }
@ -137,18 +152,21 @@ class SocialAuthService
$account = $this->newSocialAccount($socialDriver, $socialUser); $account = $this->newSocialAccount($socialDriver, $socialUser);
$currentUser->socialAccounts()->save($account); $currentUser->socialAccounts()->save($account);
session()->flash('success', trans('settings.users_social_connected', ['socialAccount' => $titleCaseDriver])); session()->flash('success', trans('settings.users_social_connected', ['socialAccount' => $titleCaseDriver]));
return redirect($currentUser->getEditUrl()); return redirect($currentUser->getEditUrl());
} }
// When a user is logged in and the social account exists and is already linked to the current user. // When a user is logged in and the social account exists and is already linked to the current user.
if ($isLoggedIn && $socialAccount !== null && $socialAccount->user->id === $currentUser->id) { if ($isLoggedIn && $socialAccount !== null && $socialAccount->user->id === $currentUser->id) {
session()->flash('error', trans('errors.social_account_existing', ['socialAccount' => $titleCaseDriver])); session()->flash('error', trans('errors.social_account_existing', ['socialAccount' => $titleCaseDriver]));
return redirect($currentUser->getEditUrl()); return redirect($currentUser->getEditUrl());
} }
// When a user is logged in, A social account exists but the users do not match. // When a user is logged in, A social account exists but the users do not match.
if ($isLoggedIn && $socialAccount !== null && $socialAccount->user->id != $currentUser->id) { if ($isLoggedIn && $socialAccount !== null && $socialAccount->user->id != $currentUser->id) {
session()->flash('error', trans('errors.social_account_already_used_existing', ['socialAccount' => $titleCaseDriver])); session()->flash('error', trans('errors.social_account_already_used_existing', ['socialAccount' => $titleCaseDriver]));
return redirect($currentUser->getEditUrl()); return redirect($currentUser->getEditUrl());
} }
@ -163,6 +181,7 @@ class SocialAuthService
/** /**
* Ensure the social driver is correct and supported. * Ensure the social driver is correct and supported.
*
* @throws SocialDriverNotConfigured * @throws SocialDriverNotConfigured
*/ */
protected function validateDriver(string $socialDriver): string protected function validateDriver(string $socialDriver): string
@ -188,6 +207,7 @@ class SocialAuthService
$lowerName = strtolower($driver); $lowerName = strtolower($driver);
$configPrefix = 'services.' . $lowerName . '.'; $configPrefix = 'services.' . $lowerName . '.';
$config = [config($configPrefix . 'client_id'), config($configPrefix . 'client_secret'), config('services.callback_url')]; $config = [config($configPrefix . 'client_id'), config($configPrefix . 'client_secret'), config('services.callback_url')];
return !in_array(false, $config) && !in_array(null, $config); return !in_array(false, $config) && !in_array(null, $config);
} }
@ -239,7 +259,7 @@ class SocialAuthService
return new SocialAccount([ return new SocialAccount([
'driver' => $socialDriver, 'driver' => $socialDriver,
'driver_id' => $socialUser->getId(), 'driver_id' => $socialUser->getId(),
'avatar' => $socialUser->getAvatar() 'avatar' => $socialUser->getAvatar(),
]); ]);
} }
@ -252,7 +272,7 @@ class SocialAuthService
} }
/** /**
* Provide redirect options per service for the Laravel Socialite driver * Provide redirect options per service for the Laravel Socialite driver.
*/ */
protected function getDriverForRedirect(string $driverName): Provider protected function getDriverForRedirect(string $driverName): Provider
{ {

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Auth\Access; <?php
namespace BookStack\Auth\Access;
use BookStack\Auth\User; use BookStack\Auth\User;
use BookStack\Notifications\UserInvite; use BookStack\Notifications\UserInvite;
@ -11,6 +13,7 @@ class UserInviteService extends UserTokenService
/** /**
* Send an invitation to a user to sign into BookStack * Send an invitation to a user to sign into BookStack
* Removes existing invitation tokens. * Removes existing invitation tokens.
*
* @param User $user * @param User $user
*/ */
public function sendInvitation(User $user) public function sendInvitation(User $user)

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Auth\Access; <?php
namespace BookStack\Auth\Access;
use BookStack\Auth\User; use BookStack\Auth\User;
use BookStack\Exceptions\UserTokenExpiredException; use BookStack\Exceptions\UserTokenExpiredException;
@ -10,15 +12,16 @@ use stdClass;
class UserTokenService class UserTokenService
{ {
/** /**
* Name of table where user tokens are stored. * Name of table where user tokens are stored.
*
* @var string * @var string
*/ */
protected $tokenTable = 'user_tokens'; protected $tokenTable = 'user_tokens';
/** /**
* Token expiry time in hours. * Token expiry time in hours.
*
* @var int * @var int
*/ */
protected $expiryTime = 24; protected $expiryTime = 24;
@ -27,6 +30,7 @@ class UserTokenService
/** /**
* UserTokenService constructor. * UserTokenService constructor.
*
* @param Database $db * @param Database $db
*/ */
public function __construct(Database $db) public function __construct(Database $db)
@ -36,7 +40,9 @@ class UserTokenService
/** /**
* Delete all email confirmations that belong to a user. * Delete all email confirmations that belong to a user.
*
* @param User $user * @param User $user
*
* @return mixed * @return mixed
*/ */
public function deleteByUser(User $user) public function deleteByUser(User $user)
@ -48,10 +54,13 @@ class UserTokenService
/** /**
* Get the user id from a token, while check the token exists and has not expired. * Get the user id from a token, while check the token exists and has not expired.
*
* @param string $token * @param string $token
* @return int *
* @throws UserTokenNotFoundException * @throws UserTokenNotFoundException
* @throws UserTokenExpiredException * @throws UserTokenExpiredException
*
* @return int
*/ */
public function checkTokenAndGetUserId(string $token): int public function checkTokenAndGetUserId(string $token): int
{ {
@ -70,6 +79,7 @@ class UserTokenService
/** /**
* Creates a unique token within the email confirmation database. * Creates a unique token within the email confirmation database.
*
* @return string * @return string
*/ */
protected function generateToken(): string protected function generateToken(): string
@ -78,12 +88,15 @@ class UserTokenService
while ($this->tokenExists($token)) { while ($this->tokenExists($token)) {
$token = Str::random(25); $token = Str::random(25);
} }
return $token; return $token;
} }
/** /**
* Generate and store a token for the given user. * Generate and store a token for the given user.
*
* @param User $user * @param User $user
*
* @return string * @return string
*/ */
protected function createTokenForUser(User $user): string protected function createTokenForUser(User $user): string
@ -93,14 +106,17 @@ class UserTokenService
'user_id' => $user->id, 'user_id' => $user->id,
'token' => $token, 'token' => $token,
'created_at' => Carbon::now(), 'created_at' => Carbon::now(),
'updated_at' => Carbon::now() 'updated_at' => Carbon::now(),
]); ]);
return $token; return $token;
} }
/** /**
* Check if the given token exists. * Check if the given token exists.
*
* @param string $token * @param string $token
*
* @return bool * @return bool
*/ */
protected function tokenExists(string $token): bool protected function tokenExists(string $token): bool
@ -111,7 +127,9 @@ class UserTokenService
/** /**
* Get a token entry for the given token. * Get a token entry for the given token.
*
* @param string $token * @param string $token
*
* @return object|null * @return object|null
*/ */
protected function getEntryByToken(string $token) protected function getEntryByToken(string $token)
@ -123,7 +141,9 @@ class UserTokenService
/** /**
* Check if the given token entry has expired. * Check if the given token entry has expired.
*
* @param stdClass $tokenEntry * @param stdClass $tokenEntry
*
* @return bool * @return bool
*/ */
protected function entryExpired(stdClass $tokenEntry): bool protected function entryExpired(stdClass $tokenEntry): bool

View File

@ -1,15 +1,17 @@
<?php namespace BookStack\Auth\Permissions; <?php
namespace BookStack\Auth\Permissions;
use BookStack\Model; use BookStack\Model;
class EntityPermission extends Model class EntityPermission extends Model
{ {
protected $fillable = ['role_id', 'action']; protected $fillable = ['role_id', 'action'];
public $timestamps = false; public $timestamps = false;
/** /**
* Get all this restriction's attached entity. * Get all this restriction's attached entity.
*
* @return \Illuminate\Database\Eloquent\Relations\MorphTo * @return \Illuminate\Database\Eloquent\Relations\MorphTo
*/ */
public function restrictable() public function restrictable()

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Auth\Permissions; <?php
namespace BookStack\Auth\Permissions;
use BookStack\Auth\Role; use BookStack\Auth\Role;
use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\Entity;

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Auth\Permissions; <?php
namespace BookStack\Auth\Permissions;
use BookStack\Auth\Role; use BookStack\Auth\Role;
use BookStack\Auth\User; use BookStack\Auth\User;
@ -48,7 +50,7 @@ class PermissionService
} }
/** /**
* Set the database connection * Set the database connection.
*/ */
public function setConnection(Connection $connection) public function setConnection(Connection $connection)
{ {
@ -56,7 +58,8 @@ class PermissionService
} }
/** /**
* Prepare the local entity cache and ensure it's empty * Prepare the local entity cache and ensure it's empty.
*
* @param Entity[] $entities * @param Entity[] $entities
*/ */
protected function readyEntityCache(array $entities = []) protected function readyEntityCache(array $entities = [])
@ -73,7 +76,7 @@ class PermissionService
} }
/** /**
* Get a book via ID, Checks local cache * Get a book via ID, Checks local cache.
*/ */
protected function getBook(int $bookId): ?Book protected function getBook(int $bookId): ?Book
{ {
@ -85,7 +88,7 @@ class PermissionService
} }
/** /**
* Get a chapter via ID, Checks local cache * Get a chapter via ID, Checks local cache.
*/ */
protected function getChapter(int $chapterId): ?Chapter protected function getChapter(int $chapterId): ?Chapter
{ {
@ -151,12 +154,13 @@ class PermissionService
}, },
'pages' => function ($query) { 'pages' => function ($query) {
$query->withTrashed()->select(['id', 'restricted', 'owned_by', 'book_id', 'chapter_id']); $query->withTrashed()->select(['id', 'restricted', 'owned_by', 'book_id', 'chapter_id']);
} },
]); ]);
} }
/** /**
* Build joint permissions for the given shelf and role combinations. * Build joint permissions for the given shelf and role combinations.
*
* @throws Throwable * @throws Throwable
*/ */
protected function buildJointPermissionsForShelves(EloquentCollection $shelves, array $roles, bool $deleteOld = false) protected function buildJointPermissionsForShelves(EloquentCollection $shelves, array $roles, bool $deleteOld = false)
@ -169,6 +173,7 @@ class PermissionService
/** /**
* Build joint permissions for the given book and role combinations. * Build joint permissions for the given book and role combinations.
*
* @throws Throwable * @throws Throwable
*/ */
protected function buildJointPermissionsForBooks(EloquentCollection $books, array $roles, bool $deleteOld = false) protected function buildJointPermissionsForBooks(EloquentCollection $books, array $roles, bool $deleteOld = false)
@ -193,6 +198,7 @@ class PermissionService
/** /**
* Rebuild the entity jointPermissions for a particular entity. * Rebuild the entity jointPermissions for a particular entity.
*
* @throws Throwable * @throws Throwable
*/ */
public function buildJointPermissionsForEntity(Entity $entity) public function buildJointPermissionsForEntity(Entity $entity)
@ -201,6 +207,7 @@ class PermissionService
if ($entity instanceof Book) { if ($entity instanceof Book) {
$books = $this->bookFetchQuery()->where('id', '=', $entity->id)->get(); $books = $this->bookFetchQuery()->where('id', '=', $entity->id)->get();
$this->buildJointPermissionsForBooks($books, Role::query()->get()->all(), true); $this->buildJointPermissionsForBooks($books, Role::query()->get()->all(), true);
return; return;
} }
@ -224,6 +231,7 @@ class PermissionService
/** /**
* Rebuild the entity jointPermissions for a collection of entities. * Rebuild the entity jointPermissions for a collection of entities.
*
* @throws Throwable * @throws Throwable
*/ */
public function buildJointPermissionsForEntities(array $entities) public function buildJointPermissionsForEntities(array $entities)
@ -263,6 +271,7 @@ class PermissionService
/** /**
* Delete all of the entity jointPermissions for a list of entities. * Delete all of the entity jointPermissions for a list of entities.
*
* @param Role[] $roles * @param Role[] $roles
*/ */
protected function deleteManyJointPermissionsForRoles($roles) protected function deleteManyJointPermissionsForRoles($roles)
@ -275,7 +284,9 @@ class PermissionService
/** /**
* Delete the entity jointPermissions for a particular entity. * Delete the entity jointPermissions for a particular entity.
*
* @param Entity $entity * @param Entity $entity
*
* @throws Throwable * @throws Throwable
*/ */
public function deleteJointPermissionsForEntity(Entity $entity) public function deleteJointPermissionsForEntity(Entity $entity)
@ -285,7 +296,9 @@ class PermissionService
/** /**
* Delete all of the entity jointPermissions for a list of entities. * Delete all of the entity jointPermissions for a list of entities.
*
* @param Entity[] $entities * @param Entity[] $entities
*
* @throws Throwable * @throws Throwable
*/ */
protected function deleteManyJointPermissionsForEntities(array $entities) protected function deleteManyJointPermissionsForEntities(array $entities)
@ -295,7 +308,6 @@ class PermissionService
} }
$this->db->transaction(function () use ($entities) { $this->db->transaction(function () use ($entities) {
foreach (array_chunk($entities, 1000) as $entityChunk) { foreach (array_chunk($entities, 1000) as $entityChunk) {
$query = $this->db->table('joint_permissions'); $query = $this->db->table('joint_permissions');
foreach ($entityChunk as $entity) { foreach ($entityChunk as $entity) {
@ -311,8 +323,10 @@ class PermissionService
/** /**
* Create & Save entity jointPermissions for many entities and roles. * Create & Save entity jointPermissions for many entities and roles.
*
* @param Entity[] $entities * @param Entity[] $entities
* @param Role[] $roles * @param Role[] $roles
*
* @throws Throwable * @throws Throwable
*/ */
protected function createManyJointPermissions(array $entities, array $roles) protected function createManyJointPermissions(array $entities, array $roles)
@ -363,7 +377,6 @@ class PermissionService
}); });
} }
/** /**
* Get the actions related to an entity. * Get the actions related to an entity.
*/ */
@ -376,6 +389,7 @@ class PermissionService
if ($entity instanceof Book) { if ($entity instanceof Book) {
$baseActions[] = 'chapter-create'; $baseActions[] = 'chapter-create';
} }
return $baseActions; return $baseActions;
} }
@ -397,6 +411,7 @@ class PermissionService
if ($entity->restricted) { if ($entity->restricted) {
$hasAccess = $this->mapHasActiveRestriction($permissionMap, $entity, $role, $restrictionAction); $hasAccess = $this->mapHasActiveRestriction($permissionMap, $entity, $role, $restrictionAction);
return $this->createJointPermissionDataArray($entity, $role, $action, $hasAccess, $hasAccess); return $this->createJointPermissionDataArray($entity, $role, $action, $hasAccess, $hasAccess);
} }
@ -433,6 +448,7 @@ class PermissionService
protected function mapHasActiveRestriction(array $entityMap, Entity $entity, Role $role, string $action): bool protected function mapHasActiveRestriction(array $entityMap, Entity $entity, Role $role, string $action): bool
{ {
$key = $entity->getMorphClass() . ':' . $entity->getRawAttribute('id') . ':' . $role->getRawAttribute('id') . ':' . $action; $key = $entity->getMorphClass() . ':' . $entity->getRawAttribute('id') . ':' . $role->getRawAttribute('id') . ':' . $action;
return $entityMap[$key] ?? false; return $entityMap[$key] ?? false;
} }
@ -455,6 +471,7 @@ class PermissionService
/** /**
* Checks if an entity has a restriction set upon it. * Checks if an entity has a restriction set upon it.
*
* @param HasCreatorAndUpdater|HasOwner $ownable * @param HasCreatorAndUpdater|HasOwner $ownable
*/ */
public function checkOwnableUserAccess(Model $ownable, string $permission): bool public function checkOwnableUserAccess(Model $ownable, string $permission): bool
@ -473,7 +490,8 @@ class PermissionService
$ownPermission = $user && $user->can($permission . '-own'); $ownPermission = $user && $user->can($permission . '-own');
$ownerField = ($ownable instanceof Entity) ? 'owned_by' : 'created_by'; $ownerField = ($ownable instanceof Entity) ? 'owned_by' : 'created_by';
$isOwner = $user && $user->id === $ownable->$ownerField; $isOwner = $user && $user->id === $ownable->$ownerField;
return ($allPermission || ($isOwner && $ownPermission));
return $allPermission || ($isOwner && $ownPermission);
} }
// Handle abnormal create jointPermissions // Handle abnormal create jointPermissions
@ -483,6 +501,7 @@ class PermissionService
$hasAccess = $this->entityRestrictionQuery($baseQuery, $action)->count() > 0; $hasAccess = $this->entityRestrictionQuery($baseQuery, $action)->count() > 0;
$this->clean(); $this->clean();
return $hasAccess; return $hasAccess;
} }
@ -509,6 +528,7 @@ class PermissionService
$hasPermission = $permissionQuery->count() > 0; $hasPermission = $permissionQuery->count() > 0;
$this->clean(); $this->clean();
return $hasPermission; return $hasPermission;
} }
@ -529,6 +549,7 @@ class PermissionService
}); });
$this->clean(); $this->clean();
return $q; return $q;
} }
@ -539,6 +560,7 @@ class PermissionService
public function restrictEntityQuery(Builder $query, string $ability = 'view'): Builder public function restrictEntityQuery(Builder $query, string $ability = 'view'): Builder
{ {
$this->clean(); $this->clean();
return $query->where(function (Builder $parentQuery) use ($ability) { return $query->where(function (Builder $parentQuery) use ($ability) {
$parentQuery->whereHas('jointPermissions', function (Builder $permissionQuery) use ($ability) { $parentQuery->whereHas('jointPermissions', function (Builder $permissionQuery) use ($ability) {
$permissionQuery->whereIn('role_id', $this->getCurrentUserRoles()) $permissionQuery->whereIn('role_id', $this->getCurrentUserRoles())
@ -580,6 +602,7 @@ class PermissionService
/** /**
* Filter items that have entities set as a polymorphic relation. * Filter items that have entities set as a polymorphic relation.
*
* @param Builder|\Illuminate\Database\Query\Builder $query * @param Builder|\Illuminate\Database\Query\Builder $query
*/ */
public function filterRestrictedEntityRelations($query, string $tableName, string $entityIdColumn, string $entityTypeColumn, string $action = 'view') public function filterRestrictedEntityRelations($query, string $tableName, string $entityIdColumn, string $entityTypeColumn, string $action = 'view')
@ -600,6 +623,7 @@ class PermissionService
}); });
$this->clean(); $this->clean();
return $q; return $q;
} }
@ -628,12 +652,14 @@ class PermissionService
}); });
$this->clean(); $this->clean();
return $q; return $q;
} }
/** /**
* Add the query for checking the given user id has permission * Add the query for checking the given user id has permission
* within the join_permissions table. * within the join_permissions table.
*
* @param QueryBuilder|Builder $query * @param QueryBuilder|Builder $query
*/ */
protected function addJointHasPermissionCheck($query, int $userIdToCheck) protected function addJointHasPermissionCheck($query, int $userIdToCheck)
@ -645,7 +671,7 @@ class PermissionService
} }
/** /**
* Get the current user * Get the current user.
*/ */
private function currentUser(): User private function currentUser(): User
{ {

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Auth\Permissions; <?php
namespace BookStack\Auth\Permissions;
use BookStack\Actions\ActivityType; use BookStack\Actions\ActivityType;
use BookStack\Auth\Role; use BookStack\Auth\Role;
@ -9,7 +11,6 @@ use Illuminate\Database\Eloquent\Collection;
class PermissionsRepo class PermissionsRepo
{ {
protected $permission; protected $permission;
protected $role; protected $role;
protected $permissionService; protected $permissionService;
@ -62,6 +63,7 @@ class PermissionsRepo
$this->assignRolePermissions($role, $permissions); $this->assignRolePermissions($role, $permissions);
$this->permissionService->buildJointPermissionForRole($role); $this->permissionService->buildJointPermissionForRole($role);
Activity::add(ActivityType::ROLE_CREATE, $role); Activity::add(ActivityType::ROLE_CREATE, $role);
return $role; return $role;
} }
@ -116,6 +118,7 @@ class PermissionsRepo
* Check it's not an admin role or set as default before deleting. * Check it's not an admin role or set as default before deleting.
* If an migration Role ID is specified the users assign to the current role * If an migration Role ID is specified the users assign to the current role
* will be added to the role of the specified id. * will be added to the role of the specified id.
*
* @throws PermissionsException * @throws PermissionsException
* @throws Exception * @throws Exception
*/ */

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Auth\Permissions; <?php
namespace BookStack\Auth\Permissions;
use BookStack\Auth\Role; use BookStack\Auth\Role;
use BookStack\Model; use BookStack\Model;
@ -18,7 +20,9 @@ class RolePermission extends Model
/** /**
* Get the permission object by name. * Get the permission object by name.
*
* @param $name * @param $name
*
* @return mixed * @return mixed
*/ */
public static function getByName($name) public static function getByName($name)

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Auth; <?php
namespace BookStack\Auth;
use BookStack\Auth\Permissions\JointPermission; use BookStack\Auth\Permissions\JointPermission;
use BookStack\Auth\Permissions\RolePermission; use BookStack\Auth\Permissions\RolePermission;
@ -9,7 +11,8 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
/** /**
* Class Role * Class Role.
*
* @property int $id * @property int $id
* @property string $display_name * @property string $display_name
* @property string $description * @property string $description
@ -18,7 +21,6 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
*/ */
class Role extends Model implements Loggable class Role extends Model implements Loggable
{ {
protected $fillable = ['display_name', 'description', 'external_auth_id']; protected $fillable = ['display_name', 'description', 'external_auth_id'];
/** /**
@ -56,6 +58,7 @@ class Role extends Model implements Loggable
return true; return true;
} }
} }
return false; return false;
} }

View File

@ -1,16 +1,18 @@
<?php namespace BookStack\Auth; <?php
namespace BookStack\Auth;
use BookStack\Interfaces\Loggable; use BookStack\Interfaces\Loggable;
use BookStack\Model; use BookStack\Model;
/** /**
* Class SocialAccount * Class SocialAccount.
*
* @property string $driver * @property string $driver
* @property User $user * @property User $user
*/ */
class SocialAccount extends Model implements Loggable class SocialAccount extends Model implements Loggable
{ {
protected $fillable = ['user_id', 'driver', 'driver_id', 'timestamps']; protected $fillable = ['user_id', 'driver', 'driver_id', 'timestamps'];
public function user() public function user()

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Auth; <?php
namespace BookStack\Auth;
use BookStack\Actions\Favourite; use BookStack\Actions\Favourite;
use BookStack\Api\ApiToken; use BookStack\Api\ApiToken;
@ -22,7 +24,8 @@ use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
/** /**
* Class User * Class User.
*
* @property string $id * @property string $id
* @property string $name * @property string $name
* @property string $slug * @property string $slug
@ -38,16 +41,20 @@ use Illuminate\Support\Collection;
*/ */
class User extends Model implements AuthenticatableContract, CanResetPasswordContract, Loggable, Sluggable class User extends Model implements AuthenticatableContract, CanResetPasswordContract, Loggable, Sluggable
{ {
use Authenticatable, CanResetPassword, Notifiable; use Authenticatable;
use CanResetPassword;
use Notifiable;
/** /**
* The database table used by the model. * The database table used by the model.
*
* @var string * @var string
*/ */
protected $table = 'users'; protected $table = 'users';
/** /**
* The attributes that are mass assignable. * The attributes that are mass assignable.
*
* @var array * @var array
*/ */
protected $fillable = ['name', 'email']; protected $fillable = ['name', 'email'];
@ -56,6 +63,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
/** /**
* The attributes excluded from the model's JSON form. * The attributes excluded from the model's JSON form.
*
* @var array * @var array
*/ */
protected $hidden = [ protected $hidden = [
@ -65,12 +73,14 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
/** /**
* This holds the user's permissions when loaded. * This holds the user's permissions when loaded.
*
* @var ?Collection * @var ?Collection
*/ */
protected $permissions; protected $permissions;
/** /**
* This holds the default user when loaded. * This holds the default user when loaded.
*
* @var null|User * @var null|User
*/ */
protected static $defaultUser = null; protected static $defaultUser = null;
@ -85,6 +95,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
} }
static::$defaultUser = static::query()->where('system_name', '=', 'public')->first(); static::$defaultUser = static::query()->where('system_name', '=', 'public')->first();
return static::$defaultUser; return static::$defaultUser;
} }
@ -98,6 +109,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
/** /**
* The roles that belong to the user. * The roles that belong to the user.
*
* @return BelongsToMany * @return BelongsToMany
*/ */
public function roles() public function roles()
@ -105,6 +117,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
if ($this->id === 0) { if ($this->id === 0) {
return; return;
} }
return $this->belongsToMany(Role::class); return $this->belongsToMany(Role::class);
} }
@ -194,7 +207,9 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
/** /**
* Check if the user has a social account, * Check if the user has a social account,
* If a driver is passed it checks for that single account type. * If a driver is passed it checks for that single account type.
*
* @param bool|string $socialDriver * @param bool|string $socialDriver
*
* @return bool * @return bool
*/ */
public function hasSocialAccount($socialDriver = false) public function hasSocialAccount($socialDriver = false)
@ -207,7 +222,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
} }
/** /**
* Returns a URL to the user's avatar * Returns a URL to the user's avatar.
*/ */
public function getAvatar(int $size = 50): string public function getAvatar(int $size = 50): string
{ {
@ -222,6 +237,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
} catch (Exception $err) { } catch (Exception $err) {
$avatar = $default; $avatar = $default;
} }
return $avatar; return $avatar;
} }
@ -268,6 +284,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
public function getEditUrl(string $path = ''): string public function getEditUrl(string $path = ''): string
{ {
$uri = '/settings/users/' . $this->id . '/' . trim($path, '/'); $uri = '/settings/users/' . $this->id . '/' . trim($path, '/');
return url(rtrim($uri, '/')); return url(rtrim($uri, '/'));
} }
@ -298,7 +315,9 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
/** /**
* Send the password reset notification. * Send the password reset notification.
*
* @param string $token * @param string $token
*
* @return void * @return void
*/ */
public function sendPasswordResetNotification($token) public function sendPasswordResetNotification($token)
@ -320,6 +339,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
public function refreshSlug(): string public function refreshSlug(): string
{ {
$this->slug = app(SlugGenerator::class)->generate($this); $this->slug = app(SlugGenerator::class)->generate($this);
return $this->slug; return $this->slug;
} }
} }

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Auth; <?php
namespace BookStack\Auth;
use Activity; use Activity;
use BookStack\Entities\EntityProvider; use BookStack\Entities\EntityProvider;
@ -96,6 +98,7 @@ class UserRepo
/** /**
* Assign a user to a system-level role. * Assign a user to a system-level role.
*
* @throws NotFoundException * @throws NotFoundException
*/ */
public function attachSystemRole(User $user, string $systemRoleName) public function attachSystemRole(User $user, string $systemRoleName)
@ -126,6 +129,7 @@ class UserRepo
/** /**
* Set the assigned user roles via an array of role IDs. * Set the assigned user roles via an array of role IDs.
*
* @throws UserUpdateException * @throws UserUpdateException
*/ */
public function setUserRoles(User $user, array $roles) public function setUserRoles(User $user, array $roles)
@ -176,6 +180,7 @@ class UserRepo
/** /**
* Remove the given user from storage, Delete all related content. * Remove the given user from storage, Delete all related content.
*
* @throws Exception * @throws Exception
*/ */
public function destroy(User $user, ?int $newOwnerId = null) public function destroy(User $user, ?int $newOwnerId = null)
@ -201,7 +206,7 @@ class UserRepo
*/ */
protected function migrateOwnership(User $fromUser, User $toUser) protected function migrateOwnership(User $fromUser, User $toUser)
{ {
$entities = (new EntityProvider)->all(); $entities = (new EntityProvider())->all();
foreach ($entities as $instance) { foreach ($entities as $instance) {
$instance->newQuery()->where('owned_by', '=', $fromUser->id) $instance->newQuery()->where('owned_by', '=', $fromUser->id)
->update(['owned_by' => $toUser->id]); ->update(['owned_by' => $toUser->id]);
@ -242,6 +247,7 @@ class UserRepo
public function getAssetCounts(User $user): array public function getAssetCounts(User $user): array
{ {
$createdBy = ['created_by' => $user->id]; $createdBy = ['created_by' => $user->id];
return [ return [
'pages' => Page::visible()->where($createdBy)->count(), 'pages' => Page::visible()->where($createdBy)->count(),
'chapters' => Chapter::visible()->where($createdBy)->count(), 'chapters' => Chapter::visible()->where($createdBy)->count(),

View File

@ -18,6 +18,6 @@ return [
'max_item_count' => env('API_MAX_ITEM_COUNT', 500), 'max_item_count' => env('API_MAX_ITEM_COUNT', 500),
// The number of API requests that can be made per minute by a single user. // The number of API requests that can be made per minute by a single user.
'requests_per_minute' => env('API_REQUESTS_PER_MIN', 180) 'requests_per_minute' => env('API_REQUESTS_PER_MIN', 180),
]; ];

View File

@ -56,7 +56,7 @@ return [
'locale' => env('APP_LANG', 'en'), 'locale' => env('APP_LANG', 'en'),
// Locales available // Locales available
'locales' => ['en', 'ar', 'bg', 'bs', 'ca', 'cs', 'da', 'de', 'de_informal', 'es', 'es_AR', 'fa', 'fr', 'he', 'hr', 'hu', 'id', 'it', 'ja', 'ko', 'lv', 'nl', 'nb', 'pt', 'pt_BR', 'sk', 'sl', 'sv', 'pl', 'ru', 'th', 'tr', 'uk', 'vi', 'zh_CN', 'zh_TW',], 'locales' => ['en', 'ar', 'bg', 'bs', 'ca', 'cs', 'da', 'de', 'de_informal', 'es', 'es_AR', 'fa', 'fr', 'he', 'hr', 'hu', 'id', 'it', 'ja', 'ko', 'lv', 'nl', 'nb', 'pt', 'pt_BR', 'sk', 'sl', 'sv', 'pl', 'ru', 'th', 'tr', 'uk', 'vi', 'zh_CN', 'zh_TW'],
// Application Fallback Locale // Application Fallback Locale
'fallback_locale' => 'en', 'fallback_locale' => 'en',

View File

@ -46,7 +46,6 @@ return [
'driver' => 'null', 'driver' => 'null',
], ],
], ],
]; ];

View File

@ -1,7 +1,7 @@
<?php <?php
/** /**
* Debugbar Configuration Options * Debugbar Configuration Options.
* *
* Changes to these config files are not supported by BookStack and may break upon updates. * Changes to these config files are not supported by BookStack and may break upon updates.
* Configuration should be altered via the `.env` file or environment variables. * Configuration should be altered via the `.env` file or environment variables.
@ -16,10 +16,9 @@ return [
// You can provide an array of URI's that must be ignored (eg. 'api/*') // You can provide an array of URI's that must be ignored (eg. 'api/*')
'enabled' => env('DEBUGBAR_ENABLED', false), 'enabled' => env('DEBUGBAR_ENABLED', false),
'except' => [ 'except' => [
'telescope*' 'telescope*',
], ],
// DebugBar stores data for session/ajax requests. // DebugBar stores data for session/ajax requests.
// You can disable this, so the debugbar stores data in headers/session, // You can disable this, so the debugbar stores data in headers/session,
// but this can cause problems with large data collectors. // but this can cause problems with large data collectors.
@ -30,7 +29,7 @@ return [
'driver' => 'file', // redis, file, pdo, custom 'driver' => 'file', // redis, file, pdo, custom
'path' => storage_path('debugbar'), // For file driver 'path' => storage_path('debugbar'), // For file driver
'connection' => null, // Leave null for default connection (Redis/PDO) 'connection' => null, // Leave null for default connection (Redis/PDO)
'provider' => '' // Instance of StorageInterface for custom driver 'provider' => '', // Instance of StorageInterface for custom driver
], ],
// Vendor files are included by default, but can be set to false. // Vendor files are included by default, but can be set to false.
@ -98,19 +97,19 @@ return [
'hints' => true, // Show hints for common mistakes 'hints' => true, // Show hints for common mistakes
], ],
'mail' => [ 'mail' => [
'full_log' => false 'full_log' => false,
], ],
'views' => [ 'views' => [
'data' => false, //Note: Can slow down the application, because the data can be quite large.. 'data' => false, //Note: Can slow down the application, because the data can be quite large..
], ],
'route' => [ 'route' => [
'label' => true // show complete route on bar 'label' => true, // show complete route on bar
], ],
'logs' => [ 'logs' => [
'file' => null 'file' => null,
], ],
'cache' => [ 'cache' => [
'values' => true // collect cache values 'values' => true, // collect cache values
], ],
], ],

View File

@ -10,12 +10,11 @@
return [ return [
'show_warnings' => false, // Throw an Exception on warnings from dompdf 'show_warnings' => false, // Throw an Exception on warnings from dompdf
'orientation' => 'portrait', 'orientation' => 'portrait',
'defines' => [ 'defines' => [
/** /**
* The location of the DOMPDF font directory * The location of the DOMPDF font directory.
* *
* The location of the directory where DOMPDF will store fonts and font metrics * The location of the directory where DOMPDF will store fonts and font metrics
* Note: This directory must exist and be writable by the webserver process. * Note: This directory must exist and be writable by the webserver process.
@ -38,17 +37,17 @@ return [
* Times-Roman, Times-Bold, Times-BoldItalic, Times-Italic, * Times-Roman, Times-Bold, Times-BoldItalic, Times-Italic,
* Symbol, ZapfDingbats. * Symbol, ZapfDingbats.
*/ */
"DOMPDF_FONT_DIR" => storage_path('fonts/'), // advised by dompdf (https://github.com/dompdf/dompdf/pull/782) 'DOMPDF_FONT_DIR' => storage_path('fonts/'), // advised by dompdf (https://github.com/dompdf/dompdf/pull/782)
/** /**
* The location of the DOMPDF font cache directory * The location of the DOMPDF font cache directory.
* *
* This directory contains the cached font metrics for the fonts used by DOMPDF. * This directory contains the cached font metrics for the fonts used by DOMPDF.
* This directory can be the same as DOMPDF_FONT_DIR * This directory can be the same as DOMPDF_FONT_DIR
* *
* Note: This directory must exist and be writable by the webserver process. * Note: This directory must exist and be writable by the webserver process.
*/ */
"DOMPDF_FONT_CACHE" => storage_path('fonts/'), 'DOMPDF_FONT_CACHE' => storage_path('fonts/'),
/** /**
* The location of a temporary directory. * The location of a temporary directory.
@ -57,10 +56,10 @@ return [
* The temporary directory is required to download remote images and when * The temporary directory is required to download remote images and when
* using the PFDLib back end. * using the PFDLib back end.
*/ */
"DOMPDF_TEMP_DIR" => sys_get_temp_dir(), 'DOMPDF_TEMP_DIR' => sys_get_temp_dir(),
/** /**
* ==== IMPORTANT ==== * ==== IMPORTANT ====.
* *
* dompdf's "chroot": Prevents dompdf from accessing system files or other * dompdf's "chroot": Prevents dompdf from accessing system files or other
* files on the webserver. All local files opened by dompdf must be in a * files on the webserver. All local files opened by dompdf must be in a
@ -71,7 +70,7 @@ return [
* direct class use like: * direct class use like:
* $dompdf = new DOMPDF(); $dompdf->load_html($htmldata); $dompdf->render(); $pdfdata = $dompdf->output(); * $dompdf = new DOMPDF(); $dompdf->load_html($htmldata); $dompdf->render(); $pdfdata = $dompdf->output();
*/ */
"DOMPDF_CHROOT" => realpath(base_path()), 'DOMPDF_CHROOT' => realpath(base_path()),
/** /**
* Whether to use Unicode fonts or not. * Whether to use Unicode fonts or not.
@ -82,20 +81,19 @@ return [
* When enabled, dompdf can support all Unicode glyphs. Any glyphs used in a * When enabled, dompdf can support all Unicode glyphs. Any glyphs used in a
* document must be present in your fonts, however. * document must be present in your fonts, however.
*/ */
"DOMPDF_UNICODE_ENABLED" => true, 'DOMPDF_UNICODE_ENABLED' => true,
/** /**
* Whether to enable font subsetting or not. * Whether to enable font subsetting or not.
*/ */
"DOMPDF_ENABLE_FONTSUBSETTING" => false, 'DOMPDF_ENABLE_FONTSUBSETTING' => false,
/** /**
* The PDF rendering backend to use * The PDF rendering backend to use.
* *
* Valid settings are 'PDFLib', 'CPDF' (the bundled R&OS PDF class), 'GD' and * Valid settings are 'PDFLib', 'CPDF' (the bundled R&OS PDF class), 'GD' and
* 'auto'. 'auto' will look for PDFLib and use it if found, or if not it will * 'auto'. 'auto' will look for PDFLib and use it if found, or if not it will
* fall back on CPDF. 'GD' renders PDFs to graphic files. {@link * fall back on CPDF. 'GD' renders PDFs to graphic files. {@link * Canvas_Factory} ultimately determines which rendering class to instantiate
* Canvas_Factory} ultimately determines which rendering class to instantiate
* based on this setting. * based on this setting.
* *
* Both PDFLib & CPDF rendering backends provide sufficient rendering * Both PDFLib & CPDF rendering backends provide sufficient rendering
@ -117,10 +115,10 @@ return [
* @link http://www.ros.co.nz/pdf * @link http://www.ros.co.nz/pdf
* @link http://www.php.net/image * @link http://www.php.net/image
*/ */
"DOMPDF_PDF_BACKEND" => "CPDF", 'DOMPDF_PDF_BACKEND' => 'CPDF',
/** /**
* PDFlib license key * PDFlib license key.
* *
* If you are using a licensed, commercial version of PDFlib, specify * If you are using a licensed, commercial version of PDFlib, specify
* your license key here. If you are using PDFlib-Lite or are evaluating * your license key here. If you are using PDFlib-Lite or are evaluating
@ -143,7 +141,7 @@ return [
* the desired content might be different (e.g. screen or projection view of html file). * the desired content might be different (e.g. screen or projection view of html file).
* Therefore allow specification of content here. * Therefore allow specification of content here.
*/ */
"DOMPDF_DEFAULT_MEDIA_TYPE" => "print", 'DOMPDF_DEFAULT_MEDIA_TYPE' => 'print',
/** /**
* The default paper size. * The default paper size.
@ -152,18 +150,19 @@ return [
* *
* @see CPDF_Adapter::PAPER_SIZES for valid sizes ('letter', 'legal', 'A4', etc.) * @see CPDF_Adapter::PAPER_SIZES for valid sizes ('letter', 'legal', 'A4', etc.)
*/ */
"DOMPDF_DEFAULT_PAPER_SIZE" => "a4", 'DOMPDF_DEFAULT_PAPER_SIZE' => 'a4',
/** /**
* The default font family * The default font family.
* *
* Used if no suitable fonts can be found. This must exist in the font folder. * Used if no suitable fonts can be found. This must exist in the font folder.
*
* @var string * @var string
*/ */
"DOMPDF_DEFAULT_FONT" => "dejavu sans", 'DOMPDF_DEFAULT_FONT' => 'dejavu sans',
/** /**
* Image DPI setting * Image DPI setting.
* *
* This setting determines the default DPI setting for images and fonts. The * This setting determines the default DPI setting for images and fonts. The
* DPI may be overridden for inline images by explictly setting the * DPI may be overridden for inline images by explictly setting the
@ -195,10 +194,10 @@ return [
* *
* @var int * @var int
*/ */
"DOMPDF_DPI" => 96, 'DOMPDF_DPI' => 96,
/** /**
* Enable inline PHP * Enable inline PHP.
* *
* If this setting is set to true then DOMPDF will automatically evaluate * If this setting is set to true then DOMPDF will automatically evaluate
* inline PHP contained within <script type="text/php"> ... </script> tags. * inline PHP contained within <script type="text/php"> ... </script> tags.
@ -209,20 +208,20 @@ return [
* *
* @var bool * @var bool
*/ */
"DOMPDF_ENABLE_PHP" => false, 'DOMPDF_ENABLE_PHP' => false,
/** /**
* Enable inline Javascript * Enable inline Javascript.
* *
* If this setting is set to true then DOMPDF will automatically insert * If this setting is set to true then DOMPDF will automatically insert
* JavaScript code contained within <script type="text/javascript"> ... </script> tags. * JavaScript code contained within <script type="text/javascript"> ... </script> tags.
* *
* @var bool * @var bool
*/ */
"DOMPDF_ENABLE_JAVASCRIPT" => false, 'DOMPDF_ENABLE_JAVASCRIPT' => false,
/** /**
* Enable remote file access * Enable remote file access.
* *
* If this setting is set to true, DOMPDF will access remote sites for * If this setting is set to true, DOMPDF will access remote sites for
* images and CSS files as required. * images and CSS files as required.
@ -238,29 +237,27 @@ return [
* *
* @var bool * @var bool
*/ */
"DOMPDF_ENABLE_REMOTE" => true, 'DOMPDF_ENABLE_REMOTE' => true,
/** /**
* A ratio applied to the fonts height to be more like browsers' line height * A ratio applied to the fonts height to be more like browsers' line height.
*/ */
"DOMPDF_FONT_HEIGHT_RATIO" => 1.1, 'DOMPDF_FONT_HEIGHT_RATIO' => 1.1,
/** /**
* Enable CSS float * Enable CSS float.
* *
* Allows people to disabled CSS float support * Allows people to disabled CSS float support
*
* @var bool * @var bool
*/ */
"DOMPDF_ENABLE_CSS_FLOAT" => true, 'DOMPDF_ENABLE_CSS_FLOAT' => true,
/** /**
* Use the more-than-experimental HTML5 Lib parser * Use the more-than-experimental HTML5 Lib parser.
*/ */
"DOMPDF_ENABLE_HTML5PARSER" => true, 'DOMPDF_ENABLE_HTML5PARSER' => true,
], ],
]; ];

View File

@ -84,7 +84,7 @@ return [
'handler_with' => [4], 'handler_with' => [4],
'formatter' => LineFormatter::class, 'formatter' => LineFormatter::class,
'formatter_with' => [ 'formatter_with' => [
'format' => "%message%", 'format' => '%message%',
], ],
], ],
@ -101,7 +101,6 @@ return [
], ],
], ],
// Failed Login Message // Failed Login Message
// Allows a configurable message to be logged when a login request fails. // Allows a configurable message to be logged when a login request fails.
'failed_login' => [ 'failed_login' => [

View File

@ -23,7 +23,7 @@ return [
// Global "From" address & name // Global "From" address & name
'from' => [ 'from' => [
'address' => env('MAIL_FROM', 'mail@bookstackapp.com'), 'address' => env('MAIL_FROM', 'mail@bookstackapp.com'),
'name' => env('MAIL_FROM_NAME', 'BookStack') 'name' => env('MAIL_FROM_NAME', 'BookStack'),
], ],
// Email encryption protocol // Email encryption protocol

View File

@ -17,7 +17,6 @@ return [
// Queue connection configuration // Queue connection configuration
'connections' => [ 'connections' => [
'sync' => [ 'sync' => [
'driver' => 'sync', 'driver' => 'sync',
], ],

View File

@ -31,7 +31,6 @@ return [
// Overrides, in JSON format, to the configuration passed to underlying onelogin library. // Overrides, in JSON format, to the configuration passed to underlying onelogin library.
'onelogin_overrides' => env('SAML2_ONELOGIN_OVERRIDES', null), 'onelogin_overrides' => env('SAML2_ONELOGIN_OVERRIDES', null),
'onelogin' => [ 'onelogin' => [
// If 'strict' is True, then the PHP Toolkit will reject unsigned // If 'strict' is True, then the PHP Toolkit will reject unsigned
// or unencrypted messages if it expects them signed or encrypted // or unencrypted messages if it expects them signed or encrypted

View File

@ -1,6 +1,6 @@
<?php <?php
use \Illuminate\Support\Str; use Illuminate\Support\Str;
/** /**
* Session configuration options. * Session configuration options.

View File

@ -14,7 +14,7 @@ return [
'binary' => file_exists(base_path('wkhtmltopdf')) ? base_path('wkhtmltopdf') : env('WKHTMLTOPDF', false), 'binary' => file_exists(base_path('wkhtmltopdf')) ? base_path('wkhtmltopdf') : env('WKHTMLTOPDF', false),
'timeout' => false, 'timeout' => false,
'options' => [ 'options' => [
'outline' => true 'outline' => true,
], ],
'env' => [], 'env' => [],
], ],

View File

@ -25,11 +25,11 @@ class CleanupImages extends Command
*/ */
protected $description = 'Cleanup images and drawings'; protected $description = 'Cleanup images and drawings';
protected $imageService; protected $imageService;
/** /**
* Create a new command instance. * Create a new command instance.
*
* @param \BookStack\Uploads\ImageService $imageService * @param \BookStack\Uploads\ImageService $imageService
*/ */
public function __construct(ImageService $imageService) public function __construct(ImageService $imageService)
@ -63,6 +63,7 @@ class CleanupImages extends Command
$this->comment($deleteCount . ' images found that would have been deleted'); $this->comment($deleteCount . ' images found that would have been deleted');
$this->showDeletedImages($deleted); $this->showDeletedImages($deleted);
$this->comment('Run with -f or --force to perform deletions'); $this->comment('Run with -f or --force to perform deletions');
return; return;
} }

View File

@ -23,7 +23,6 @@ class ClearViews extends Command
/** /**
* Create a new command instance. * Create a new command instance.
*
*/ */
public function __construct() public function __construct()
{ {

View File

@ -54,6 +54,7 @@ class CopyShelfPermissions extends Command
if (!$cascadeAll && !$shelfSlug) { if (!$cascadeAll && !$shelfSlug) {
$this->error('Either a --slug or --all option must be provided.'); $this->error('Either a --slug or --all option must be provided.');
return; return;
} }

View File

@ -38,8 +38,9 @@ class CreateAdmin extends Command
/** /**
* Execute the console command. * Execute the console command.
* *
* @return mixed
* @throws \BookStack\Exceptions\NotFoundException * @throws \BookStack\Exceptions\NotFoundException
*
* @return mixed
*/ */
public function handle() public function handle()
{ {
@ -71,7 +72,6 @@ class CreateAdmin extends Command
return $this->error('Invalid password provided, Must be at least 5 characters'); return $this->error('Invalid password provided, Must be at least 5 characters');
} }
$user = $this->userRepo->create(['email' => $email, 'name' => $name, 'password' => $password]); $user = $this->userRepo->create(['email' => $email, 'name' => $name, 'password' => $password]);
$this->userRepo->attachSystemRole($user, 'admin'); $this->userRepo->attachSystemRole($user, 'admin');
$this->userRepo->downloadAndAssignUserAvatar($user); $this->userRepo->downloadAndAssignUserAvatar($user);

View File

@ -8,7 +8,6 @@ use Illuminate\Console\Command;
class DeleteUsers extends Command class DeleteUsers extends Command
{ {
/** /**
* The name and signature of the console command. * The name and signature of the console command.
* *
@ -47,7 +46,7 @@ class DeleteUsers extends Command
continue; continue;
} }
$this->userRepo->destroy($user); $this->userRepo->destroy($user);
++$numDeleted; $numDeleted++;
} }
$this->info("Deleted $numDeleted of $totalUsers total users."); $this->info("Deleted $numDeleted of $totalUsers total users.");
} else { } else {

View File

@ -4,7 +4,6 @@ namespace BookStack\Console\Commands;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Database\Connection; use Illuminate\Database\Connection;
use Illuminate\Support\Facades\DB;
class UpdateUrl extends Command class UpdateUrl extends Command
{ {
@ -49,7 +48,8 @@ class UpdateUrl extends Command
$urlPattern = '/https?:\/\/(.+)/'; $urlPattern = '/https?:\/\/(.+)/';
if (!preg_match($urlPattern, $oldUrl) || !preg_match($urlPattern, $newUrl)) { if (!preg_match($urlPattern, $oldUrl) || !preg_match($urlPattern, $newUrl)) {
$this->error("The given urls are expected to be full urls starting with http:// or https://"); $this->error('The given urls are expected to be full urls starting with http:// or https://');
return 1; return 1;
} }
@ -58,11 +58,11 @@ class UpdateUrl extends Command
} }
$columnsToUpdateByTable = [ $columnsToUpdateByTable = [
"attachments" => ["path"], 'attachments' => ['path'],
"pages" => ["html", "text", "markdown"], 'pages' => ['html', 'text', 'markdown'],
"images" => ["url"], 'images' => ['url'],
"settings" => ["value"], 'settings' => ['value'],
"comments" => ["html", "text"], 'comments' => ['html', 'text'],
]; ];
foreach ($columnsToUpdateByTable as $table => $columns) { foreach ($columnsToUpdateByTable as $table => $columns) {
@ -73,7 +73,7 @@ class UpdateUrl extends Command
} }
$jsonColumnsToUpdateByTable = [ $jsonColumnsToUpdateByTable = [
"settings" => ["value"], 'settings' => ['value'],
]; ];
foreach ($jsonColumnsToUpdateByTable as $table => $columns) { foreach ($jsonColumnsToUpdateByTable as $table => $columns) {
@ -85,10 +85,11 @@ class UpdateUrl extends Command
} }
} }
$this->info("URL update procedure complete."); $this->info('URL update procedure complete.');
$this->info('============================================================================'); $this->info('============================================================================');
$this->info('Be sure to run "php artisan cache:clear" to clear any old URLs in the cache.'); $this->info('Be sure to run "php artisan cache:clear" to clear any old URLs in the cache.');
$this->info('============================================================================'); $this->info('============================================================================');
return 0; return 0;
} }
@ -100,8 +101,9 @@ class UpdateUrl extends Command
{ {
$oldQuoted = $this->db->getPdo()->quote($oldUrl); $oldQuoted = $this->db->getPdo()->quote($oldUrl);
$newQuoted = $this->db->getPdo()->quote($newUrl); $newQuoted = $this->db->getPdo()->quote($newUrl);
return $this->db->table($table)->update([ return $this->db->table($table)->update([
$column => $this->db->raw("REPLACE({$column}, {$oldQuoted}, {$newQuoted})") $column => $this->db->raw("REPLACE({$column}, {$oldQuoted}, {$newQuoted})"),
]); ]);
} }
@ -112,8 +114,8 @@ class UpdateUrl extends Command
protected function checkUserOkayToProceed(string $oldUrl, string $newUrl): bool protected function checkUserOkayToProceed(string $oldUrl, string $newUrl): bool
{ {
$dangerWarning = "This will search for \"{$oldUrl}\" in your database and replace it with \"{$newUrl}\".\n"; $dangerWarning = "This will search for \"{$oldUrl}\" in your database and replace it with \"{$newUrl}\".\n";
$dangerWarning .= "Are you sure you want to proceed?"; $dangerWarning .= 'Are you sure you want to proceed?';
$backupConfirmation = "This operation could cause issues if used incorrectly. Have you made a backup of your existing database?"; $backupConfirmation = 'This operation could cause issues if used incorrectly. Have you made a backup of your existing database?';
return $this->confirm($dangerWarning) && $this->confirm($backupConfirmation); return $this->confirm($dangerWarning) && $this->confirm($backupConfirmation);
} }

View File

@ -23,7 +23,6 @@ class UpgradeDatabaseEncoding extends Command
/** /**
* Create a new command instance. * Create a new command instance.
*
*/ */
public function __construct() public function __construct()
{ {

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Console; <?php
namespace BookStack\Console;
use Illuminate\Console\Scheduling\Schedule; use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel; use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
@ -18,6 +20,7 @@ class Kernel extends ConsoleKernel
* Define the application's command schedule. * Define the application's command schedule.
* *
* @param \Illuminate\Console\Scheduling\Schedule $schedule * @param \Illuminate\Console\Scheduling\Schedule $schedule
*
* @return void * @return void
*/ */
protected function schedule(Schedule $schedule) protected function schedule(Schedule $schedule)

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Entities; <?php
namespace BookStack\Entities;
use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Book;
use BookStack\Entities\Tools\ShelfContext; use BookStack\Entities\Tools\ShelfContext;
@ -6,11 +8,11 @@ use Illuminate\View\View;
class BreadcrumbsViewComposer class BreadcrumbsViewComposer
{ {
protected $entityContextManager; protected $entityContextManager;
/** /**
* BreadcrumbsViewComposer constructor. * BreadcrumbsViewComposer constructor.
*
* @param ShelfContext $entityContextManager * @param ShelfContext $entityContextManager
*/ */
public function __construct(ShelfContext $entityContextManager) public function __construct(ShelfContext $entityContextManager)
@ -20,6 +22,7 @@ class BreadcrumbsViewComposer
/** /**
* Modify data when the view is composed. * Modify data when the view is composed.
*
* @param View $view * @param View $view
*/ */
public function compose(View $view) public function compose(View $view)

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Entities; <?php
namespace BookStack\Entities;
use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Bookshelf; use BookStack\Entities\Models\Bookshelf;
@ -8,7 +10,7 @@ use BookStack\Entities\Models\Page;
use BookStack\Entities\Models\PageRevision; use BookStack\Entities\Models\PageRevision;
/** /**
* Class EntityProvider * Class EntityProvider.
* *
* Provides access to the core entity models. * Provides access to the core entity models.
* Wrapped up in this provider since they are often used together * Wrapped up in this provider since they are often used together
@ -16,7 +18,6 @@ use BookStack\Entities\Models\PageRevision;
*/ */
class EntityProvider class EntityProvider
{ {
/** /**
* @var Bookshelf * @var Bookshelf
*/ */
@ -42,7 +43,6 @@ class EntityProvider
*/ */
public $pageRevision; public $pageRevision;
public function __construct() public function __construct()
{ {
$this->bookshelf = new Bookshelf(); $this->bookshelf = new Bookshelf();
@ -55,6 +55,7 @@ class EntityProvider
/** /**
* Fetch all core entity types as an associated array * Fetch all core entity types as an associated array
* with their basic names as the keys. * with their basic names as the keys.
*
* @return array<Entity> * @return array<Entity>
*/ */
public function all(): array public function all(): array
@ -73,6 +74,7 @@ class EntityProvider
public function get(string $type): Entity public function get(string $type): Entity
{ {
$type = strtolower($type); $type = strtolower($type);
return $this->all()[$type]; return $this->all()[$type];
} }
@ -86,6 +88,7 @@ class EntityProvider
$model = $this->get($type); $model = $this->get($type);
$morphClasses[] = $model->getMorphClass(); $morphClasses[] = $model->getMorphClass();
} }
return $morphClasses; return $morphClasses;
} }
} }

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Entities\Models; <?php
namespace BookStack\Entities\Models;
use BookStack\Uploads\Image; use BookStack\Uploads\Image;
use Exception; use Exception;
@ -8,7 +10,8 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
/** /**
* Class Book * Class Book.
*
* @property string $description * @property string $description
* @property int $image_id * @property int $image_id
* @property Image|null $cover * @property Image|null $cover
@ -30,8 +33,10 @@ class Book extends Entity implements HasCoverImage
/** /**
* Returns book cover image, if book cover not exists return default cover image. * Returns book cover image, if book cover not exists return default cover image.
*
* @param int $width - Width of the image * @param int $width - Width of the image
* @param int $height - Height of the image * @param int $height - Height of the image
*
* @return string * @return string
*/ */
public function getBookCover($width = 440, $height = 250) public function getBookCover($width = 440, $height = 250)
@ -46,11 +51,12 @@ class Book extends Entity implements HasCoverImage
} catch (Exception $err) { } catch (Exception $err) {
$cover = $default; $cover = $default;
} }
return $cover; return $cover;
} }
/** /**
* Get the cover image of the book * Get the cover image of the book.
*/ */
public function cover(): BelongsTo public function cover(): BelongsTo
{ {
@ -67,6 +73,7 @@ class Book extends Entity implements HasCoverImage
/** /**
* Get all pages within this book. * Get all pages within this book.
*
* @return HasMany * @return HasMany
*/ */
public function pages() public function pages()
@ -76,6 +83,7 @@ class Book extends Entity implements HasCoverImage
/** /**
* Get the direct child pages of this book. * Get the direct child pages of this book.
*
* @return HasMany * @return HasMany
*/ */
public function directPages() public function directPages()
@ -85,6 +93,7 @@ class Book extends Entity implements HasCoverImage
/** /**
* Get all chapters within this book. * Get all chapters within this book.
*
* @return HasMany * @return HasMany
*/ */
public function chapters() public function chapters()
@ -94,6 +103,7 @@ class Book extends Entity implements HasCoverImage
/** /**
* Get the shelves this book is contained within. * Get the shelves this book is contained within.
*
* @return BelongsToMany * @return BelongsToMany
*/ */
public function shelves() public function shelves()
@ -103,12 +113,14 @@ class Book extends Entity implements HasCoverImage
/** /**
* Get the direct child items within this book. * Get the direct child items within this book.
*
* @return Collection * @return Collection
*/ */
public function getDirectChildren(): Collection public function getDirectChildren(): Collection
{ {
$pages = $this->directPages()->visible()->get(); $pages = $this->directPages()->visible()->get();
$chapters = $this->chapters()->visible()->get(); $chapters = $this->chapters()->visible()->get();
return $pages->concat($chapters)->sortBy('priority')->sortByDesc('draft'); return $pages->concat($chapters)->sortBy('priority')->sortByDesc('draft');
} }
} }

View File

@ -1,18 +1,21 @@
<?php namespace BookStack\Entities\Models; <?php
namespace BookStack\Entities\Models;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
/** /**
* Class BookChild * Class BookChild.
*
* @property int $book_id * @property int $book_id
* @property int $priority * @property int $priority
* @property Book $book * @property Book $book
*
* @method Builder whereSlugs(string $bookSlug, string $childSlug) * @method Builder whereSlugs(string $bookSlug, string $childSlug)
*/ */
abstract class BookChild extends Entity abstract class BookChild extends Entity
{ {
/** /**
* Scope a query to find items where the the child has the given childSlug * Scope a query to find items where the the child has the given childSlug
* where its parent has the bookSlug. * where its parent has the bookSlug.

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Entities\Models; <?php
namespace BookStack\Entities\Models;
use BookStack\Uploads\Image; use BookStack\Uploads\Image;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
@ -17,6 +19,7 @@ class Bookshelf extends Entity implements HasCoverImage
/** /**
* Get the books in this shelf. * Get the books in this shelf.
* Should not be used directly since does not take into account permissions. * Should not be used directly since does not take into account permissions.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/ */
public function books() public function books()
@ -44,8 +47,10 @@ class Bookshelf extends Entity implements HasCoverImage
/** /**
* Returns BookShelf cover image, if cover does not exists return default cover image. * Returns BookShelf cover image, if cover does not exists return default cover image.
*
* @param int $width - Width of the image * @param int $width - Width of the image
* @param int $height - Height of the image * @param int $height - Height of the image
*
* @return string * @return string
*/ */
public function getBookCover($width = 440, $height = 250) public function getBookCover($width = 440, $height = 250)
@ -61,11 +66,12 @@ class Bookshelf extends Entity implements HasCoverImage
} catch (\Exception $err) { } catch (\Exception $err) {
$cover = $default; $cover = $default;
} }
return $cover; return $cover;
} }
/** /**
* Get the cover image of the shelf * Get the cover image of the shelf.
*/ */
public function cover(): BelongsTo public function cover(): BelongsTo
{ {
@ -82,7 +88,9 @@ class Bookshelf extends Entity implements HasCoverImage
/** /**
* Check if this shelf contains the given book. * Check if this shelf contains the given book.
*
* @param Book $book * @param Book $book
*
* @return bool * @return bool
*/ */
public function contains(Book $book): bool public function contains(Book $book): bool
@ -92,6 +100,7 @@ class Bookshelf extends Entity implements HasCoverImage
/** /**
* Add a book to the end of this shelf. * Add a book to the end of this shelf.
*
* @param Book $book * @param Book $book
*/ */
public function appendBook(Book $book) public function appendBook(Book $book)

View File

@ -1,9 +1,12 @@
<?php namespace BookStack\Entities\Models; <?php
namespace BookStack\Entities\Models;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
/** /**
* Class Chapter * Class Chapter.
*
* @property Collection<Page> $pages * @property Collection<Page> $pages
* @property mixed description * @property mixed description
*/ */
@ -16,7 +19,9 @@ class Chapter extends BookChild
/** /**
* Get the pages that this chapter contains. * Get the pages that this chapter contains.
*
* @param string $dir * @param string $dir
*
* @return mixed * @return mixed
*/ */
public function pages($dir = 'ASC') public function pages($dir = 'ASC')

View File

@ -1,7 +1,8 @@
<?php namespace BookStack\Entities\Models; <?php
namespace BookStack\Entities\Models;
use BookStack\Auth\User; use BookStack\Auth\User;
use BookStack\Entities\Models\Entity;
use BookStack\Interfaces\Loggable; use BookStack\Interfaces\Loggable;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
@ -12,7 +13,6 @@ use Illuminate\Database\Eloquent\Relations\MorphTo;
*/ */
class Deletion extends Model implements Loggable class Deletion extends Model implements Loggable
{ {
/** /**
* Get the related deletable record. * Get the related deletable record.
*/ */
@ -40,12 +40,14 @@ class Deletion extends Model implements Loggable
'deletable_id' => $entity->id, 'deletable_id' => $entity->id,
]); ]);
$record->save(); $record->save();
return $record; return $record;
} }
public function logDescriptor(): string public function logDescriptor(): string
{ {
$deletable = $this->deletable()->first(); $deletable = $this->deletable()->first();
return "Deletion ({$this->id}) for {$deletable->getType()} ({$deletable->id}) {$deletable->name}"; return "Deletion ({$this->id}) for {$deletable->getType()} ({$deletable->id}) {$deletable->name}";
} }

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Entities\Models; <?php
namespace BookStack\Entities\Models;
use BookStack\Actions\Activity; use BookStack\Actions\Activity;
use BookStack\Actions\Comment; use BookStack\Actions\Comment;
@ -34,8 +36,9 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @property Carbon $updated_at * @property Carbon $updated_at
* @property int $created_by * @property int $created_by
* @property int $updated_by * @property int $updated_by
* @property boolean $restricted * @property bool $restricted
* @property Collection $tags * @property Collection $tags
*
* @method static Entity|Builder visible() * @method static Entity|Builder visible()
* @method static Entity|Builder hasPermission(string $permission) * @method static Entity|Builder hasPermission(string $permission)
* @method static Builder withLastView() * @method static Builder withLastView()
@ -154,11 +157,12 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
} }
/** /**
* Get the comments for an entity * Get the comments for an entity.
*/ */
public function comments(bool $orderByCreated = true): MorphMany public function comments(bool $orderByCreated = true): MorphMany
{ {
$query = $this->morphMany(Comment::class, 'entity'); $query = $this->morphMany(Comment::class, 'entity');
return $orderByCreated ? $query->orderBy('created_at', 'asc') : $query; return $orderByCreated ? $query->orderBy('created_at', 'asc') : $query;
} }
@ -205,7 +209,7 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
/** /**
* Check if this instance or class is a certain type of entity. * Check if this instance or class is a certain type of entity.
* Examples of $type are 'page', 'book', 'chapter' * Examples of $type are 'page', 'book', 'chapter'.
*/ */
public static function isA(string $type): bool public static function isA(string $type): bool
{ {
@ -218,6 +222,7 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
public static function getType(): string public static function getType(): string
{ {
$className = array_slice(explode('\\', static::class), -1, 1)[0]; $className = array_slice(explode('\\', static::class), -1, 1)[0];
return strtolower($className); return strtolower($className);
} }
@ -229,6 +234,7 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
if (mb_strlen($this->name) <= $length) { if (mb_strlen($this->name) <= $length) {
return $this->name; return $this->name;
} }
return mb_substr($this->name, 0, $length - 3) . '...'; return mb_substr($this->name, 0, $length - 3) . '...';
} }
@ -255,7 +261,7 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
} }
/** /**
* Get the url of this entity * Get the url of this entity.
*/ */
abstract public function getUrl(string $path = '/'): string; abstract public function getUrl(string $path = '/'): string;
@ -272,6 +278,7 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
if ($this instanceof Chapter) { if ($this instanceof Chapter) {
return $this->book()->withTrashed()->first(); return $this->book()->withTrashed()->first();
} }
return null; return null;
} }
@ -285,7 +292,7 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
} }
/** /**
* Index the current entity for search * Index the current entity for search.
*/ */
public function indexForSearch() public function indexForSearch()
{ {
@ -298,6 +305,7 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
public function refreshSlug(): string public function refreshSlug(): string
{ {
$this->slug = app(SlugGenerator::class)->generate($this); $this->slug = app(SlugGenerator::class)->generate($this);
return $this->slug; return $this->slug;
} }

View File

@ -1,13 +1,11 @@
<?php <?php
namespace BookStack\Entities\Models; namespace BookStack\Entities\Models;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
interface HasCoverImage interface HasCoverImage
{ {
/** /**
* Get the cover image for this item. * Get the cover image for this item.
*/ */

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Entities\Models; <?php
namespace BookStack\Entities\Models;
use BookStack\Entities\Tools\PageContent; use BookStack\Entities\Tools\PageContent;
use BookStack\Uploads\Attachment; use BookStack\Uploads\Attachment;
@ -9,7 +11,8 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
use Permissions; use Permissions;
/** /**
* Class Page * Class Page.
*
* @property int $chapter_id * @property int $chapter_id
* @property string $html * @property string $html
* @property string $markdown * @property string $markdown
@ -41,22 +44,26 @@ class Page extends BookChild
public function scopeVisible(Builder $query): Builder public function scopeVisible(Builder $query): Builder
{ {
$query = Permissions::enforceDraftVisibilityOnQuery($query); $query = Permissions::enforceDraftVisibilityOnQuery($query);
return parent::scopeVisible($query); return parent::scopeVisible($query);
} }
/** /**
* Converts this page into a simplified array. * Converts this page into a simplified array.
*
* @return mixed * @return mixed
*/ */
public function toSimpleArray() public function toSimpleArray()
{ {
$array = array_intersect_key($this->toArray(), array_flip($this->simpleAttributes)); $array = array_intersect_key($this->toArray(), array_flip($this->simpleAttributes));
$array['url'] = $this->getUrl(); $array['url'] = $this->getUrl();
return $array; return $array;
} }
/** /**
* Get the chapter that this page is in, If applicable. * Get the chapter that this page is in, If applicable.
*
* @return BelongsTo * @return BelongsTo
*/ */
public function chapter() public function chapter()
@ -66,6 +73,7 @@ class Page extends BookChild
/** /**
* Check if this page has a chapter. * Check if this page has a chapter.
*
* @return bool * @return bool
*/ */
public function hasChapter() public function hasChapter()
@ -96,6 +104,7 @@ class Page extends BookChild
/** /**
* Get the attachments assigned to this page. * Get the attachments assigned to this page.
*
* @return HasMany * @return HasMany
*/ */
public function attachments() public function attachments()
@ -120,7 +129,8 @@ class Page extends BookChild
} }
/** /**
* Get the current revision for the page if existing * Get the current revision for the page if existing.
*
* @return PageRevision|null * @return PageRevision|null
*/ */
public function getCurrentRevision() public function getCurrentRevision()
@ -136,6 +146,7 @@ class Page extends BookChild
$refreshed = $this->refresh()->unsetRelations()->load(['tags', 'createdBy', 'updatedBy', 'ownedBy']); $refreshed = $this->refresh()->unsetRelations()->load(['tags', 'createdBy', 'updatedBy', 'ownedBy']);
$refreshed->setHidden(array_diff($refreshed->getHidden(), ['html', 'markdown'])); $refreshed->setHidden(array_diff($refreshed->getHidden(), ['html', 'markdown']));
$refreshed->html = (new PageContent($refreshed))->render(); $refreshed->html = (new PageContent($refreshed))->render();
return $refreshed; return $refreshed;
} }
} }

View File

@ -1,12 +1,14 @@
<?php namespace BookStack\Entities\Models; <?php
namespace BookStack\Entities\Models;
use BookStack\Auth\User; use BookStack\Auth\User;
use BookStack\Entities\Models\Page;
use BookStack\Model; use BookStack\Model;
use Carbon\Carbon; use Carbon\Carbon;
/** /**
* Class PageRevision * Class PageRevision.
*
* @property int $page_id * @property int $page_id
* @property string $slug * @property string $slug
* @property string $book_slug * @property string $book_slug
@ -23,7 +25,8 @@ class PageRevision extends Model
protected $fillable = ['name', 'html', 'text', 'markdown', 'summary']; protected $fillable = ['name', 'html', 'text', 'markdown', 'summary'];
/** /**
* Get the user that created the page revision * Get the user that created the page revision.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/ */
public function createdBy() public function createdBy()
@ -33,6 +36,7 @@ class PageRevision extends Model
/** /**
* Get the page this revision originates from. * Get the page this revision originates from.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/ */
public function page() public function page()
@ -42,7 +46,9 @@ class PageRevision extends Model
/** /**
* Get the url for this revision. * Get the url for this revision.
*
* @param null|string $path * @param null|string $path
*
* @return string * @return string
*/ */
public function getUrl($path = null) public function getUrl($path = null)
@ -51,11 +57,13 @@ class PageRevision extends Model
if ($path) { if ($path) {
return $url . '/' . trim($path, '/'); return $url . '/' . trim($path, '/');
} }
return $url; return $url;
} }
/** /**
* Get the previous revision for the same page if existing * Get the previous revision for the same page if existing.
*
* @return \BookStack\Entities\PageRevision|null * @return \BookStack\Entities\PageRevision|null
*/ */
public function getPrevious() public function getPrevious()
@ -74,8 +82,10 @@ class PageRevision extends Model
/** /**
* Allows checking of the exact class, Used to check entity type. * Allows checking of the exact class, Used to check entity type.
* Included here to align with entities in similar use cases. * Included here to align with entities in similar use cases.
* (Yup, Bit of an awkward hack) * (Yup, Bit of an awkward hack).
*
* @param $type * @param $type
*
* @return bool * @return bool
*/ */
public static function isA($type) public static function isA($type)

View File

@ -1,15 +1,17 @@
<?php namespace BookStack\Entities\Models; <?php
namespace BookStack\Entities\Models;
use BookStack\Model; use BookStack\Model;
class SearchTerm extends Model class SearchTerm extends Model
{ {
protected $fillable = ['term', 'entity_id', 'entity_type', 'score']; protected $fillable = ['term', 'entity_id', 'entity_type', 'score'];
public $timestamps = false; public $timestamps = false;
/** /**
* Get the entity that this term belongs to * Get the entity that this term belongs to.
*
* @return \Illuminate\Database\Eloquent\Relations\MorphTo * @return \Illuminate\Database\Eloquent\Relations\MorphTo
*/ */
public function entity() public function entity()

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Entities\Queries; <?php
namespace BookStack\Entities\Queries;
use BookStack\Auth\Permissions\PermissionService; use BookStack\Auth\Permissions\PermissionService;
use BookStack\Entities\EntityProvider; use BookStack\Entities\EntityProvider;

View File

@ -1,5 +1,6 @@
<?php namespace BookStack\Entities\Queries; <?php
namespace BookStack\Entities\Queries;
use BookStack\Actions\View; use BookStack\Actions\View;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
@ -25,5 +26,4 @@ class Popular extends EntityQuery
->pluck('viewable') ->pluck('viewable')
->filter(); ->filter();
} }
} }

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Entities\Queries; <?php
namespace BookStack\Entities\Queries;
use BookStack\Actions\View; use BookStack\Actions\View;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Entities\Queries; <?php
namespace BookStack\Entities\Queries;
use BookStack\Actions\Favourite; use BookStack\Actions\Favourite;
use Illuminate\Database\Query\JoinClause; use Illuminate\Database\Query\JoinClause;

View File

@ -2,24 +2,18 @@
namespace BookStack\Entities\Repos; namespace BookStack\Entities\Repos;
use BookStack\Actions\ActivityType;
use BookStack\Actions\TagRepo; use BookStack\Actions\TagRepo;
use BookStack\Auth\User;
use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\HasCoverImage; use BookStack\Entities\Models\HasCoverImage;
use BookStack\Exceptions\ImageUploadException; use BookStack\Exceptions\ImageUploadException;
use BookStack\Facades\Activity;
use BookStack\Uploads\ImageRepo; use BookStack\Uploads\ImageRepo;
use Illuminate\Http\UploadedFile; use Illuminate\Http\UploadedFile;
use Illuminate\Support\Collection;
class BaseRepo class BaseRepo
{ {
protected $tagRepo; protected $tagRepo;
protected $imageRepo; protected $imageRepo;
public function __construct(TagRepo $tagRepo, ImageRepo $imageRepo) public function __construct(TagRepo $tagRepo, ImageRepo $imageRepo)
{ {
$this->tagRepo = $tagRepo; $this->tagRepo = $tagRepo;
@ -27,7 +21,7 @@ class BaseRepo
} }
/** /**
* Create a new entity in the system * Create a new entity in the system.
*/ */
public function create(Entity $entity, array $input) public function create(Entity $entity, array $input)
{ {
@ -72,6 +66,7 @@ class BaseRepo
/** /**
* Update the given items' cover image, or clear it. * Update the given items' cover image, or clear it.
*
* @throws ImageUploadException * @throws ImageUploadException
* @throws \Exception * @throws \Exception
*/ */

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Entities\Repos; <?php
namespace BookStack\Entities\Repos;
use BookStack\Actions\ActivityType; use BookStack\Actions\ActivityType;
use BookStack\Actions\TagRepo; use BookStack\Actions\TagRepo;
@ -15,7 +17,6 @@ use Illuminate\Support\Collection;
class BookRepo class BookRepo
{ {
protected $baseRepo; protected $baseRepo;
protected $tagRepo; protected $tagRepo;
protected $imageRepo; protected $imageRepo;
@ -84,13 +85,14 @@ class BookRepo
} }
/** /**
* Create a new book in the system * Create a new book in the system.
*/ */
public function create(array $input): Book public function create(array $input): Book
{ {
$book = new Book(); $book = new Book();
$this->baseRepo->create($book, $input); $this->baseRepo->create($book, $input);
Activity::addForEntity($book, ActivityType::BOOK_CREATE); Activity::addForEntity($book, ActivityType::BOOK_CREATE);
return $book; return $book;
} }
@ -101,11 +103,13 @@ class BookRepo
{ {
$this->baseRepo->update($book, $input); $this->baseRepo->update($book, $input);
Activity::addForEntity($book, ActivityType::BOOK_UPDATE); Activity::addForEntity($book, ActivityType::BOOK_UPDATE);
return $book; return $book;
} }
/** /**
* Update the given book's cover image, or clear it. * Update the given book's cover image, or clear it.
*
* @throws ImageUploadException * @throws ImageUploadException
* @throws Exception * @throws Exception
*/ */
@ -116,6 +120,7 @@ class BookRepo
/** /**
* Remove a book from the system. * Remove a book from the system.
*
* @throws Exception * @throws Exception
*/ */
public function destroy(Book $book) public function destroy(Book $book)

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Entities\Repos; <?php
namespace BookStack\Entities\Repos;
use BookStack\Actions\ActivityType; use BookStack\Actions\ActivityType;
use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Book;
@ -89,6 +91,7 @@ class BookshelfRepo
$this->baseRepo->create($shelf, $input); $this->baseRepo->create($shelf, $input);
$this->updateBooks($shelf, $bookIds); $this->updateBooks($shelf, $bookIds);
Activity::addForEntity($shelf, ActivityType::BOOKSHELF_CREATE); Activity::addForEntity($shelf, ActivityType::BOOKSHELF_CREATE);
return $shelf; return $shelf;
} }
@ -104,6 +107,7 @@ class BookshelfRepo
} }
Activity::addForEntity($shelf, ActivityType::BOOKSHELF_UPDATE); Activity::addForEntity($shelf, ActivityType::BOOKSHELF_UPDATE);
return $shelf; return $shelf;
} }
@ -129,6 +133,7 @@ class BookshelfRepo
/** /**
* Update the given shelf cover image, or clear it. * Update the given shelf cover image, or clear it.
*
* @throws ImageUploadException * @throws ImageUploadException
* @throws Exception * @throws Exception
*/ */
@ -164,6 +169,7 @@ class BookshelfRepo
/** /**
* Remove a bookshelf from the system. * Remove a bookshelf from the system.
*
* @throws Exception * @throws Exception
*/ */
public function destroy(Bookshelf $shelf) public function destroy(Bookshelf $shelf)

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Entities\Repos; <?php
namespace BookStack\Entities\Repos;
use BookStack\Actions\ActivityType; use BookStack\Actions\ActivityType;
use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Book;
@ -9,11 +11,9 @@ use BookStack\Exceptions\MoveOperationException;
use BookStack\Exceptions\NotFoundException; use BookStack\Exceptions\NotFoundException;
use BookStack\Facades\Activity; use BookStack\Facades\Activity;
use Exception; use Exception;
use Illuminate\Support\Collection;
class ChapterRepo class ChapterRepo
{ {
protected $baseRepo; protected $baseRepo;
/** /**
@ -26,6 +26,7 @@ class ChapterRepo
/** /**
* Get a chapter via the slug. * Get a chapter via the slug.
*
* @throws NotFoundException * @throws NotFoundException
*/ */
public function getBySlug(string $bookSlug, string $chapterSlug): Chapter public function getBySlug(string $bookSlug, string $chapterSlug): Chapter
@ -49,6 +50,7 @@ class ChapterRepo
$chapter->priority = (new BookContents($parentBook))->getLastPriority() + 1; $chapter->priority = (new BookContents($parentBook))->getLastPriority() + 1;
$this->baseRepo->create($chapter, $input); $this->baseRepo->create($chapter, $input);
Activity::addForEntity($chapter, ActivityType::CHAPTER_CREATE); Activity::addForEntity($chapter, ActivityType::CHAPTER_CREATE);
return $chapter; return $chapter;
} }
@ -59,11 +61,13 @@ class ChapterRepo
{ {
$this->baseRepo->update($chapter, $input); $this->baseRepo->update($chapter, $input);
Activity::addForEntity($chapter, ActivityType::CHAPTER_UPDATE); Activity::addForEntity($chapter, ActivityType::CHAPTER_UPDATE);
return $chapter; return $chapter;
} }
/** /**
* Remove a chapter from the system. * Remove a chapter from the system.
*
* @throws Exception * @throws Exception
*/ */
public function destroy(Chapter $chapter) public function destroy(Chapter $chapter)
@ -77,7 +81,8 @@ class ChapterRepo
/** /**
* Move the given chapter into a new parent book. * Move the given chapter into a new parent book.
* The $parentIdentifier must be a string of the following format: * The $parentIdentifier must be a string of the following format:
* 'book:<id>' (book:5) * 'book:<id>' (book:5).
*
* @throws MoveOperationException * @throws MoveOperationException
*/ */
public function move(Chapter $chapter, string $parentIdentifier): Book public function move(Chapter $chapter, string $parentIdentifier): Book

View File

@ -1,14 +1,16 @@
<?php namespace BookStack\Entities\Repos; <?php
namespace BookStack\Entities\Repos;
use BookStack\Actions\ActivityType; use BookStack\Actions\ActivityType;
use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\Page;
use BookStack\Entities\Models\PageRevision;
use BookStack\Entities\Tools\BookContents; use BookStack\Entities\Tools\BookContents;
use BookStack\Entities\Tools\PageContent; use BookStack\Entities\Tools\PageContent;
use BookStack\Entities\Tools\TrashCan; use BookStack\Entities\Tools\TrashCan;
use BookStack\Entities\Models\Page;
use BookStack\Entities\Models\PageRevision;
use BookStack\Exceptions\MoveOperationException; use BookStack\Exceptions\MoveOperationException;
use BookStack\Exceptions\NotFoundException; use BookStack\Exceptions\NotFoundException;
use BookStack\Exceptions\PermissionsException; use BookStack\Exceptions\PermissionsException;
@ -16,11 +18,9 @@ use BookStack\Facades\Activity;
use Exception; use Exception;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
class PageRepo class PageRepo
{ {
protected $baseRepo; protected $baseRepo;
/** /**
@ -33,6 +33,7 @@ class PageRepo
/** /**
* Get a page by ID. * Get a page by ID.
*
* @throws NotFoundException * @throws NotFoundException
*/ */
public function getById(int $id, array $relations = ['book']): Page public function getById(int $id, array $relations = ['book']): Page
@ -48,6 +49,7 @@ class PageRepo
/** /**
* Get a page its book and own slug. * Get a page its book and own slug.
*
* @throws NotFoundException * @throws NotFoundException
*/ */
public function getBySlug(string $bookSlug, string $pageSlug): Page public function getBySlug(string $bookSlug, string $pageSlug): Page
@ -77,6 +79,7 @@ class PageRepo
->orderBy('created_at', 'desc') ->orderBy('created_at', 'desc')
->with('page') ->with('page')
->first(); ->first();
return $revision ? $revision->page : null; return $revision ? $revision->page : null;
} }
@ -119,6 +122,7 @@ class PageRepo
public function getUserDraft(Page $page): ?PageRevision public function getUserDraft(Page $page): ?PageRevision
{ {
$revision = $this->getUserDraftQuery($page)->first(); $revision = $this->getUserDraftQuery($page)->first();
return $revision; return $revision;
} }
@ -144,6 +148,7 @@ class PageRepo
$page->save(); $page->save();
$page->refresh()->rebuildPermissions(); $page->refresh()->rebuildPermissions();
return $page; return $page;
} }
@ -166,6 +171,7 @@ class PageRepo
$draft->refresh(); $draft->refresh();
Activity::addForEntity($draft, ActivityType::PAGE_CREATE); Activity::addForEntity($draft, ActivityType::PAGE_CREATE);
return $draft; return $draft;
} }
@ -190,7 +196,7 @@ class PageRepo
$this->getUserDraftQuery($page)->delete(); $this->getUserDraftQuery($page)->delete();
// Save a revision after updating // Save a revision after updating
$summary = trim($input['summary'] ?? ""); $summary = trim($input['summary'] ?? '');
$htmlChanged = isset($input['html']) && $input['html'] !== $oldHtml; $htmlChanged = isset($input['html']) && $input['html'] !== $oldHtml;
$nameChanged = isset($input['name']) && $input['name'] !== $oldName; $nameChanged = isset($input['name']) && $input['name'] !== $oldName;
$markdownChanged = isset($input['markdown']) && $input['markdown'] !== $oldMarkdown; $markdownChanged = isset($input['markdown']) && $input['markdown'] !== $oldMarkdown;
@ -199,6 +205,7 @@ class PageRepo
} }
Activity::addForEntity($page, ActivityType::PAGE_UPDATE); Activity::addForEntity($page, ActivityType::PAGE_UPDATE);
return $page; return $page;
} }
@ -234,6 +241,7 @@ class PageRepo
$revision->save(); $revision->save();
$this->deleteOldRevisions($page); $this->deleteOldRevisions($page);
return $revision; return $revision;
} }
@ -249,6 +257,7 @@ class PageRepo
} }
$page->fill($input); $page->fill($input);
$page->save(); $page->save();
return $page; return $page;
} }
@ -260,11 +269,13 @@ class PageRepo
} }
$draft->save(); $draft->save();
return $draft; return $draft;
} }
/** /**
* Destroy a page from the system. * Destroy a page from the system.
*
* @throws Exception * @throws Exception
*/ */
public function destroy(Page $page) public function destroy(Page $page)
@ -301,13 +312,15 @@ class PageRepo
$this->savePageRevision($page, $summary); $this->savePageRevision($page, $summary);
Activity::addForEntity($page, ActivityType::PAGE_RESTORE); Activity::addForEntity($page, ActivityType::PAGE_RESTORE);
return $page; return $page;
} }
/** /**
* Move the given page into a new parent book or chapter. * Move the given page into a new parent book or chapter.
* The $parentIdentifier must be a string of the following format: * The $parentIdentifier must be a string of the following format:
* 'book:<id>' (book:5) * 'book:<id>' (book:5).
*
* @throws MoveOperationException * @throws MoveOperationException
* @throws PermissionsException * @throws PermissionsException
*/ */
@ -327,12 +340,14 @@ class PageRepo
$page->rebuildPermissions(); $page->rebuildPermissions();
Activity::addForEntity($page, ActivityType::PAGE_MOVE); Activity::addForEntity($page, ActivityType::PAGE_MOVE);
return $parent; return $parent;
} }
/** /**
* Copy an existing page in the system. * Copy an existing page in the system.
* Optionally providing a new parent via string identifier and a new name. * Optionally providing a new parent via string identifier and a new name.
*
* @throws MoveOperationException * @throws MoveOperationException
* @throws PermissionsException * @throws PermissionsException
*/ */
@ -369,7 +384,8 @@ class PageRepo
/** /**
* Find a page parent entity via a identifier string in the format: * Find a page parent entity via a identifier string in the format:
* {type}:{id} * {type}:{id}
* Example: (book:5) * Example: (book:5).
*
* @throws MoveOperationException * @throws MoveOperationException
*/ */
protected function findParentByIdentifier(string $identifier): ?Entity protected function findParentByIdentifier(string $identifier): ?Entity
@ -383,6 +399,7 @@ class PageRepo
} }
$parentClass = $entityType === 'book' ? Book::class : Chapter::class; $parentClass = $entityType === 'book' ? Book::class : Chapter::class;
return $parentClass::visible()->where('id', '=', $entityId)->first(); return $parentClass::visible()->where('id', '=', $entityId)->first();
} }
@ -420,6 +437,7 @@ class PageRepo
$draft->book_slug = $page->book->slug; $draft->book_slug = $page->book->slug;
$draft->created_by = user()->id; $draft->created_by = user()->id;
$draft->type = 'update_draft'; $draft->type = 'update_draft';
return $draft; return $draft;
} }
@ -445,13 +463,14 @@ class PageRepo
} }
/** /**
* Get a new priority for a page * Get a new priority for a page.
*/ */
protected function getNewPriority(Page $page): int protected function getNewPriority(Page $page): int
{ {
$parent = $page->getParent(); $parent = $page->getParent();
if ($parent instanceof Chapter) { if ($parent instanceof Chapter) {
$lastPage = $parent->pages('desc')->first(); $lastPage = $parent->pages('desc')->first();
return $lastPage ? $lastPage->priority + 1 : 0; return $lastPage ? $lastPage->priority + 1 : 0;
} }

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Entities\Tools; <?php
namespace BookStack\Entities\Tools;
use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\BookChild; use BookStack\Entities\Models\BookChild;
@ -10,7 +12,6 @@ use Illuminate\Support\Collection;
class BookContents class BookContents
{ {
/** /**
* @var Book * @var Book
*/ */
@ -35,6 +36,7 @@ class BookContents
->where('chapter_id', '=', 0)->max('priority'); ->where('chapter_id', '=', 0)->max('priority');
$maxChapter = Chapter::visible()->where('book_id', '=', $this->book->id) $maxChapter = Chapter::visible()->where('book_id', '=', $this->book->id)
->max('priority'); ->max('priority');
return max($maxChapter, $maxPage, 1); return max($maxChapter, $maxPage, 1);
} }
@ -83,6 +85,7 @@ class BookContents
if (isset($entity['draft']) && $entity['draft']) { if (isset($entity['draft']) && $entity['draft']) {
return -100; return -100;
} }
return $entity['priority'] ?? 0; return $entity['priority'] ?? 0;
}; };
} }
@ -110,9 +113,10 @@ class BookContents
* +"parentChapter": false (ID of parent chapter, as string, or false) * +"parentChapter": false (ID of parent chapter, as string, or false)
* +"type": "page" (Entity type of item) * +"type": "page" (Entity type of item)
* +"book": "1" (Id of book to place item in) * +"book": "1" (Id of book to place item in)
* } * }.
* *
* Returns a list of books that were involved in the operation. * Returns a list of books that were involved in the operation.
*
* @throws SortOperationException * @throws SortOperationException
*/ */
public function sortUsingMap(Collection $sortMap): Collection public function sortUsingMap(Collection $sortMap): Collection
@ -190,6 +194,7 @@ class BookContents
/** /**
* Get the books involved in a sort. * Get the books involved in a sort.
* The given sort map should have its models loaded first. * The given sort map should have its models loaded first.
*
* @throws SortOperationException * @throws SortOperationException
*/ */
protected function getBooksInvolvedInSort(Collection $sortMap): Collection protected function getBooksInvolvedInSort(Collection $sortMap): Collection
@ -202,7 +207,7 @@ class BookContents
$books = Book::hasPermission('update')->whereIn('id', $bookIdsInvolved)->get(); $books = Book::hasPermission('update')->whereIn('id', $bookIdsInvolved)->get();
if (count($books) !== count($bookIdsInvolved)) { if (count($books) !== count($bookIdsInvolved)) {
throw new SortOperationException("Could not find all books requested in sort operation"); throw new SortOperationException('Could not find all books requested in sort operation');
} }
return $books; return $books;

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Entities\Tools; <?php
namespace BookStack\Entities\Tools;
use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Chapter;
@ -12,7 +14,6 @@ use Throwable;
class ExportFormatter class ExportFormatter
{ {
protected $imageService; protected $imageService;
/** /**
@ -26,6 +27,7 @@ class ExportFormatter
/** /**
* Convert a page to a self-contained HTML file. * Convert a page to a self-contained HTML file.
* Includes required CSS & image content. Images are base64 encoded into the HTML. * Includes required CSS & image content. Images are base64 encoded into the HTML.
*
* @throws Throwable * @throws Throwable
*/ */
public function pageToContainedHtml(Page $page) public function pageToContainedHtml(Page $page)
@ -35,11 +37,13 @@ class ExportFormatter
'page' => $page, 'page' => $page,
'format' => 'html', 'format' => 'html',
])->render(); ])->render();
return $this->containHtml($pageHtml); return $this->containHtml($pageHtml);
} }
/** /**
* Convert a chapter to a self-contained HTML file. * Convert a chapter to a self-contained HTML file.
*
* @throws Throwable * @throws Throwable
*/ */
public function chapterToContainedHtml(Chapter $chapter) public function chapterToContainedHtml(Chapter $chapter)
@ -53,11 +57,13 @@ class ExportFormatter
'pages' => $pages, 'pages' => $pages,
'format' => 'html', 'format' => 'html',
])->render(); ])->render();
return $this->containHtml($html); return $this->containHtml($html);
} }
/** /**
* Convert a book to a self-contained HTML file. * Convert a book to a self-contained HTML file.
*
* @throws Throwable * @throws Throwable
*/ */
public function bookToContainedHtml(Book $book) public function bookToContainedHtml(Book $book)
@ -68,11 +74,13 @@ class ExportFormatter
'bookChildren' => $bookTree, 'bookChildren' => $bookTree,
'format' => 'html', 'format' => 'html',
])->render(); ])->render();
return $this->containHtml($html); return $this->containHtml($html);
} }
/** /**
* Convert a page to a PDF file. * Convert a page to a PDF file.
*
* @throws Throwable * @throws Throwable
*/ */
public function pageToPdf(Page $page) public function pageToPdf(Page $page)
@ -82,11 +90,13 @@ class ExportFormatter
'page' => $page, 'page' => $page,
'format' => 'pdf', 'format' => 'pdf',
])->render(); ])->render();
return $this->htmlToPdf($html); return $this->htmlToPdf($html);
} }
/** /**
* Convert a chapter to a PDF file. * Convert a chapter to a PDF file.
*
* @throws Throwable * @throws Throwable
*/ */
public function chapterToPdf(Chapter $chapter) public function chapterToPdf(Chapter $chapter)
@ -107,6 +117,7 @@ class ExportFormatter
/** /**
* Convert a book to a PDF file. * Convert a book to a PDF file.
*
* @throws Throwable * @throws Throwable
*/ */
public function bookToPdf(Book $book) public function bookToPdf(Book $book)
@ -117,11 +128,13 @@ class ExportFormatter
'bookChildren' => $bookTree, 'bookChildren' => $bookTree,
'format' => 'pdf', 'format' => 'pdf',
])->render(); ])->render();
return $this->htmlToPdf($html); return $this->htmlToPdf($html);
} }
/** /**
* Convert normal web-page HTML to a PDF. * Convert normal web-page HTML to a PDF.
*
* @throws Exception * @throws Exception
*/ */
protected function htmlToPdf(string $html): string protected function htmlToPdf(string $html): string
@ -134,11 +147,13 @@ class ExportFormatter
} else { } else {
$pdf = DomPDF::loadHTML($containedHtml); $pdf = DomPDF::loadHTML($containedHtml);
} }
return $pdf->output(); return $pdf->output();
} }
/** /**
* Bundle of the contents of a html file to be self-contained. * Bundle of the contents of a html file to be self-contained.
*
* @throws Exception * @throws Exception
*/ */
protected function containHtml(string $htmlContent): string protected function containHtml(string $htmlContent): string
@ -195,6 +210,7 @@ class ExportFormatter
$text = html_entity_decode($text); $text = html_entity_decode($text);
// Add title // Add title
$text = $page->name . "\n\n" . $text; $text = $page->name . "\n\n" . $text;
return $text; return $text;
} }
@ -208,6 +224,7 @@ class ExportFormatter
foreach ($chapter->getVisiblePages() as $page) { foreach ($chapter->getVisiblePages() as $page) {
$text .= $this->pageToPlainText($page); $text .= $this->pageToPlainText($page);
} }
return $text; return $text;
} }
@ -225,6 +242,7 @@ class ExportFormatter
$text .= $this->pageToPlainText($bookChild); $text .= $this->pageToPlainText($bookChild);
} }
} }
return $text; return $text;
} }
@ -234,10 +252,10 @@ class ExportFormatter
public function pageToMarkdown(Page $page): string public function pageToMarkdown(Page $page): string
{ {
if ($page->markdown) { if ($page->markdown) {
return "# " . $page->name . "\n\n" . $page->markdown; return '# ' . $page->name . "\n\n" . $page->markdown;
} }
return "# " . $page->name . "\n\n" . (new HtmlToMarkdown($page->html))->convert(); return '# ' . $page->name . "\n\n" . (new HtmlToMarkdown($page->html))->convert();
} }
/** /**
@ -245,11 +263,12 @@ class ExportFormatter
*/ */
public function chapterToMarkdown(Chapter $chapter): string public function chapterToMarkdown(Chapter $chapter): string
{ {
$text = "# " . $chapter->name . "\n\n"; $text = '# ' . $chapter->name . "\n\n";
$text .= $chapter->description . "\n\n"; $text .= $chapter->description . "\n\n";
foreach ($chapter->pages as $page) { foreach ($chapter->pages as $page) {
$text .= $this->pageToMarkdown($page) . "\n\n"; $text .= $this->pageToMarkdown($page) . "\n\n";
} }
return $text; return $text;
} }
@ -259,7 +278,7 @@ class ExportFormatter
public function bookToMarkdown(Book $book): string public function bookToMarkdown(Book $book): string
{ {
$bookTree = (new BookContents($book))->getTree(false, true); $bookTree = (new BookContents($book))->getTree(false, true);
$text = "# " . $book->name . "\n\n"; $text = '# ' . $book->name . "\n\n";
foreach ($bookTree as $bookChild) { foreach ($bookTree as $bookChild) {
if ($bookChild instanceof Chapter) { if ($bookChild instanceof Chapter) {
$text .= $this->chapterToMarkdown($bookChild); $text .= $this->chapterToMarkdown($bookChild);
@ -267,6 +286,7 @@ class ExportFormatter
$text .= $this->pageToMarkdown($bookChild); $text .= $this->pageToMarkdown($bookChild);
} }
} }
return $text; return $text;
} }
} }

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Entities\Tools\Markdown; <?php
namespace BookStack\Entities\Tools\Markdown;
use League\HTMLToMarkdown\Converter\ParagraphConverter; use League\HTMLToMarkdown\Converter\ParagraphConverter;
use League\HTMLToMarkdown\ElementInterface; use League\HTMLToMarkdown\ElementInterface;

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Entities\Tools\Markdown; <?php
namespace BookStack\Entities\Tools\Markdown;
use League\CommonMark\ConfigurableEnvironmentInterface; use League\CommonMark\ConfigurableEnvironmentInterface;
use League\CommonMark\Extension\ExtensionInterface; use League\CommonMark\Extension\ExtensionInterface;
@ -7,7 +9,6 @@ use League\CommonMark\Extension\Strikethrough\StrikethroughDelimiterProcessor;
class CustomStrikeThroughExtension implements ExtensionInterface class CustomStrikeThroughExtension implements ExtensionInterface
{ {
public function register(ConfigurableEnvironmentInterface $environment) public function register(ConfigurableEnvironmentInterface $environment)
{ {
$environment->addDelimiterProcessor(new StrikethroughDelimiterProcessor()); $environment->addDelimiterProcessor(new StrikethroughDelimiterProcessor());

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Entities\Tools\Markdown; <?php
namespace BookStack\Entities\Tools\Markdown;
use League\CommonMark\ElementRendererInterface; use League\CommonMark\ElementRendererInterface;
use League\CommonMark\Extension\Strikethrough\Strikethrough; use League\CommonMark\Extension\Strikethrough\Strikethrough;

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Entities\Tools\Markdown; <?php
namespace BookStack\Entities\Tools\Markdown;
use League\HTMLToMarkdown\Converter\BlockquoteConverter; use League\HTMLToMarkdown\Converter\BlockquoteConverter;
use League\HTMLToMarkdown\Converter\CodeConverter; use League\HTMLToMarkdown\Converter\CodeConverter;
@ -27,12 +29,13 @@ class HtmlToMarkdown
} }
/** /**
* Run the conversion * Run the conversion.
*/ */
public function convert(): string public function convert(): string
{ {
$converter = new HtmlConverter($this->getConverterEnvironment()); $converter = new HtmlConverter($this->getConverterEnvironment());
$html = $this->prepareHtml($this->html); $html = $this->prepareHtml($this->html);
return $converter->convert($html); return $converter->convert($html);
} }

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Entities\Tools; <?php
namespace BookStack\Entities\Tools;
use BookStack\Entities\Models\BookChild; use BookStack\Entities\Models\BookChild;
use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\Entity;
@ -48,6 +50,7 @@ class NextPreviousContentLocator
return get_class($entity) === get_class($this->relativeBookItem) return get_class($entity) === get_class($this->relativeBookItem)
&& $entity->id === $this->relativeBookItem->id; && $entity->id === $this->relativeBookItem->id;
}); });
return $index === false ? null : $index; return $index === false ? null : $index;
} }
@ -64,6 +67,7 @@ class NextPreviousContentLocator
$childPages = $item->visible_pages ?? []; $childPages = $item->visible_pages ?? [];
$flatOrdered = $flatOrdered->concat($childPages); $flatOrdered = $flatOrdered->concat($childPages);
} }
return $flatOrdered; return $flatOrdered;
} }
} }

View File

@ -1,12 +1,14 @@
<?php namespace BookStack\Entities\Tools; <?php
namespace BookStack\Entities\Tools;
use BookStack\Entities\Models\Page; use BookStack\Entities\Models\Page;
use BookStack\Entities\Tools\Markdown\CustomStrikeThroughExtension; use BookStack\Entities\Tools\Markdown\CustomStrikeThroughExtension;
use BookStack\Exceptions\ImageUploadException; use BookStack\Exceptions\ImageUploadException;
use BookStack\Facades\Theme; use BookStack\Facades\Theme;
use BookStack\Theming\ThemeEvents; use BookStack\Theming\ThemeEvents;
use BookStack\Util\HtmlContentFilter;
use BookStack\Uploads\ImageRepo; use BookStack\Uploads\ImageRepo;
use BookStack\Util\HtmlContentFilter;
use DOMDocument; use DOMDocument;
use DOMNodeList; use DOMNodeList;
use DOMXPath; use DOMXPath;
@ -18,7 +20,6 @@ use League\CommonMark\Extension\TaskList\TaskListExtension;
class PageContent class PageContent
{ {
protected $page; protected $page;
/** /**
@ -62,11 +63,12 @@ class PageContent
$environment->addExtension(new CustomStrikeThroughExtension()); $environment->addExtension(new CustomStrikeThroughExtension());
$environment = Theme::dispatch(ThemeEvents::COMMONMARK_ENVIRONMENT_CONFIGURE, $environment) ?? $environment; $environment = Theme::dispatch(ThemeEvents::COMMONMARK_ENVIRONMENT_CONFIGURE, $environment) ?? $environment;
$converter = new CommonMarkConverter([], $environment); $converter = new CommonMarkConverter([], $environment);
return $converter->convertToHtml($markdown); return $converter->convertToHtml($markdown);
} }
/** /**
* Convert all base64 image data to saved images * Convert all base64 image data to saved images.
*/ */
public function extractBase64Images(Page $page, string $htmlText): string public function extractBase64Images(Page $page, string $htmlText): string
{ {
@ -97,6 +99,7 @@ class PageContent
// Save image from data with a random name // Save image from data with a random name
$imageName = 'embedded-image-' . Str::random(8) . '.' . $extension; $imageName = 'embedded-image-' . Str::random(8) . '.' . $extension;
try { try {
$image = $imageRepo->saveNewFromData($imageName, base64_decode($base64ImageData), 'gallery', $page->id); $image = $imageRepo->saveNewFromData($imageName, base64_decode($base64ImageData), 'gallery', $page->id);
$imageNode->setAttribute('src', $image->url); $imageNode->setAttribute('src', $image->url);
@ -171,7 +174,7 @@ class PageContent
/** /**
* Set a unique id on the given DOMElement. * Set a unique id on the given DOMElement.
* A map for existing ID's should be passed in to check for current existence. * A map for existing ID's should be passed in to check for current existence.
* Returns a pair of strings in the format [old_id, new_id] * Returns a pair of strings in the format [old_id, new_id].
*/ */
protected function setUniqueId(\DOMNode $element, array &$idMap): array protected function setUniqueId(\DOMNode $element, array &$idMap): array
{ {
@ -183,6 +186,7 @@ class PageContent
$existingId = $element->getAttribute('id'); $existingId = $element->getAttribute('id');
if (strpos($existingId, 'bkmrk') === 0 && !isset($idMap[$existingId])) { if (strpos($existingId, 'bkmrk') === 0 && !isset($idMap[$existingId])) {
$idMap[$existingId] = true; $idMap[$existingId] = true;
return [$existingId, $existingId]; return [$existingId, $existingId];
} }
@ -200,6 +204,7 @@ class PageContent
$element->setAttribute('id', $newId); $element->setAttribute('id', $newId);
$idMap[$newId] = true; $idMap[$newId] = true;
return [$existingId, $newId]; return [$existingId, $newId];
} }
@ -209,11 +214,12 @@ class PageContent
protected function toPlainText(): string protected function toPlainText(): string
{ {
$html = $this->render(true); $html = $this->render(true);
return html_entity_decode(strip_tags($html)); return html_entity_decode(strip_tags($html));
} }
/** /**
* Render the page for viewing * Render the page for viewing.
*/ */
public function render(bool $blankIncludes = false): string public function render(bool $blankIncludes = false): string
{ {
@ -233,7 +239,7 @@ class PageContent
} }
/** /**
* Parse the headers on the page to get a navigation menu * Parse the headers on the page to get a navigation menu.
*/ */
public function getNavigation(string $htmlContent): array public function getNavigation(string $htmlContent): array
{ {
@ -243,7 +249,7 @@ class PageContent
$doc = $this->loadDocumentFromHtml($htmlContent); $doc = $this->loadDocumentFromHtml($htmlContent);
$xPath = new DOMXPath($doc); $xPath = new DOMXPath($doc);
$headers = $xPath->query("//h1|//h2|//h3|//h4|//h5|//h6"); $headers = $xPath->query('//h1|//h2|//h3|//h4|//h5|//h6');
return $headers ? $this->headerNodesToLevelList($headers) : []; return $headers ? $this->headerNodesToLevelList($headers) : [];
} }
@ -272,6 +278,7 @@ class PageContent
$levelChange = ($tree->pluck('level')->min() - 1); $levelChange = ($tree->pluck('level')->min() - 1);
$tree = $tree->map(function ($header) use ($levelChange) { $tree = $tree->map(function ($header) use ($levelChange) {
$header['level'] -= ($levelChange); $header['level'] -= ($levelChange);
return $header; return $header;
}); });
@ -325,7 +332,6 @@ class PageContent
return $html; return $html;
} }
/** /**
* Fetch the content from a specific section of the given page. * Fetch the content from a specific section of the given page.
*/ */
@ -365,6 +371,7 @@ class PageContent
$doc = new DOMDocument(); $doc = new DOMDocument();
$html = '<body>' . $html . '</body>'; $html = '<body>' . $html . '</body>';
$doc->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8')); $doc->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
return $doc; return $doc;
} }
} }

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Entities\Tools; <?php
namespace BookStack\Entities\Tools;
use BookStack\Entities\Models\Page; use BookStack\Entities\Models\Page;
use BookStack\Entities\Models\PageRevision; use BookStack\Entities\Models\PageRevision;
@ -7,7 +9,6 @@ use Illuminate\Database\Eloquent\Builder;
class PageEditActivity class PageEditActivity
{ {
protected $page; protected $page;
/** /**
@ -20,6 +21,7 @@ class PageEditActivity
/** /**
* Check if there's active editing being performed on this page. * Check if there's active editing being performed on this page.
*
* @return bool * @return bool
*/ */
public function hasActiveEditing(): bool public function hasActiveEditing(): bool
@ -37,12 +39,15 @@ class PageEditActivity
$userMessage = $count > 1 ? trans('entities.pages_draft_edit_active.start_a', ['count' => $count]) : trans('entities.pages_draft_edit_active.start_b', ['userName' => $pageDraftEdits->first()->createdBy->name]); $userMessage = $count > 1 ? trans('entities.pages_draft_edit_active.start_a', ['count' => $count]) : trans('entities.pages_draft_edit_active.start_b', ['userName' => $pageDraftEdits->first()->createdBy->name]);
$timeMessage = trans('entities.pages_draft_edit_active.time_b', ['minCount'=> 60]); $timeMessage = trans('entities.pages_draft_edit_active.time_b', ['minCount'=> 60]);
return trans('entities.pages_draft_edit_active.message', ['start' => $userMessage, 'time' => $timeMessage]); return trans('entities.pages_draft_edit_active.message', ['start' => $userMessage, 'time' => $timeMessage]);
} }
/** /**
* Get the message to show when the user will be editing one of their drafts. * Get the message to show when the user will be editing one of their drafts.
*
* @param PageRevision $draft * @param PageRevision $draft
*
* @return string * @return string
*/ */
public function getEditingActiveDraftMessage(PageRevision $draft): string public function getEditingActiveDraftMessage(PageRevision $draft): string
@ -51,6 +56,7 @@ class PageEditActivity
if ($draft->page->updated_at->timestamp <= $draft->updated_at->timestamp) { if ($draft->page->updated_at->timestamp <= $draft->updated_at->timestamp) {
return $message; return $message;
} }
return $message . "\n" . trans('entities.pages_draft_edited_notification'); return $message . "\n" . trans('entities.pages_draft_edited_notification');
} }

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Entities\Tools; <?php
namespace BookStack\Entities\Tools;
use BookStack\Actions\ActivityType; use BookStack\Actions\ActivityType;
use BookStack\Auth\User; use BookStack\Auth\User;
@ -9,7 +11,6 @@ use Illuminate\Support\Collection;
class PermissionsUpdater class PermissionsUpdater
{ {
/** /**
* Update an entities permissions from a permission form submit request. * Update an entities permissions from a permission form submit request.
*/ */

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Entities\Tools; <?php
namespace BookStack\Entities\Tools;
use BookStack\Entities\EntityProvider; use BookStack\Entities\EntityProvider;
use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\Entity;
@ -17,14 +19,12 @@ class SearchIndex
*/ */
protected $entityProvider; protected $entityProvider;
public function __construct(SearchTerm $searchTerm, EntityProvider $entityProvider) public function __construct(SearchTerm $searchTerm, EntityProvider $entityProvider)
{ {
$this->searchTerm = $searchTerm; $this->searchTerm = $searchTerm;
$this->entityProvider = $entityProvider; $this->entityProvider = $entityProvider;
} }
/** /**
* Index the given entity. * Index the given entity.
*/ */
@ -42,7 +42,8 @@ class SearchIndex
} }
/** /**
* Index multiple Entities at once * Index multiple Entities at once.
*
* @param Entity[] $entities * @param Entity[] $entities
*/ */
protected function indexEntities(array $entities) protected function indexEntities(array $entities)
@ -111,7 +112,7 @@ class SearchIndex
foreach ($tokenMap as $token => $count) { foreach ($tokenMap as $token => $count) {
$terms[] = [ $terms[] = [
'term' => $token, 'term' => $token,
'score' => $count * $scoreAdjustment 'score' => $count * $scoreAdjustment,
]; ];
} }

View File

@ -1,10 +1,11 @@
<?php namespace BookStack\Entities\Tools; <?php
namespace BookStack\Entities\Tools;
use Illuminate\Http\Request; use Illuminate\Http\Request;
class SearchOptions class SearchOptions
{ {
/** /**
* @var array * @var array
*/ */
@ -35,6 +36,7 @@ class SearchOptions
foreach ($decoded as $type => $value) { foreach ($decoded as $type => $value) {
$instance->$type = $value; $instance->$type = $value;
} }
return $instance; return $instance;
} }
@ -67,6 +69,7 @@ class SearchOptions
if (isset($inputs['types']) && count($inputs['types']) < 4) { if (isset($inputs['types']) && count($inputs['types']) < 4) {
$instance->filters['type'] = implode('|', $inputs['types']); $instance->filters['type'] = implode('|', $inputs['types']);
} }
return $instance; return $instance;
} }
@ -79,13 +82,13 @@ class SearchOptions
'searches' => [], 'searches' => [],
'exacts' => [], 'exacts' => [],
'tags' => [], 'tags' => [],
'filters' => [] 'filters' => [],
]; ];
$patterns = [ $patterns = [
'exacts' => '/"(.*?)"/', 'exacts' => '/"(.*?)"/',
'tags' => '/\[(.*?)\]/', 'tags' => '/\[(.*?)\]/',
'filters' => '/\{(.*?)\}/' 'filters' => '/\{(.*?)\}/',
]; ];
// Parse special terms // Parse special terms

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Entities\Tools; <?php
namespace BookStack\Entities\Tools;
use BookStack\Auth\Permissions\PermissionService; use BookStack\Auth\Permissions\PermissionService;
use BookStack\Auth\User; use BookStack\Auth\User;
@ -13,7 +15,6 @@ use Illuminate\Support\Str;
class SearchRunner class SearchRunner
{ {
/** /**
* @var EntityProvider * @var EntityProvider
*/ */
@ -29,14 +30,13 @@ class SearchRunner
*/ */
protected $permissionService; protected $permissionService;
/** /**
* Acceptable operators to be used in a query * Acceptable operators to be used in a query.
*
* @var array * @var array
*/ */
protected $queryOperators = ['<=', '>=', '=', '<', '>', 'like', '!=']; protected $queryOperators = ['<=', '>=', '=', '<', '>', 'like', '!='];
public function __construct(EntityProvider $entityProvider, Connection $db, PermissionService $permissionService) public function __construct(EntityProvider $entityProvider, Connection $db, PermissionService $permissionService)
{ {
$this->entityProvider = $entityProvider; $this->entityProvider = $entityProvider;
@ -85,9 +85,8 @@ class SearchRunner
]; ];
} }
/** /**
* Search a book for entities * Search a book for entities.
*/ */
public function searchBook(int $bookId, string $searchString): Collection public function searchBook(int $bookId, string $searchString): Collection
{ {
@ -108,12 +107,13 @@ class SearchRunner
} }
/** /**
* Search a chapter for entities * Search a chapter for entities.
*/ */
public function searchChapter(int $chapterId, string $searchString): Collection public function searchChapter(int $chapterId, string $searchString): Collection
{ {
$opts = SearchOptions::fromString($searchString); $opts = SearchOptions::fromString($searchString);
$pages = $this->buildEntitySearchQuery($opts, 'page')->where('chapter_id', '=', $chapterId)->take(20)->get(); $pages = $this->buildEntitySearchQuery($opts, 'page')->where('chapter_id', '=', $chapterId)->take(20)->get();
return $pages->sortByDesc('score'); return $pages->sortByDesc('score');
} }
@ -121,6 +121,7 @@ class SearchRunner
* Search across a particular entity type. * Search across a particular entity type.
* Setting getCount = true will return the total * Setting getCount = true will return the total
* matching instead of the items themselves. * matching instead of the items themselves.
*
* @return \Illuminate\Database\Eloquent\Collection|int|static[] * @return \Illuminate\Database\Eloquent\Collection|int|static[]
*/ */
protected function searchEntityTable(SearchOptions $searchOpts, string $entityType = 'page', int $page = 1, int $count = 20, string $action = 'view', bool $getCount = false) protected function searchEntityTable(SearchOptions $searchOpts, string $entityType = 'page', int $page = 1, int $count = 20, string $action = 'view', bool $getCount = false)
@ -131,11 +132,12 @@ class SearchRunner
} }
$query = $query->skip(($page - 1) * $count)->take($count); $query = $query->skip(($page - 1) * $count)->take($count);
return $query->get(); return $query->get();
} }
/** /**
* Create a search query for an entity * Create a search query for an entity.
*/ */
protected function buildEntitySearchQuery(SearchOptions $searchOpts, string $entityType = 'page', string $action = 'view'): EloquentBuilder protected function buildEntitySearchQuery(SearchOptions $searchOpts, string $entityType = 'page', string $action = 'view'): EloquentBuilder
{ {
@ -191,6 +193,7 @@ class SearchRunner
foreach ($this->queryOperators as $operator) { foreach ($this->queryOperators as $operator) {
$escapedOperators[] = preg_quote($operator); $escapedOperators[] = preg_quote($operator);
} }
return join('|', $escapedOperators); return join('|', $escapedOperators);
} }
@ -199,7 +202,7 @@ class SearchRunner
*/ */
protected function applyTagSearch(EloquentBuilder $query, string $tagTerm): EloquentBuilder protected function applyTagSearch(EloquentBuilder $query, string $tagTerm): EloquentBuilder
{ {
preg_match("/^(.*?)((".$this->getRegexEscapedOperators().")(.*?))?$/", $tagTerm, $tagSplit); preg_match('/^(.*?)((' . $this->getRegexEscapedOperators() . ')(.*?))?$/', $tagTerm, $tagSplit);
$query->whereHas('tags', function (EloquentBuilder $query) use ($tagSplit) { $query->whereHas('tags', function (EloquentBuilder $query) use ($tagSplit) {
$tagName = $tagSplit[1]; $tagName = $tagSplit[1];
$tagOperator = count($tagSplit) > 2 ? $tagSplit[3] : ''; $tagOperator = count($tagSplit) > 2 ? $tagSplit[3] : '';
@ -222,13 +225,13 @@ class SearchRunner
$query->where('name', '=', $tagName); $query->where('name', '=', $tagName);
} }
}); });
return $query; return $query;
} }
/** /**
* Custom entity search filters * Custom entity search filters.
*/ */
protected function filterUpdatedAfter(EloquentBuilder $query, Entity $model, $input) protected function filterUpdatedAfter(EloquentBuilder $query, Entity $model, $input)
{ {
try { try {
@ -338,11 +341,9 @@ class SearchRunner
} }
} }
/** /**
* Sorting filter options * Sorting filter options.
*/ */
protected function sortByLastCommented(EloquentBuilder $query, Entity $model) protected function sortByLastCommented(EloquentBuilder $query, Entity $model)
{ {
$commentsTable = $this->db->getTablePrefix() . 'comments'; $commentsTable = $this->db->getTablePrefix() . 'comments';

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Entities\Tools; <?php
namespace BookStack\Entities\Tools;
use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Bookshelf; use BookStack\Entities\Models\Bookshelf;

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Entities\Tools; <?php
namespace BookStack\Entities\Tools;
use BookStack\Entities\EntityProvider; use BookStack\Entities\EntityProvider;
use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Book;
@ -7,13 +9,12 @@ use Illuminate\Support\Collection;
class SiblingFetcher class SiblingFetcher
{ {
/** /**
* Search among the siblings of the entity of given type and id. * Search among the siblings of the entity of given type and id.
*/ */
public function fetch(string $entityType, int $entityId): Collection public function fetch(string $entityType, int $entityId): Collection
{ {
$entity = (new EntityProvider)->get($entityType)->visible()->findOrFail($entityId); $entity = (new EntityProvider())->get($entityType)->visible()->findOrFail($entityId);
$entities = []; $entities = [];
// Page in chapter // Page in chapter
@ -29,7 +30,7 @@ class SiblingFetcher
// Book // Book
// Gets just the books in a shelf if shelf is in context // Gets just the books in a shelf if shelf is in context
if ($entity->isA('book')) { if ($entity->isA('book')) {
$contextShelf = (new ShelfContext)->getContextualShelfForBook($entity); $contextShelf = (new ShelfContext())->getContextualShelfForBook($entity);
if ($contextShelf) { if ($contextShelf) {
$entities = $contextShelf->visibleBooks()->get(); $entities = $contextShelf->visibleBooks()->get();
} else { } else {

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Entities\Tools; <?php
namespace BookStack\Entities\Tools;
use BookStack\Entities\Models\BookChild; use BookStack\Entities\Models\BookChild;
use BookStack\Interfaces\Sluggable; use BookStack\Interfaces\Sluggable;
@ -6,7 +8,6 @@ use Illuminate\Support\Str;
class SlugGenerator class SlugGenerator
{ {
/** /**
* Generate a fresh slug for the given entity. * Generate a fresh slug for the given entity.
* The slug will generated so it does not conflict within the same parent item. * The slug will generated so it does not conflict within the same parent item.
@ -17,6 +18,7 @@ class SlugGenerator
while ($this->slugInUse($slug, $model)) { while ($this->slugInUse($slug, $model)) {
$slug .= '-' . Str::random(3); $slug .= '-' . Str::random(3);
} }
return $slug; return $slug;
} }
@ -26,9 +28,10 @@ class SlugGenerator
protected function formatNameAsSlug(string $name): string protected function formatNameAsSlug(string $name): string
{ {
$slug = Str::slug($name); $slug = Str::slug($name);
if ($slug === "") { if ($slug === '') {
$slug = substr(md5(rand(1, 500)), 0, 5); $slug = substr(md5(rand(1, 500)), 0, 5);
} }
return $slug; return $slug;
} }

View File

@ -1,11 +1,13 @@
<?php namespace BookStack\Entities\Tools; <?php
namespace BookStack\Entities\Tools;
use BookStack\Entities\EntityProvider;
use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Bookshelf; use BookStack\Entities\Models\Bookshelf;
use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Deletion; use BookStack\Entities\Models\Deletion;
use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\Entity;
use BookStack\Entities\EntityProvider;
use BookStack\Entities\Models\HasCoverImage; use BookStack\Entities\Models\HasCoverImage;
use BookStack\Entities\Models\Page; use BookStack\Entities\Models\Page;
use BookStack\Exceptions\NotifyException; use BookStack\Exceptions\NotifyException;
@ -17,7 +19,6 @@ use Illuminate\Support\Carbon;
class TrashCan class TrashCan
{ {
/** /**
* Send a shelf to the recycle bin. * Send a shelf to the recycle bin.
*/ */
@ -29,6 +30,7 @@ class TrashCan
/** /**
* Send a book to the recycle bin. * Send a book to the recycle bin.
*
* @throws Exception * @throws Exception
*/ */
public function softDestroyBook(Book $book) public function softDestroyBook(Book $book)
@ -48,6 +50,7 @@ class TrashCan
/** /**
* Send a chapter to the recycle bin. * Send a chapter to the recycle bin.
*
* @throws Exception * @throws Exception
*/ */
public function softDestroyChapter(Chapter $chapter, bool $recordDelete = true) public function softDestroyChapter(Chapter $chapter, bool $recordDelete = true)
@ -67,6 +70,7 @@ class TrashCan
/** /**
* Send a page to the recycle bin. * Send a page to the recycle bin.
*
* @throws Exception * @throws Exception
*/ */
public function softDestroyPage(Page $page, bool $recordDelete = true) public function softDestroyPage(Page $page, bool $recordDelete = true)
@ -89,18 +93,21 @@ class TrashCan
/** /**
* Remove a bookshelf from the system. * Remove a bookshelf from the system.
*
* @throws Exception * @throws Exception
*/ */
protected function destroyShelf(Bookshelf $shelf): int protected function destroyShelf(Bookshelf $shelf): int
{ {
$this->destroyCommonRelations($shelf); $this->destroyCommonRelations($shelf);
$shelf->forceDelete(); $shelf->forceDelete();
return 1; return 1;
} }
/** /**
* Remove a book from the system. * Remove a book from the system.
* Destroys any child chapters and pages. * Destroys any child chapters and pages.
*
* @throws Exception * @throws Exception
*/ */
protected function destroyBook(Book $book): int protected function destroyBook(Book $book): int
@ -120,12 +127,14 @@ class TrashCan
$this->destroyCommonRelations($book); $this->destroyCommonRelations($book);
$book->forceDelete(); $book->forceDelete();
return $count + 1; return $count + 1;
} }
/** /**
* Remove a chapter from the system. * Remove a chapter from the system.
* Destroys all pages within. * Destroys all pages within.
*
* @throws Exception * @throws Exception
*/ */
protected function destroyChapter(Chapter $chapter): int protected function destroyChapter(Chapter $chapter): int
@ -141,11 +150,13 @@ class TrashCan
$this->destroyCommonRelations($chapter); $this->destroyCommonRelations($chapter);
$chapter->forceDelete(); $chapter->forceDelete();
return $count + 1; return $count + 1;
} }
/** /**
* Remove a page from the system. * Remove a page from the system.
*
* @throws Exception * @throws Exception
*/ */
protected function destroyPage(Page $page): int protected function destroyPage(Page $page): int
@ -160,6 +171,7 @@ class TrashCan
} }
$page->forceDelete(); $page->forceDelete();
return 1; return 1;
} }
@ -172,7 +184,7 @@ class TrashCan
$counts = []; $counts = [];
/** @var Entity $instance */ /** @var Entity $instance */
foreach ((new EntityProvider)->all() as $key => $instance) { foreach ((new EntityProvider())->all() as $key => $instance) {
$counts[$key] = $instance->newQuery()->onlyTrashed()->count(); $counts[$key] = $instance->newQuery()->onlyTrashed()->count();
} }
@ -181,6 +193,7 @@ class TrashCan
/** /**
* Destroy all items that have pending deletions. * Destroy all items that have pending deletions.
*
* @throws Exception * @throws Exception
*/ */
public function empty(): int public function empty(): int
@ -190,11 +203,13 @@ class TrashCan
foreach ($deletions as $deletion) { foreach ($deletions as $deletion) {
$deleteCount += $this->destroyFromDeletion($deletion); $deleteCount += $this->destroyFromDeletion($deletion);
} }
return $deleteCount; return $deleteCount;
} }
/** /**
* Destroy an element from the given deletion model. * Destroy an element from the given deletion model.
*
* @throws Exception * @throws Exception
*/ */
public function destroyFromDeletion(Deletion $deletion): int public function destroyFromDeletion(Deletion $deletion): int
@ -207,11 +222,13 @@ class TrashCan
$count = $this->destroyEntity($deletion->deletable); $count = $this->destroyEntity($deletion->deletable);
} }
$deletion->delete(); $deletion->delete();
return $count; return $count;
} }
/** /**
* Restore the content within the given deletion. * Restore the content within the given deletion.
*
* @throws Exception * @throws Exception
*/ */
public function restoreFromDeletion(Deletion $deletion): int public function restoreFromDeletion(Deletion $deletion): int
@ -229,6 +246,7 @@ class TrashCan
} }
$deletion->delete(); $deletion->delete();
return $restoreCount; return $restoreCount;
} }
@ -236,6 +254,7 @@ class TrashCan
* Automatically clear old content from the recycle bin * Automatically clear old content from the recycle bin
* depending on the configured lifetime. * depending on the configured lifetime.
* Returns the total number of deleted elements. * Returns the total number of deleted elements.
*
* @throws Exception * @throws Exception
*/ */
public function autoClearOld(): int public function autoClearOld(): int
@ -287,6 +306,7 @@ class TrashCan
/** /**
* Destroy the given entity. * Destroy the given entity.
*
* @throws Exception * @throws Exception
*/ */
protected function destroyEntity(Entity $entity): int protected function destroyEntity(Entity $entity): int

View File

@ -4,5 +4,4 @@ namespace BookStack\Exceptions;
class ApiAuthException extends UnauthorizedException class ApiAuthException extends UnauthorizedException
{ {
} }

View File

@ -1,6 +1,7 @@
<?php namespace BookStack\Exceptions; <?php
namespace BookStack\Exceptions;
class ConfirmationEmailException extends NotifyException class ConfirmationEmailException extends NotifyException
{ {
} }

View File

@ -1,6 +1,7 @@
<?php namespace BookStack\Exceptions; <?php
namespace BookStack\Exceptions;
class FileUploadException extends PrettyException class FileUploadException extends PrettyException
{ {
} }

View File

@ -35,9 +35,10 @@ class Handler extends ExceptionHandler
* Report or log an exception. * Report or log an exception.
* *
* @param Exception $exception * @param Exception $exception
* @return void
* *
* @throws Exception * @throws Exception
*
* @return void
*/ */
public function report(Exception $exception) public function report(Exception $exception)
{ {
@ -49,6 +50,7 @@ class Handler extends ExceptionHandler
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param Exception $e * @param Exception $e
*
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function render($request, Exception $e) public function render($request, Exception $e)
@ -83,7 +85,7 @@ class Handler extends ExceptionHandler
$responseData = [ $responseData = [
'error' => [ 'error' => [
'message' => $e->getMessage(), 'message' => $e->getMessage(),
] ],
]; ];
if ($e instanceof ValidationException) { if ($e instanceof ValidationException) {
@ -92,6 +94,7 @@ class Handler extends ExceptionHandler
} }
$responseData['error']['code'] = $code; $responseData['error']['code'] = $code;
return new JsonResponse($responseData, $code, $headers); return new JsonResponse($responseData, $code, $headers);
} }
@ -100,6 +103,7 @@ class Handler extends ExceptionHandler
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param \Illuminate\Auth\AuthenticationException $exception * @param \Illuminate\Auth\AuthenticationException $exception
*
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
protected function unauthenticated($request, AuthenticationException $exception) protected function unauthenticated($request, AuthenticationException $exception)
@ -116,6 +120,7 @@ class Handler extends ExceptionHandler
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param \Illuminate\Validation\ValidationException $exception * @param \Illuminate\Validation\ValidationException $exception
*
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
*/ */
protected function invalidJson($request, ValidationException $exception) protected function invalidJson($request, ValidationException $exception)

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Exceptions; <?php
namespace BookStack\Exceptions;
use Exception; use Exception;

View File

@ -1,6 +1,7 @@
<?php namespace BookStack\Exceptions; <?php
namespace BookStack\Exceptions;
class ImageUploadException extends PrettyException class ImageUploadException extends PrettyException
{ {
} }

View File

@ -1,10 +1,11 @@
<?php namespace BookStack\Exceptions; <?php
namespace BookStack\Exceptions;
use Exception; use Exception;
class JsonDebugException extends Exception class JsonDebugException extends Exception
{ {
protected $data; protected $data;
/** /**

Some files were not shown because too many files have changed in this diff Show More