Merge pull request #2820 from BookStackApp/analysis-6470L9

Apply fixes from StyleCI
This commit is contained in:
Dan Brown 2021-06-26 16:28:09 +01:00 committed by GitHub
commit 0155525945
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
349 changed files with 3655 additions and 2625 deletions

View File

@ -11,16 +11,15 @@ use Illuminate\Support\Str;
/**
* @property string $type
* @property User $user
* @property User $user
* @property Entity $entity
* @property string $detail
* @property string $entity_type
* @property int $entity_id
* @property int $user_id
* @property int $entity_id
* @property int $user_id
*/
class Activity extends Model
{
/**
* Get the entity for this activity.
*/
@ -29,6 +28,7 @@ class Activity extends Model
if ($this->entity_type === '') {
$this->entity_type = null;
}
return $this->morphTo('entity');
}
@ -54,7 +54,7 @@ class Activity extends Model
public function isForEntity(): bool
{
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\User;
@ -33,6 +35,7 @@ class ActivityService
/**
* Add a generic activity event to the database.
*
* @param string|Loggable $detail
*/
public function add(string $type, $detail = '')
@ -54,7 +57,7 @@ class ActivityService
{
return $this->activity->newInstance()->forceFill([
'type' => strtolower($type),
'user_id' => user()->id,
'user_id' => user()->id,
]);
}
@ -67,8 +70,8 @@ class ActivityService
{
$entity->activity()->update([
'detail' => $entity->name,
'entity_id' => null,
'entity_type' => null,
'entity_id' => null,
'entity_type' => null,
]);
}
@ -98,10 +101,10 @@ class ActivityService
$queryIds = [$entity->getMorphClass() => [$entity->id]];
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')) {
$queryIds[(new Page)->getMorphClass()] = $entity->pages()->visible()->pluck('id');
$queryIds[(new Page())->getMorphClass()] = $entity->pages()->visible()->pluck('id');
}
$query = $this->activity->newQuery();
@ -143,7 +146,9 @@ class ActivityService
/**
* Filters out similar activity.
*
* @param Activity[] $activities
*
* @return array
*/
protected function filterSimilar(iterable $activities): array
@ -185,7 +190,7 @@ class ActivityService
return;
}
$message = str_replace("%u", $username, $message);
$message = str_replace('%u', $username, $message);
$channel = config('logging.failed_login.channel');
Log::channel($channel)->warning($message);
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Actions;
<?php
namespace BookStack\Actions;
use BookStack\Model;
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'];
/**
* Get the entity that this tag belongs to
* Get the entity that this tag belongs to.
*/
public function entity(): MorphTo
{
@ -21,7 +23,7 @@ class Tag extends Model
*/
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
{
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\Entities\Models\Entity;
@ -7,7 +9,6 @@ use Illuminate\Support\Collection;
class TagRepo
{
protected $tag;
protected $permissionService;
@ -37,6 +38,7 @@ class TagRepo
}
$query = $this->permissionService->filterRestrictedEntityRelations($query, 'tags', 'entity_id', 'entity_type');
return $query->get(['name'])->pluck('name');
}
@ -62,11 +64,12 @@ class TagRepo
}
$query = $this->permissionService->filterRestrictedEntityRelations($query, 'tags', 'entity_id', 'entity_type');
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
{
@ -89,6 +92,7 @@ class TagRepo
{
$name = trim($input['name']);
$value = isset($input['value']) ? trim($input['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\Model;
@ -16,7 +18,6 @@ use Illuminate\Database\Eloquent\Relations\MorphTo;
*/
class View extends Model
{
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 Illuminate\Contracts\Container\BindingResolutionException;
@ -12,7 +14,6 @@ use ReflectionMethod;
class ApiDocsGenerator
{
protected $reflectionClasses = [];
protected $controllerClasses = [];
@ -30,6 +31,7 @@ class ApiDocsGenerator
$docs = (new static())->generate();
Cache::put($cacheKey, $docs, 60 * 24);
}
return $docs;
}
@ -42,6 +44,7 @@ class ApiDocsGenerator
$apiRoutes = $this->loadDetailsFromControllers($apiRoutes);
$apiRoutes = $this->loadDetailsFromFiles($apiRoutes);
$apiRoutes = $apiRoutes->groupBy('base_model');
return $apiRoutes;
}
@ -57,6 +60,7 @@ class ApiDocsGenerator
$exampleContent = file_exists($exampleFile) ? file_get_contents($exampleFile) : null;
$route["example_{$exampleType}"] = $exampleContent;
}
return $route;
});
}
@ -71,12 +75,14 @@ class ApiDocsGenerator
$comment = $method->getDocComment();
$route['description'] = $comment ? $this->parseDescriptionFromMethodComment($comment) : null;
$route['body_params'] = $this->getBodyParamsFromClass($route['controller'], $route['controller_method']);
return $route;
});
}
/**
* Load body params and their rules by inspecting the given class and method name.
*
* @throws BindingResolutionException
*/
protected function getBodyParamsFromClass(string $className, string $methodName): ?array
@ -92,6 +98,7 @@ class ApiDocsGenerator
foreach ($rules as $param => $ruleString) {
$rules[$param] = explode('|', $ruleString);
}
return count($rules) > 0 ? $rules : null;
}
@ -102,11 +109,13 @@ class ApiDocsGenerator
{
$matches = [];
preg_match_all('/^\s*?\*\s((?![@\s]).*?)$/m', $comment, $matches);
return implode(' ', $matches[1] ?? []);
}
/**
* Get a reflection method from the given class name and method name.
*
* @throws ReflectionException
*/
protected function getReflectionMethod(string $className, string $methodName): ReflectionMethod
@ -131,14 +140,15 @@ class ApiDocsGenerator
[$controller, $controllerMethod] = explode('@', $route->action['uses']);
$baseModelName = explode('.', explode('/', $route->uri)[1])[0];
$shortName = $baseModelName . '-' . $controllerMethod;
return [
'name' => $shortName,
'uri' => $route->uri,
'method' => $route->methods[0],
'controller' => $controller,
'controller_method' => $controllerMethod,
'name' => $shortName,
'uri' => $route->uri,
'method' => $route->methods[0],
'controller' => $controller,
'controller_method' => $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\Interfaces\Loggable;
@ -7,19 +9,20 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Carbon;
/**
* Class ApiToken
* @property int $id
* Class ApiToken.
*
* @property int $id
* @property string $token_id
* @property string $secret
* @property string $name
* @property Carbon $expires_at
* @property User $user
* @property User $user
*/
class ApiToken extends Model implements Loggable
{
protected $fillable = ['name', 'expires_at'];
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
{
use GuardHelpers;
/**
@ -20,9 +19,9 @@ class ApiTokenGuard implements Guard
*/
protected $request;
/**
* The last auth exception thrown in this request.
*
* @var ApiAuthException
*/
protected $lastAuthException;
@ -34,7 +33,7 @@ class ApiTokenGuard implements Guard
{
$this->request = $request;
}
/**
* @inheritDoc
*/
@ -47,6 +46,7 @@ class ApiTokenGuard implements Guard
}
$user = null;
try {
$user = $this->getAuthorisedUserFromRequest();
} catch (ApiAuthException $exception) {
@ -54,19 +54,20 @@ class ApiTokenGuard implements Guard
}
$this->user = $user;
return $user;
}
/**
* Determine if current user is authenticated. If not, throw an exception.
*
* @return \Illuminate\Contracts\Auth\Authenticatable
*
* @throws ApiAuthException
*
* @return \Illuminate\Contracts\Auth\Authenticatable
*/
public function authenticate()
{
if (! is_null($user = $this->user())) {
if (!is_null($user = $this->user())) {
return $user;
}
@ -79,6 +80,7 @@ class ApiTokenGuard implements Guard
/**
* Check the API token in the request and fetch a valid authorised user.
*
* @throws ApiAuthException
*/
protected function getAuthorisedUserFromRequest(): Authenticatable
@ -98,6 +100,7 @@ class ApiTokenGuard implements Guard
/**
* Validate the format of the token header value string.
*
* @throws ApiAuthException
*/
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
* currently has access to the instance API.
*
* @throws ApiAuthException
*/
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\Collection;
@ -6,7 +8,6 @@ use Illuminate\Http\Request;
class ListingResponseBuilder
{
protected $query;
protected $request;
protected $fields;
@ -18,7 +19,7 @@ class ListingResponseBuilder
'lt' => '<',
'gte' => '>=',
'lte' => '<=',
'like' => 'like'
'like' => 'like',
];
/**
@ -42,7 +43,7 @@ class ListingResponseBuilder
$data = $this->fetchData($filteredQuery);
return response()->json([
'data' => $data,
'data' => $data,
'total' => $total,
]);
}
@ -54,6 +55,7 @@ class ListingResponseBuilder
{
$query = $this->countAndOffsetQuery($query);
$query = $this->sortQuery($query);
return $query->get($this->fields);
}
@ -95,6 +97,7 @@ class ListingResponseBuilder
}
$queryOperator = $this->filterOperators[$filterOperator];
return [$field, $queryOperator, $value];
}

View File

@ -4,11 +4,11 @@ namespace BookStack;
class Application extends \Illuminate\Foundation\Application
{
/**
* 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
*/
public function configPath($path = '')
@ -18,6 +18,6 @@ class Application extends \Illuminate\Foundation\Application
. 'app'
. DIRECTORY_SEPARATOR
. '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\Exceptions\ConfirmationEmailException;
@ -12,7 +14,9 @@ class EmailConfirmationService extends UserTokenService
/**
* Create new confirmation for a user,
* Also removes any existing old ones.
*
* @param User $user
*
* @throws ConfirmationEmailException
*/
public function sendConfirmation(User $user)
@ -29,9 +33,10 @@ class EmailConfirmationService extends UserTokenService
/**
* Check if confirmation is required in this instance.
*
* @return bool
*/
public function confirmationRequired() : bool
public function confirmationRequired(): bool
{
return setting('registration-confirmation')
|| 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\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
class ExternalAuthService
{
@ -19,6 +19,7 @@ class ExternalAuthService
}
$roleName = str_replace(' ', '-', trim(strtolower($role->display_name)));
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
{

View File

@ -7,7 +7,6 @@ use Illuminate\Contracts\Auth\UserProvider;
class ExternalBaseUserProvider implements UserProvider
{
/**
* The user model.
*
@ -17,7 +16,8 @@ class ExternalBaseUserProvider implements UserProvider
/**
* LdapUserProvider constructor.
* @param $model
*
* @param $model
*/
public function __construct(string $model)
{
@ -32,13 +32,15 @@ class ExternalBaseUserProvider implements UserProvider
public function createModel()
{
$class = '\\' . ltrim($this->model, '\\');
return new $class;
return new $class();
}
/**
* Retrieve a user by their unique identifier.
*
* @param mixed $identifier
* @param mixed $identifier
*
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function retrieveById($identifier)
@ -49,8 +51,9 @@ class ExternalBaseUserProvider implements UserProvider
/**
* Retrieve a user by their unique identifier and "remember me" token.
*
* @param mixed $identifier
* @param string $token
* @param mixed $identifier
* @param string $token
*
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function retrieveByToken($identifier, $token)
@ -58,12 +61,12 @@ class ExternalBaseUserProvider implements UserProvider
return null;
}
/**
* Update the "remember me" token for the given user in storage.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param string $token
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param string $token
*
* @return void
*/
public function updateRememberToken(Authenticatable $user, $token)
@ -74,13 +77,15 @@ class ExternalBaseUserProvider implements UserProvider
/**
* Retrieve a user by the given credentials.
*
* @param array $credentials
* @param array $credentials
*
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function retrieveByCredentials(array $credentials)
{
// Search current user base by looking up a uid
$model = $this->createModel();
return $model->newQuery()
->where('external_auth_id', $credentials['external_auth_id'])
->first();
@ -89,8 +94,9 @@ class ExternalBaseUserProvider implements UserProvider
/**
* Validate a user against the given credentials.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param array $credentials
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param array $credentials
*
* @return bool
*/
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
// 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.
if (! is_null($this->user)) {
if (!is_null($this->user)) {
return $this->user;
}
@ -92,7 +92,7 @@ class ExternalBaseSessionGuard implements StatefulGuard
// First we will try to load the user using the
// identifier in the session if one exists.
if (! is_null($id)) {
if (!is_null($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.
*
* @param array $credentials
* @param array $credentials
*
* @return bool
*/
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.
*
* @param mixed $id
* @param mixed $id
*
* @return \Illuminate\Contracts\Auth\Authenticatable|false
*/
public function onceUsingId($id)
{
if (! is_null($user = $this->provider->retrieveById($id))) {
if (!is_null($user = $this->provider->retrieveById($id))) {
$this->setUser($user);
return $user;
@ -152,7 +154,8 @@ class ExternalBaseSessionGuard implements StatefulGuard
/**
* Validate a user's credentials.
*
* @param array $credentials
* @param array $credentials
*
* @return bool
*/
public function validate(array $credentials = [])
@ -160,12 +163,12 @@ class ExternalBaseSessionGuard implements StatefulGuard
return false;
}
/**
* Attempt to authenticate a user using the given credentials.
*
* @param array $credentials
* @param bool $remember
* @param array $credentials
* @param bool $remember
*
* @return bool
*/
public function attempt(array $credentials = [], $remember = false)
@ -176,13 +179,14 @@ class ExternalBaseSessionGuard implements StatefulGuard
/**
* Log the given user ID into the application.
*
* @param mixed $id
* @param bool $remember
* @param mixed $id
* @param bool $remember
*
* @return \Illuminate\Contracts\Auth\Authenticatable|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);
return $user;
@ -194,8 +198,9 @@ class ExternalBaseSessionGuard implements StatefulGuard
/**
* Log a user into the application.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param bool $remember
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param bool $remember
*
* @return void
*/
public function login(AuthenticatableContract $user, $remember = false)
@ -208,7 +213,8 @@ class ExternalBaseSessionGuard implements StatefulGuard
/**
* Update the session with the given ID.
*
* @param string $id
* @param string $id
*
* @return void
*/
protected function updateSession($id)
@ -262,7 +268,7 @@ class ExternalBaseSessionGuard implements StatefulGuard
*/
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.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param \Illuminate\Contracts\Auth\Authenticatable $user
*
* @return $this
*/
public function setUser(AuthenticatableContract $user)

View File

@ -6,8 +6,8 @@ use BookStack\Auth\Access\LdapService;
use BookStack\Auth\Access\RegistrationService;
use BookStack\Auth\User;
use BookStack\Exceptions\LdapException;
use BookStack\Exceptions\LoginAttemptException;
use BookStack\Exceptions\LoginAttemptEmailNeededException;
use BookStack\Exceptions\LoginAttemptException;
use BookStack\Exceptions\UserRegistrationException;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Contracts\Session\Session;
@ -15,7 +15,6 @@ use Illuminate\Support\Str;
class LdapSessionGuard extends ExternalBaseSessionGuard
{
protected $ldapService;
/**
@ -36,8 +35,10 @@ class LdapSessionGuard extends ExternalBaseSessionGuard
* Validate a user's credentials.
*
* @param array $credentials
* @return bool
*
* @throws LdapException
*
* @return bool
*/
public function validate(array $credentials = [])
{
@ -45,7 +46,7 @@ class LdapSessionGuard extends ExternalBaseSessionGuard
if (isset($userDetails['uid'])) {
$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.
*
* @param array $credentials
* @param bool $remember
* @return bool
* @param bool $remember
*
* @throws LoginAttemptException
* @throws LdapException
*
* @return bool
*/
public function attempt(array $credentials = [], $remember = false)
{
@ -69,7 +72,7 @@ class LdapSessionGuard extends ExternalBaseSessionGuard
$user = null;
if (isset($userDetails['uid'])) {
$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);
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 LoginAttemptException
* @throws UserRegistrationException
@ -114,14 +119,15 @@ class LdapSessionGuard extends ExternalBaseSessionGuard
}
$details = [
'name' => $ldapUserDetails['name'],
'email' => $ldapUserDetails['email'] ?: $credentials['email'],
'name' => $ldapUserDetails['name'],
'email' => $ldapUserDetails['email'] ?: $credentials['email'],
'external_auth_id' => $ldapUserDetails['uid'],
'password' => Str::random(32),
'password' => Str::random(32),
];
$user = $this->registrationService->registerUser($details, null, false);
$this->ldapService->saveAndAttachAvatar($user, $ldapUserDetails);
return $user;
}
}

View File

@ -3,7 +3,7 @@
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
* 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.
*
* @param array $credentials
*
* @return bool
*/
public function validate(array $credentials = [])
@ -27,7 +28,8 @@ class Saml2SessionGuard extends ExternalBaseSessionGuard
* Attempt to authenticate a user using the given credentials.
*
* @param array $credentials
* @param bool $remember
* @param bool $remember
*
* @return bool
*/
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
@ -7,11 +9,12 @@
*/
class Ldap
{
/**
* Connect to a LDAP server.
*
* @param string $hostName
* @param int $port
*
* @return resource
*/
public function connect($hostName, $port)
@ -21,9 +24,11 @@ class Ldap
/**
* Set the value of a LDAP option for the given connection.
*
* @param resource $ldapConnection
* @param int $option
* @param mixed $value
* @param int $option
* @param mixed $value
*
* @return bool
*/
public function setOption($ldapConnection, $option, $value)
@ -41,8 +46,10 @@ class Ldap
/**
* Set the version number for the given ldap connection.
*
* @param $ldapConnection
* @param $version
*
* @return bool
*/
public function setVersion($ldapConnection, $version)
@ -52,10 +59,12 @@ class Ldap
/**
* Search LDAP tree using the provided filter.
*
* @param resource $ldapConnection
* @param string $baseDn
* @param string $filter
* @param array|null $attributes
*
* @return resource
*/
public function search($ldapConnection, $baseDn, $filter, array $attributes = null)
@ -65,8 +74,10 @@ class Ldap
/**
* Get entries from an ldap search result.
*
* @param resource $ldapConnection
* @param resource $ldapSearchResult
*
* @return array
*/
public function getEntries($ldapConnection, $ldapSearchResult)
@ -76,23 +87,28 @@ class Ldap
/**
* Search and get entries immediately.
*
* @param resource $ldapConnection
* @param string $baseDn
* @param string $filter
* @param array|null $attributes
*
* @return resource
*/
public function searchAndGetEntries($ldapConnection, $baseDn, $filter, array $attributes = null)
{
$search = $this->search($ldapConnection, $baseDn, $filter, $attributes);
return $this->getEntries($ldapConnection, $search);
}
/**
* Bind to LDAP directory.
*
* @param resource $ldapConnection
* @param string $bindRdn
* @param string $bindPassword
*
* @return bool
*/
public function bind($ldapConnection, $bindRdn = null, $bindPassword = null)
@ -102,8 +118,10 @@ class Ldap
/**
* Explode a LDAP dn string into an array of components.
*
* @param string $dn
* @param int $withAttrib
* @param int $withAttrib
*
* @return array
*/
public function explodeDn(string $dn, int $withAttrib)
@ -113,12 +131,14 @@ class Ldap
/**
* Escape a string for use in an LDAP filter.
*
* @param string $value
* @param string $ignore
* @param int $flags
* @param int $flags
*
* @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);
}

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Auth\Access;
<?php
namespace BookStack\Auth\Access;
use BookStack\Auth\User;
use BookStack\Exceptions\JsonDebugException;
@ -13,7 +15,6 @@ use Illuminate\Support\Facades\Log;
*/
class LdapService extends ExternalAuthService
{
protected $ldap;
protected $ldapConnection;
protected $userAvatars;
@ -33,6 +34,7 @@ class LdapService extends ExternalAuthService
/**
* Check if groups should be synced.
*
* @return bool
*/
public function shouldSyncGroups()
@ -42,6 +44,7 @@ class LdapService extends ExternalAuthService
/**
* Search for attributes for a specific user on the ldap.
*
* @throws LdapException
*/
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.
* User found via configurable user filter.
*
* @throws LdapException
*/
public function getUserDetails(string $userName): ?array
@ -92,16 +96,16 @@ class LdapService extends ExternalAuthService
$userCn = $this->getUserResponseProperty($user, 'cn', null);
$formatted = [
'uid' => $this->getUserResponseProperty($user, $idAttr, $user['dn']),
'name' => $this->getUserResponseProperty($user, $displayNameAttr, $userCn),
'dn' => $user['dn'],
'uid' => $this->getUserResponseProperty($user, $idAttr, $user['dn']),
'name' => $this->getUserResponseProperty($user, $displayNameAttr, $userCn),
'dn' => $user['dn'],
'email' => $this->getUserResponseProperty($user, $emailAttr, null),
'avatar'=> $thumbnailAttr ? $this->getUserResponseProperty($user, $thumbnailAttr, null) : null,
];
if ($this->config['dump_user_details']) {
throw new JsonDebugException([
'details_from_ldap' => $user,
'details_from_ldap' => $user,
'details_bookstack_parsed' => $formatted,
]);
}
@ -137,6 +141,7 @@ class LdapService extends ExternalAuthService
/**
* Check if the given credentials are valid for the given user.
*
* @throws LdapException
*/
public function validateUserCredentials(?array $ldapUserDetails, string $password): bool
@ -146,6 +151,7 @@ class LdapService extends ExternalAuthService
}
$ldapConnection = $this->getConnection();
try {
$ldapBind = $this->ldap->bind($ldapConnection, $ldapUserDetails['dn'], $password);
} catch (ErrorException $e) {
@ -158,7 +164,9 @@ class LdapService extends ExternalAuthService
/**
* Bind the system user to the LDAP connection using the given credentials
* otherwise anonymous access is attempted.
*
* @param $connection
*
* @throws LdapException
*/
protected function bindSystemUser($connection)
@ -181,8 +189,10 @@ class LdapService extends ExternalAuthService
/**
* Get the connection to the LDAP server.
* Creates a new connection if one does not exist.
* @return resource
*
* @throws LdapException
*
* @return resource
*/
protected function getConnection()
{
@ -222,6 +232,7 @@ class LdapService extends ExternalAuthService
}
$this->ldapConnection = $ldapConnection;
return $this->ldapConnection;
}
@ -241,6 +252,7 @@ class LdapService extends ExternalAuthService
// Otherwise, extract the port out
$hostName = $serverNameParts[0];
$ldapPort = (count($serverNameParts) > 1) ? intval($serverNameParts[1]) : 389;
return ['host' => $hostName, 'port' => $ldapPort];
}
@ -254,11 +266,13 @@ class LdapService extends ExternalAuthService
$newKey = '${' . $key . '}';
$newAttrs[$newKey] = $this->ldap->escape($attrText);
}
return strtr($filterString, $newAttrs);
}
/**
* Get the groups a user is a part of on ldap.
*
* @throws LdapException
*/
public function getUserGroups(string $userName): array
@ -272,11 +286,13 @@ class LdapService extends ExternalAuthService
$userGroups = $this->groupFilter($user);
$userGroups = $this->getGroupsRecursive($userGroups, []);
return $userGroups;
}
/**
* Get the parent groups of an array of groups.
*
* @throws LdapException
*/
private function getGroupsRecursive(array $groupsArray, array $checked): array
@ -303,6 +319,7 @@ class LdapService extends ExternalAuthService
/**
* Get the parent groups of a single group.
*
* @throws LdapException
*/
private function getGroupGroups(string $groupName): array
@ -336,7 +353,7 @@ class LdapService extends ExternalAuthService
$count = 0;
if (isset($userGroupSearchResponse[$groupsAttr]['count'])) {
$count = (int)$userGroupSearchResponse[$groupsAttr]['count'];
$count = (int) $userGroupSearchResponse[$groupsAttr]['count'];
}
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.
*
* @throws LdapException
*/
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\Auth\SocialAccount;
@ -12,7 +14,6 @@ use Exception;
class RegistrationService
{
protected $userRepo;
protected $emailConfirmationService;
@ -27,6 +28,7 @@ class RegistrationService
/**
* Check whether or not registrations are allowed in the app settings.
*
* @throws UserRegistrationException
*/
public function ensureRegistrationAllowed()
@ -44,11 +46,13 @@ class RegistrationService
{
$authMethod = config('auth.method');
$authMethodsWithRegistration = ['standard'];
return in_array($authMethod, $authMethodsWithRegistration) && setting('registration-enabled');
}
/**
* The registrations flow for all users.
*
* @throws UserRegistrationException
*/
public function registerUser(array $userData, ?SocialAccount $socialAccount = null, bool $emailConfirmed = false): User
@ -84,6 +88,7 @@ class RegistrationService
session()->flash('sent-email-confirmation', true);
} catch (Exception $e) {
$message = trans('auth.email_confirm_send_error');
throw new UserRegistrationException($message, '/register/confirm');
}
}
@ -94,6 +99,7 @@ class RegistrationService
/**
* 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 UserRegistrationException
*/
protected function ensureEmailDomainAllowed(string $userEmail): void
@ -105,9 +111,10 @@ class RegistrationService
}
$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)) {
$redirect = $this->registrationAllowed() ? '/register' : '/login';
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\Auth\User;
@ -37,20 +39,23 @@ class Saml2Service extends ExternalAuthService
/**
* Initiate a login flow.
*
* @throws Error
*/
public function login(): array
{
$toolKit = $this->getToolkit();
$returnRoute = url('/saml2/acs');
return [
'url' => $toolKit->login($returnRoute, [], false, false, true),
'id' => $toolKit->getLastRequestID(),
'id' => $toolKit->getLastRequestID(),
];
}
/**
* Initiate a logout flow.
*
* @throws Error
*/
public function logout(): array
@ -78,6 +83,7 @@ class Saml2Service extends ExternalAuthService
* Process the ACS response from the idp and return the
* matching, or new if registration active, user matched to the idp.
* Returns null if not authenticated.
*
* @throws Error
* @throws SamlException
* @throws ValidationError
@ -92,7 +98,7 @@ class Saml2Service extends ExternalAuthService
if (!empty($errors)) {
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.
*
* @throws Error
*/
public function processSlsResponse(?string $requestId): ?string
@ -119,11 +126,12 @@ class Saml2Service extends ExternalAuthService
if (!empty($errors)) {
throw new Error(
'Invalid SLS Response: '.implode(', ', $errors)
'Invalid SLS Response: ' . implode(', ', $errors)
);
}
$this->actionLogout();
return $redirect;
}
@ -138,6 +146,7 @@ class Saml2Service extends ExternalAuthService
/**
* Get the metadata for this service provider.
*
* @throws Error
*/
public function metadata(): string
@ -149,7 +158,7 @@ class Saml2Service extends ExternalAuthService
if (!empty($errors)) {
throw new Error(
'Invalid SP metadata: '.implode(', ', $errors),
'Invalid SP metadata: ' . implode(', ', $errors),
Error::METADATA_SP_INVALID
);
}
@ -159,6 +168,7 @@ class Saml2Service extends ExternalAuthService
/**
* Load the underlying Onelogin SAML2 toolkit.
*
* @throws Error
* @throws Exception
*/
@ -178,6 +188,7 @@ class Saml2Service extends ExternalAuthService
$spSettings = $this->loadOneloginServiceProviderDetails();
$settings = array_replace_recursive($settings, $spSettings, $metaDataSettings, $overrides);
return new Auth($settings);
}
@ -187,18 +198,18 @@ class Saml2Service extends ExternalAuthService
protected function loadOneloginServiceProviderDetails(): array
{
$spDetails = [
'entityId' => url('/saml2/metadata'),
'entityId' => url('/saml2/metadata'),
'assertionConsumerService' => [
'url' => url('/saml2/acs'),
],
'singleLogoutService' => [
'url' => url('/saml2/sls')
'url' => url('/saml2/sls'),
],
];
return [
'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
{
@ -261,9 +272,9 @@ class Saml2Service extends ExternalAuthService
return [
'external_id' => $externalId,
'name' => $this->getUserDisplayName($samlAttributes, $externalId),
'email' => $email,
'saml_id' => $samlID,
'name' => $this->getUserDisplayName($samlAttributes, $externalId),
'email' => $email,
'saml_id' => $samlID,
];
}
@ -297,6 +308,7 @@ class Saml2Service extends ExternalAuthService
$data = $data[0];
break;
}
return $data;
}
@ -315,6 +327,7 @@ class Saml2Service extends ExternalAuthService
/**
* Get the user from the database for the specified details.
*
* @throws UserRegistrationException
*/
protected function getOrRegisterUser(array $userDetails): ?User
@ -325,9 +338,9 @@ class Saml2Service extends ExternalAuthService
if (is_null($user)) {
$userData = [
'name' => $userDetails['name'],
'email' => $userDetails['email'],
'password' => Str::random(32),
'name' => $userDetails['name'],
'email' => $userDetails['email'],
'password' => Str::random(32),
'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
* they exist, optionally registering them automatically.
*
* @throws SamlException
* @throws JsonDebugException
* @throws UserRegistrationException
@ -351,8 +365,8 @@ class Saml2Service extends ExternalAuthService
if ($this->config['dump_user_details']) {
throw new JsonDebugException([
'id_from_idp' => $samlID,
'attrs_from_idp' => $samlAttributes,
'id_from_idp' => $samlID,
'attrs_from_idp' => $samlAttributes,
'attrs_after_parsing' => $userDetails,
]);
}
@ -378,6 +392,7 @@ class Saml2Service extends ExternalAuthService
auth()->login($user);
Activity::add(ActivityType::AUTH_LOGIN, "saml2; {$user->logDescriptor()}");
Theme::dispatch(ThemeEvents::AUTH_LOGIN, 'saml2', $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\Auth\SocialAccount;
@ -21,12 +23,14 @@ class SocialAuthService
{
/**
* The core socialite library used.
*
* @var Socialite
*/
protected $socialite;
/**
* The default built-in social drivers we support.
*
* @var string[]
*/
protected $validSocialDrivers = [
@ -39,7 +43,7 @@ class SocialAuthService
'okta',
'gitlab',
'twitch',
'discord'
'discord',
];
/**
@ -47,6 +51,7 @@ class SocialAuthService
* for an initial redirect action.
* Array is keyed by social driver name.
* Callbacks are passed an instance of the driver.
*
* @var array<string, callable>
*/
protected $configureForRedirectCallbacks = [];
@ -61,26 +66,31 @@ class SocialAuthService
/**
* Start the social login path.
*
* @throws SocialDriverNotConfigured
*/
public function startLogIn(string $socialDriver): RedirectResponse
{
$driver = $this->validateDriver($socialDriver);
return $this->getDriverForRedirect($driver)->redirect();
}
/**
* Start the social registration process
* Start the social registration process.
*
* @throws SocialDriverNotConfigured
*/
public function startRegister(string $socialDriver): RedirectResponse
{
$driver = $this->validateDriver($socialDriver);
return $this->getDriverForRedirect($driver)->redirect();
}
/**
* Handle the social registration process on callback.
*
* @throws UserRegistrationException
*/
public function handleRegistrationCallback(string $socialDriver, SocialUser $socialUser): SocialUser
@ -92,6 +102,7 @@ class SocialAuthService
if (User::query()->where('email', '=', $socialUser->getEmail())->exists()) {
$email = $socialUser->getEmail();
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.
*
* @throws SocialDriverNotConfigured
*/
public function getSocialUser(string $socialDriver): SocialUser
{
$driver = $this->validateDriver($socialDriver);
return $this->socialite->driver($driver)->user();
}
/**
* Handle the login process on a oAuth callback.
*
* @throws SocialSignInAccountNotUsed
*/
public function handleLoginCallback(string $socialDriver, SocialUser $socialUser)
@ -128,6 +142,7 @@ class SocialAuthService
auth()->login($socialAccount->user);
Activity::add(ActivityType::AUTH_LOGIN, $socialAccount);
Theme::dispatch(ThemeEvents::AUTH_LOGIN, $socialDriver, $socialAccount->user);
return redirect()->intended('/');
}
@ -137,18 +152,21 @@ class SocialAuthService
$account = $this->newSocialAccount($socialDriver, $socialUser);
$currentUser->socialAccounts()->save($account);
session()->flash('success', trans('settings.users_social_connected', ['socialAccount' => $titleCaseDriver]));
return redirect($currentUser->getEditUrl());
}
// 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) {
session()->flash('error', trans('errors.social_account_existing', ['socialAccount' => $titleCaseDriver]));
return redirect($currentUser->getEditUrl());
}
// 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) {
session()->flash('error', trans('errors.social_account_already_used_existing', ['socialAccount' => $titleCaseDriver]));
return redirect($currentUser->getEditUrl());
}
@ -163,6 +181,7 @@ class SocialAuthService
/**
* Ensure the social driver is correct and supported.
*
* @throws SocialDriverNotConfigured
*/
protected function validateDriver(string $socialDriver): string
@ -188,6 +207,7 @@ class SocialAuthService
$lowerName = strtolower($driver);
$configPrefix = 'services.' . $lowerName . '.';
$config = [config($configPrefix . 'client_id'), config($configPrefix . 'client_secret'), config('services.callback_url')];
return !in_array(false, $config) && !in_array(null, $config);
}
@ -237,9 +257,9 @@ class SocialAuthService
public function newSocialAccount(string $socialDriver, SocialUser $socialUser): SocialAccount
{
return new SocialAccount([
'driver' => $socialDriver,
'driver' => $socialDriver,
'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
{

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Auth\Access;
<?php
namespace BookStack\Auth\Access;
use BookStack\Auth\User;
use BookStack\Notifications\UserInvite;
@ -11,6 +13,7 @@ class UserInviteService extends UserTokenService
/**
* Send an invitation to a user to sign into BookStack
* Removes existing invitation tokens.
*
* @param 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\Exceptions\UserTokenExpiredException;
@ -10,15 +12,16 @@ use stdClass;
class UserTokenService
{
/**
* Name of table where user tokens are stored.
*
* @var string
*/
protected $tokenTable = 'user_tokens';
/**
* Token expiry time in hours.
*
* @var int
*/
protected $expiryTime = 24;
@ -27,6 +30,7 @@ class UserTokenService
/**
* UserTokenService constructor.
*
* @param Database $db
*/
public function __construct(Database $db)
@ -36,7 +40,9 @@ class UserTokenService
/**
* Delete all email confirmations that belong to a user.
*
* @param User $user
*
* @return mixed
*/
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.
*
* @param string $token
* @return int
*
* @throws UserTokenNotFoundException
* @throws UserTokenExpiredException
*
* @return int
*/
public function checkTokenAndGetUserId(string $token) : int
public function checkTokenAndGetUserId(string $token): int
{
$entry = $this->getEntryByToken($token);
@ -70,40 +79,47 @@ class UserTokenService
/**
* Creates a unique token within the email confirmation database.
*
* @return string
*/
protected function generateToken() : string
protected function generateToken(): string
{
$token = Str::random(24);
while ($this->tokenExists($token)) {
$token = Str::random(25);
}
return $token;
}
/**
* Generate and store a token for the given user.
*
* @param User $user
*
* @return string
*/
protected function createTokenForUser(User $user) : string
protected function createTokenForUser(User $user): string
{
$token = $this->generateToken();
$this->db->table($this->tokenTable)->insert([
'user_id' => $user->id,
'token' => $token,
'user_id' => $user->id,
'token' => $token,
'created_at' => Carbon::now(),
'updated_at' => Carbon::now()
'updated_at' => Carbon::now(),
]);
return $token;
}
/**
* Check if the given token exists.
*
* @param string $token
*
* @return bool
*/
protected function tokenExists(string $token) : bool
protected function tokenExists(string $token): bool
{
return $this->db->table($this->tokenTable)
->where('token', '=', $token)->exists();
@ -111,7 +127,9 @@ class UserTokenService
/**
* Get a token entry for the given token.
*
* @param string $token
*
* @return object|null
*/
protected function getEntryByToken(string $token)
@ -123,10 +141,12 @@ class UserTokenService
/**
* Check if the given token entry has expired.
*
* @param stdClass $tokenEntry
*
* @return bool
*/
protected function entryExpired(stdClass $tokenEntry) : bool
protected function entryExpired(stdClass $tokenEntry): bool
{
return Carbon::now()->subHours($this->expiryTime)
->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;
class EntityPermission extends Model
{
protected $fillable = ['role_id', 'action'];
public $timestamps = false;
/**
* Get all this restriction's attached entity.
*
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
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\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\User;
@ -48,7 +50,7 @@ class PermissionService
}
/**
* Set the database connection
* Set the database 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
*/
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
{
@ -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
{
@ -151,12 +154,13 @@ class PermissionService
},
'pages' => function ($query) {
$query->withTrashed()->select(['id', 'restricted', 'owned_by', 'book_id', 'chapter_id']);
}
},
]);
}
/**
* Build joint permissions for the given shelf and role combinations.
*
* @throws Throwable
*/
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.
*
* @throws Throwable
*/
protected function buildJointPermissionsForBooks(EloquentCollection $books, array $roles, bool $deleteOld = false)
@ -193,6 +198,7 @@ class PermissionService
/**
* Rebuild the entity jointPermissions for a particular entity.
*
* @throws Throwable
*/
public function buildJointPermissionsForEntity(Entity $entity)
@ -201,6 +207,7 @@ class PermissionService
if ($entity instanceof Book) {
$books = $this->bookFetchQuery()->where('id', '=', $entity->id)->get();
$this->buildJointPermissionsForBooks($books, Role::query()->get()->all(), true);
return;
}
@ -224,6 +231,7 @@ class PermissionService
/**
* Rebuild the entity jointPermissions for a collection of entities.
*
* @throws Throwable
*/
public function buildJointPermissionsForEntities(array $entities)
@ -263,6 +271,7 @@ class PermissionService
/**
* Delete all of the entity jointPermissions for a list of entities.
*
* @param Role[] $roles
*/
protected function deleteManyJointPermissionsForRoles($roles)
@ -275,7 +284,9 @@ class PermissionService
/**
* Delete the entity jointPermissions for a particular entity.
*
* @param Entity $entity
*
* @throws Throwable
*/
public function deleteJointPermissionsForEntity(Entity $entity)
@ -285,7 +296,9 @@ class PermissionService
/**
* Delete all of the entity jointPermissions for a list of entities.
*
* @param Entity[] $entities
*
* @throws Throwable
*/
protected function deleteManyJointPermissionsForEntities(array $entities)
@ -295,7 +308,6 @@ class PermissionService
}
$this->db->transaction(function () use ($entities) {
foreach (array_chunk($entities, 1000) as $entityChunk) {
$query = $this->db->table('joint_permissions');
foreach ($entityChunk as $entity) {
@ -311,8 +323,10 @@ class PermissionService
/**
* Create & Save entity jointPermissions for many entities and roles.
*
* @param Entity[] $entities
* @param Role[] $roles
* @param Role[] $roles
*
* @throws Throwable
*/
protected function createManyJointPermissions(array $entities, array $roles)
@ -363,7 +377,6 @@ class PermissionService
});
}
/**
* Get the actions related to an entity.
*/
@ -376,6 +389,7 @@ class PermissionService
if ($entity instanceof Book) {
$baseActions[] = 'chapter-create';
}
return $baseActions;
}
@ -397,6 +411,7 @@ class PermissionService
if ($entity->restricted) {
$hasAccess = $this->mapHasActiveRestriction($permissionMap, $entity, $role, $restrictionAction);
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
{
$key = $entity->getMorphClass() . ':' . $entity->getRawAttribute('id') . ':' . $role->getRawAttribute('id') . ':' . $action;
return $entityMap[$key] ?? false;
}
@ -443,18 +459,19 @@ class PermissionService
protected function createJointPermissionDataArray(Entity $entity, Role $role, string $action, bool $permissionAll, bool $permissionOwn): array
{
return [
'role_id' => $role->getRawAttribute('id'),
'entity_id' => $entity->getRawAttribute('id'),
'entity_type' => $entity->getMorphClass(),
'action' => $action,
'has_permission' => $permissionAll,
'role_id' => $role->getRawAttribute('id'),
'entity_id' => $entity->getRawAttribute('id'),
'entity_type' => $entity->getMorphClass(),
'action' => $action,
'has_permission' => $permissionAll,
'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.
*
* @param HasCreatorAndUpdater|HasOwner $ownable
*/
public function checkOwnableUserAccess(Model $ownable, string $permission): bool
@ -473,7 +490,8 @@ class PermissionService
$ownPermission = $user && $user->can($permission . '-own');
$ownerField = ($ownable instanceof Entity) ? 'owned_by' : 'created_by';
$isOwner = $user && $user->id === $ownable->$ownerField;
return ($allPermission || ($isOwner && $ownPermission));
return $allPermission || ($isOwner && $ownPermission);
}
// Handle abnormal create jointPermissions
@ -483,6 +501,7 @@ class PermissionService
$hasAccess = $this->entityRestrictionQuery($baseQuery, $action)->count() > 0;
$this->clean();
return $hasAccess;
}
@ -509,6 +528,7 @@ class PermissionService
$hasPermission = $permissionQuery->count() > 0;
$this->clean();
return $hasPermission;
}
@ -529,6 +549,7 @@ class PermissionService
});
$this->clean();
return $q;
}
@ -539,6 +560,7 @@ class PermissionService
public function restrictEntityQuery(Builder $query, string $ability = 'view'): Builder
{
$this->clean();
return $query->where(function (Builder $parentQuery) use ($ability) {
$parentQuery->whereHas('jointPermissions', function (Builder $permissionQuery) use ($ability) {
$permissionQuery->whereIn('role_id', $this->getCurrentUserRoles())
@ -580,6 +602,7 @@ class PermissionService
/**
* Filter items that have entities set as a polymorphic relation.
*
* @param Builder|\Illuminate\Database\Query\Builder $query
*/
public function filterRestrictedEntityRelations($query, string $tableName, string $entityIdColumn, string $entityTypeColumn, string $action = 'view')
@ -600,6 +623,7 @@ class PermissionService
});
$this->clean();
return $q;
}
@ -628,12 +652,14 @@ class PermissionService
});
$this->clean();
return $q;
}
/**
* Add the query for checking the given user id has permission
* within the join_permissions table.
*
* @param QueryBuilder|Builder $query
*/
protected function addJointHasPermissionCheck($query, int $userIdToCheck)
@ -645,7 +671,7 @@ class PermissionService
}
/**
* Get the current user
* Get the current 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\Auth\Role;
@ -9,7 +11,6 @@ use Illuminate\Database\Eloquent\Collection;
class PermissionsRepo
{
protected $permission;
protected $role;
protected $permissionService;
@ -62,6 +63,7 @@ class PermissionsRepo
$this->assignRolePermissions($role, $permissions);
$this->permissionService->buildJointPermissionForRole($role);
Activity::add(ActivityType::ROLE_CREATE, $role);
return $role;
}
@ -116,6 +118,7 @@ class PermissionsRepo
* 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
* will be added to the role of the specified id.
*
* @throws PermissionsException
* @throws Exception
*/
@ -127,7 +130,7 @@ class PermissionsRepo
// Prevent deleting admin role or default registration role.
if ($role->system_name && in_array($role->system_name, $this->systemRoles)) {
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'));
}

View File

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

View File

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

View File

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

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Auth;
<?php
namespace BookStack\Auth;
use Activity;
use BookStack\Entities\EntityProvider;
@ -82,7 +84,7 @@ class UserRepo
return $query->paginate($count);
}
/**
/**
* Creates a new user and attaches a role to them.
*/
public function registerNew(array $data, bool $emailConfirmed = false): User
@ -96,6 +98,7 @@ class UserRepo
/**
* Assign a user to a system-level role.
*
* @throws NotFoundException
*/
public function attachSystemRole(User $user, string $systemRoleName)
@ -126,6 +129,7 @@ class UserRepo
/**
* Set the assigned user roles via an array of role IDs.
*
* @throws UserUpdateException
*/
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
* contains the admin role.
*/
protected function demotingLastAdmin(User $user, array $newRoles) : bool
protected function demotingLastAdmin(User $user, array $newRoles): bool
{
if ($this->isOnlyAdmin($user)) {
$adminRole = Role::getSystemRole('admin');
@ -159,10 +163,10 @@ class UserRepo
public function create(array $data, bool $emailConfirmed = false): User
{
$details = [
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
'email_confirmed' => $emailConfirmed,
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
'email_confirmed' => $emailConfirmed,
'external_auth_id' => $data['external_auth_id'] ?? '',
];
@ -176,6 +180,7 @@ class UserRepo
/**
* Remove the given user from storage, Delete all related content.
*
* @throws Exception
*/
public function destroy(User $user, ?int $newOwnerId = null)
@ -184,7 +189,7 @@ class UserRepo
$user->apiTokens()->delete();
$user->favourites()->delete();
$user->delete();
// Delete user profile images
$this->userAvatar->destroyAllForUser($user);
@ -201,7 +206,7 @@ class UserRepo
*/
protected function migrateOwnership(User $fromUser, User $toUser)
{
$entities = (new EntityProvider)->all();
$entities = (new EntityProvider())->all();
foreach ($entities as $instance) {
$instance->newQuery()->where('owned_by', '=', $fromUser->id)
->update(['owned_by' => $toUser->id]);
@ -242,11 +247,12 @@ class UserRepo
public function getAssetCounts(User $user): array
{
$createdBy = ['created_by' => $user->id];
return [
'pages' => Page::visible()->where($createdBy)->count(),
'chapters' => Chapter::visible()->where($createdBy)->count(),
'books' => Book::visible()->where($createdBy)->count(),
'shelves' => Bookshelf::visible()->where($createdBy)->count(),
'pages' => Page::visible()->where($createdBy)->count(),
'chapters' => Chapter::visible()->where($createdBy)->count(),
'books' => Book::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),
// 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'),
// 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
'fallback_locale' => 'en',
@ -140,52 +140,52 @@ return [
'aliases' => [
// Laravel
'App' => Illuminate\Support\Facades\App::class,
'Arr' => Illuminate\Support\Arr::class,
'Artisan' => Illuminate\Support\Facades\Artisan::class,
'Auth' => Illuminate\Support\Facades\Auth::class,
'Blade' => Illuminate\Support\Facades\Blade::class,
'Bus' => Illuminate\Support\Facades\Bus::class,
'Cache' => Illuminate\Support\Facades\Cache::class,
'Config' => Illuminate\Support\Facades\Config::class,
'Cookie' => Illuminate\Support\Facades\Cookie::class,
'Crypt' => Illuminate\Support\Facades\Crypt::class,
'DB' => Illuminate\Support\Facades\DB::class,
'Eloquent' => Illuminate\Database\Eloquent\Model::class,
'Event' => Illuminate\Support\Facades\Event::class,
'File' => Illuminate\Support\Facades\File::class,
'Hash' => Illuminate\Support\Facades\Hash::class,
'Input' => Illuminate\Support\Facades\Input::class,
'Inspiring' => Illuminate\Foundation\Inspiring::class,
'Lang' => Illuminate\Support\Facades\Lang::class,
'Log' => Illuminate\Support\Facades\Log::class,
'Mail' => Illuminate\Support\Facades\Mail::class,
'App' => Illuminate\Support\Facades\App::class,
'Arr' => Illuminate\Support\Arr::class,
'Artisan' => Illuminate\Support\Facades\Artisan::class,
'Auth' => Illuminate\Support\Facades\Auth::class,
'Blade' => Illuminate\Support\Facades\Blade::class,
'Bus' => Illuminate\Support\Facades\Bus::class,
'Cache' => Illuminate\Support\Facades\Cache::class,
'Config' => Illuminate\Support\Facades\Config::class,
'Cookie' => Illuminate\Support\Facades\Cookie::class,
'Crypt' => Illuminate\Support\Facades\Crypt::class,
'DB' => Illuminate\Support\Facades\DB::class,
'Eloquent' => Illuminate\Database\Eloquent\Model::class,
'Event' => Illuminate\Support\Facades\Event::class,
'File' => Illuminate\Support\Facades\File::class,
'Hash' => Illuminate\Support\Facades\Hash::class,
'Input' => Illuminate\Support\Facades\Input::class,
'Inspiring' => Illuminate\Foundation\Inspiring::class,
'Lang' => Illuminate\Support\Facades\Lang::class,
'Log' => Illuminate\Support\Facades\Log::class,
'Mail' => Illuminate\Support\Facades\Mail::class,
'Notification' => Illuminate\Support\Facades\Notification::class,
'Password' => Illuminate\Support\Facades\Password::class,
'Queue' => Illuminate\Support\Facades\Queue::class,
'Redirect' => Illuminate\Support\Facades\Redirect::class,
'Redis' => Illuminate\Support\Facades\Redis::class,
'Request' => Illuminate\Support\Facades\Request::class,
'Response' => Illuminate\Support\Facades\Response::class,
'Route' => Illuminate\Support\Facades\Route::class,
'Schema' => Illuminate\Support\Facades\Schema::class,
'Session' => Illuminate\Support\Facades\Session::class,
'Storage' => Illuminate\Support\Facades\Storage::class,
'Str' => Illuminate\Support\Str::class,
'URL' => Illuminate\Support\Facades\URL::class,
'Validator' => Illuminate\Support\Facades\Validator::class,
'View' => Illuminate\Support\Facades\View::class,
'Socialite' => Laravel\Socialite\Facades\Socialite::class,
'Password' => Illuminate\Support\Facades\Password::class,
'Queue' => Illuminate\Support\Facades\Queue::class,
'Redirect' => Illuminate\Support\Facades\Redirect::class,
'Redis' => Illuminate\Support\Facades\Redis::class,
'Request' => Illuminate\Support\Facades\Request::class,
'Response' => Illuminate\Support\Facades\Response::class,
'Route' => Illuminate\Support\Facades\Route::class,
'Schema' => Illuminate\Support\Facades\Schema::class,
'Session' => Illuminate\Support\Facades\Session::class,
'Storage' => Illuminate\Support\Facades\Storage::class,
'Str' => Illuminate\Support\Str::class,
'URL' => Illuminate\Support\Facades\URL::class,
'Validator' => Illuminate\Support\Facades\Validator::class,
'View' => Illuminate\Support\Facades\View::class,
'Socialite' => Laravel\Socialite\Facades\Socialite::class,
// Third Party
'ImageTool' => Intervention\Image\Facades\Image::class,
'DomPDF' => Barryvdh\DomPDF\Facade::class,
'DomPDF' => Barryvdh\DomPDF\Facade::class,
'SnappyPDF' => Barryvdh\Snappy\Facades\SnappyPdf::class,
// Custom BookStack
'Activity' => BookStack\Facades\Activity::class,
'Activity' => BookStack\Facades\Activity::class,
'Permissions' => BookStack\Facades\Permissions::class,
'Theme' => BookStack\Facades\Theme::class,
'Theme' => BookStack\Facades\Theme::class,
],
// Proxy configuration

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
<?php
/**
* Debugbar Configuration Options
* Debugbar Configuration Options.
*
* 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.
@ -10,53 +10,52 @@
return [
// 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 provide an array of URI's that must be ignored (eg. 'api/*')
// 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 provide an array of URI's that must be ignored (eg. 'api/*')
'enabled' => env('DEBUGBAR_ENABLED', false),
'except' => [
'telescope*'
'except' => [
'telescope*',
],
// DebugBar stores data for session/ajax requests.
// You can disable this, so the debugbar stores data in headers/session,
// but this can cause problems with large data collectors.
// By default, file storage (in the storage folder) is used. Redis and PDO
// can also be used. For PDO, run the package migrations first.
// DebugBar stores data for session/ajax requests.
// You can disable this, so the debugbar stores data in headers/session,
// but this can cause problems with large data collectors.
// By default, file storage (in the storage folder) is used. Redis and PDO
// can also be used. For PDO, run the package migrations first.
'storage' => [
'enabled' => true,
'driver' => 'file', // redis, file, pdo, custom
'path' => storage_path('debugbar'), // For file driver
'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.
// 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)
// and for js: jquery and and highlight.js
// So if you want syntax highlighting, set it to true.
// jQuery is set to not conflict with existing jQuery scripts.
// 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.
// Vendor files are for css: font-awesome (including fonts) and highlight.js (css files)
// and for js: jquery and and highlight.js
// So if you want syntax highlighting, set it to true.
// jQuery is set to not conflict with existing jQuery scripts.
'include_vendors' => true,
// 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.
// Optionally, you can also send ServerTiming headers on ajax requests for the Chrome DevTools.
// 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.
// Optionally, you can also send ServerTiming headers on ajax requests for the Chrome DevTools.
'capture_ajax' => true,
'capture_ajax' => true,
'add_ajax_timing' => false,
// When enabled, the Debugbar shows deprecated warnings for Symfony components
// in the Messages tab.
// When enabled, the Debugbar shows deprecated warnings for Symfony components
// in the Messages tab.
'error_handler' => false,
// The Debugbar can emulate the Clockwork headers, so you can use the Chrome
// Extension, without the server-side code. It uses Debugbar collectors instead.
// The Debugbar can emulate the Clockwork headers, so you can use the Chrome
// Extension, without the server-side code. It uses Debugbar collectors instead.
'clockwork' => false,
// Enable/disable DataCollectors
// Enable/disable DataCollectors
'collectors' => [
'phpinfo' => true, // Php version
'messages' => true, // Messages
@ -82,7 +81,7 @@ return [
'models' => true, // Display models
],
// Configure some DataCollectors
// Configure some DataCollectors
'options' => [
'auth' => [
'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
'backtrace' => true, // Use a backtrace to find the origin of the query in your files.
'timeline' => false, // Add the queries to the timeline
'explain' => [ // Show EXPLAIN output on queries
'explain' => [ // Show EXPLAIN output on queries
'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
],
'mail' => [
'full_log' => false
'full_log' => false,
],
'views' => [
'data' => false, //Note: Can slow down the application, because the data can be quite large..
],
'route' => [
'label' => true // show complete route on bar
'label' => true, // show complete route on bar
],
'logs' => [
'file' => null
'file' => null,
],
'cache' => [
'values' => true // collect cache values
'values' => true, // collect cache values
],
],
// Inject Debugbar into the response
// 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
// in your template yourself. See http://phpdebugbar.com/docs/rendering.html
// Inject Debugbar into the response
// 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
// in your template yourself. See http://phpdebugbar.com/docs/rendering.html
'inject' => true,
// DebugBar route prefix
// 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
// from trying to overcome bugs like this: http://trac.nginx.org/nginx/ticket/97
// DebugBar route prefix
// 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
// from trying to overcome bugs like this: http://trac.nginx.org/nginx/ticket/97
'route_prefix' => '_debugbar',
// DebugBar route domain
// By default DebugBar route served from the same domain that request served.
// To override default domain, specify it as a non-empty value.
// DebugBar route domain
// By default DebugBar route served from the same domain that request served.
// To override default domain, specify it as a non-empty value.
'route_domain' => env('APP_URL', '') === 'http://bookstack.dev' ? '' : env('APP_URL', ''),
];

View File

@ -10,12 +10,11 @@
return [
'show_warnings' => false, // Throw an Exception on warnings from dompdf
'orientation' => 'portrait',
'defines' => [
'orientation' => 'portrait',
'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
* 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,
* 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 can be the same as DOMPDF_FONT_DIR
*
* 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.
@ -57,10 +56,10 @@ return [
* The temporary directory is required to download remote images and when
* 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
* files on the webserver. All local files opened by dompdf must be in a
@ -71,7 +70,7 @@ return [
* direct class use like:
* $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.
@ -82,20 +81,19 @@ return [
* When enabled, dompdf can support all Unicode glyphs. Any glyphs used in a
* document must be present in your fonts, however.
*/
"DOMPDF_UNICODE_ENABLED" => true,
'DOMPDF_UNICODE_ENABLED' => true,
/**
* 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
* '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
* Canvas_Factory} ultimately determines which rendering class to instantiate
* fall back on CPDF. 'GD' renders PDFs to graphic files. {@link * Canvas_Factory} ultimately determines which rendering class to instantiate
* based on this setting.
*
* Both PDFLib & CPDF rendering backends provide sufficient rendering
@ -117,10 +115,10 @@ return [
* @link http://www.ros.co.nz/pdf
* @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
* 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).
* Therefore allow specification of content here.
*/
"DOMPDF_DEFAULT_MEDIA_TYPE" => "print",
'DOMPDF_DEFAULT_MEDIA_TYPE' => 'print',
/**
* The default paper size.
@ -152,18 +150,19 @@ return [
*
* @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.
*
* @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
* DPI may be overridden for inline images by explictly setting the
@ -195,10 +194,10 @@ return [
*
* @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
* inline PHP contained within <script type="text/php"> ... </script> tags.
@ -209,20 +208,20 @@ return [
*
* @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
* JavaScript code contained within <script type="text/javascript"> ... </script> tags.
*
* @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
* images and CSS files as required.
@ -238,29 +237,27 @@ return [
*
* @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
*
* @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' => [
'driver' => 'local',
'root' => public_path(),
'root' => public_path(),
],
'local_secure' => [
@ -43,12 +43,12 @@ return [
],
's3' => [
'driver' => 's3',
'key' => env('STORAGE_S3_KEY', 'your-key'),
'secret' => env('STORAGE_S3_SECRET', 'your-secret'),
'region' => env('STORAGE_S3_REGION', 'your-region'),
'bucket' => env('STORAGE_S3_BUCKET', 'your-bucket'),
'endpoint' => env('STORAGE_S3_ENDPOINT', null),
'driver' => 's3',
'key' => env('STORAGE_S3_KEY', 'your-key'),
'secret' => env('STORAGE_S3_SECRET', 'your-secret'),
'region' => env('STORAGE_S3_REGION', 'your-region'),
'bucket' => env('STORAGE_S3_BUCKET', 'your-bucket'),
'endpoint' => env('STORAGE_S3_ENDPOINT', 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
// to control the amount of time it takes to hash the given password.
'argon' => [
'memory' => 1024,
'memory' => 1024,
'threads' => 2,
'time' => 2,
'time' => 2,
],
];

View File

@ -30,66 +30,66 @@ return [
// "custom", "stack"
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['daily'],
'driver' => 'stack',
'channels' => ['daily'],
'ignore_exceptions' => false,
],
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => 'debug',
'days' => 14,
'path' => storage_path('logs/laravel.log'),
'level' => 'debug',
'days' => 14,
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => 'debug',
'days' => 7,
'path' => storage_path('logs/laravel.log'),
'level' => 'debug',
'days' => 7,
],
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => 'Laravel Log',
'emoji' => ':boom:',
'level' => 'critical',
'emoji' => ':boom:',
'level' => 'critical',
],
'stderr' => [
'driver' => 'monolog',
'driver' => 'monolog',
'handler' => StreamHandler::class,
'with' => [
'with' => [
'stream' => 'php://stderr',
],
],
'syslog' => [
'driver' => 'syslog',
'level' => 'debug',
'level' => 'debug',
],
'errorlog' => [
'driver' => 'errorlog',
'level' => 'debug',
'level' => 'debug',
],
// Custom errorlog implementation that logs out a plain,
// non-formatted message intended for the webserver log.
'errorlog_plain_webserver' => [
'driver' => 'monolog',
'level' => 'debug',
'handler' => ErrorLogHandler::class,
'handler_with' => [4],
'formatter' => LineFormatter::class,
'driver' => 'monolog',
'level' => 'debug',
'handler' => ErrorLogHandler::class,
'handler_with' => [4],
'formatter' => LineFormatter::class,
'formatter_with' => [
'format' => "%message%",
'format' => '%message%',
],
],
'null' => [
'driver' => 'monolog',
'driver' => 'monolog',
'handler' => NullHandler::class,
],
@ -101,7 +101,6 @@ return [
],
],
// Failed Login Message
// Allows a configurable message to be logged when a login request fails.
'failed_login' => [

View File

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

View File

@ -17,24 +17,23 @@ return [
// Queue connection configuration
'connections' => [
'sync' => [
'driver' => 'sync',
],
'database' => [
'driver' => 'database',
'table' => 'jobs',
'queue' => 'default',
'driver' => 'database',
'table' => 'jobs',
'queue' => 'default',
'retry_after' => 90,
],
'redis' => [
'driver' => 'redis',
'connection' => 'default',
'queue' => env('REDIS_QUEUE', 'default'),
'driver' => 'redis',
'connection' => 'default',
'queue' => env('REDIS_QUEUE', 'default'),
'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.
'onelogin_overrides' => env('SAML2_ONELOGIN_OVERRIDES', null),
'onelogin' => [
// If 'strict' is True, then the PHP Toolkit will reject unsigned
// 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',
// 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
'x509cert' => '',
'x509cert' => '',
'privateKey' => '',
],
// 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',
'name' => 'GitHub',
'auto_register' => env('GITHUB_AUTO_REGISTER', false),
'auto_confirm' => env('GITHUB_AUTO_CONFIRM_EMAIL', false),
'auto_confirm' => env('GITHUB_AUTO_CONFIRM_EMAIL', false),
],
'google' => [
'client_id' => env('GOOGLE_APP_ID', false),
'client_secret' => env('GOOGLE_APP_SECRET', false),
'redirect' => env('APP_URL') . '/login/service/google/callback',
'name' => 'Google',
'auto_register' => env('GOOGLE_AUTO_REGISTER', false),
'auto_confirm' => env('GOOGLE_AUTO_CONFIRM_EMAIL', false),
'client_id' => env('GOOGLE_APP_ID', false),
'client_secret' => env('GOOGLE_APP_SECRET', false),
'redirect' => env('APP_URL') . '/login/service/google/callback',
'name' => 'Google',
'auto_register' => env('GOOGLE_AUTO_REGISTER', false),
'auto_confirm' => env('GOOGLE_AUTO_CONFIRM_EMAIL', false),
'select_account' => env('GOOGLE_SELECT_ACCOUNT', false),
],
@ -47,7 +47,7 @@ return [
'redirect' => env('APP_URL') . '/login/service/slack/callback',
'name' => 'Slack',
'auto_register' => env('SLACK_AUTO_REGISTER', false),
'auto_confirm' => env('SLACK_AUTO_CONFIRM_EMAIL', false),
'auto_confirm' => env('SLACK_AUTO_CONFIRM_EMAIL', false),
],
'facebook' => [
@ -56,7 +56,7 @@ return [
'redirect' => env('APP_URL') . '/login/service/facebook/callback',
'name' => 'Facebook',
'auto_register' => env('FACEBOOK_AUTO_REGISTER', false),
'auto_confirm' => env('FACEBOOK_AUTO_CONFIRM_EMAIL', false),
'auto_confirm' => env('FACEBOOK_AUTO_CONFIRM_EMAIL', false),
],
'twitter' => [
@ -65,27 +65,27 @@ return [
'redirect' => env('APP_URL') . '/login/service/twitter/callback',
'name' => 'Twitter',
'auto_register' => env('TWITTER_AUTO_REGISTER', false),
'auto_confirm' => env('TWITTER_AUTO_CONFIRM_EMAIL', false),
'auto_confirm' => env('TWITTER_AUTO_CONFIRM_EMAIL', false),
],
'azure' => [
'client_id' => env('AZURE_APP_ID', 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',
'name' => 'Microsoft Azure',
'auto_register' => env('AZURE_AUTO_REGISTER', false),
'auto_confirm' => env('AZURE_AUTO_CONFIRM_EMAIL', false),
'auto_confirm' => env('AZURE_AUTO_CONFIRM_EMAIL', false),
],
'okta' => [
'client_id' => env('OKTA_APP_ID'),
'client_id' => env('OKTA_APP_ID'),
'client_secret' => env('OKTA_APP_SECRET'),
'redirect' => env('APP_URL') . '/login/service/okta/callback',
'base_url' => env('OKTA_BASE_URL'),
'redirect' => env('APP_URL') . '/login/service/okta/callback',
'base_url' => env('OKTA_BASE_URL'),
'name' => 'Okta',
'auto_register' => env('OKTA_AUTO_REGISTER', false),
'auto_confirm' => env('OKTA_AUTO_CONFIRM_EMAIL', false),
'auto_confirm' => env('OKTA_AUTO_CONFIRM_EMAIL', false),
],
'gitlab' => [
@ -95,45 +95,45 @@ return [
'instance_uri' => env('GITLAB_BASE_URI'), // Needed only for self hosted instances
'name' => 'GitLab',
'auto_register' => env('GITLAB_AUTO_REGISTER', false),
'auto_confirm' => env('GITLAB_AUTO_CONFIRM_EMAIL', false),
'auto_confirm' => env('GITLAB_AUTO_CONFIRM_EMAIL', false),
],
'twitch' => [
'client_id' => env('TWITCH_APP_ID'),
'client_id' => env('TWITCH_APP_ID'),
'client_secret' => env('TWITCH_APP_SECRET'),
'redirect' => env('APP_URL') . '/login/service/twitch/callback',
'redirect' => env('APP_URL') . '/login/service/twitch/callback',
'name' => 'Twitch',
'auto_register' => env('TWITCH_AUTO_REGISTER', false),
'auto_confirm' => env('TWITCH_AUTO_CONFIRM_EMAIL', false),
'auto_confirm' => env('TWITCH_AUTO_CONFIRM_EMAIL', false),
],
'discord' => [
'client_id' => env('DISCORD_APP_ID'),
'client_id' => env('DISCORD_APP_ID'),
'client_secret' => env('DISCORD_APP_SECRET'),
'redirect' => env('APP_URL') . '/login/service/discord/callback',
'name' => 'Discord',
'redirect' => env('APP_URL') . '/login/service/discord/callback',
'name' => 'Discord',
'auto_register' => env('DISCORD_AUTO_REGISTER', false),
'auto_confirm' => env('DISCORD_AUTO_CONFIRM_EMAIL', false),
'auto_confirm' => env('DISCORD_AUTO_CONFIRM_EMAIL', false),
],
'ldap' => [
'server' => env('LDAP_SERVER', false),
'dump_user_details' => env('LDAP_DUMP_USER_DETAILS', false),
'dn' => env('LDAP_DN', false),
'pass' => env('LDAP_PASS', false),
'base_dn' => env('LDAP_BASE_DN', false),
'user_filter' => env('LDAP_USER_FILTER', '(&(uid=${user}))'),
'version' => env('LDAP_VERSION', false),
'id_attribute' => env('LDAP_ID_ATTRIBUTE', 'uid'),
'email_attribute' => env('LDAP_EMAIL_ATTRIBUTE', 'mail'),
'server' => env('LDAP_SERVER', false),
'dump_user_details' => env('LDAP_DUMP_USER_DETAILS', false),
'dn' => env('LDAP_DN', false),
'pass' => env('LDAP_PASS', false),
'base_dn' => env('LDAP_BASE_DN', false),
'user_filter' => env('LDAP_USER_FILTER', '(&(uid=${user}))'),
'version' => env('LDAP_VERSION', false),
'id_attribute' => env('LDAP_ID_ATTRIBUTE', 'uid'),
'email_attribute' => env('LDAP_EMAIL_ATTRIBUTE', 'mail'),
'display_name_attribute' => env('LDAP_DISPLAY_NAME_ATTRIBUTE', 'cn'),
'follow_referrals' => env('LDAP_FOLLOW_REFERRALS', false),
'user_to_groups' => env('LDAP_USER_TO_GROUPS', false),
'group_attribute' => env('LDAP_GROUP_ATTRIBUTE', 'memberOf'),
'remove_from_groups' => env('LDAP_REMOVE_FROM_GROUPS', false),
'tls_insecure' => env('LDAP_TLS_INSECURE', false),
'start_tls' => env('LDAP_START_TLS', false),
'thumbnail_attribute' => env('LDAP_THUMBNAIL_ATTRIBUTE', null),
'follow_referrals' => env('LDAP_FOLLOW_REFERRALS', false),
'user_to_groups' => env('LDAP_USER_TO_GROUPS', false),
'group_attribute' => env('LDAP_GROUP_ATTRIBUTE', 'memberOf'),
'remove_from_groups' => env('LDAP_REMOVE_FROM_GROUPS', false),
'tls_insecure' => env('LDAP_TLS_INSECURE', false),
'start_tls' => env('LDAP_START_TLS', false),
'thumbnail_attribute' => env('LDAP_THUMBNAIL_ATTRIBUTE', null),
],
];

View File

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

View File

@ -26,10 +26,10 @@ return [
// User-level default settings
'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'),
'bookshelf_view_type' =>env('APP_VIEWS_BOOKSHELF', 'grid'),
'books_view_type' => env('APP_VIEWS_BOOKS', 'grid'),
'bookshelf_view_type' => env('APP_VIEWS_BOOKSHELF', '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),
'timeout' => false,
'options' => [
'outline' => true
'outline' => true,
],
'env' => [],
],

View File

@ -25,11 +25,11 @@ class CleanupImages extends Command
*/
protected $description = 'Cleanup images and drawings';
protected $imageService;
/**
* Create a new command instance.
*
* @param \BookStack\Uploads\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->showDeletedImages($deleted);
$this->comment('Run with -f or --force to perform deletions');
return;
}

View File

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

View File

@ -54,13 +54,14 @@ class CopyShelfPermissions extends Command
if (!$cascadeAll && !$shelfSlug) {
$this->error('Either a --slug or --all option must be provided.');
return;
}
if ($cascadeAll) {
$continue = $this->confirm(
'Permission settings for all shelves will be cascaded. '.
'Books assigned to multiple shelves will receive only the permissions of it\'s last processed shelf. '.
'Permission settings for all shelves will be cascaded. ' .
'Books assigned to multiple shelves will receive only the permissions of it\'s last processed shelf. ' .
'Are you sure you want to proceed?'
);

View File

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

View File

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

View File

@ -4,7 +4,6 @@ namespace BookStack\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Database\Connection;
use Illuminate\Support\Facades\DB;
class UpdateUrl extends Command
{
@ -49,7 +48,8 @@ class UpdateUrl extends Command
$urlPattern = '/https?:\/\/(.+)/';
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;
}
@ -58,11 +58,11 @@ class UpdateUrl extends Command
}
$columnsToUpdateByTable = [
"attachments" => ["path"],
"pages" => ["html", "text", "markdown"],
"images" => ["url"],
"settings" => ["value"],
"comments" => ["html", "text"],
'attachments' => ['path'],
'pages' => ['html', 'text', 'markdown'],
'images' => ['url'],
'settings' => ['value'],
'comments' => ['html', 'text'],
];
foreach ($columnsToUpdateByTable as $table => $columns) {
@ -73,7 +73,7 @@ class UpdateUrl extends Command
}
$jsonColumnsToUpdateByTable = [
"settings" => ["value"],
'settings' => ['value'],
];
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('Be sure to run "php artisan cache:clear" to clear any old URLs in the cache.');
$this->info('============================================================================');
return 0;
}
@ -100,8 +101,9 @@ class UpdateUrl extends Command
{
$oldQuoted = $this->db->getPdo()->quote($oldUrl);
$newQuoted = $this->db->getPdo()->quote($newUrl);
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
{
$dangerWarning = "This will search for \"{$oldUrl}\" in your database and replace it with \"{$newUrl}\".\n";
$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?";
$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?';
return $this->confirm($dangerWarning) && $this->confirm($backupConfirmation);
}

View File

@ -23,7 +23,6 @@ class UpgradeDatabaseEncoding extends Command
/**
* Create a new command instance.
*
*/
public function __construct()
{
@ -44,12 +43,12 @@ class UpgradeDatabaseEncoding extends Command
$database = DB::getDatabaseName();
$tables = DB::select('SHOW TABLES');
$this->line('ALTER DATABASE `'.$database.'` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
$this->line('USE `'.$database.'`;');
$this->line('ALTER DATABASE `' . $database . '` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
$this->line('USE `' . $database . '`;');
$key = 'Tables_in_' . $database;
foreach ($tables as $table) {
$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);

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Console;
<?php
namespace BookStack\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
@ -17,7 +19,8 @@ class Kernel extends ConsoleKernel
/**
* Define the application's command schedule.
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @param \Illuminate\Console\Scheduling\Schedule $schedule
*
* @return void
*/
protected function schedule(Schedule $schedule)
@ -32,6 +35,6 @@ class Kernel extends ConsoleKernel
*/
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\Tools\ShelfContext;
@ -6,11 +8,11 @@ use Illuminate\View\View;
class BreadcrumbsViewComposer
{
protected $entityContextManager;
/**
* BreadcrumbsViewComposer constructor.
*
* @param ShelfContext $entityContextManager
*/
public function __construct(ShelfContext $entityContextManager)
@ -20,6 +22,7 @@ class BreadcrumbsViewComposer
/**
* Modify data when the view is composed.
*
* @param 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\Bookshelf;
@ -8,7 +10,7 @@ use BookStack\Entities\Models\Page;
use BookStack\Entities\Models\PageRevision;
/**
* Class EntityProvider
* Class EntityProvider.
*
* Provides access to the core entity models.
* Wrapped up in this provider since they are often used together
@ -16,7 +18,6 @@ use BookStack\Entities\Models\PageRevision;
*/
class EntityProvider
{
/**
* @var Bookshelf
*/
@ -42,7 +43,6 @@ class EntityProvider
*/
public $pageRevision;
public function __construct()
{
$this->bookshelf = new Bookshelf();
@ -55,15 +55,16 @@ class EntityProvider
/**
* Fetch all core entity types as an associated array
* with their basic names as the keys.
*
* @return array<Entity>
*/
public function all(): array
{
return [
'bookshelf' => $this->bookshelf,
'book' => $this->book,
'chapter' => $this->chapter,
'page' => $this->page,
'book' => $this->book,
'chapter' => $this->chapter,
'page' => $this->page,
];
}
@ -73,6 +74,7 @@ class EntityProvider
public function get(string $type): Entity
{
$type = strtolower($type);
return $this->all()[$type];
}
@ -86,6 +88,7 @@ class EntityProvider
$model = $this->get($type);
$morphClasses[] = $model->getMorphClass();
}
return $morphClasses;
}
}

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Entities\Models;
<?php
namespace BookStack\Entities\Models;
use BookStack\Uploads\Image;
use Exception;
@ -8,9 +10,10 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Collection;
/**
* Class Book
* @property string $description
* @property int $image_id
* Class Book.
*
* @property string $description
* @property int $image_id
* @property Image|null $cover
*/
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.
* @param int $width - Width of the image
*
* @param int $width - Width of the image
* @param int $height - Height of the image
*
* @return string
*/
public function getBookCover($width = 440, $height = 250)
@ -46,11 +51,12 @@ class Book extends Entity implements HasCoverImage
} catch (Exception $err) {
$cover = $default;
}
return $cover;
}
/**
* Get the cover image of the book
* Get the cover image of the book.
*/
public function cover(): BelongsTo
{
@ -67,6 +73,7 @@ class Book extends Entity implements HasCoverImage
/**
* Get all pages within this book.
*
* @return HasMany
*/
public function pages()
@ -76,6 +83,7 @@ class Book extends Entity implements HasCoverImage
/**
* Get the direct child pages of this book.
*
* @return HasMany
*/
public function directPages()
@ -85,6 +93,7 @@ class Book extends Entity implements HasCoverImage
/**
* Get all chapters within this book.
*
* @return HasMany
*/
public function chapters()
@ -94,6 +103,7 @@ class Book extends Entity implements HasCoverImage
/**
* Get the shelves this book is contained within.
*
* @return BelongsToMany
*/
public function shelves()
@ -103,12 +113,14 @@ class Book extends Entity implements HasCoverImage
/**
* Get the direct child items within this book.
*
* @return Collection
*/
public function getDirectChildren(): Collection
{
$pages = $this->directPages()->visible()->get();
$chapters = $this->chapters()->visible()->get();
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\Relations\BelongsTo;
/**
* Class BookChild
* @property int $book_id
* @property int $priority
* Class BookChild.
*
* @property int $book_id
* @property int $priority
* @property Book $book
*
* @method Builder whereSlugs(string $bookSlug, string $childSlug)
*/
abstract class BookChild extends Entity
{
/**
* Scope a query to find items where the the child has the given childSlug
* 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 Illuminate\Database\Eloquent\Relations\BelongsTo;
@ -17,6 +19,7 @@ class Bookshelf extends Entity implements HasCoverImage
/**
* Get the books in this shelf.
* Should not be used directly since does not take into account permissions.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
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.
* @param int $width - Width of the image
*
* @param int $width - Width of the image
* @param int $height - Height of the image
*
* @return string
*/
public function getBookCover($width = 440, $height = 250)
@ -61,11 +66,12 @@ class Bookshelf extends Entity implements HasCoverImage
} catch (\Exception $err) {
$cover = $default;
}
return $cover;
}
/**
* Get the cover image of the shelf
* Get the cover image of the shelf.
*/
public function cover(): BelongsTo
{
@ -82,7 +88,9 @@ class Bookshelf extends Entity implements HasCoverImage
/**
* Check if this shelf contains the given book.
*
* @param Book $book
*
* @return 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.
*
* @param 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;
/**
* Class Chapter
* Class Chapter.
*
* @property Collection<Page> $pages
* @property mixed description
*/
@ -16,7 +19,9 @@ class Chapter extends BookChild
/**
* Get the pages that this chapter contains.
*
* @param string $dir
*
* @return mixed
*/
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\Entities\Models\Entity;
use BookStack\Interfaces\Loggable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
@ -12,7 +13,6 @@ use Illuminate\Database\Eloquent\Relations\MorphTo;
*/
class Deletion extends Model implements Loggable
{
/**
* Get the related deletable record.
*/
@ -35,17 +35,19 @@ class Deletion extends Model implements Loggable
public static function createForEntity(Entity $entity): Deletion
{
$record = (new self())->forceFill([
'deleted_by' => user()->id,
'deleted_by' => user()->id,
'deletable_type' => $entity->getMorphClass(),
'deletable_id' => $entity->id,
'deletable_id' => $entity->id,
]);
$record->save();
return $record;
}
public function logDescriptor(): string
{
$deletable = $this->deletable()->first();
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\Comment;
@ -27,15 +29,16 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* The base class for book-like items such as pages, chapters & books.
* This is not a database model in itself but extended.
*
* @property int $id
* @property string $name
* @property string $slug
* @property Carbon $created_at
* @property Carbon $updated_at
* @property int $created_by
* @property int $updated_by
* @property boolean $restricted
* @property int $id
* @property string $name
* @property string $slug
* @property Carbon $created_at
* @property Carbon $updated_at
* @property int $created_by
* @property int $updated_by
* @property bool $restricted
* @property Collection $tags
*
* @method static Entity|Builder visible()
* @method static Entity|Builder hasPermission(string $permission)
* @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
{
$query = $this->morphMany(Comment::class, 'entity');
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.
* Examples of $type are 'page', 'book', 'chapter'
* Examples of $type are 'page', 'book', 'chapter'.
*/
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
{
$className = array_slice(explode('\\', static::class), -1, 1)[0];
return strtolower($className);
}
@ -229,6 +234,7 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
if (mb_strlen($this->name) <= $length) {
return $this->name;
}
return mb_substr($this->name, 0, $length - 3) . '...';
}
@ -248,14 +254,14 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
$text = $this->getText();
if (mb_strlen($text) > $length) {
$text = mb_substr($text, 0, $length-3) . '...';
$text = mb_substr($text, 0, $length - 3) . '...';
}
return trim($text);
}
/**
* Get the url of this entity
* Get the url of this entity.
*/
abstract public function getUrl(string $path = '/'): string;
@ -272,6 +278,7 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
if ($this instanceof Chapter) {
return $this->book()->withTrashed()->first();
}
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()
{
@ -298,6 +305,7 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
public function refreshSlug(): string
{
$this->slug = app(SlugGenerator::class)->generate($this);
return $this->slug;
}

View File

@ -1,13 +1,11 @@
<?php
namespace BookStack\Entities\Models;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
interface HasCoverImage
{
/**
* 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\Uploads\Attachment;
@ -9,15 +11,16 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
use Permissions;
/**
* Class Page
* @property int $chapter_id
* @property string $html
* @property string $markdown
* @property string $text
* @property bool $template
* @property bool $draft
* @property int $revision_count
* @property Chapter $chapter
* Class Page.
*
* @property int $chapter_id
* @property string $html
* @property string $markdown
* @property string $text
* @property bool $template
* @property bool $draft
* @property int $revision_count
* @property Chapter $chapter
* @property Collection $attachments
*/
class Page extends BookChild
@ -31,7 +34,7 @@ class Page extends BookChild
protected $hidden = ['html', 'markdown', 'text', 'restricted', 'pivot', 'deleted_at'];
protected $casts = [
'draft' => 'boolean',
'draft' => 'boolean',
'template' => 'boolean',
];
@ -41,22 +44,26 @@ class Page extends BookChild
public function scopeVisible(Builder $query): Builder
{
$query = Permissions::enforceDraftVisibilityOnQuery($query);
return parent::scopeVisible($query);
}
/**
* Converts this page into a simplified array.
*
* @return mixed
*/
public function toSimpleArray()
{
$array = array_intersect_key($this->toArray(), array_flip($this->simpleAttributes));
$array['url'] = $this->getUrl();
return $array;
}
/**
* Get the chapter that this page is in, If applicable.
*
* @return BelongsTo
*/
public function chapter()
@ -66,6 +73,7 @@ class Page extends BookChild
/**
* Check if this page has a chapter.
*
* @return bool
*/
public function hasChapter()
@ -96,6 +104,7 @@ class Page extends BookChild
/**
* Get the attachments assigned to this page.
*
* @return HasMany
*/
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
*/
public function getCurrentRevision()
@ -136,6 +146,7 @@ class Page extends BookChild
$refreshed = $this->refresh()->unsetRelations()->load(['tags', 'createdBy', 'updatedBy', 'ownedBy']);
$refreshed->setHidden(array_diff($refreshed->getHidden(), ['html', 'markdown']));
$refreshed->html = (new PageContent($refreshed))->render();
return $refreshed;
}
}

View File

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

View File

@ -1,15 +1,17 @@
<?php namespace BookStack\Entities\Models;
<?php
namespace BookStack\Entities\Models;
use BookStack\Model;
class SearchTerm extends Model
{
protected $fillable = ['term', 'entity_id', 'entity_type', 'score'];
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
*/
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\Entities\EntityProvider;
@ -14,4 +16,4 @@ abstract class EntityQuery
{
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 Illuminate\Support\Facades\DB;
@ -25,5 +26,4 @@ class Popular extends EntityQuery
->pluck('viewable')
->filter();
}
}
}

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Entities\Queries;
<?php
namespace BookStack\Entities\Queries;
use BookStack\Actions\View;
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 Illuminate\Database\Query\JoinClause;

View File

@ -2,24 +2,18 @@
namespace BookStack\Entities\Repos;
use BookStack\Actions\ActivityType;
use BookStack\Actions\TagRepo;
use BookStack\Auth\User;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\HasCoverImage;
use BookStack\Exceptions\ImageUploadException;
use BookStack\Facades\Activity;
use BookStack\Uploads\ImageRepo;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Collection;
class BaseRepo
{
protected $tagRepo;
protected $imageRepo;
public function __construct(TagRepo $tagRepo, ImageRepo $imageRepo)
{
$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)
{
@ -35,7 +29,7 @@ class BaseRepo
$entity->forceFill([
'created_by' => user()->id,
'updated_by' => user()->id,
'owned_by' => user()->id,
'owned_by' => user()->id,
]);
$entity->refreshSlug();
$entity->save();
@ -72,6 +66,7 @@ class BaseRepo
/**
* Update the given items' cover image, or clear it.
*
* @throws ImageUploadException
* @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\TagRepo;
@ -15,7 +17,6 @@ use Illuminate\Support\Collection;
class BookRepo
{
protected $baseRepo;
protected $tagRepo;
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
{
$book = new Book();
$this->baseRepo->create($book, $input);
Activity::addForEntity($book, ActivityType::BOOK_CREATE);
return $book;
}
@ -101,11 +103,13 @@ class BookRepo
{
$this->baseRepo->update($book, $input);
Activity::addForEntity($book, ActivityType::BOOK_UPDATE);
return $book;
}
/**
* Update the given book's cover image, or clear it.
*
* @throws ImageUploadException
* @throws Exception
*/
@ -116,6 +120,7 @@ class BookRepo
/**
* Remove a book from the system.
*
* @throws Exception
*/
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\Entities\Models\Book;
@ -89,6 +91,7 @@ class BookshelfRepo
$this->baseRepo->create($shelf, $input);
$this->updateBooks($shelf, $bookIds);
Activity::addForEntity($shelf, ActivityType::BOOKSHELF_CREATE);
return $shelf;
}
@ -104,6 +107,7 @@ class BookshelfRepo
}
Activity::addForEntity($shelf, ActivityType::BOOKSHELF_UPDATE);
return $shelf;
}
@ -129,6 +133,7 @@ class BookshelfRepo
/**
* Update the given shelf cover image, or clear it.
*
* @throws ImageUploadException
* @throws Exception
*/
@ -164,6 +169,7 @@ class BookshelfRepo
/**
* Remove a bookshelf from the system.
*
* @throws Exception
*/
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\Entities\Models\Book;
@ -9,11 +11,9 @@ use BookStack\Exceptions\MoveOperationException;
use BookStack\Exceptions\NotFoundException;
use BookStack\Facades\Activity;
use Exception;
use Illuminate\Support\Collection;
class ChapterRepo
{
protected $baseRepo;
/**
@ -26,6 +26,7 @@ class ChapterRepo
/**
* Get a chapter via the slug.
*
* @throws NotFoundException
*/
public function getBySlug(string $bookSlug, string $chapterSlug): Chapter
@ -49,6 +50,7 @@ class ChapterRepo
$chapter->priority = (new BookContents($parentBook))->getLastPriority() + 1;
$this->baseRepo->create($chapter, $input);
Activity::addForEntity($chapter, ActivityType::CHAPTER_CREATE);
return $chapter;
}
@ -59,11 +61,13 @@ class ChapterRepo
{
$this->baseRepo->update($chapter, $input);
Activity::addForEntity($chapter, ActivityType::CHAPTER_UPDATE);
return $chapter;
}
/**
* Remove a chapter from the system.
*
* @throws Exception
*/
public function destroy(Chapter $chapter)
@ -77,7 +81,8 @@ class ChapterRepo
/**
* Move the given chapter into a new parent book.
* The $parentIdentifier must be a string of the following format:
* 'book:<id>' (book:5)
* 'book:<id>' (book:5).
*
* @throws MoveOperationException
*/
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\Entities\Models\Book;
use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\Page;
use BookStack\Entities\Models\PageRevision;
use BookStack\Entities\Tools\BookContents;
use BookStack\Entities\Tools\PageContent;
use BookStack\Entities\Tools\TrashCan;
use BookStack\Entities\Models\Page;
use BookStack\Entities\Models\PageRevision;
use BookStack\Exceptions\MoveOperationException;
use BookStack\Exceptions\NotFoundException;
use BookStack\Exceptions\PermissionsException;
@ -16,11 +18,9 @@ use BookStack\Facades\Activity;
use Exception;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
class PageRepo
{
protected $baseRepo;
/**
@ -33,6 +33,7 @@ class PageRepo
/**
* Get a page by ID.
*
* @throws NotFoundException
*/
public function getById(int $id, array $relations = ['book']): Page
@ -48,6 +49,7 @@ class PageRepo
/**
* Get a page its book and own slug.
*
* @throws NotFoundException
*/
public function getBySlug(string $bookSlug, string $pageSlug): Page
@ -77,6 +79,7 @@ class PageRepo
->orderBy('created_at', 'desc')
->with('page')
->first();
return $revision ? $revision->page : null;
}
@ -119,6 +122,7 @@ class PageRepo
public function getUserDraft(Page $page): ?PageRevision
{
$revision = $this->getUserDraftQuery($page)->first();
return $revision;
}
@ -128,11 +132,11 @@ class PageRepo
public function getNewDraftPage(Entity $parent)
{
$page = (new Page())->forceFill([
'name' => trans('entities.pages_initial_name'),
'name' => trans('entities.pages_initial_name'),
'created_by' => user()->id,
'owned_by' => user()->id,
'owned_by' => user()->id,
'updated_by' => user()->id,
'draft' => true,
'draft' => true,
]);
if ($parent instanceof Chapter) {
@ -144,6 +148,7 @@ class PageRepo
$page->save();
$page->refresh()->rebuildPermissions();
return $page;
}
@ -166,6 +171,7 @@ class PageRepo
$draft->refresh();
Activity::addForEntity($draft, ActivityType::PAGE_CREATE);
return $draft;
}
@ -190,7 +196,7 @@ class PageRepo
$this->getUserDraftQuery($page)->delete();
// Save a revision after updating
$summary = trim($input['summary'] ?? "");
$summary = trim($input['summary'] ?? '');
$htmlChanged = isset($input['html']) && $input['html'] !== $oldHtml;
$nameChanged = isset($input['name']) && $input['name'] !== $oldName;
$markdownChanged = isset($input['markdown']) && $input['markdown'] !== $oldMarkdown;
@ -199,6 +205,7 @@ class PageRepo
}
Activity::addForEntity($page, ActivityType::PAGE_UPDATE);
return $page;
}
@ -234,6 +241,7 @@ class PageRepo
$revision->save();
$this->deleteOldRevisions($page);
return $revision;
}
@ -249,6 +257,7 @@ class PageRepo
}
$page->fill($input);
$page->save();
return $page;
}
@ -260,11 +269,13 @@ class PageRepo
}
$draft->save();
return $draft;
}
/**
* Destroy a page from the system.
*
* @throws Exception
*/
public function destroy(Page $page)
@ -291,7 +302,7 @@ class PageRepo
} else {
$content->setNewHTML($revision->html);
}
$page->updated_by = user()->id;
$page->refreshSlug();
$page->save();
@ -301,13 +312,15 @@ class PageRepo
$this->savePageRevision($page, $summary);
Activity::addForEntity($page, ActivityType::PAGE_RESTORE);
return $page;
}
/**
* Move the given page into a new parent book or chapter.
* The $parentIdentifier must be a string of the following format:
* 'book:<id>' (book:5)
* 'book:<id>' (book:5).
*
* @throws MoveOperationException
* @throws PermissionsException
*/
@ -327,12 +340,14 @@ class PageRepo
$page->rebuildPermissions();
Activity::addForEntity($page, ActivityType::PAGE_MOVE);
return $parent;
}
/**
* Copy an existing page in the system.
* Optionally providing a new parent via string identifier and a new name.
*
* @throws MoveOperationException
* @throws PermissionsException
*/
@ -369,7 +384,8 @@ class PageRepo
/**
* Find a page parent entity via a identifier string in the format:
* {type}:{id}
* Example: (book:5)
* Example: (book:5).
*
* @throws MoveOperationException
*/
protected function findParentByIdentifier(string $identifier): ?Entity
@ -383,6 +399,7 @@ class PageRepo
}
$parentClass = $entityType === 'book' ? Book::class : Chapter::class;
return $parentClass::visible()->where('id', '=', $entityId)->first();
}
@ -420,6 +437,7 @@ class PageRepo
$draft->book_slug = $page->book->slug;
$draft->created_by = user()->id;
$draft->type = 'update_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
{
$parent = $page->getParent();
if ($parent instanceof Chapter) {
$lastPage = $parent->pages('desc')->first();
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\BookChild;
@ -10,7 +12,6 @@ use Illuminate\Support\Collection;
class BookContents
{
/**
* @var Book
*/
@ -35,6 +36,7 @@ class BookContents
->where('chapter_id', '=', 0)->max('priority');
$maxChapter = Chapter::visible()->where('book_id', '=', $this->book->id)
->max('priority');
return max($maxChapter, $maxPage, 1);
}
@ -83,6 +85,7 @@ class BookContents
if (isset($entity['draft']) && $entity['draft']) {
return -100;
}
return $entity['priority'] ?? 0;
};
}
@ -110,9 +113,10 @@ class BookContents
* +"parentChapter": false (ID of parent chapter, as string, or false)
* +"type": "page" (Entity type of item)
* +"book": "1" (Id of book to place item in)
* }
* }.
*
* Returns a list of books that were involved in the operation.
*
* @throws SortOperationException
*/
public function sortUsingMap(Collection $sortMap): Collection
@ -190,6 +194,7 @@ class BookContents
/**
* Get the books involved in a sort.
* The given sort map should have its models loaded first.
*
* @throws SortOperationException
*/
protected function getBooksInvolvedInSort(Collection $sortMap): Collection
@ -202,7 +207,7 @@ class BookContents
$books = Book::hasPermission('update')->whereIn('id', $bookIdsInvolved)->get();
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;

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\Chapter;
@ -12,7 +14,6 @@ use Throwable;
class ExportFormatter
{
protected $imageService;
/**
@ -26,20 +27,23 @@ class ExportFormatter
/**
* Convert a page to a self-contained HTML file.
* Includes required CSS & image content. Images are base64 encoded into the HTML.
*
* @throws Throwable
*/
public function pageToContainedHtml(Page $page)
{
$page->html = (new PageContent($page))->render();
$pageHtml = view('pages.export', [
'page' => $page,
'page' => $page,
'format' => 'html',
])->render();
return $this->containHtml($pageHtml);
}
/**
* Convert a chapter to a self-contained HTML file.
*
* @throws Throwable
*/
public function chapterToContainedHtml(Chapter $chapter)
@ -50,43 +54,49 @@ class ExportFormatter
});
$html = view('chapters.export', [
'chapter' => $chapter,
'pages' => $pages,
'format' => 'html',
'pages' => $pages,
'format' => 'html',
])->render();
return $this->containHtml($html);
}
/**
* Convert a book to a self-contained HTML file.
*
* @throws Throwable
*/
public function bookToContainedHtml(Book $book)
{
$bookTree = (new BookContents($book))->getTree(false, true);
$html = view('books.export', [
'book' => $book,
'book' => $book,
'bookChildren' => $bookTree,
'format' => 'html',
'format' => 'html',
])->render();
return $this->containHtml($html);
}
/**
* Convert a page to a PDF file.
*
* @throws Throwable
*/
public function pageToPdf(Page $page)
{
$page->html = (new PageContent($page))->render();
$html = view('pages.export', [
'page' => $page,
'page' => $page,
'format' => 'pdf',
])->render();
return $this->htmlToPdf($html);
}
/**
* Convert a chapter to a PDF file.
*
* @throws Throwable
*/
public function chapterToPdf(Chapter $chapter)
@ -98,8 +108,8 @@ class ExportFormatter
$html = view('chapters.export', [
'chapter' => $chapter,
'pages' => $pages,
'format' => 'pdf',
'pages' => $pages,
'format' => 'pdf',
])->render();
return $this->htmlToPdf($html);
@ -107,21 +117,24 @@ class ExportFormatter
/**
* Convert a book to a PDF file.
*
* @throws Throwable
*/
public function bookToPdf(Book $book)
{
$bookTree = (new BookContents($book))->getTree(false, true);
$html = view('books.export', [
'book' => $book,
'book' => $book,
'bookChildren' => $bookTree,
'format' => 'pdf',
'format' => 'pdf',
])->render();
return $this->htmlToPdf($html);
}
/**
* Convert normal web-page HTML to a PDF.
*
* @throws Exception
*/
protected function htmlToPdf(string $html): string
@ -134,11 +147,13 @@ class ExportFormatter
} else {
$pdf = DomPDF::loadHTML($containedHtml);
}
return $pdf->output();
}
/**
* Bundle of the contents of a html file to be self-contained.
*
* @throws Exception
*/
protected function containHtml(string $htmlContent): string
@ -195,6 +210,7 @@ class ExportFormatter
$text = html_entity_decode($text);
// Add title
$text = $page->name . "\n\n" . $text;
return $text;
}
@ -208,6 +224,7 @@ class ExportFormatter
foreach ($chapter->getVisiblePages() as $page) {
$text .= $this->pageToPlainText($page);
}
return $text;
}
@ -225,6 +242,7 @@ class ExportFormatter
$text .= $this->pageToPlainText($bookChild);
}
}
return $text;
}
@ -234,10 +252,10 @@ class ExportFormatter
public function pageToMarkdown(Page $page): string
{
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
{
$text = "# " . $chapter->name . "\n\n";
$text = '# ' . $chapter->name . "\n\n";
$text .= $chapter->description . "\n\n";
foreach ($chapter->pages as $page) {
$text .= $this->pageToMarkdown($page) . "\n\n";
}
return $text;
}
@ -259,7 +278,7 @@ class ExportFormatter
public function bookToMarkdown(Book $book): string
{
$bookTree = (new BookContents($book))->getTree(false, true);
$text = "# " . $book->name . "\n\n";
$text = '# ' . $book->name . "\n\n";
foreach ($bookTree as $bookChild) {
if ($bookChild instanceof Chapter) {
$text .= $this->chapterToMarkdown($bookChild);
@ -267,6 +286,7 @@ class ExportFormatter
$text .= $this->pageToMarkdown($bookChild);
}
}
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\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\Extension\ExtensionInterface;
@ -7,7 +9,6 @@ use League\CommonMark\Extension\Strikethrough\StrikethroughDelimiterProcessor;
class CustomStrikeThroughExtension implements ExtensionInterface
{
public function register(ConfigurableEnvironmentInterface $environment)
{
$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\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\CodeConverter;
@ -27,12 +29,13 @@ class HtmlToMarkdown
}
/**
* Run the conversion
* Run the conversion.
*/
public function convert(): string
{
$converter = new HtmlConverter($this->getConverterEnvironment());
$html = $this->prepareHtml($this->html);
return $converter->convert($html);
}
@ -54,19 +57,19 @@ class HtmlToMarkdown
protected function getConverterEnvironment(): Environment
{
$environment = new Environment([
'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
'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.
'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
'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.
'bold_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'
'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 '+'
'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
'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
'bold_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'
'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 '+'
'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
'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
]);
$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\Entity;
@ -48,6 +50,7 @@ class NextPreviousContentLocator
return get_class($entity) === get_class($this->relativeBookItem)
&& $entity->id === $this->relativeBookItem->id;
});
return $index === false ? null : $index;
}
@ -64,6 +67,7 @@ class NextPreviousContentLocator
$childPages = $item->visible_pages ?? [];
$flatOrdered = $flatOrdered->concat($childPages);
}
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\Tools\Markdown\CustomStrikeThroughExtension;
use BookStack\Exceptions\ImageUploadException;
use BookStack\Facades\Theme;
use BookStack\Theming\ThemeEvents;
use BookStack\Util\HtmlContentFilter;
use BookStack\Uploads\ImageRepo;
use BookStack\Util\HtmlContentFilter;
use DOMDocument;
use DOMNodeList;
use DOMXPath;
@ -18,7 +20,6 @@ use League\CommonMark\Extension\TaskList\TaskListExtension;
class PageContent
{
protected $page;
/**
@ -62,11 +63,12 @@ class PageContent
$environment->addExtension(new CustomStrikeThroughExtension());
$environment = Theme::dispatch(ThemeEvents::COMMONMARK_ENVIRONMENT_CONFIGURE, $environment) ?? $environment;
$converter = new CommonMarkConverter([], $environment);
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
{
@ -97,6 +99,7 @@ class PageContent
// Save image from data with a random name
$imageName = 'embedded-image-' . Str::random(8) . '.' . $extension;
try {
$image = $imageRepo->saveNewFromData($imageName, base64_decode($base64ImageData), 'gallery', $page->id);
$imageNode->setAttribute('src', $image->url);
@ -171,7 +174,7 @@ class PageContent
/**
* Set a unique id on the given DOMElement.
* 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
{
@ -183,6 +186,7 @@ class PageContent
$existingId = $element->getAttribute('id');
if (strpos($existingId, 'bkmrk') === 0 && !isset($idMap[$existingId])) {
$idMap[$existingId] = true;
return [$existingId, $existingId];
}
@ -200,6 +204,7 @@ class PageContent
$element->setAttribute('id', $newId);
$idMap[$newId] = true;
return [$existingId, $newId];
}
@ -209,11 +214,12 @@ class PageContent
protected function toPlainText(): string
{
$html = $this->render(true);
return html_entity_decode(strip_tags($html));
}
/**
* Render the page for viewing
* Render the page for viewing.
*/
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
{
@ -243,7 +249,7 @@ class PageContent
$doc = $this->loadDocumentFromHtml($htmlContent);
$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) : [];
}
@ -260,9 +266,9 @@ class PageContent
return [
'nodeName' => strtolower($header->nodeName),
'level' => intval(str_replace('h', '', $header->nodeName)),
'link' => '#' . $header->getAttribute('id'),
'text' => $text,
'level' => intval(str_replace('h', '', $header->nodeName)),
'link' => '#' . $header->getAttribute('id'),
'text' => $text,
];
})->filter(function ($header) {
return mb_strlen($header['text']) > 0;
@ -272,6 +278,7 @@ class PageContent
$levelChange = ($tree->pluck('level')->min() - 1);
$tree = $tree->map(function ($header) use ($levelChange) {
$header['level'] -= ($levelChange);
return $header;
});
@ -325,7 +332,6 @@ class PageContent
return $html;
}
/**
* Fetch the content from a specific section of the given page.
*/
@ -365,6 +371,7 @@ class PageContent
$doc = new DOMDocument();
$html = '<body>' . $html . '</body>';
$doc->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
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\PageRevision;
@ -7,7 +9,6 @@ use Illuminate\Database\Eloquent\Builder;
class PageEditActivity
{
protected $page;
/**
@ -20,6 +21,7 @@ class PageEditActivity
/**
* Check if there's active editing being performed on this page.
*
* @return bool
*/
public function hasActiveEditing(): bool
@ -35,14 +37,17 @@ class PageEditActivity
$pageDraftEdits = $this->activePageEditingQuery(60)->get();
$count = $pageDraftEdits->count();
$userMessage = $count > 1 ? trans('entities.pages_draft_edit_active.start_a', ['count' => $count]): trans('entities.pages_draft_edit_active.start_b', ['userName' => $pageDraftEdits->first()->createdBy->name]);
$userMessage = $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]);
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.
*
* @param PageRevision $draft
*
* @return string
*/
public function getEditingActiveDraftMessage(PageRevision $draft): string
@ -51,6 +56,7 @@ class PageEditActivity
if ($draft->page->updated_at->timestamp <= $draft->updated_at->timestamp) {
return $message;
}
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\Auth\User;
@ -9,7 +11,6 @@ use Illuminate\Support\Collection;
class PermissionsUpdater
{
/**
* 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 [
'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\Models\Entity;
@ -17,14 +19,12 @@ class SearchIndex
*/
protected $entityProvider;
public function __construct(SearchTerm $searchTerm, EntityProvider $entityProvider)
{
$this->searchTerm = $searchTerm;
$this->entityProvider = $entityProvider;
}
/**
* Index the given entity.
*/
@ -42,7 +42,8 @@ class SearchIndex
}
/**
* Index multiple Entities at once
* Index multiple Entities at once.
*
* @param Entity[] $entities
*/
protected function indexEntities(array $entities)
@ -110,8 +111,8 @@ class SearchIndex
$terms = [];
foreach ($tokenMap as $token => $count) {
$terms[] = [
'term' => $token,
'score' => $count * $scoreAdjustment
'term' => $token,
'score' => $count * $scoreAdjustment,
];
}

