mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-30 15:42:41 +01:00
Merge pull request #2820 from BookStackApp/analysis-6470L9
Apply fixes from StyleCI
This commit is contained in:
commit
0155525945
@ -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_',
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
<?php namespace BookStack\Actions;
|
||||
<?php
|
||||
|
||||
namespace BookStack\Actions;
|
||||
|
||||
class ActivityType
|
||||
{
|
||||
|
@ -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()
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
<?php namespace BookStack\Actions;
|
||||
<?php
|
||||
|
||||
namespace BookStack\Actions;
|
||||
|
||||
use BookStack\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
@ -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'];
|
||||
|
||||
/**
|
||||
|
@ -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,
|
||||
];
|
||||
});
|
||||
}
|
||||
|
@ -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',
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -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];
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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)
|
||||
|
@ -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));
|
||||
|
@ -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()
|
||||
|
@ -1,4 +1,6 @@
|
||||
<?php namespace BookStack\Auth\Permissions;
|
||||
<?php
|
||||
|
||||
namespace BookStack\Auth\Permissions;
|
||||
|
||||
use BookStack\Auth\Role;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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'));
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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(),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
|
||||
];
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
],
|
||||
],
|
||||
|
||||
|
@ -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',
|
||||
],
|
||||
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
|
@ -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',
|
||||
],
|
||||
|
||||
|
@ -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,
|
||||
],
|
||||
|
||||
],
|
||||
|
@ -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', ''),
|
||||
];
|
||||
|
@ -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,
|
||||
|
||||
],
|
||||
|
||||
|
||||
];
|
||||
|
@ -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,
|
||||
],
|
||||
|
||||
|
@ -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,
|
||||
],
|
||||
|
||||
];
|
||||
|
@ -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' => [
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
],
|
||||
|
||||
],
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
],
|
||||
|
||||
];
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
use \Illuminate\Support\Str;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Session configuration options.
|
||||
|
@ -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'),
|
||||
],
|
||||
|
||||
];
|
||||
|
@ -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' => [],
|
||||
],
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,6 @@ class ClearViews extends Command
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
|
@ -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?'
|
||||
);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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')
|
||||
|
@ -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}";
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,11 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace BookStack\Entities\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
interface HasCoverImage
|
||||
{
|
||||
|
||||
/**
|
||||
* Get the cover image for this item.
|
||||
*/
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
<?php namespace BookStack\Entities\Queries;
|
||||
<?php
|
||||
|
||||
namespace BookStack\Entities\Queries;
|
||||
|
||||
use BookStack\Actions\View;
|
||||
use Illuminate\Support\Collection;
|
||||
|
@ -1,4 +1,6 @@
|
||||
<?php namespace BookStack\Entities\Queries;
|
||||
<?php
|
||||
|
||||
namespace BookStack\Entities\Queries;
|
||||
|
||||
use BookStack\Actions\Favourite;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -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,
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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');
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
<?php namespace BookStack\Entities\Tools;
|
||||
<?php
|
||||
|
||||
namespace BookStack\Entities\Tools;
|
||||
|
||||
use BookStack\Entities\Models\Book;
|
||||
use BookStack\Entities\Models\Bookshelf;
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user