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

@ -11,16 +11,15 @@ use Illuminate\Support\Str;
/** /**
* @property string $type * @property string $type
* @property User $user * @property User $user
* @property Entity $entity * @property Entity $entity
* @property string $detail * @property string $detail
* @property string $entity_type * @property string $entity_type
* @property int $entity_id * @property int $entity_id
* @property int $user_id * @property int $user_id
*/ */
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 = '')
@ -54,7 +57,7 @@ class ActivityService
{ {
return $this->activity->newInstance()->forceFill([ return $this->activity->newInstance()->forceFill([
'type' => strtolower($type), 'type' => strtolower($type),
'user_id' => user()->id, 'user_id' => user()->id,
]); ]);
} }
@ -67,8 +70,8 @@ class ActivityService
{ {
$entity->activity()->update([ $entity->activity()->update([
'detail' => $entity->name, 'detail' => $entity->name,
'entity_id' => null, 'entity_id' => null,
'entity_type' => null, 'entity_type' => null,
]); ]);
} }
@ -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;
} }
@ -75,8 +77,8 @@ class CommentRepo
public function commentToHtml(string $commentText): string public function commentToHtml(string $commentText): string
{ {
$converter = new CommonMarkConverter([ $converter = new CommonMarkConverter([
'html_input' => 'strip', 'html_input' => 'strip',
'max_nesting_level' => 10, 'max_nesting_level' => 10,
'allow_unsafe_links' => false, 'allow_unsafe_links' => false,
]); ]);
@ -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
{ {
@ -21,7 +23,7 @@ class Tag extends Model
*/ */
public function nameUrl(): string public function nameUrl(): string
{ {
return url('/search?term=%5B' . urlencode($this->name) .'%5D'); return url('/search?term=%5B' . urlencode($this->name) . '%5D');
} }
/** /**
@ -29,6 +31,6 @@ class Tag extends Model
*/ */
public function valueUrl(): string public function valueUrl(): string
{ {
return url('/search?term=%5B' . urlencode($this->name) .'%3D' . urlencode($this->value) . '%5D'); return url('/search?term=%5B' . urlencode($this->name) . '%3D' . urlencode($this->value) . '%5D');
} }
} }

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,14 +140,15 @@ 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,
'method' => $route->methods[0], 'method' => $route->methods[0],
'controller' => $controller, 'controller' => $controller,
'controller_method' => $controllerMethod, 'controller_method' => $controllerMethod,
'controller_method_kebab' => Str::kebab($controllerMethod), 'controller_method_kebab' => Str::kebab($controllerMethod),
'base_model' => $baseModelName, 'base_model' => $baseModelName,
]; ];
}); });
} }

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,19 +9,20 @@ 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
* @property string $name * @property string $name
* @property Carbon $expires_at * @property Carbon $expires_at
* @property User $user * @property User $user
*/ */
class ApiToken extends Model implements Loggable 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;
@ -34,7 +33,7 @@ class ApiTokenGuard implements Guard
{ {
$this->request = $request; $this->request = $request;
} }
/** /**
* @inheritDoc * @inheritDoc
*/ */
@ -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,19 +54,20 @@ 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()
{ {
if (! is_null($user = $this->user())) { if (!is_null($user = $this->user())) {
return $user; return $user;
} }
@ -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',
]; ];
/** /**
@ -42,7 +43,7 @@ class ListingResponseBuilder
$data = $this->fetchData($filteredQuery); $data = $this->fetchData($filteredQuery);
return response()->json([ return response()->json([
'data' => $data, 'data' => $data,
'total' => $total, 'total' => $total,
]); ]);
} }
@ -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 = '')
@ -18,6 +18,6 @@ class Application extends \Illuminate\Foundation\Application
. 'app' . 'app'
. DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR
. 'Config' . 'Config'
. ($path ? DIRECTORY_SEPARATOR.$path : $path); . ($path ? DIRECTORY_SEPARATOR . $path : $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,9 +33,10 @@ 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
{ {
return setting('registration-confirmation') return setting('registration-confirmation')
|| setting('registration-restrict'); || setting('registration-restrict');

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,7 +16,8 @@ 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)
@ -49,8 +51,9 @@ class ExternalBaseUserProvider implements UserProvider
/** /**
* Retrieve a user by their unique identifier and "remember me" token. * Retrieve a user by their unique identifier and "remember me" token.
* *
* @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)
@ -74,13 +77,15 @@ 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();
@ -89,8 +94,9 @@ class ExternalBaseUserProvider implements UserProvider
/** /**
* Validate a user against the given credentials. * Validate a user against the given credentials.
* *
* @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

@ -84,7 +84,7 @@ class ExternalBaseSessionGuard implements StatefulGuard
// If we've already retrieved the user for the current request we can just // If we've already retrieved the user for the current request we can just
// return it back immediately. We do not want to fetch the user data on // return it back immediately. We do not want to fetch the user data on
// every call to this method because that would be tremendously slow. // every call to this method because that would be tremendously slow.
if (! is_null($this->user)) { if (!is_null($this->user)) {
return $this->user; return $this->user;
} }
@ -92,7 +92,7 @@ class ExternalBaseSessionGuard implements StatefulGuard
// First we will try to load the user using the // First we will try to load the user using the
// identifier in the session if one exists. // identifier in the session if one exists.
if (! is_null($id)) { if (!is_null($id)) {
$this->user = $this->provider->retrieveById($id); $this->user = $this->provider->retrieveById($id);
} }
@ -118,7 +118,8 @@ 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 = [])
@ -135,12 +136,13 @@ 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)
{ {
if (! is_null($user = $this->provider->retrieveById($id))) { if (!is_null($user = $this->provider->retrieveById($id))) {
$this->setUser($user); $this->setUser($user);
return $user; return $user;
@ -152,7 +154,8 @@ 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)
@ -176,13 +179,14 @@ class ExternalBaseSessionGuard implements StatefulGuard
/** /**
* Log the given user ID into the application. * Log the given user ID into the application.
* *
* @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)
{ {
if (! is_null($user = $this->provider->retrieveById($id))) { if (!is_null($user = $this->provider->retrieveById($id))) {
$this->login($user, $remember); $this->login($user, $remember);
return $user; return $user;
@ -194,8 +198,9 @@ class ExternalBaseSessionGuard implements StatefulGuard
/** /**
* Log a user into the application. * Log a user into the application.
* *
* @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)
@ -208,7 +213,8 @@ 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)
@ -262,7 +268,7 @@ class ExternalBaseSessionGuard implements StatefulGuard
*/ */
public function getName() public function getName()
{ {
return 'login_'.$this->name.'_'.sha1(static::class); return 'login_' . $this->name . '_' . sha1(static::class);
} }
/** /**
@ -288,7 +294,8 @@ 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'],
]); ]);
} }
@ -56,10 +57,12 @@ class LdapSessionGuard extends ExternalBaseSessionGuard
* 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 *
* @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
@ -114,14 +119,15 @@ class LdapSessionGuard extends ExternalBaseSessionGuard
} }
$details = [ $details = [
'name' => $ldapUserDetails['name'], 'name' => $ldapUserDetails['name'],
'email' => $ldapUserDetails['email'] ?: $credentials['email'], 'email' => $ldapUserDetails['email'] ?: $credentials['email'],
'external_auth_id' => $ldapUserDetails['uid'], 'external_auth_id' => $ldapUserDetails['uid'],
'password' => Str::random(32), 'password' => Str::random(32),
]; ];
$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 = [])
@ -27,7 +28,8 @@ class Saml2SessionGuard extends ExternalBaseSessionGuard
* 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)

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
@ -92,16 +96,16 @@ class LdapService extends ExternalAuthService
$userCn = $this->getUserResponseProperty($user, 'cn', null); $userCn = $this->getUserResponseProperty($user, 'cn', null);
$formatted = [ $formatted = [
'uid' => $this->getUserResponseProperty($user, $idAttr, $user['dn']), 'uid' => $this->getUserResponseProperty($user, $idAttr, $user['dn']),
'name' => $this->getUserResponseProperty($user, $displayNameAttr, $userCn), 'name' => $this->getUserResponseProperty($user, $displayNameAttr, $userCn),
'dn' => $user['dn'], 'dn' => $user['dn'],
'email' => $this->getUserResponseProperty($user, $emailAttr, null), 'email' => $this->getUserResponseProperty($user, $emailAttr, null),
'avatar'=> $thumbnailAttr ? $this->getUserResponseProperty($user, $thumbnailAttr, null) : null, 'avatar'=> $thumbnailAttr ? $this->getUserResponseProperty($user, $thumbnailAttr, null) : null,
]; ];
if ($this->config['dump_user_details']) { if ($this->config['dump_user_details']) {
throw new JsonDebugException([ throw new JsonDebugException([
'details_from_ldap' => $user, 'details_from_ldap' => $user,
'details_bookstack_parsed' => $formatted, 'details_bookstack_parsed' => $formatted,
]); ]);
} }
@ -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
@ -336,7 +353,7 @@ class LdapService extends ExternalAuthService
$count = 0; $count = 0;
if (isset($userGroupSearchResponse[$groupsAttr]['count'])) { if (isset($userGroupSearchResponse[$groupsAttr]['count'])) {
$count = (int)$userGroupSearchResponse[$groupsAttr]['count']; $count = (int) $userGroupSearchResponse[$groupsAttr]['count'];
} }
for ($i = 0; $i < $count; $i++) { for ($i = 0; $i < $count; $i++) {
@ -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,20 +39,23 @@ 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(),
]; ];
} }
/** /**
* 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
@ -92,7 +98,7 @@ class Saml2Service extends ExternalAuthService
if (!empty($errors)) { if (!empty($errors)) {
throw new Error( throw new Error(
'Invalid ACS Response: '.implode(', ', $errors) 'Invalid ACS Response: ' . implode(', ', $errors)
); );
} }
@ -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
@ -119,11 +126,12 @@ class Saml2Service extends ExternalAuthService
if (!empty($errors)) { if (!empty($errors)) {
throw new Error( throw new Error(
'Invalid SLS Response: '.implode(', ', $errors) 'Invalid SLS Response: ' . implode(', ', $errors)
); );
} }
$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
@ -149,7 +158,7 @@ class Saml2Service extends ExternalAuthService
if (!empty($errors)) { if (!empty($errors)) {
throw new Error( throw new Error(
'Invalid SP metadata: '.implode(', ', $errors), 'Invalid SP metadata: ' . implode(', ', $errors),
Error::METADATA_SP_INVALID Error::METADATA_SP_INVALID
); );
} }
@ -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);
} }
@ -187,18 +198,18 @@ class Saml2Service extends ExternalAuthService
protected function loadOneloginServiceProviderDetails(): array protected function loadOneloginServiceProviderDetails(): array
{ {
$spDetails = [ $spDetails = [
'entityId' => url('/saml2/metadata'), 'entityId' => url('/saml2/metadata'),
'assertionConsumerService' => [ 'assertionConsumerService' => [
'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
{ {
@ -261,9 +272,9 @@ class Saml2Service extends ExternalAuthService
return [ return [
'external_id' => $externalId, 'external_id' => $externalId,
'name' => $this->getUserDisplayName($samlAttributes, $externalId), 'name' => $this->getUserDisplayName($samlAttributes, $externalId),
'email' => $email, 'email' => $email,
'saml_id' => $samlID, 'saml_id' => $samlID,
]; ];
} }
@ -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
@ -325,9 +338,9 @@ class Saml2Service extends ExternalAuthService
if (is_null($user)) { if (is_null($user)) {
$userData = [ $userData = [
'name' => $userDetails['name'], 'name' => $userDetails['name'],
'email' => $userDetails['email'], 'email' => $userDetails['email'],
'password' => Str::random(32), 'password' => Str::random(32),
'external_auth_id' => $userDetails['external_id'], 'external_auth_id' => $userDetails['external_id'],
]; ];
@ -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
@ -351,8 +365,8 @@ class Saml2Service extends ExternalAuthService
if ($this->config['dump_user_details']) { if ($this->config['dump_user_details']) {
throw new JsonDebugException([ throw new JsonDebugException([
'id_from_idp' => $samlID, 'id_from_idp' => $samlID,
'attrs_from_idp' => $samlAttributes, 'attrs_from_idp' => $samlAttributes,
'attrs_after_parsing' => $userDetails, 'attrs_after_parsing' => $userDetails,
]); ]);
} }
@ -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);
} }
@ -237,9 +257,9 @@ class SocialAuthService
public function newSocialAccount(string $socialDriver, SocialUser $socialUser): SocialAccount public function newSocialAccount(string $socialDriver, SocialUser $socialUser): SocialAccount
{ {
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,12 +54,15 @@ 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
{ {
$entry = $this->getEntryByToken($token); $entry = $this->getEntryByToken($token);
@ -70,40 +79,47 @@ 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
{ {
$token = Str::random(24); $token = Str::random(24);
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
{ {
$token = $this->generateToken(); $token = $this->generateToken();
$this->db->table($this->tokenTable)->insert([ $this->db->table($this->tokenTable)->insert([
'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
{ {
return $this->db->table($this->tokenTable) return $this->db->table($this->tokenTable)
->where('token', '=', $token)->exists(); ->where('token', '=', $token)->exists();
@ -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,10 +141,12 @@ 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
{ {
return Carbon::now()->subHours($this->expiryTime) return Carbon::now()->subHours($this->expiryTime)
->gt(new Carbon($tokenEntry->created_at)); ->gt(new Carbon($tokenEntry->created_at));

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;
} }
@ -443,18 +459,19 @@ class PermissionService
protected function createJointPermissionDataArray(Entity $entity, Role $role, string $action, bool $permissionAll, bool $permissionOwn): array protected function createJointPermissionDataArray(Entity $entity, Role $role, string $action, bool $permissionAll, bool $permissionOwn): array
{ {
return [ return [
'role_id' => $role->getRawAttribute('id'), 'role_id' => $role->getRawAttribute('id'),
'entity_id' => $entity->getRawAttribute('id'), 'entity_id' => $entity->getRawAttribute('id'),
'entity_type' => $entity->getMorphClass(), 'entity_type' => $entity->getMorphClass(),
'action' => $action, 'action' => $action,
'has_permission' => $permissionAll, 'has_permission' => $permissionAll,
'has_permission_own' => $permissionOwn, 'has_permission_own' => $permissionOwn,
'owned_by' => $entity->getRawAttribute('owned_by'), 'owned_by' => $entity->getRawAttribute('owned_by'),
]; ];
} }
/** /**
* 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
*/ */
@ -127,7 +130,7 @@ class PermissionsRepo
// Prevent deleting admin role or default registration role. // Prevent deleting admin role or default registration role.
if ($role->system_name && in_array($role->system_name, $this->systemRoles)) { if ($role->system_name && in_array($role->system_name, $this->systemRoles)) {
throw new PermissionsException(trans('errors.role_system_cannot_be_deleted')); throw new PermissionsException(trans('errors.role_system_cannot_be_deleted'));
} else if ($role->id === intval(setting('registration-role'))) { } elseif ($role->id === intval(setting('registration-role'))) {
throw new PermissionsException(trans('errors.role_registration_default_cannot_delete')); throw new PermissionsException(trans('errors.role_registration_default_cannot_delete'));
} }

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,8 +11,9 @@ 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
* @property string $external_auth_id * @property string $external_auth_id
@ -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,32 +24,37 @@ use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
/** /**
* Class User * Class User.
* @property string $id *
* @property string $name * @property string $id
* @property string $slug * @property string $name
* @property string $email * @property string $slug
* @property string $password * @property string $email
* @property Carbon $created_at * @property string $password
* @property Carbon $updated_at * @property Carbon $created_at
* @property bool $email_confirmed * @property Carbon $updated_at
* @property int $image_id * @property bool $email_confirmed
* @property string $external_auth_id * @property int $image_id
* @property string $system_name * @property string $external_auth_id
* @property string $system_name
* @property Collection $roles * @property Collection $roles
*/ */
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;
@ -83,8 +93,9 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
if (!is_null(static::$defaultUser)) { if (!is_null(static::$defaultUser)) {
return static::$defaultUser; return static::$defaultUser;
} }
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,13 +109,15 @@ 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()
{ {
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;
@ -82,7 +84,7 @@ class UserRepo
return $query->paginate($count); return $query->paginate($count);
} }
/** /**
* Creates a new user and attaches a role to them. * Creates a new user and attaches a role to them.
*/ */
public function registerNew(array $data, bool $emailConfirmed = false): User public function registerNew(array $data, bool $emailConfirmed = false): User
@ -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)
@ -141,7 +145,7 @@ class UserRepo
* Check if the given user is the last admin and their new roles no longer * Check if the given user is the last admin and their new roles no longer
* contains the admin role. * contains the admin role.
*/ */
protected function demotingLastAdmin(User $user, array $newRoles) : bool protected function demotingLastAdmin(User $user, array $newRoles): bool
{ {
if ($this->isOnlyAdmin($user)) { if ($this->isOnlyAdmin($user)) {
$adminRole = Role::getSystemRole('admin'); $adminRole = Role::getSystemRole('admin');
@ -159,10 +163,10 @@ class UserRepo
public function create(array $data, bool $emailConfirmed = false): User public function create(array $data, bool $emailConfirmed = false): User
{ {
$details = [ $details = [
'name' => $data['name'], 'name' => $data['name'],
'email' => $data['email'], 'email' => $data['email'],
'password' => bcrypt($data['password']), 'password' => bcrypt($data['password']),
'email_confirmed' => $emailConfirmed, 'email_confirmed' => $emailConfirmed,
'external_auth_id' => $data['external_auth_id'] ?? '', 'external_auth_id' => $data['external_auth_id'] ?? '',
]; ];
@ -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)
@ -184,7 +189,7 @@ class UserRepo
$user->apiTokens()->delete(); $user->apiTokens()->delete();
$user->favourites()->delete(); $user->favourites()->delete();
$user->delete(); $user->delete();
// Delete user profile images // Delete user profile images
$this->userAvatar->destroyAllForUser($user); $this->userAvatar->destroyAllForUser($user);
@ -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,11 +247,12 @@ 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(),
'books' => Book::visible()->where($createdBy)->count(), 'books' => Book::visible()->where($createdBy)->count(),
'shelves' => Bookshelf::visible()->where($createdBy)->count(), 'shelves' => Bookshelf::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',
@ -140,52 +140,52 @@ return [
'aliases' => [ 'aliases' => [
// Laravel // Laravel
'App' => Illuminate\Support\Facades\App::class, 'App' => Illuminate\Support\Facades\App::class,
'Arr' => Illuminate\Support\Arr::class, 'Arr' => Illuminate\Support\Arr::class,
'Artisan' => Illuminate\Support\Facades\Artisan::class, 'Artisan' => Illuminate\Support\Facades\Artisan::class,
'Auth' => Illuminate\Support\Facades\Auth::class, 'Auth' => Illuminate\Support\Facades\Auth::class,
'Blade' => Illuminate\Support\Facades\Blade::class, 'Blade' => Illuminate\Support\Facades\Blade::class,
'Bus' => Illuminate\Support\Facades\Bus::class, 'Bus' => Illuminate\Support\Facades\Bus::class,
'Cache' => Illuminate\Support\Facades\Cache::class, 'Cache' => Illuminate\Support\Facades\Cache::class,
'Config' => Illuminate\Support\Facades\Config::class, 'Config' => Illuminate\Support\Facades\Config::class,
'Cookie' => Illuminate\Support\Facades\Cookie::class, 'Cookie' => Illuminate\Support\Facades\Cookie::class,
'Crypt' => Illuminate\Support\Facades\Crypt::class, 'Crypt' => Illuminate\Support\Facades\Crypt::class,
'DB' => Illuminate\Support\Facades\DB::class, 'DB' => Illuminate\Support\Facades\DB::class,
'Eloquent' => Illuminate\Database\Eloquent\Model::class, 'Eloquent' => Illuminate\Database\Eloquent\Model::class,
'Event' => Illuminate\Support\Facades\Event::class, 'Event' => Illuminate\Support\Facades\Event::class,
'File' => Illuminate\Support\Facades\File::class, 'File' => Illuminate\Support\Facades\File::class,
'Hash' => Illuminate\Support\Facades\Hash::class, 'Hash' => Illuminate\Support\Facades\Hash::class,
'Input' => Illuminate\Support\Facades\Input::class, 'Input' => Illuminate\Support\Facades\Input::class,
'Inspiring' => Illuminate\Foundation\Inspiring::class, 'Inspiring' => Illuminate\Foundation\Inspiring::class,
'Lang' => Illuminate\Support\Facades\Lang::class, 'Lang' => Illuminate\Support\Facades\Lang::class,
'Log' => Illuminate\Support\Facades\Log::class, 'Log' => Illuminate\Support\Facades\Log::class,
'Mail' => Illuminate\Support\Facades\Mail::class, 'Mail' => Illuminate\Support\Facades\Mail::class,
'Notification' => Illuminate\Support\Facades\Notification::class, 'Notification' => Illuminate\Support\Facades\Notification::class,
'Password' => Illuminate\Support\Facades\Password::class, 'Password' => Illuminate\Support\Facades\Password::class,
'Queue' => Illuminate\Support\Facades\Queue::class, 'Queue' => Illuminate\Support\Facades\Queue::class,
'Redirect' => Illuminate\Support\Facades\Redirect::class, 'Redirect' => Illuminate\Support\Facades\Redirect::class,
'Redis' => Illuminate\Support\Facades\Redis::class, 'Redis' => Illuminate\Support\Facades\Redis::class,
'Request' => Illuminate\Support\Facades\Request::class, 'Request' => Illuminate\Support\Facades\Request::class,
'Response' => Illuminate\Support\Facades\Response::class, 'Response' => Illuminate\Support\Facades\Response::class,
'Route' => Illuminate\Support\Facades\Route::class, 'Route' => Illuminate\Support\Facades\Route::class,
'Schema' => Illuminate\Support\Facades\Schema::class, 'Schema' => Illuminate\Support\Facades\Schema::class,
'Session' => Illuminate\Support\Facades\Session::class, 'Session' => Illuminate\Support\Facades\Session::class,
'Storage' => Illuminate\Support\Facades\Storage::class, 'Storage' => Illuminate\Support\Facades\Storage::class,
'Str' => Illuminate\Support\Str::class, 'Str' => Illuminate\Support\Str::class,
'URL' => Illuminate\Support\Facades\URL::class, 'URL' => Illuminate\Support\Facades\URL::class,
'Validator' => Illuminate\Support\Facades\Validator::class, 'Validator' => Illuminate\Support\Facades\Validator::class,
'View' => Illuminate\Support\Facades\View::class, 'View' => Illuminate\Support\Facades\View::class,
'Socialite' => Laravel\Socialite\Facades\Socialite::class, 'Socialite' => Laravel\Socialite\Facades\Socialite::class,
// Third Party // Third Party
'ImageTool' => Intervention\Image\Facades\Image::class, 'ImageTool' => Intervention\Image\Facades\Image::class,
'DomPDF' => Barryvdh\DomPDF\Facade::class, 'DomPDF' => Barryvdh\DomPDF\Facade::class,
'SnappyPDF' => Barryvdh\Snappy\Facades\SnappyPdf::class, 'SnappyPDF' => Barryvdh\Snappy\Facades\SnappyPdf::class,
// Custom BookStack // Custom BookStack
'Activity' => BookStack\Facades\Activity::class, 'Activity' => BookStack\Facades\Activity::class,
'Permissions' => BookStack\Facades\Permissions::class, 'Permissions' => BookStack\Facades\Permissions::class,
'Theme' => BookStack\Facades\Theme::class, 'Theme' => BookStack\Facades\Theme::class,
], ],
// Proxy configuration // Proxy configuration

View File

@ -18,7 +18,7 @@ return [
// This option controls the default authentication "guard" and password // This option controls the default authentication "guard" and password
// reset options for your application. // reset options for your application.
'defaults' => [ 'defaults' => [
'guard' => env('AUTH_METHOD', 'standard'), 'guard' => env('AUTH_METHOD', 'standard'),
'passwords' => 'users', 'passwords' => 'users',
], ],
@ -29,15 +29,15 @@ return [
// Supported drivers: "session", "api-token", "ldap-session" // Supported drivers: "session", "api-token", "ldap-session"
'guards' => [ 'guards' => [
'standard' => [ 'standard' => [
'driver' => 'session', 'driver' => 'session',
'provider' => 'users', 'provider' => 'users',
], ],
'ldap' => [ 'ldap' => [
'driver' => 'ldap-session', 'driver' => 'ldap-session',
'provider' => 'external', 'provider' => 'external',
], ],
'saml2' => [ 'saml2' => [
'driver' => 'saml2-session', 'driver' => 'saml2-session',
'provider' => 'external', 'provider' => 'external',
], ],
'api' => [ 'api' => [
@ -52,11 +52,11 @@ return [
'providers' => [ 'providers' => [
'users' => [ 'users' => [
'driver' => 'eloquent', 'driver' => 'eloquent',
'model' => \BookStack\Auth\User::class, 'model' => \BookStack\Auth\User::class,
], ],
'external' => [ 'external' => [
'driver' => 'external-users', 'driver' => 'external-users',
'model' => \BookStack\Auth\User::class, 'model' => \BookStack\Auth\User::class,
], ],
], ],
@ -67,9 +67,9 @@ return [
'passwords' => [ 'passwords' => [
'users' => [ 'users' => [
'provider' => 'users', 'provider' => 'users',
'email' => 'emails.password', 'email' => 'emails.password',
'table' => 'password_resets', 'table' => 'password_resets',
'expire' => 60, 'expire' => 60,
], ],
], ],

View File

@ -23,18 +23,18 @@ return [
'connections' => [ 'connections' => [
'pusher' => [ 'pusher' => [
'driver' => 'pusher', 'driver' => 'pusher',
'key' => env('PUSHER_APP_KEY'), 'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'), 'secret' => env('PUSHER_APP_SECRET'),
'app_id' => env('PUSHER_APP_ID'), 'app_id' => env('PUSHER_APP_ID'),
'options' => [ 'options' => [
'cluster' => env('PUSHER_APP_CLUSTER'), 'cluster' => env('PUSHER_APP_CLUSTER'),
'useTLS' => true, 'useTLS' => true,
], ],
], ],
'redis' => [ 'redis' => [
'driver' => 'redis', 'driver' => 'redis',
'connection' => 'default', 'connection' => 'default',
], ],
@ -46,7 +46,6 @@ return [
'driver' => 'null', 'driver' => 'null',
], ],
], ],
]; ];

View File

@ -42,8 +42,8 @@ return [
], ],
'database' => [ 'database' => [
'driver' => 'database', 'driver' => 'database',
'table' => 'cache', 'table' => 'cache',
'connection' => null, 'connection' => null,
], ],
@ -58,7 +58,7 @@ return [
], ],
'redis' => [ 'redis' => [
'driver' => 'redis', 'driver' => 'redis',
'connection' => 'default', 'connection' => 'default',
], ],

View File

@ -59,38 +59,38 @@ return [
'connections' => [ 'connections' => [
'mysql' => [ 'mysql' => [
'driver' => 'mysql', 'driver' => 'mysql',
'url' => env('DATABASE_URL'), 'url' => env('DATABASE_URL'),
'host' => $mysql_host, 'host' => $mysql_host,
'database' => env('DB_DATABASE', 'forge'), 'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'), 'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''), 'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''), 'unix_socket' => env('DB_SOCKET', ''),
'port' => $mysql_port, 'port' => $mysql_port,
'charset' => 'utf8mb4', 'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci', 'collation' => 'utf8mb4_unicode_ci',
'prefix' => '', 'prefix' => '',
'prefix_indexes' => true, 'prefix_indexes' => true,
'strict' => false, 'strict' => false,
'engine' => null, 'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([ 'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
]) : [], ]) : [],
], ],
'mysql_testing' => [ 'mysql_testing' => [
'driver' => 'mysql', 'driver' => 'mysql',
'url' => env('TEST_DATABASE_URL'), 'url' => env('TEST_DATABASE_URL'),
'host' => '127.0.0.1', 'host' => '127.0.0.1',
'database' => 'bookstack-test', 'database' => 'bookstack-test',
'username' => env('MYSQL_USER', 'bookstack-test'), 'username' => env('MYSQL_USER', 'bookstack-test'),
'password' => env('MYSQL_PASSWORD', 'bookstack-test'), 'password' => env('MYSQL_PASSWORD', 'bookstack-test'),
'port' => $mysql_port, 'port' => $mysql_port,
'charset' => 'utf8mb4', 'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci', 'collation' => 'utf8mb4_unicode_ci',
'prefix' => '', 'prefix' => '',
'prefix_indexes' => true, 'prefix_indexes' => true,
'strict' => false, 'strict' => false,
], ],
], ],

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.
@ -10,53 +10,52 @@
return [ return [
// Debugbar is enabled by default, when debug is set to true in app.php. // Debugbar is enabled by default, when debug is set to true in app.php.
// You can override the value by setting enable to true or false instead of null. // You can override the value by setting enable to true or false instead of null.
// //
// 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. // By default, file storage (in the storage folder) is used. Redis and PDO
// By default, file storage (in the storage folder) is used. Redis and PDO // can also be used. For PDO, run the package migrations first.
// can also be used. For PDO, run the package migrations first.
'storage' => [ 'storage' => [
'enabled' => true, 'enabled' => true,
'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.
// This can also be set to 'js' or 'css', to only include javascript or css vendor files. // This can also be set to 'js' or 'css', to only include javascript or css vendor files.
// Vendor files are for css: font-awesome (including fonts) and highlight.js (css files) // Vendor files are for css: font-awesome (including fonts) and highlight.js (css files)
// and for js: jquery and and highlight.js // and for js: jquery and and highlight.js
// So if you want syntax highlighting, set it to true. // So if you want syntax highlighting, set it to true.
// jQuery is set to not conflict with existing jQuery scripts. // jQuery is set to not conflict with existing jQuery scripts.
'include_vendors' => true, 'include_vendors' => true,
// The Debugbar can capture Ajax requests and display them. If you don't want this (ie. because of errors), // The Debugbar can capture Ajax requests and display them. If you don't want this (ie. because of errors),
// you can use this option to disable sending the data through the headers. // you can use this option to disable sending the data through the headers.
// Optionally, you can also send ServerTiming headers on ajax requests for the Chrome DevTools. // Optionally, you can also send ServerTiming headers on ajax requests for the Chrome DevTools.
'capture_ajax' => true, 'capture_ajax' => true,
'add_ajax_timing' => false, 'add_ajax_timing' => false,
// When enabled, the Debugbar shows deprecated warnings for Symfony components // When enabled, the Debugbar shows deprecated warnings for Symfony components
// in the Messages tab. // in the Messages tab.
'error_handler' => false, 'error_handler' => false,
// The Debugbar can emulate the Clockwork headers, so you can use the Chrome // The Debugbar can emulate the Clockwork headers, so you can use the Chrome
// Extension, without the server-side code. It uses Debugbar collectors instead. // Extension, without the server-side code. It uses Debugbar collectors instead.
'clockwork' => false, 'clockwork' => false,
// Enable/disable DataCollectors // Enable/disable DataCollectors
'collectors' => [ 'collectors' => [
'phpinfo' => true, // Php version 'phpinfo' => true, // Php version
'messages' => true, // Messages 'messages' => true, // Messages
@ -82,7 +81,7 @@ return [
'models' => true, // Display models 'models' => true, // Display models
], ],
// Configure some DataCollectors // Configure some DataCollectors
'options' => [ 'options' => [
'auth' => [ 'auth' => [
'show_name' => true, // Also show the users name/email in the debugbar 'show_name' => true, // Also show the users name/email in the debugbar
@ -91,43 +90,43 @@ return [
'with_params' => true, // Render SQL with the parameters substituted 'with_params' => true, // Render SQL with the parameters substituted
'backtrace' => true, // Use a backtrace to find the origin of the query in your files. 'backtrace' => true, // Use a backtrace to find the origin of the query in your files.
'timeline' => false, // Add the queries to the timeline 'timeline' => false, // Add the queries to the timeline
'explain' => [ // Show EXPLAIN output on queries 'explain' => [ // Show EXPLAIN output on queries
'enabled' => false, 'enabled' => false,
'types' => ['SELECT'], // ['SELECT', 'INSERT', 'UPDATE', 'DELETE']; for MySQL 5.6.3+ 'types' => ['SELECT'], // ['SELECT', 'INSERT', 'UPDATE', 'DELETE']; for MySQL 5.6.3+
], ],
'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
], ],
], ],
// Inject Debugbar into the response // Inject Debugbar into the response
// Usually, the debugbar is added just before </body>, by listening to the // Usually, the debugbar is added just before </body>, by listening to the
// Response after the App is done. If you disable this, you have to add them // Response after the App is done. If you disable this, you have to add them
// in your template yourself. See http://phpdebugbar.com/docs/rendering.html // in your template yourself. See http://phpdebugbar.com/docs/rendering.html
'inject' => true, 'inject' => true,
// DebugBar route prefix // DebugBar route prefix
// Sometimes you want to set route prefix to be used by DebugBar to load // Sometimes you want to set route prefix to be used by DebugBar to load
// its resources from. Usually the need comes from misconfigured web server or // its resources from. Usually the need comes from misconfigured web server or
// from trying to overcome bugs like this: http://trac.nginx.org/nginx/ticket/97 // from trying to overcome bugs like this: http://trac.nginx.org/nginx/ticket/97
'route_prefix' => '_debugbar', 'route_prefix' => '_debugbar',
// DebugBar route domain // DebugBar route domain
// By default DebugBar route served from the same domain that request served. // By default DebugBar route served from the same domain that request served.
// To override default domain, specify it as a non-empty value. // To override default domain, specify it as a non-empty value.
'route_domain' => env('APP_URL', '') === 'http://bookstack.dev' ? '' : env('APP_URL', ''), 'route_domain' => env('APP_URL', '') === 'http://bookstack.dev' ? '' : env('APP_URL', ''),
]; ];

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

@ -34,7 +34,7 @@ return [
'local' => [ 'local' => [
'driver' => 'local', 'driver' => 'local',
'root' => public_path(), 'root' => public_path(),
], ],
'local_secure' => [ 'local_secure' => [
@ -43,12 +43,12 @@ return [
], ],
's3' => [ 's3' => [
'driver' => 's3', 'driver' => 's3',
'key' => env('STORAGE_S3_KEY', 'your-key'), 'key' => env('STORAGE_S3_KEY', 'your-key'),
'secret' => env('STORAGE_S3_SECRET', 'your-secret'), 'secret' => env('STORAGE_S3_SECRET', 'your-secret'),
'region' => env('STORAGE_S3_REGION', 'your-region'), 'region' => env('STORAGE_S3_REGION', 'your-region'),
'bucket' => env('STORAGE_S3_BUCKET', 'your-bucket'), 'bucket' => env('STORAGE_S3_BUCKET', 'your-bucket'),
'endpoint' => env('STORAGE_S3_ENDPOINT', null), 'endpoint' => env('STORAGE_S3_ENDPOINT', null),
'use_path_style_endpoint' => env('STORAGE_S3_ENDPOINT', null) !== null, 'use_path_style_endpoint' => env('STORAGE_S3_ENDPOINT', null) !== null,
], ],

View File

@ -29,9 +29,9 @@ return [
// passwords are hashed using the Argon algorithm. These will allow you // passwords are hashed using the Argon algorithm. These will allow you
// to control the amount of time it takes to hash the given password. // to control the amount of time it takes to hash the given password.
'argon' => [ 'argon' => [
'memory' => 1024, 'memory' => 1024,
'threads' => 2, 'threads' => 2,
'time' => 2, 'time' => 2,
], ],
]; ];

View File

@ -30,66 +30,66 @@ return [
// "custom", "stack" // "custom", "stack"
'channels' => [ 'channels' => [
'stack' => [ 'stack' => [
'driver' => 'stack', 'driver' => 'stack',
'channels' => ['daily'], 'channels' => ['daily'],
'ignore_exceptions' => false, 'ignore_exceptions' => false,
], ],
'single' => [ 'single' => [
'driver' => 'single', 'driver' => 'single',
'path' => storage_path('logs/laravel.log'), 'path' => storage_path('logs/laravel.log'),
'level' => 'debug', 'level' => 'debug',
'days' => 14, 'days' => 14,
], ],
'daily' => [ 'daily' => [
'driver' => 'daily', 'driver' => 'daily',
'path' => storage_path('logs/laravel.log'), 'path' => storage_path('logs/laravel.log'),
'level' => 'debug', 'level' => 'debug',
'days' => 7, 'days' => 7,
], ],
'slack' => [ 'slack' => [
'driver' => 'slack', 'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'), 'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => 'Laravel Log', 'username' => 'Laravel Log',
'emoji' => ':boom:', 'emoji' => ':boom:',
'level' => 'critical', 'level' => 'critical',
], ],
'stderr' => [ 'stderr' => [
'driver' => 'monolog', 'driver' => 'monolog',
'handler' => StreamHandler::class, 'handler' => StreamHandler::class,
'with' => [ 'with' => [
'stream' => 'php://stderr', 'stream' => 'php://stderr',
], ],
], ],
'syslog' => [ 'syslog' => [
'driver' => 'syslog', 'driver' => 'syslog',
'level' => 'debug', 'level' => 'debug',
], ],
'errorlog' => [ 'errorlog' => [
'driver' => 'errorlog', 'driver' => 'errorlog',
'level' => 'debug', 'level' => 'debug',
], ],
// Custom errorlog implementation that logs out a plain, // Custom errorlog implementation that logs out a plain,
// non-formatted message intended for the webserver log. // non-formatted message intended for the webserver log.
'errorlog_plain_webserver' => [ 'errorlog_plain_webserver' => [
'driver' => 'monolog', 'driver' => 'monolog',
'level' => 'debug', 'level' => 'debug',
'handler' => ErrorLogHandler::class, 'handler' => ErrorLogHandler::class,
'handler_with' => [4], 'handler_with' => [4],
'formatter' => LineFormatter::class, 'formatter' => LineFormatter::class,
'formatter_with' => [ 'formatter_with' => [
'format' => "%message%", 'format' => '%message%',
], ],
], ],
'null' => [ 'null' => [
'driver' => 'monolog', 'driver' => 'monolog',
'handler' => NullHandler::class, 'handler' => NullHandler::class,
], ],
@ -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,24 +17,23 @@ return [
// Queue connection configuration // Queue connection configuration
'connections' => [ 'connections' => [
'sync' => [ 'sync' => [
'driver' => 'sync', 'driver' => 'sync',
], ],
'database' => [ 'database' => [
'driver' => 'database', 'driver' => 'database',
'table' => 'jobs', 'table' => 'jobs',
'queue' => 'default', 'queue' => 'default',
'retry_after' => 90, 'retry_after' => 90,
], ],
'redis' => [ 'redis' => [
'driver' => 'redis', 'driver' => 'redis',
'connection' => 'default', 'connection' => 'default',
'queue' => env('REDIS_QUEUE', 'default'), 'queue' => env('REDIS_QUEUE', 'default'),
'retry_after' => 90, 'retry_after' => 90,
'block_for' => null, 'block_for' => null,
], ],
], ],

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
@ -81,7 +80,7 @@ return [
'NameIDFormat' => 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress', 'NameIDFormat' => 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
// Usually x509cert and privateKey of the SP are provided by files placed at // Usually x509cert and privateKey of the SP are provided by files placed at
// the certs folder. But we can also provide them with the following parameters // the certs folder. But we can also provide them with the following parameters
'x509cert' => '', 'x509cert' => '',
'privateKey' => '', 'privateKey' => '',
], ],
// Identity Provider Data that we want connect with our SP // Identity Provider Data that we want connect with our SP

View File

@ -28,16 +28,16 @@ return [
'redirect' => env('APP_URL') . '/login/service/github/callback', 'redirect' => env('APP_URL') . '/login/service/github/callback',
'name' => 'GitHub', 'name' => 'GitHub',
'auto_register' => env('GITHUB_AUTO_REGISTER', false), 'auto_register' => env('GITHUB_AUTO_REGISTER', false),
'auto_confirm' => env('GITHUB_AUTO_CONFIRM_EMAIL', false), 'auto_confirm' => env('GITHUB_AUTO_CONFIRM_EMAIL', false),
], ],
'google' => [ 'google' => [
'client_id' => env('GOOGLE_APP_ID', false), 'client_id' => env('GOOGLE_APP_ID', false),
'client_secret' => env('GOOGLE_APP_SECRET', false), 'client_secret' => env('GOOGLE_APP_SECRET', false),
'redirect' => env('APP_URL') . '/login/service/google/callback', 'redirect' => env('APP_URL') . '/login/service/google/callback',
'name' => 'Google', 'name' => 'Google',
'auto_register' => env('GOOGLE_AUTO_REGISTER', false), 'auto_register' => env('GOOGLE_AUTO_REGISTER', false),
'auto_confirm' => env('GOOGLE_AUTO_CONFIRM_EMAIL', false), 'auto_confirm' => env('GOOGLE_AUTO_CONFIRM_EMAIL', false),
'select_account' => env('GOOGLE_SELECT_ACCOUNT', false), 'select_account' => env('GOOGLE_SELECT_ACCOUNT', false),
], ],
@ -47,7 +47,7 @@ return [
'redirect' => env('APP_URL') . '/login/service/slack/callback', 'redirect' => env('APP_URL') . '/login/service/slack/callback',
'name' => 'Slack', 'name' => 'Slack',
'auto_register' => env('SLACK_AUTO_REGISTER', false), 'auto_register' => env('SLACK_AUTO_REGISTER', false),
'auto_confirm' => env('SLACK_AUTO_CONFIRM_EMAIL', false), 'auto_confirm' => env('SLACK_AUTO_CONFIRM_EMAIL', false),
], ],
'facebook' => [ 'facebook' => [
@ -56,7 +56,7 @@ return [
'redirect' => env('APP_URL') . '/login/service/facebook/callback', 'redirect' => env('APP_URL') . '/login/service/facebook/callback',
'name' => 'Facebook', 'name' => 'Facebook',
'auto_register' => env('FACEBOOK_AUTO_REGISTER', false), 'auto_register' => env('FACEBOOK_AUTO_REGISTER', false),
'auto_confirm' => env('FACEBOOK_AUTO_CONFIRM_EMAIL', false), 'auto_confirm' => env('FACEBOOK_AUTO_CONFIRM_EMAIL', false),
], ],
'twitter' => [ 'twitter' => [
@ -65,27 +65,27 @@ return [
'redirect' => env('APP_URL') . '/login/service/twitter/callback', 'redirect' => env('APP_URL') . '/login/service/twitter/callback',
'name' => 'Twitter', 'name' => 'Twitter',
'auto_register' => env('TWITTER_AUTO_REGISTER', false), 'auto_register' => env('TWITTER_AUTO_REGISTER', false),
'auto_confirm' => env('TWITTER_AUTO_CONFIRM_EMAIL', false), 'auto_confirm' => env('TWITTER_AUTO_CONFIRM_EMAIL', false),
], ],
'azure' => [ 'azure' => [
'client_id' => env('AZURE_APP_ID', false), 'client_id' => env('AZURE_APP_ID', false),
'client_secret' => env('AZURE_APP_SECRET', false), 'client_secret' => env('AZURE_APP_SECRET', false),
'tenant' => env('AZURE_TENANT', false), 'tenant' => env('AZURE_TENANT', false),
'redirect' => env('APP_URL') . '/login/service/azure/callback', 'redirect' => env('APP_URL') . '/login/service/azure/callback',
'name' => 'Microsoft Azure', 'name' => 'Microsoft Azure',
'auto_register' => env('AZURE_AUTO_REGISTER', false), 'auto_register' => env('AZURE_AUTO_REGISTER', false),
'auto_confirm' => env('AZURE_AUTO_CONFIRM_EMAIL', false), 'auto_confirm' => env('AZURE_AUTO_CONFIRM_EMAIL', false),
], ],
'okta' => [ 'okta' => [
'client_id' => env('OKTA_APP_ID'), 'client_id' => env('OKTA_APP_ID'),
'client_secret' => env('OKTA_APP_SECRET'), 'client_secret' => env('OKTA_APP_SECRET'),
'redirect' => env('APP_URL') . '/login/service/okta/callback', 'redirect' => env('APP_URL') . '/login/service/okta/callback',
'base_url' => env('OKTA_BASE_URL'), 'base_url' => env('OKTA_BASE_URL'),
'name' => 'Okta', 'name' => 'Okta',
'auto_register' => env('OKTA_AUTO_REGISTER', false), 'auto_register' => env('OKTA_AUTO_REGISTER', false),
'auto_confirm' => env('OKTA_AUTO_CONFIRM_EMAIL', false), 'auto_confirm' => env('OKTA_AUTO_CONFIRM_EMAIL', false),
], ],
'gitlab' => [ 'gitlab' => [
@ -95,45 +95,45 @@ return [
'instance_uri' => env('GITLAB_BASE_URI'), // Needed only for self hosted instances 'instance_uri' => env('GITLAB_BASE_URI'), // Needed only for self hosted instances
'name' => 'GitLab', 'name' => 'GitLab',
'auto_register' => env('GITLAB_AUTO_REGISTER', false), 'auto_register' => env('GITLAB_AUTO_REGISTER', false),
'auto_confirm' => env('GITLAB_AUTO_CONFIRM_EMAIL', false), 'auto_confirm' => env('GITLAB_AUTO_CONFIRM_EMAIL', false),
], ],
'twitch' => [ 'twitch' => [
'client_id' => env('TWITCH_APP_ID'), 'client_id' => env('TWITCH_APP_ID'),
'client_secret' => env('TWITCH_APP_SECRET'), 'client_secret' => env('TWITCH_APP_SECRET'),
'redirect' => env('APP_URL') . '/login/service/twitch/callback', 'redirect' => env('APP_URL') . '/login/service/twitch/callback',
'name' => 'Twitch', 'name' => 'Twitch',
'auto_register' => env('TWITCH_AUTO_REGISTER', false), 'auto_register' => env('TWITCH_AUTO_REGISTER', false),
'auto_confirm' => env('TWITCH_AUTO_CONFIRM_EMAIL', false), 'auto_confirm' => env('TWITCH_AUTO_CONFIRM_EMAIL', false),
], ],
'discord' => [ 'discord' => [
'client_id' => env('DISCORD_APP_ID'), 'client_id' => env('DISCORD_APP_ID'),
'client_secret' => env('DISCORD_APP_SECRET'), 'client_secret' => env('DISCORD_APP_SECRET'),
'redirect' => env('APP_URL') . '/login/service/discord/callback', 'redirect' => env('APP_URL') . '/login/service/discord/callback',
'name' => 'Discord', 'name' => 'Discord',
'auto_register' => env('DISCORD_AUTO_REGISTER', false), 'auto_register' => env('DISCORD_AUTO_REGISTER', false),
'auto_confirm' => env('DISCORD_AUTO_CONFIRM_EMAIL', false), 'auto_confirm' => env('DISCORD_AUTO_CONFIRM_EMAIL', false),
], ],
'ldap' => [ 'ldap' => [
'server' => env('LDAP_SERVER', false), 'server' => env('LDAP_SERVER', false),
'dump_user_details' => env('LDAP_DUMP_USER_DETAILS', false), 'dump_user_details' => env('LDAP_DUMP_USER_DETAILS', false),
'dn' => env('LDAP_DN', false), 'dn' => env('LDAP_DN', false),
'pass' => env('LDAP_PASS', false), 'pass' => env('LDAP_PASS', false),
'base_dn' => env('LDAP_BASE_DN', false), 'base_dn' => env('LDAP_BASE_DN', false),
'user_filter' => env('LDAP_USER_FILTER', '(&(uid=${user}))'), 'user_filter' => env('LDAP_USER_FILTER', '(&(uid=${user}))'),
'version' => env('LDAP_VERSION', false), 'version' => env('LDAP_VERSION', false),
'id_attribute' => env('LDAP_ID_ATTRIBUTE', 'uid'), 'id_attribute' => env('LDAP_ID_ATTRIBUTE', 'uid'),
'email_attribute' => env('LDAP_EMAIL_ATTRIBUTE', 'mail'), 'email_attribute' => env('LDAP_EMAIL_ATTRIBUTE', 'mail'),
'display_name_attribute' => env('LDAP_DISPLAY_NAME_ATTRIBUTE', 'cn'), 'display_name_attribute' => env('LDAP_DISPLAY_NAME_ATTRIBUTE', 'cn'),
'follow_referrals' => env('LDAP_FOLLOW_REFERRALS', false), 'follow_referrals' => env('LDAP_FOLLOW_REFERRALS', false),
'user_to_groups' => env('LDAP_USER_TO_GROUPS', false), 'user_to_groups' => env('LDAP_USER_TO_GROUPS', false),
'group_attribute' => env('LDAP_GROUP_ATTRIBUTE', 'memberOf'), 'group_attribute' => env('LDAP_GROUP_ATTRIBUTE', 'memberOf'),
'remove_from_groups' => env('LDAP_REMOVE_FROM_GROUPS', false), 'remove_from_groups' => env('LDAP_REMOVE_FROM_GROUPS', false),
'tls_insecure' => env('LDAP_TLS_INSECURE', false), 'tls_insecure' => env('LDAP_TLS_INSECURE', false),
'start_tls' => env('LDAP_START_TLS', false), 'start_tls' => env('LDAP_START_TLS', false),
'thumbnail_attribute' => env('LDAP_THUMBNAIL_ATTRIBUTE', null), 'thumbnail_attribute' => env('LDAP_THUMBNAIL_ATTRIBUTE', null),
], ],
]; ];

View File

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

View File

@ -26,10 +26,10 @@ return [
// User-level default settings // User-level default settings
'user' => [ 'user' => [
'dark-mode-enabled' => env('APP_DEFAULT_DARK_MODE', false), 'dark-mode-enabled' => env('APP_DEFAULT_DARK_MODE', false),
'bookshelves_view_type' => env('APP_VIEWS_BOOKSHELVES', 'grid'), 'bookshelves_view_type' => env('APP_VIEWS_BOOKSHELVES', 'grid'),
'bookshelf_view_type' =>env('APP_VIEWS_BOOKSHELF', 'grid'), 'bookshelf_view_type' => env('APP_VIEWS_BOOKSHELF', 'grid'),
'books_view_type' => env('APP_VIEWS_BOOKS', 'grid'), 'books_view_type' => env('APP_VIEWS_BOOKS', 'grid'),
], ],
]; ];

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,13 +54,14 @@ 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;
} }
if ($cascadeAll) { if ($cascadeAll) {
$continue = $this->confirm( $continue = $this->confirm(
'Permission settings for all shelves will be cascaded. '. 'Permission settings for all shelves will be cascaded. ' .
'Books assigned to multiple shelves will receive only the permissions of it\'s last processed shelf. '. 'Books assigned to multiple shelves will receive only the permissions of it\'s last processed shelf. ' .
'Are you sure you want to proceed?' 'Are you sure you want to proceed?'
); );

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()
{ {
@ -44,12 +43,12 @@ class UpgradeDatabaseEncoding extends Command
$database = DB::getDatabaseName(); $database = DB::getDatabaseName();
$tables = DB::select('SHOW TABLES'); $tables = DB::select('SHOW TABLES');
$this->line('ALTER DATABASE `'.$database.'` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;'); $this->line('ALTER DATABASE `' . $database . '` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
$this->line('USE `'.$database.'`;'); $this->line('USE `' . $database . '`;');
$key = 'Tables_in_' . $database; $key = 'Tables_in_' . $database;
foreach ($tables as $table) { foreach ($tables as $table) {
$tableName = $table->$key; $tableName = $table->$key;
$this->line('ALTER TABLE `'.$tableName.'` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;'); $this->line('ALTER TABLE `' . $tableName . '` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
} }
DB::setDefaultConnection($connection); DB::setDefaultConnection($connection);

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;
@ -17,7 +19,8 @@ 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)
@ -32,6 +35,6 @@ class Kernel extends ConsoleKernel
*/ */
protected function commands() protected function commands()
{ {
$this->load(__DIR__.'/Commands'); $this->load(__DIR__ . '/Commands');
} }
} }

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,15 +55,16 @@ 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
{ {
return [ return [
'bookshelf' => $this->bookshelf, 'bookshelf' => $this->bookshelf,
'book' => $this->book, 'book' => $this->book,
'chapter' => $this->chapter, 'chapter' => $this->chapter,
'page' => $this->page, 'page' => $this->page,
]; ];
} }
@ -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,9 +10,10 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
/** /**
* Class Book * Class Book.
* @property string $description *
* @property int $image_id * @property string $description
* @property int $image_id
* @property Image|null $cover * @property Image|null $cover
*/ */
class Book extends Entity implements HasCoverImage class Book extends Entity implements HasCoverImage
@ -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 $priority * @property int $book_id
* @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.
*/ */
@ -35,17 +35,19 @@ class Deletion extends Model implements Loggable
public static function createForEntity(Entity $entity): Deletion public static function createForEntity(Entity $entity): Deletion
{ {
$record = (new self())->forceFill([ $record = (new self())->forceFill([
'deleted_by' => user()->id, 'deleted_by' => user()->id,
'deletable_type' => $entity->getMorphClass(), 'deletable_type' => $entity->getMorphClass(),
'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;
@ -27,15 +29,16 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* The base class for book-like items such as pages, chapters & books. * The base class for book-like items such as pages, chapters & books.
* This is not a database model in itself but extended. * This is not a database model in itself but extended.
* *
* @property int $id * @property int $id
* @property string $name * @property string $name
* @property string $slug * @property string $slug
* @property Carbon $created_at * @property Carbon $created_at
* @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) . '...';
} }
@ -248,14 +254,14 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
$text = $this->getText(); $text = $this->getText();
if (mb_strlen($text) > $length) { if (mb_strlen($text) > $length) {
$text = mb_substr($text, 0, $length-3) . '...'; $text = mb_substr($text, 0, $length - 3) . '...';
} }
return trim($text); return trim($text);
} }
/** /**
* 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,15 +11,16 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
use Permissions; use Permissions;
/** /**
* Class Page * Class Page.
* @property int $chapter_id *
* @property string $html * @property int $chapter_id
* @property string $markdown * @property string $html
* @property string $text * @property string $markdown
* @property bool $template * @property string $text
* @property bool $draft * @property bool $template
* @property int $revision_count * @property bool $draft
* @property Chapter $chapter * @property int $revision_count
* @property Chapter $chapter
* @property Collection $attachments * @property Collection $attachments
*/ */
class Page extends BookChild class Page extends BookChild
@ -31,7 +34,7 @@ class Page extends BookChild
protected $hidden = ['html', 'markdown', 'text', 'restricted', 'pivot', 'deleted_at']; protected $hidden = ['html', 'markdown', 'text', 'restricted', 'pivot', 'deleted_at'];
protected $casts = [ protected $casts = [
'draft' => 'boolean', 'draft' => 'boolean',
'template' => 'boolean', 'template' => 'boolean',
]; ];
@ -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,29 +1,32 @@
<?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
* @property int $created_by * @property int $created_by
* @property Carbon $created_at * @property Carbon $created_at
* @property string $type * @property string $type
* @property string $summary * @property string $summary
* @property string $markdown * @property string $markdown
* @property string $html * @property string $html
* @property int $revision_number * @property int $revision_number
*/ */
class PageRevision extends Model 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;
@ -14,4 +16,4 @@ abstract class EntityQuery
{ {
return app()->make(EntityProvider::class); return app()->make(EntityProvider::class);
} }
} }

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)
{ {
@ -35,7 +29,7 @@ class BaseRepo
$entity->forceFill([ $entity->forceFill([
'created_by' => user()->id, 'created_by' => user()->id,
'updated_by' => user()->id, 'updated_by' => user()->id,
'owned_by' => user()->id, 'owned_by' => user()->id,
]); ]);
$entity->refreshSlug(); $entity->refreshSlug();
$entity->save(); $entity->save();
@ -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;
} }
@ -128,11 +132,11 @@ class PageRepo
public function getNewDraftPage(Entity $parent) public function getNewDraftPage(Entity $parent)
{ {
$page = (new Page())->forceFill([ $page = (new Page())->forceFill([
'name' => trans('entities.pages_initial_name'), 'name' => trans('entities.pages_initial_name'),
'created_by' => user()->id, 'created_by' => user()->id,
'owned_by' => user()->id, 'owned_by' => user()->id,
'updated_by' => user()->id, 'updated_by' => user()->id,
'draft' => true, 'draft' => true,
]); ]);
if ($parent instanceof Chapter) { if ($parent instanceof Chapter) {
@ -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)
@ -291,7 +302,7 @@ class PageRepo
} else { } else {
$content->setNewHTML($revision->html); $content->setNewHTML($revision->html);
} }
$page->updated_by = user()->id; $page->updated_by = user()->id;
$page->refreshSlug(); $page->refreshSlug();
$page->save(); $page->save();
@ -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,20 +27,23 @@ 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)
{ {
$page->html = (new PageContent($page))->render(); $page->html = (new PageContent($page))->render();
$pageHtml = view('pages.export', [ $pageHtml = view('pages.export', [
'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)
@ -50,43 +54,49 @@ class ExportFormatter
}); });
$html = view('chapters.export', [ $html = view('chapters.export', [
'chapter' => $chapter, 'chapter' => $chapter,
'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)
{ {
$bookTree = (new BookContents($book))->getTree(false, true); $bookTree = (new BookContents($book))->getTree(false, true);
$html = view('books.export', [ $html = view('books.export', [
'book' => $book, 'book' => $book,
'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)
{ {
$page->html = (new PageContent($page))->render(); $page->html = (new PageContent($page))->render();
$html = view('pages.export', [ $html = view('pages.export', [
'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)
@ -98,8 +108,8 @@ class ExportFormatter
$html = view('chapters.export', [ $html = view('chapters.export', [
'chapter' => $chapter, 'chapter' => $chapter,
'pages' => $pages, 'pages' => $pages,
'format' => 'pdf', 'format' => 'pdf',
])->render(); ])->render();
return $this->htmlToPdf($html); return $this->htmlToPdf($html);
@ -107,21 +117,24 @@ 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)
{ {
$bookTree = (new BookContents($book))->getTree(false, true); $bookTree = (new BookContents($book))->getTree(false, true);
$html = view('books.export', [ $html = view('books.export', [
'book' => $book, 'book' => $book,
'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);
} }
@ -54,19 +57,19 @@ class HtmlToMarkdown
protected function getConverterEnvironment(): Environment protected function getConverterEnvironment(): Environment
{ {
$environment = new Environment([ $environment = new Environment([
'header_style' => 'atx', // Set to 'atx' to output H1 and H2 headers as # Header1 and ## Header2 'header_style' => 'atx', // Set to 'atx' to output H1 and H2 headers as # Header1 and ## Header2
'suppress_errors' => true, // Set to false to show warnings when loading malformed HTML 'suppress_errors' => true, // Set to false to show warnings when loading malformed HTML
'strip_tags' => false, // Set to true to strip tags that don't have markdown equivalents. N.B. Strips tags, not their content. Useful to clean MS Word HTML output. 'strip_tags' => false, // Set to true to strip tags that don't have markdown equivalents. N.B. Strips tags, not their content. Useful to clean MS Word HTML output.
'strip_placeholder_links' => false, // Set to true to remove <a> that doesn't have href. 'strip_placeholder_links' => false, // Set to true to remove <a> that doesn't have href.
'bold_style' => '**', // DEPRECATED: Set to '__' if you prefer the underlined style 'bold_style' => '**', // DEPRECATED: Set to '__' if you prefer the underlined style
'italic_style' => '*', // DEPRECATED: Set to '_' if you prefer the underlined style 'italic_style' => '*', // DEPRECATED: Set to '_' if you prefer the underlined style
'remove_nodes' => '', // space-separated list of dom nodes that should be removed. example: 'meta style script' 'remove_nodes' => '', // space-separated list of dom nodes that should be removed. example: 'meta style script'
'hard_break' => false, // Set to true to turn <br> into `\n` instead of ` \n` 'hard_break' => false, // Set to true to turn <br> into `\n` instead of ` \n`
'list_item_style' => '-', // Set the default character for each <li> in a <ul>. Can be '-', '*', or '+' 'list_item_style' => '-', // Set the default character for each <li> in a <ul>. Can be '-', '*', or '+'
'preserve_comments' => false, // Set to true to preserve comments, or set to an array of strings to preserve specific comments 'preserve_comments' => false, // Set to true to preserve comments, or set to an array of strings to preserve specific comments
'use_autolinks' => false, // Set to true to use simple link syntax if possible. Will always use []() if set to false 'use_autolinks' => false, // Set to true to use simple link syntax if possible. Will always use []() if set to false
'table_pipe_escape' => '\|', // Replacement string for pipe characters inside markdown table cells 'table_pipe_escape' => '\|', // Replacement string for pipe characters inside markdown table cells
'table_caption_side' => 'top', // Set to 'top' or 'bottom' to show <caption> content before or after table, null to suppress 'table_caption_side' => 'top', // Set to 'top' or 'bottom' to show <caption> content before or after table, null to suppress
]); ]);
$environment->addConverter(new BlockquoteConverter()); $environment->addConverter(new BlockquoteConverter());

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) : [];
} }
@ -260,9 +266,9 @@ class PageContent
return [ return [
'nodeName' => strtolower($header->nodeName), 'nodeName' => strtolower($header->nodeName),
'level' => intval(str_replace('h', '', $header->nodeName)), 'level' => intval(str_replace('h', '', $header->nodeName)),
'link' => '#' . $header->getAttribute('id'), 'link' => '#' . $header->getAttribute('id'),
'text' => $text, 'text' => $text,
]; ];
})->filter(function ($header) { })->filter(function ($header) {
return mb_strlen($header['text']) > 0; return mb_strlen($header['text']) > 0;
@ -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
@ -35,14 +37,17 @@ class PageEditActivity
$pageDraftEdits = $this->activePageEditingQuery(60)->get(); $pageDraftEdits = $this->activePageEditingQuery(60)->get();
$count = $pageDraftEdits->count(); $count = $pageDraftEdits->count();
$userMessage = $count > 1 ? trans('entities.pages_draft_edit_active.start_a', ['count' => $count]): trans('entities.pages_draft_edit_active.start_b', ['userName' => $pageDraftEdits->first()->createdBy->name]); $userMessage = $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.
*/ */
@ -60,8 +61,8 @@ class PermissionsUpdater
return collect($restrictions)->keys()->map(function ($action) use ($roleId) { return collect($restrictions)->keys()->map(function ($action) use ($roleId) {
return [ return [
'role_id' => $roleId, 'role_id' => $roleId,
'action' => strtolower($action), 'action' => strtolower($action),
] ; ];
}); });
}); });
} }

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)
@ -110,8 +111,8 @@ class SearchIndex
$terms = []; $terms = [];
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;
} }
@ -77,15 +80,15 @@ class SearchOptions
{ {
$terms = [ $terms = [
'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;
@ -56,7 +56,7 @@ class SearchRunner
if ($entityType !== 'all') { if ($entityType !== 'all') {
$entityTypesToSearch = $entityType; $entityTypesToSearch = $entityType;
} else if (isset($searchOpts->filters['type'])) { } elseif (isset($searchOpts->filters['type'])) {
$entityTypesToSearch = explode('|', $searchOpts->filters['type']); $entityTypesToSearch = explode('|', $searchOpts->filters['type']);
} }
@ -78,16 +78,15 @@ class SearchRunner
} }
return [ return [
'total' => $total, 'total' => $total,
'count' => count($results), 'count' => count($results),
'has_more' => $hasMore, 'has_more' => $hasMore,
'results' => $results->sortByDesc('score')->values(), 'results' => $results->sortByDesc('score')->values(),
]; ];
} }
/** /**
* 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)
@ -130,12 +131,13 @@ class SearchRunner
return $query->count(); return $query->count();
} }
$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
{ {
@ -149,20 +151,20 @@ class SearchRunner
$subQuery->where('entity_type', '=', $entity->getMorphClass()); $subQuery->where('entity_type', '=', $entity->getMorphClass());
$subQuery->where(function (Builder $query) use ($searchOpts) { $subQuery->where(function (Builder $query) use ($searchOpts) {
foreach ($searchOpts->searches as $inputTerm) { foreach ($searchOpts->searches as $inputTerm) {
$query->orWhere('term', 'like', $inputTerm .'%'); $query->orWhere('term', 'like', $inputTerm . '%');
} }
})->groupBy('entity_type', 'entity_id'); })->groupBy('entity_type', 'entity_id');
$entitySelect->join($this->db->raw('(' . $subQuery->toSql() . ') as s'), function (JoinClause $join) { $entitySelect->join($this->db->raw('(' . $subQuery->toSql() . ') as s'), function (JoinClause $join) {
$join->on('id', '=', 'entity_id'); $join->on('id', '=', 'entity_id');
})->selectRaw($entity->getTable().'.*, s.score')->orderBy('score', 'desc'); })->selectRaw($entity->getTable() . '.*, s.score')->orderBy('score', 'desc');
$entitySelect->mergeBindings($subQuery); $entitySelect->mergeBindings($subQuery);
} }
// Handle exact term matching // Handle exact term matching
foreach ($searchOpts->exacts as $inputTerm) { foreach ($searchOpts->exacts as $inputTerm) {
$entitySelect->where(function (EloquentBuilder $query) use ($inputTerm, $entity) { $entitySelect->where(function (EloquentBuilder $query) use ($inputTerm, $entity) {
$query->where('name', 'like', '%'.$inputTerm .'%') $query->where('name', 'like', '%' . $inputTerm . '%')
->orWhere($entity->textField, 'like', '%'.$inputTerm .'%'); ->orWhere($entity->textField, 'like', '%' . $inputTerm . '%');
}); });
} }
@ -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 {
@ -298,7 +301,7 @@ class SearchRunner
protected function filterInName(EloquentBuilder $query, Entity $model, $input) protected function filterInName(EloquentBuilder $query, Entity $model, $input)
{ {
$query->where('name', 'like', '%' .$input. '%'); $query->where('name', 'like', '%' . $input . '%');
} }
protected function filterInTitle(EloquentBuilder $query, Entity $model, $input) protected function filterInTitle(EloquentBuilder $query, Entity $model, $input)
@ -308,7 +311,7 @@ class SearchRunner
protected function filterInBody(EloquentBuilder $query, Entity $model, $input) protected function filterInBody(EloquentBuilder $query, Entity $model, $input)
{ {
$query->where($model->textField, 'like', '%' .$input. '%'); $query->where($model->textField, 'like', '%' . $input . '%');
} }
protected function filterIsRestricted(EloquentBuilder $query, Entity $model, $input) protected function filterIsRestricted(EloquentBuilder $query, Entity $model, $input)
@ -338,16 +341,14 @@ 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';
$morphClass = str_replace('\\', '\\\\', $model->getMorphClass()); $morphClass = str_replace('\\', '\\\\', $model->getMorphClass());
$commentQuery = $this->db->raw('(SELECT c1.entity_id, c1.entity_type, c1.created_at as last_commented FROM '.$commentsTable.' c1 LEFT JOIN '.$commentsTable.' c2 ON (c1.entity_id = c2.entity_id AND c1.entity_type = c2.entity_type AND c1.created_at < c2.created_at) WHERE c1.entity_type = \''. $morphClass .'\' AND c2.created_at IS NULL) as comments'); $commentQuery = $this->db->raw('(SELECT c1.entity_id, c1.entity_type, c1.created_at as last_commented FROM ' . $commentsTable . ' c1 LEFT JOIN ' . $commentsTable . ' c2 ON (c1.entity_id = c2.entity_id AND c1.entity_type = c2.entity_type AND c1.created_at < c2.created_at) WHERE c1.entity_type = \'' . $morphClass . '\' AND c2.created_at IS NULL) as comments');
$query->join($commentQuery, $model->getTable() . '.id', '=', 'comments.entity_id')->orderBy('last_commented', 'desc'); $query->join($commentQuery, $model->getTable() . '.id', '=', 'comments.entity_id')->orderBy('last_commented', 'desc');
} }

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

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