View File

@ -1,10 +1,11 @@
<?php namespace BookStack\Entities\Tools;
<?php
namespace BookStack\Entities\Tools;
use Illuminate\Http\Request;
class SearchOptions
{
/**
* @var array
*/
@ -35,6 +36,7 @@ class SearchOptions
foreach ($decoded as $type => $value) {
$instance->$type = $value;
}
return $instance;
}
@ -67,6 +69,7 @@ class SearchOptions
if (isset($inputs['types']) && count($inputs['types']) < 4) {
$instance->filters['type'] = implode('|', $inputs['types']);
}
return $instance;
}
@ -77,15 +80,15 @@ class SearchOptions
{
$terms = [
'searches' => [],
'exacts' => [],
'tags' => [],
'filters' => []
'exacts' => [],
'tags' => [],
'filters' => [],
];
$patterns = [
'exacts' => '/"(.*?)"/',
'tags' => '/\[(.*?)\]/',
'filters' => '/\{(.*?)\}/'
'exacts' => '/"(.*?)"/',
'tags' => '/\[(.*?)\]/',
'filters' => '/\{(.*?)\}/',
];
// 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\User;
@ -13,7 +15,6 @@ use Illuminate\Support\Str;
class SearchRunner
{
/**
* @var EntityProvider
*/
@ -29,14 +30,13 @@ class SearchRunner
*/
protected $permissionService;
/**
* Acceptable operators to be used in a query
* Acceptable operators to be used in a query.
*
* @var array
*/
protected $queryOperators = ['<=', '>=', '=', '<', '>', 'like', '!='];
public function __construct(EntityProvider $entityProvider, Connection $db, PermissionService $permissionService)
{
$this->entityProvider = $entityProvider;
@ -56,7 +56,7 @@ class SearchRunner
if ($entityType !== 'all') {
$entityTypesToSearch = $entityType;
} else if (isset($searchOpts->filters['type'])) {
} elseif (isset($searchOpts->filters['type'])) {
$entityTypesToSearch = explode('|', $searchOpts->filters['type']);
}
@ -78,16 +78,15 @@ class SearchRunner
}
return [
'total' => $total,
'count' => count($results),
'total' => $total,
'count' => count($results),
'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
{
@ -108,12 +107,13 @@ class SearchRunner
}
/**
* Search a chapter for entities
* Search a chapter for entities.
*/
public function searchChapter(int $chapterId, string $searchString): Collection
{
$opts = SearchOptions::fromString($searchString);
$pages = $this->buildEntitySearchQuery($opts, 'page')->where('chapter_id', '=', $chapterId)->take(20)->get();
return $pages->sortByDesc('score');
}
@ -121,6 +121,7 @@ class SearchRunner
* Search across a particular entity type.
* Setting getCount = true will return the total
* matching instead of the items themselves.
*
* @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)
@ -130,12 +131,13 @@ class SearchRunner
return $query->count();
}
$query = $query->skip(($page-1) * $count)->take($count);
$query = $query->skip(($page - 1) * $count)->take($count);
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
{
@ -149,20 +151,20 @@ class SearchRunner
$subQuery->where('entity_type', '=', $entity->getMorphClass());
$subQuery->where(function (Builder $query) use ($searchOpts) {
foreach ($searchOpts->searches as $inputTerm) {
$query->orWhere('term', 'like', $inputTerm .'%');
$query->orWhere('term', 'like', $inputTerm . '%');
}
})->groupBy('entity_type', 'entity_id');
$entitySelect->join($this->db->raw('(' . $subQuery->toSql() . ') as s'), function (JoinClause $join) {
$join->on('id', '=', 'entity_id');
})->selectRaw($entity->getTable().'.*, s.score')->orderBy('score', 'desc');
})->selectRaw($entity->getTable() . '.*, s.score')->orderBy('score', 'desc');
$entitySelect->mergeBindings($subQuery);
}
// Handle exact term matching
foreach ($searchOpts->exacts as $inputTerm) {
$entitySelect->where(function (EloquentBuilder $query) use ($inputTerm, $entity) {
$query->where('name', 'like', '%'.$inputTerm .'%')
->orWhere($entity->textField, 'like', '%'.$inputTerm .'%');
$query->where('name', 'like', '%' . $inputTerm . '%')
->orWhere($entity->textField, 'like', '%' . $inputTerm . '%');
});
}
@ -191,6 +193,7 @@ class SearchRunner
foreach ($this->queryOperators as $operator) {
$escapedOperators[] = preg_quote($operator);
}
return join('|', $escapedOperators);
}
@ -199,7 +202,7 @@ class SearchRunner
*/
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) {
$tagName = $tagSplit[1];
$tagOperator = count($tagSplit) > 2 ? $tagSplit[3] : '';
@ -222,13 +225,13 @@ class SearchRunner
$query->where('name', '=', $tagName);
}
});
return $query;
}
/**
* Custom entity search filters
* Custom entity search filters.
*/
protected function filterUpdatedAfter(EloquentBuilder $query, Entity $model, $input)
{
try {
@ -298,7 +301,7 @@ class SearchRunner
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)
@ -308,7 +311,7 @@ class SearchRunner
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)
@ -338,16 +341,14 @@ class SearchRunner
}
}
/**
* Sorting filter options
* Sorting filter options.
*/
protected function sortByLastCommented(EloquentBuilder $query, Entity $model)
{
$commentsTable = $this->db->getTablePrefix() . 'comments';
$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');
}

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\Bookshelf;

