diff --git a/app/Auth/Permissions/PermissionFormData.php b/app/Auth/Permissions/PermissionFormData.php index 07c95c534..781209043 100644 --- a/app/Auth/Permissions/PermissionFormData.php +++ b/app/Auth/Permissions/PermissionFormData.php @@ -15,16 +15,15 @@ class PermissionFormData } /** - * Get the roles with permissions assigned. + * Get the permissions with assigned roles. */ - public function rolesWithPermissions(): array + public function permissionsWithRoles(): array { return $this->entity->permissions() ->with('role') ->where('role_id', '!=', 0) - ->get(['id', 'role_id']) - ->pluck('role') - ->sortBy('display_name') + ->get() + ->sortBy('role.display_name') ->all(); } diff --git a/app/Http/Controllers/PermissionsController.php b/app/Http/Controllers/PermissionsController.php index d8dca9825..dd6c29a8a 100644 --- a/app/Http/Controllers/PermissionsController.php +++ b/app/Http/Controllers/PermissionsController.php @@ -2,7 +2,9 @@ namespace BookStack\Http\Controllers; +use BookStack\Auth\Permissions\EntityPermission; use BookStack\Auth\Permissions\PermissionFormData; +use BookStack\Auth\Role; use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Bookshelf; use BookStack\Entities\Models\Chapter; @@ -148,4 +150,20 @@ class PermissionsController extends Controller return redirect($shelf->getUrl()); } + + /** + * Get an empty entity permissions form row for the given role. + */ + public function formRowForRole(string $entityType, string $roleId) + { + $this->checkPermissionOr('restrictions-manage', fn() => userCan('restrictions-manage-all')); + + $role = Role::query()->findOrFail($roleId); + + return view('form.entity-permissions-row', [ + 'role' => $role, + 'permission' => new EntityPermission(), + 'entityType' => $entityType, + ]); + } } diff --git a/resources/js/components/entity-permissions.js b/resources/js/components/entity-permissions.js index 8b57d3376..a18fc7a97 100644 --- a/resources/js/components/entity-permissions.js +++ b/resources/js/components/entity-permissions.js @@ -1,14 +1,21 @@ - - +/** + * @extends {Component} + */ class EntityPermissions { setup() { + this.container = this.$el; + this.entityType = this.$opts.entityType; + this.everyoneInheritToggle = this.$refs.everyoneInherit; + this.roleSelect = this.$refs.roleSelect; + this.roleContainer = this.$refs.roleContainer; this.setupListeners(); } setupListeners() { + // "Everyone Else" inherit toggle this.everyoneInheritToggle.addEventListener('change', event => { const inherit = event.target.checked; const permissions = document.querySelectorAll('input[type="checkbox"][name^="restrictions[0]["]'); @@ -16,7 +23,56 @@ class EntityPermissions { permission.disabled = inherit; permission.checked = false; } - }) + }); + + // Remove role row button click + this.container.addEventListener('click', event => { + const button = event.target.closest('button'); + if (button && button.dataset.roleId) { + this.removeRowOnButtonClick(button) + } + }); + + // Role select change + this.roleSelect.addEventListener('change', event => { + const roleId = this.roleSelect.value; + if (roleId) { + this.addRoleRow(roleId); + } + }); + } + + async addRoleRow(roleId) { + this.roleSelect.disabled = true; + + // Remove option from select + const option = this.roleSelect.querySelector(`option[value="${roleId}"]`); + if (option) { + option.remove(); + } + + // Get and insert new row + const resp = await window.$http.get(`/permissions/form-row/${this.entityType}/${roleId}`); + const wrap = document.createElement('div'); + wrap.innerHTML = resp.data; + const row = wrap.children[0]; + this.roleContainer.append(row); + window.components.init(row); + + this.roleSelect.disabled = false; + } + + removeRowOnButtonClick(button) { + const row = button.closest('.content-permissions-row'); + const roleId = button.dataset.roleId; + const roleName = button.dataset.roleName; + + const option = document.createElement('option'); + option.value = roleId; + option.textContent = roleName; + + this.roleSelect.append(option); + row.remove(); } } diff --git a/resources/sass/_buttons.scss b/resources/sass/_buttons.scss index 714dfc42c..83d17352d 100644 --- a/resources/sass/_buttons.scss +++ b/resources/sass/_buttons.scss @@ -109,12 +109,23 @@ button { display: block; } -.button.icon { +.button.icon, .icon-button { .svg-icon { margin-inline-end: 0; } } +.icon-button { + text-align: center; + border: 1px solid transparent; +} +.icon-button:hover { + background-color: rgba(0, 0, 0, 0.05); + border-radius: 4px; + border-color: #DDD; + cursor: pointer; +} + .button.svg { display: flex; align-items: center; diff --git a/resources/views/form/entity-permissions-row.blade.php b/resources/views/form/entity-permissions-row.blade.php index ce8beaec3..2bf19db64 100644 --- a/resources/views/form/entity-permissions-row.blade.php +++ b/resources/views/form/entity-permissions-row.blade.php @@ -1,3 +1,9 @@ +{{-- +$role - The Role to display this row for. +$entityType - String identifier for type of entity having permissions applied. +$permission - The entity permission containing the permissions. +--}} +
@@ -15,7 +21,8 @@ @endif
@php - $inheriting = ($role->id === 0 && !$model->restricted); + // TODO + $inheriting = ($role->id === 0); @endphp @if($role->id === 0)
@@ -30,18 +37,53 @@
- @include('form.restriction-checkbox', ['name'=>'permissions', 'label' => trans('common.view'), 'action' => 'view', 'disabled' => $inheriting]) + @include('form.custom-checkbox', [ + 'name' => 'permissions[' . $role->id . '][view]', + 'label' => trans('common.view'), + 'value' => 'true', + 'checked' => $permission->view, + 'disabled' => $inheriting + ]) +
+ @if($entityType !== 'page') +
+ @include('form.custom-checkbox', [ + 'name' => 'permissions[' . $role->id . '][create]', + 'label' => trans('common.create'), + 'value' => 'true', + 'checked' => $permission->create, + 'disabled' => $inheriting + ]) +
+ @endif +
+ @include('form.custom-checkbox', [ + 'name' => 'permissions[' . $role->id . '][update]', + 'label' => trans('common.update'), + 'value' => 'true', + 'checked' => $permission->update, + 'disabled' => $inheriting + ])
- @if(!$model instanceof \BookStack\Entities\Models\Page) - @include('form.restriction-checkbox', ['name'=>'permissions', 'label' => trans('common.create'), 'action' => 'create', 'disabled' => $inheriting]) - @endif -
-
- @include('form.restriction-checkbox', ['name'=>'permissions', 'label' => trans('common.update'), 'action' => 'update', 'disabled' => $inheriting]) -
-
- @include('form.restriction-checkbox', ['name'=>'permissions', 'label' => trans('common.delete'), 'action' => 'delete', 'disabled' => $inheriting]) + @include('form.custom-checkbox', [ + 'name' => 'permissions[' . $role->id . '][delete]', + 'label' => trans('common.delete'), + 'value' => 'true', + 'checked' => $permission->delete, + 'disabled' => $inheriting + ])
+ @if($role->id !== 0) +
+ +
+ @endif
\ No newline at end of file diff --git a/resources/views/form/entity-permissions.blade.php b/resources/views/form/entity-permissions.blade.php index 2fd0a4a43..c6f5a4298 100644 --- a/resources/views/form/entity-permissions.blade.php +++ b/resources/views/form/entity-permissions.blade.php @@ -1,4 +1,7 @@ -
+ {!! csrf_field() !!} @@ -18,14 +21,34 @@

{{ trans('entities.shelves_permissions_cascade_warning') }}

@endif -
- @foreach($data->rolesWithPermissions() as $role) - @include('form.entity-permissions-row', ['role' => $role, 'model' => $model]) +
+ @foreach($data->permissionsWithRoles() as $permission) + @include('form.entity-permissions-row', [ + 'permission' => $permission, + 'role' => $permission->role, + 'entityType' => $model->getType() + ]) @endforeach
+
+
+ + +
+
+
- @include('form.entity-permissions-row', ['role' => $data->everyoneElseRole(), 'model' => $model]) + @include('form.entity-permissions-row', [ + 'role' => $data->everyoneElseRole(), + 'permission' => new \BookStack\Auth\Permissions\EntityPermission(), + 'entityType' => $model->getType(), + ])
diff --git a/routes/web.php b/routes/web.php index 8ee5d0739..5fdfda3f0 100644 --- a/routes/web.php +++ b/routes/web.php @@ -215,6 +215,9 @@ Route::middleware('auth')->group(function () { Route::get('/', [HomeController::class, 'index']); Route::get('/home', [HomeController::class, 'index']); + // Permissions + Route::get('/permissions/form-row/{entityType}/{roleId}', [PermissionsController::class, 'formRowForRole']); + // Maintenance Route::get('/settings/maintenance', [MaintenanceController::class, 'index']); Route::delete('/settings/maintenance/cleanup-images', [MaintenanceController::class, 'cleanupImages']);