diff --git a/app/Auth/Permissions/PermissionApplicator.php b/app/Auth/Permissions/PermissionApplicator.php index 3dc529e32..40a7f6116 100644 --- a/app/Auth/Permissions/PermissionApplicator.php +++ b/app/Auth/Permissions/PermissionApplicator.php @@ -72,6 +72,7 @@ class PermissionApplicator $action = $permission; } + // TODO - Use a non-query based check $hasAccess = $this->entityRestrictionQuery($baseQuery, $action)->count() > 0; $this->clean(); @@ -163,14 +164,14 @@ class PermissionApplicator /** * Add restrictions for a generic entity. */ - public function enforceEntityRestrictions(Entity $entity, Builder $query, string $action = 'view'): Builder + public function enforceEntityRestrictions(Entity $entity, Builder $query): Builder { if ($entity instanceof Page) { // Prevent drafts being visible to others. $this->enforceDraftVisibilityOnQuery($query); } - return $this->entityRestrictionQuery($query, $action); + return $this->entityRestrictionQuery($query, 'view'); } /** diff --git a/app/Auth/User.php b/app/Auth/User.php index 4e2183244..c060d5ec8 100644 --- a/app/Auth/User.php +++ b/app/Auth/User.php @@ -163,7 +163,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon } /** - * Get all permissions belonging to a the current user. + * Get all permissions belonging to the current user. */ protected function permissions(): Collection { diff --git a/app/Entities/Queries/Popular.php b/app/Entities/Queries/Popular.php index e6b22a1c9..66006df1b 100644 --- a/app/Entities/Queries/Popular.php +++ b/app/Entities/Queries/Popular.php @@ -7,10 +7,10 @@ use Illuminate\Support\Facades\DB; class Popular extends EntityQuery { - public function run(int $count, int $page, array $filterModels = null, string $action = 'view') + public function run(int $count, int $page, array $filterModels = null) { $query = $this->permissionService() - ->filterRestrictedEntityRelations(View::query(), 'views', 'viewable_id', 'viewable_type', $action) + ->filterRestrictedEntityRelations(View::query(), 'views', 'viewable_id', 'viewable_type', 'view') ->select('*', 'viewable_id', 'viewable_type', DB::raw('SUM(views) as view_count')) ->groupBy('viewable_id', 'viewable_type') ->orderBy('view_count', 'desc'); diff --git a/app/Entities/Tools/SearchRunner.php b/app/Entities/Tools/SearchRunner.php index 5d94379c9..1dcc2eb44 100644 --- a/app/Entities/Tools/SearchRunner.php +++ b/app/Entities/Tools/SearchRunner.php @@ -54,7 +54,7 @@ class SearchRunner * * @return array{total: int, count: int, has_more: bool, results: Entity[]} */ - public function searchEntities(SearchOptions $searchOpts, string $entityType = 'all', int $page = 1, int $count = 20, string $action = 'view'): array + public function searchEntities(SearchOptions $searchOpts, string $entityType = 'all', int $page = 1, int $count = 20): array { $entityTypes = array_keys($this->entityProvider->all()); $entityTypesToSearch = $entityTypes; @@ -75,7 +75,7 @@ class SearchRunner } $entityModelInstance = $this->entityProvider->get($entityType); - $searchQuery = $this->buildQuery($searchOpts, $entityModelInstance, $action); + $searchQuery = $this->buildQuery($searchOpts, $entityModelInstance); $entityTotal = $searchQuery->count(); $searchResults = $this->getPageOfDataFromQuery($searchQuery, $entityModelInstance, $page, $count); @@ -159,7 +159,7 @@ class SearchRunner /** * Create a search query for an entity. */ - protected function buildQuery(SearchOptions $searchOpts, Entity $entityModelInstance, string $action = 'view'): EloquentBuilder + protected function buildQuery(SearchOptions $searchOpts, Entity $entityModelInstance): EloquentBuilder { $entityQuery = $entityModelInstance->newQuery(); @@ -193,7 +193,7 @@ class SearchRunner } } - return $this->permissions->enforceEntityRestrictions($entityModelInstance, $entityQuery, $action); + return $this->permissions->enforceEntityRestrictions($entityModelInstance, $entityQuery); } /** diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php index 6b2be5a2d..4a002298c 100644 --- a/app/Http/Controllers/SearchController.php +++ b/app/Http/Controllers/SearchController.php @@ -12,7 +12,6 @@ use Illuminate\Http\Request; class SearchController extends Controller { protected $searchRunner; - protected $entityContextManager; public function __construct(SearchRunner $searchRunner) { @@ -79,12 +78,12 @@ class SearchController extends Controller // Search for entities otherwise show most popular if ($searchTerm !== false) { $searchTerm .= ' {type:' . implode('|', $entityTypes) . '}'; - $entities = $this->searchRunner->searchEntities(SearchOptions::fromString($searchTerm), 'all', 1, 20, $permission)['results']; + $entities = $this->searchRunner->searchEntities(SearchOptions::fromString($searchTerm), 'all', 1, 20)['results']; } else { - $entities = (new Popular())->run(20, 0, $entityTypes, $permission); + $entities = (new Popular())->run(20, 0, $entityTypes); } - return view('search.parts.entity-ajax-list', ['entities' => $entities]); + return view('search.parts.entity-ajax-list', ['entities' => $entities, 'permission' => $permission]); } /** diff --git a/resources/sass/_lists.scss b/resources/sass/_lists.scss index 19060fbbf..5e251f9c7 100644 --- a/resources/sass/_lists.scss +++ b/resources/sass/_lists.scss @@ -443,6 +443,14 @@ ul.pagination { } } +.entity-list-item.disabled { + pointer-events: none; + cursor: not-allowed; + opacity: 0.8; + user-select: none; + background: var(--bg-disabled); +} + .entity-list-item-path-sep { display: inline-block; vertical-align: top; diff --git a/resources/sass/_variables.scss b/resources/sass/_variables.scss index 6b57147ef..3cb2dd4ed 100644 --- a/resources/sass/_variables.scss +++ b/resources/sass/_variables.scss @@ -45,6 +45,12 @@ $fs-s: 12px; --color-chapter: #af4d0d; --color-book: #077b70; --color-bookshelf: #a94747; + + --bg-disabled: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='100%25' width='100%25'%3E%3Cdefs%3E%3Cpattern id='doodad' width='19' height='19' viewBox='0 0 40 40' patternUnits='userSpaceOnUse' patternTransform='rotate(143)'%3E%3Crect width='100%25' height='100%25' fill='rgba(42, 67, 101,0)'/%3E%3Cpath d='M-10 30h60v20h-60zM-10-10h60v20h-60' fill='rgba(26, 32, 44,0)'/%3E%3Cpath d='M-10 10h60v20h-60zM-10-30h60v20h-60z' fill='rgba(0, 0, 0,0.05)'/%3E%3C/pattern%3E%3C/defs%3E%3Crect fill='url(%23doodad)' height='200%25' width='200%25'/%3E%3C/svg%3E"); +} + +:root.dark-mode { + --bg-disabled: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='100%25' width='100%25'%3E%3Cdefs%3E%3Cpattern id='doodad' width='19' height='19' viewBox='0 0 40 40' patternUnits='userSpaceOnUse' patternTransform='rotate(143)'%3E%3Crect width='100%25' height='100%25' fill='rgba(42, 67, 101,0)'/%3E%3Cpath d='M-10 30h60v20h-60zM-10-10h60v20h-60' fill='rgba(26, 32, 44,0)'/%3E%3Cpath d='M-10 10h60v20h-60zM-10-30h60v20h-60z' fill='rgba(255, 255, 255,0.05)'/%3E%3C/pattern%3E%3C/defs%3E%3Crect fill='url(%23doodad)' height='200%25' width='200%25'/%3E%3C/svg%3E"); } $positive: #0f7d15; diff --git a/resources/views/entities/list-item.blade.php b/resources/views/entities/list-item.blade.php index 44e06753d..5314c8446 100644 --- a/resources/views/entities/list-item.blade.php +++ b/resources/views/entities/list-item.blade.php @@ -1,7 +1,13 @@ -@component('entities.list-item-basic', ['entity' => $entity]) +@component('entities.list-item-basic', ['entity' => $entity, 'classes' => (($locked ?? false) ? 'disabled ' : '') . ($classes ?? '') ])
+ @if($locked ?? false) +
+ @icon('lock')You don't have the required permissions to select this item. +
+ @endif + @if($showPath ?? false) @if($entity->relationLoaded('book') && $entity->book) {{ $entity->book->getShortName(42) }} diff --git a/resources/views/search/parts/entity-ajax-list.blade.php b/resources/views/search/parts/entity-ajax-list.blade.php index a4eedf75e..9340ccdc5 100644 --- a/resources/views/search/parts/entity-ajax-list.blade.php +++ b/resources/views/search/parts/entity-ajax-list.blade.php @@ -2,7 +2,12 @@ @if(count($entities) > 0) @foreach($entities as $index => $entity) - @include('entities.list-item', ['entity' => $entity, 'showPath' => true]) + @include('entities.list-item', [ + 'entity' => $entity, + 'showPath' => true, + 'locked' => $permission !== 'view' && !userCan($permission, $entity) + ]) + @if($index !== count($entities) - 1)
@endif diff --git a/routes/web.php b/routes/web.php index 5e16e5333..9b562703c 100644 --- a/routes/web.php +++ b/routes/web.php @@ -38,6 +38,13 @@ use Illuminate\View\Middleware\ShareErrorsFromSession; Route::get('/status', [StatusController::class, 'show']); Route::get('/robots.txt', [HomeController::class, 'robots']); +Route::get('/test', function() { + $book = \BookStack\Entities\Models\Book::query()->where('slug', '=', 'k5TrhXxaNb')->firstOrFail(); + $builder= app()->make(\BookStack\Auth\Permissions\JointPermissionBuilder::class); + $builder->rebuildForEntity($book); + return 'finished'; +})->withoutMiddleware('web'); + // Authenticated routes... Route::middleware('auth')->group(function () {