View File

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

View File

@ -1,4 +1,6 @@
<?php namespace BookStack\Entities\Tools;
<?php
namespace BookStack\Entities\Tools;
use BookStack\Entities\Models\BookChild;
use BookStack\Interfaces\Sluggable;
@ -6,7 +8,6 @@ use Illuminate\Support\Str;
class SlugGenerator
{
/**
* Generate a fresh slug for the given entity.
* The slug will generated so it does not conflict within the same parent item.
@ -17,6 +18,7 @@ class SlugGenerator
while ($this->slugInUse($slug, $model)) {
$slug .= '-' . Str::random(3);
}
return $slug;
}
@ -26,9 +28,10 @@ class SlugGenerator
protected function formatNameAsSlug(string $name): string
{
$slug = Str::slug($name);
if ($slug === "") {
if ($slug === '') {
$slug = substr(md5(rand(1, 500)), 0, 5);
}
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\Bookshelf;
use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Deletion;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\EntityProvider;
use BookStack\Entities\Models\HasCoverImage;
use BookStack\Entities\Models\Page;
use BookStack\Exceptions\NotifyException;
@ -17,7 +19,6 @@ use Illuminate\Support\Carbon;
class TrashCan
{
/**
* Send a shelf to the recycle bin.
*/
@ -29,6 +30,7 @@ class TrashCan
/**
* Send a book to the recycle bin.
*
* @throws Exception
*/
public function softDestroyBook(Book $book)
@ -48,6 +50,7 @@ class TrashCan
/**
* Send a chapter to the recycle bin.
*
* @throws Exception
*/
public function softDestroyChapter(Chapter $chapter, bool $recordDelete = true)
@ -67,6 +70,7 @@ class TrashCan
/**
* Send a page to the recycle bin.
*
* @throws Exception
*/
public function softDestroyPage(Page $page, bool $recordDelete = true)
@ -89,18 +93,21 @@ class TrashCan
/**
* Remove a bookshelf from the system.
*
* @throws Exception
*/
protected function destroyShelf(Bookshelf $shelf): int
{
$this->destroyCommonRelations($shelf);
$shelf->forceDelete();
return 1;
}
/**
* Remove a book from the system.
* Destroys any child chapters and pages.
*
* @throws Exception
*/
protected function destroyBook(Book $book): int
@ -120,12 +127,14 @@ class TrashCan
$this->destroyCommonRelations($book);
$book->forceDelete();
return $count + 1;
}
/**
* Remove a chapter from the system.
* Destroys all pages within.
*
* @throws Exception
*/
protected function destroyChapter(Chapter $chapter): int
@ -141,11 +150,13 @@ class TrashCan
$this->destroyCommonRelations($chapter);
$chapter->forceDelete();
return $count + 1;
}
/**
* Remove a page from the system.
*
* @throws Exception
*/
protected function destroyPage(Page $page): int
@ -160,6 +171,7 @@ class TrashCan
}
$page->forceDelete();
return 1;
}
@ -172,7 +184,7 @@ class TrashCan
$counts = [];
/** @var Entity $instance */
foreach ((new EntityProvider)->all() as $key => $instance) {
foreach ((new EntityProvider())->all() as $key => $instance) {
$counts[$key] = $instance->newQuery()->onlyTrashed()->count();
}
@ -181,6 +193,7 @@ class TrashCan
/**
* Destroy all items that have pending deletions.
*
* @throws Exception
*/
public function empty(): int
@ -190,11 +203,13 @@ class TrashCan
foreach ($deletions as $deletion) {
$deleteCount += $this->destroyFromDeletion($deletion);
}
return $deleteCount;
}
/**
* Destroy an element from the given deletion model.
*
* @throws Exception
*/
public function destroyFromDeletion(Deletion $deletion): int
@ -207,11 +222,13 @@ class TrashCan
$count = $this->destroyEntity($deletion->deletable);
}
$deletion->delete();
return $count;
}
/**
* Restore the content within the given deletion.
*
* @throws Exception
*/
public function restoreFromDeletion(Deletion $deletion): int
@ -229,6 +246,7 @@ class TrashCan
}
$deletion->delete();
return $restoreCount;
}
@ -236,6 +254,7 @@ class TrashCan
* Automatically clear old content from the recycle bin
* depending on the configured lifetime.
* Returns the total number of deleted elements.
*
* @throws Exception
*/
public function autoClearOld(): int
@ -287,6 +306,7 @@ class TrashCan
/**
* Destroy the given entity.
*
* @throws Exception
*/
protected function destroyEntity(Entity $entity): int

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