public static function incrementFor(Viewable $viewable): int
{
$user = user();
- if (is_null($user) || $user->isDefault()) {
+ if (is_null($user) || $user->isGuest()) {
return 0;
}
public function canWatch(): bool
{
- return $this->user->can('receive-notifications') && !$this->user->isDefault();
+ return $this->user->can('receive-notifications') && !$this->user->isGuest();
}
public function getWatchLevel(): string
use BookStack\Access\LoginService;
use BookStack\Access\RegistrationService;
use BookStack\Api\ApiTokenGuard;
+use BookStack\Users\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\ServiceProvider;
use Illuminate\Validation\Rules\Password;
Auth::provider('external-users', function ($app, array $config) {
return new ExternalBaseUserProvider($config['model']);
});
+
+ // Bind and provide the default system user as a singleton to the app instance when needed.
+ // This effectively "caches" fetching the user at an app-instance level.
+ $this->app->singleton('users.default', function () {
+ return User::query()->where('system_name', '=', 'public')->first();
+ });
}
}
*/
function user(): User
{
- return auth()->user() ?: User::getDefault();
-}
-
-/**
- * Check if current user is a signed in user.
- */
-function signedInUser(): bool
-{
- return auth()->user() && !auth()->user()->isDefault();
-}
-
-/**
- * Check if the current user has general access.
- */
-function hasAppAccess(): bool
-{
- return !auth()->guest() || setting('app-public');
+ return auth()->user() ?: User::getGuest();
}
/**
function userCan(string $permission, Model $ownable = null): bool
{
if ($ownable === null) {
- return user() && user()->can($permission);
+ return user()->can($permission);
}
// Check permission on ownable item
public function run(int $count, int $page): Collection
{
$user = user();
- if ($user === null || $user->isDefault()) {
+ if ($user === null || $user->isGuest()) {
return collect();
}
public function run(int $count, int $skip = 0)
{
$user = user();
- if ($user->isDefault()) {
+ if ($user->isGuest()) {
return collect();
}
*/
protected function preventGuestAccess(): void
{
- if (!signedInUser()) {
+ if (user()->isGuest()) {
$this->showPermissionError();
}
}
{
// Return if the user is already found to be signed in via session-based auth.
// This is to make it easy to browser the API via browser after just logging into the system.
- if (signedInUser() || session()->isStarted()) {
+ if (!user()->isGuest() || session()->isStarted()) {
if (!$this->sessionUserHasApiAccess()) {
throw new ApiAuthException(trans('errors.api_user_no_api_permission'), 403);
}
{
$hasApiPermission = user()->can('access-api');
- return $hasApiPermission && hasAppAccess();
+ return $hasApiPermission && user()->hasAppAccess();
}
}
*/
public function handle(Request $request, Closure $next)
{
- if (!hasAppAccess()) {
+ if (!user()->hasAppAccess()) {
if ($request->ajax()) {
return response('Unauthorized.', 401);
}
/** @var Response $response */
$response = $next($request);
- if (signedInUser()) {
+ if (!user()->isGuest()) {
$response->headers->set('Cache-Control', 'max-age=0, no-store, private');
$response->headers->set('Pragma', 'no-cache');
$response->headers->set('Expires', 'Sun, 12 Jul 2015 19:01:00 GMT');
return view('settings.' . $category, [
'category' => $category,
'version' => $version,
- 'guestUser' => User::getDefault(),
+ 'guestUser' => User::getGuest(),
]);
}
$default = config('setting-defaults.user.' . $key, false);
}
- if ($user->isDefault()) {
+ if ($user->isGuest()) {
return $this->getFromSession($key, $default);
}
*/
public function putUser(User $user, string $key, string $value): bool
{
- if ($user->isDefault()) {
+ if ($user->isGuest()) {
session()->put($key, $value);
return true;
return $default;
}
- if ($user->isDefault() && config('app.auto_detect_locale')) {
+ if ($user->isGuest() && config('app.auto_detect_locale')) {
return $this->autoDetectLocale($request, $default);
}
*/
public function forSelect(Request $request)
{
- $hasPermission = signedInUser() && (
+ $hasPermission = !user()->isGuest() && (
userCan('users-manage')
|| userCan('restrictions-manage-own')
|| userCan('restrictions-manage-all')
*/
protected string $avatarUrl = '';
- /**
- * This holds the default user when loaded.
- */
- protected static ?User $defaultUser = null;
-
/**
* Returns the default public user.
+ * Fetches from the container as a singleton to effectively cache at an app level.
*/
- public static function getDefault(): self
+ public static function getGuest(): self
{
- if (!is_null(static::$defaultUser)) {
- return static::$defaultUser;
- }
-
- static::$defaultUser = static::query()->where('system_name', '=', 'public')->first();
-
- return static::$defaultUser;
+ return app()->make('users.default');
}
- public static function clearDefault(): void
+ /**
+ * Check if the user is the default public user.
+ */
+ public function isGuest(): bool
{
- static::$defaultUser = null;
+ return $this->system_name === 'public';
}
/**
- * Check if the user is the default public user.
+ * Check if the user has general access to the application.
*/
- public function isDefault(): bool
+ public function hasAppAccess(): bool
{
- return $this->system_name === 'public';
+ return !$this->isGuest() || setting('app-public');
}
/**
@if($watchOptions->canWatch() && !$watchOptions->isWatching())
@include('entities.watch-action', ['entity' => $book])
@endif
- @if(signedInUser())
+ @if(!user()->isGuest())
@include('entities.favourite-action', ['entity' => $book])
@endif
@if(userCan('content-export'))
@if($watchOptions->canWatch() && !$watchOptions->isWatching())
@include('entities.watch-action', ['entity' => $chapter])
@endif
- @if(signedInUser())
+ @if(!user()->isGuest())
@include('entities.favourite-action', ['entity' => $chapter])
@endif
@if(userCan('content-export'))
--- /dev/null
+<div class="dropdown-container" component="dropdown" option:dropdown:bubble-escapes="true">
+ <span class="user-name py-s hide-under-l" refs="dropdown@toggle"
+ aria-haspopup="true" aria-expanded="false" aria-label="{{ trans('common.profile_menu') }}" tabindex="0">
+ <img class="avatar" src="{{$user->getAvatar(30)}}" alt="{{ $user->name }}">
+ <span class="name">{{ $user->getShortName(9) }}</span> @icon('caret-down')
+ </span>
+ <ul refs="dropdown@menu" class="dropdown-menu" role="menu">
+ <li>
+ <a href="{{ url('/favourites') }}" data-shortcut="favourites_view" class="icon-item">
+ @icon('star')
+ <div>{{ trans('entities.my_favourites') }}</div>
+ </a>
+ </li>
+ <li>
+ <a href="{{ $user->getProfileUrl() }}" data-shortcut="profile_view" class="icon-item">
+ @icon('user')
+ <div>{{ trans('common.view_profile') }}</div>
+ </a>
+ </li>
+ <li>
+ <a href="{{ $user->getEditUrl() }}" class="icon-item">
+ @icon('edit')
+ <div>{{ trans('common.edit_profile') }}</div>
+ </a>
+ </li>
+ <li>
+ <form action="{{ url(config('auth.method') === 'saml2' ? '/saml2/logout' : '/logout') }}"
+ method="post">
+ {{ csrf_field() }}
+ <button class="icon-item" data-shortcut="logout">
+ @icon('logout')
+ <div>{{ trans('auth.logout') }}</div>
+ </button>
+ </form>
+ </li>
+ <li><hr></li>
+ <li>
+ <a href="{{ url('/preferences') }}" class="icon-item">
+ @icon('user-preferences')
+ <div>{{ trans('preferences.preferences') }}</div>
+ </a>
+ </li>
+ <li>
+ @include('common.dark-mode-toggle', ['classes' => 'icon-item'])
+ </li>
+ </ul>
+</div>
\ No newline at end of file
</div>
<div class="flex-container-column items-center justify-center hide-under-l">
- @if (hasAppAccess())
+ @if (user()->hasAppAccess())
<form component="global-search" action="{{ url('/search') }}" method="GET" class="search-box" role="search" tabindex="0">
<button id="header-search-box-button"
refs="global-search@button"
<nav refs="header-mobile-toggle@menu" class="header-links">
<div class="links text-center">
- @if (hasAppAccess())
+ @if (user()->hasAppAccess())
<a class="hide-over-l" href="{{ url('/search') }}">@icon('search'){{ trans('common.search') }}</a>
@if(userCanOnAny('view', \BookStack\Entities\Models\Bookshelf::class) || userCan('bookshelf-view-all') || userCan('bookshelf-view-own'))
<a href="{{ url('/shelves') }}" data-shortcut="shelves_view">@icon('bookshelf'){{ trans('entities.shelves') }}</a>
@endif
<a href="{{ url('/books') }}" data-shortcut="books_view">@icon('books'){{ trans('entities.books') }}</a>
- @if(signedInUser() && userCan('settings-manage'))
+ @if(!user()->isGuest() && userCan('settings-manage'))
<a href="{{ url('/settings') }}" data-shortcut="settings_view">@icon('settings'){{ trans('settings.settings') }}</a>
@endif
- @if(signedInUser() && userCan('users-manage') && !userCan('settings-manage'))
+ @if(!user()->isGuest() && userCan('users-manage') && !userCan('settings-manage'))
<a href="{{ url('/settings/users') }}" data-shortcut="settings_view">@icon('users'){{ trans('settings.users') }}</a>
@endif
@endif
- @if(!signedInUser())
+ @if(user()->isGuest())
@if(setting('registration-enabled') && config('auth.method') === 'standard')
<a href="{{ url('/register') }}">@icon('new-user'){{ trans('auth.sign_up') }}</a>
@endif
<a href="{{ url('/login') }}">@icon('login'){{ trans('auth.log_in') }}</a>
@endif
</div>
- @if(signedInUser())
- <?php $currentUser = user(); ?>
- <div class="dropdown-container" component="dropdown" option:dropdown:bubble-escapes="true">
- <span class="user-name py-s hide-under-l" refs="dropdown@toggle"
- aria-haspopup="true" aria-expanded="false" aria-label="{{ trans('common.profile_menu') }}" tabindex="0">
- <img class="avatar" src="{{$currentUser->getAvatar(30)}}" alt="{{ $currentUser->name }}">
- <span class="name">{{ $currentUser->getShortName(9) }}</span> @icon('caret-down')
- </span>
- <ul refs="dropdown@menu" class="dropdown-menu" role="menu">
- <li>
- <a href="{{ url('/favourites') }}" data-shortcut="favourites_view" class="icon-item">
- @icon('star')
- <div>{{ trans('entities.my_favourites') }}</div>
- </a>
- </li>
- <li>
- <a href="{{ $currentUser->getProfileUrl() }}" data-shortcut="profile_view" class="icon-item">
- @icon('user')
- <div>{{ trans('common.view_profile') }}</div>
- </a>
- </li>
- <li>
- <a href="{{ $currentUser->getEditUrl() }}" class="icon-item">
- @icon('edit')
- <div>{{ trans('common.edit_profile') }}</div>
- </a>
- </li>
- <li>
- <form action="{{ url(config('auth.method') === 'saml2' ? '/saml2/logout' : '/logout') }}"
- method="post">
- {{ csrf_field() }}
- <button class="icon-item" data-shortcut="logout">
- @icon('logout')
- <div>{{ trans('auth.logout') }}</div>
- </button>
- </form>
- </li>
- <li><hr></li>
- <li>
- <a href="{{ url('/preferences') }}" class="icon-item">
- @icon('user-preferences')
- <div>{{ trans('preferences.preferences') }}</div>
- </a>
- </li>
- <li>
- @include('common.dark-mode-toggle', ['classes' => 'icon-item'])
- </li>
- </ul>
- </div>
+ @if(!user()->isGuest())
+ @include('common.header-user-menu', ['user' => user()])
@endif
</nav>
@extends('layouts.simple')
@section('content')
-<div class="container mt-l">
+ <div class="container mt-l">
- <div class="card mb-xl px-l pb-l pt-l">
- <div class="grid half v-center">
- <div>
- @include('errors.parts.not-found-text', [
- 'title' => $message ?? trans('errors.404_page_not_found'),
- 'subtitle' => $subtitle ?? trans('errors.sorry_page_not_found'),
- 'details' => $details ?? trans('errors.sorry_page_not_found_permission_warning'),
- ])
- </div>
- <div class="text-right">
- @if(!signedInUser())
- <a href="{{ url('/login') }}" class="button outline">{{ trans('auth.log_in') }}</a>
- @endif
- <a href="{{ url('/') }}" class="button outline">{{ trans('errors.return_home') }}</a>
+ <div class="card mb-xl px-l pb-l pt-l">
+ <div class="grid half v-center">
+ <div>
+ @include('errors.parts.not-found-text', [
+ 'title' => $message ?? trans('errors.404_page_not_found'),
+ 'subtitle' => $subtitle ?? trans('errors.sorry_page_not_found'),
+ 'details' => $details ?? trans('errors.sorry_page_not_found_permission_warning'),
+ ])
+ </div>
+ <div class="text-right">
+ @if(user()->isGuest())
+ <a href="{{ url('/login') }}" class="button outline">{{ trans('auth.log_in') }}</a>
+ @endif
+ <a href="{{ url('/') }}" class="button outline">{{ trans('errors.return_home') }}</a>
+ </div>
</div>
- </div>
- </div>
+ </div>
- @if (setting('app-public') || !user()->isDefault())
- <div class="grid third gap-xxl">
- <div>
- <div class="card mb-xl">
- <h3 class="card-title">{{ trans('entities.pages_popular') }}</h3>
- <div class="px-m">
- @include('entities.list', ['entities' => (new \BookStack\Entities\Queries\Popular)->run(10, 0, ['page']), 'style' => 'compact'])
+ @if (setting('app-public') || !user()->isGuest())
+ <div class="grid third gap-xxl">
+ <div>
+ <div class="card mb-xl">
+ <h3 class="card-title">{{ trans('entities.pages_popular') }}</h3>
+ <div class="px-m">
+ @include('entities.list', ['entities' => (new \BookStack\Entities\Queries\Popular)->run(10, 0, ['page']), 'style' => 'compact'])
+ </div>
</div>
</div>
- </div>
- <div>
- <div class="card mb-xl">
- <h3 class="card-title">{{ trans('entities.books_popular') }}</h3>
- <div class="px-m">
- @include('entities.list', ['entities' => (new \BookStack\Entities\Queries\Popular)->run(10, 0, ['book']), 'style' => 'compact'])
+ <div>
+ <div class="card mb-xl">
+ <h3 class="card-title">{{ trans('entities.books_popular') }}</h3>
+ <div class="px-m">
+ @include('entities.list', ['entities' => (new \BookStack\Entities\Queries\Popular)->run(10, 0, ['book']), 'style' => 'compact'])
+ </div>
</div>
</div>
- </div>
- <div>
- <div class="card mb-xl">
- <h3 class="card-title">{{ trans('entities.chapters_popular') }}</h3>
- <div class="px-m">
- @include('entities.list', ['entities' => (new \BookStack\Entities\Queries\Popular)->run(10, 0, ['chapter']), 'style' => 'compact'])
+ <div>
+ <div class="card mb-xl">
+ <h3 class="card-title">{{ trans('entities.chapters_popular') }}</h3>
+ <div class="px-m">
+ @include('entities.list', ['entities' => (new \BookStack\Entities\Queries\Popular)->run(10, 0, ['chapter']), 'style' => 'compact'])
+ </div>
</div>
</div>
</div>
- </div>
- @endif
-</div>
+ @endif
+ </div>
@stop
\ No newline at end of file
@if($watchOptions->canWatch() && !$watchOptions->isWatching())
@include('entities.watch-action', ['entity' => $page])
@endif
- @if(signedInUser())
+ @if(!user()->isGuest())
@include('entities.favourite-action', ['entity' => $page])
@endif
@if(userCan('content-export'))
<h6>{{ trans('entities.search_tags') }}</h6>
@include('search.parts.term-list', ['type' => 'tags', 'currentList' => $options->tags])
- @if(signedInUser())
+ @if(!user()->isGuest())
<h6>{{ trans('entities.search_options') }}</h6>
@component('search.parts.boolean-filter', ['filters' => $options->filters, 'name' => 'viewed_by_me', 'value' => null])
</a>
@endif
- @if(signedInUser())
+ @if(!user()->isGuest())
<hr class="primary-background">
@include('entities.favourite-action', ['entity' => $shelf])
@endif
</div>
</section>
- @if(signedInUser() && userCan('receive-notifications'))
+ @if(!user()->isGuest() && userCan('receive-notifications'))
<section class="card content-wrap auto-height items-center justify-space-between gap-m flex-container-row wrap">
<div class="flex min-width-m">
<h2 class="list-heading">{{ trans('preferences.notifications') }}</h2>
</section>
@endif
- @if(signedInUser())
+ @if(!user()->isGuest())
<section class="card content-wrap auto-height items-center justify-space-between gap-m flex-container-row wrap">
<div class="flex min-width-m">
<h2 class="list-heading">{{ trans('settings.users_edit_profile') }}</h2>
$page = $this->entities->page();
$this->createRevisions($page, 2);
- $pageView = $this->get($page->getUrl());
+ $pageView = $this->asViewer()->get($page->getUrl());
$pageView->assertSee('Revision #' . $page->revision_count);
}
*/
public function guest(): User
{
- return User::getDefault();
+ return User::getGuest();
}
/**
$resp = $this->post($chapter->getUrl('/create-guest-page'), ['name' => 'My guest page']);
$resp->assertRedirect($chapter->book->getUrl('/page/my-guest-page/edit'));
- $user = User::getDefault();
+ $user = $this->users->guest();
$this->assertDatabaseHas('pages', [
'name' => 'My guest page',
'chapter_id' => $chapter->id,
public function test_public_view_can_take_on_other_roles()
{
$this->setSettings(['app-public' => 'true']);
- $newRole = $this->users->attachNewRole(User::getDefault(), []);
+ $newRole = $this->users->attachNewRole($this->users->guest(), []);
$page = $this->entities->page();
$this->permissions->disableEntityInheritedPermissions($page);
$this->permissions->addEntityPermission($page, ['view', 'update'], $newRole);
$this->permissions = new PermissionsProvider($this->users);
$this->files = new FileProvider();
- User::clearDefault();
parent::setUp();
// We can uncomment the below to run tests with failings upon deprecations.
public function test_guest_profile_shows_limited_form()
{
- $guest = User::getDefault();
+ $guest = $this->users->guest();
$resp = $this->asAdmin()->get('/settings/users/' . $guest->id);
$resp->assertSee('Guest');
$this->withHtml($resp)->assertElementNotExists('#password');
public function test_guest_profile_cannot_be_deleted()
{
- $guestUser = User::getDefault();
+ $guestUser = $this->users->guest();
$resp = $this->asAdmin()->get('/settings/users/' . $guestUser->id . '/delete');
$resp->assertSee('Delete User');
$resp->assertSee('Guest');
public function test_select_requires_logged_in_user()
{
$this->setSettings(['app-public' => true]);
- $defaultUser = User::getDefault();
- $this->permissions->grantUserRolePermissions($defaultUser, ['users-manage']);
+ $this->permissions->grantUserRolePermissions($this->users->guest(), ['users-manage']);
$resp = $this->get('/search/users/select?search=a');
$this->assertPermissionError($resp);