diff --git a/app/Http/Controllers/api/v1/RoomController.php b/app/Http/Controllers/api/v1/RoomController.php index b922ffe9b..bd10069b5 100644 --- a/app/Http/Controllers/api/v1/RoomController.php +++ b/app/Http/Controllers/api/v1/RoomController.php @@ -60,11 +60,21 @@ public function index(ShowRoomsRequest $request) $query->orWhere('user_id', '=', Auth::user()->id); } - // rooms where the user is member + // rooms where the user is member (individually or via role) if ($request->filter_shared) { - // list of room ids where the user is member + // list of room ids where the user is a direct member $roomMemberships = Auth::user()->sharedRooms->modelKeys(); $query->orWhereIn('rooms.id', $roomMemberships); + + // list of room ids where the user is a member via role + $userRoleIds = Auth::user()->roles()->pluck('roles.id'); + if ($userRoleIds->isNotEmpty()) { + $query->orWhereIn('rooms.id', function ($subQuery) use ($userRoleIds) { + $subQuery->select('room_id') + ->from('room_role') + ->whereIn('role_id', $userRoleIds); + }); + } } // all rooms that are public diff --git a/app/Http/Controllers/api/v1/RoomRoleMemberController.php b/app/Http/Controllers/api/v1/RoomRoleMemberController.php new file mode 100644 index 000000000..ad062c45a --- /dev/null +++ b/app/Http/Controllers/api/v1/RoomRoleMemberController.php @@ -0,0 +1,116 @@ +query('sort_by')) { + default => 'LOWER(name)', + }; + + // Sort direction, fallback/default is asc + $sortOrder = match ($request->query('sort_direction')) { + 'desc' => 'DESC', + default => 'ASC', + }; + + // Filter by role, fallback/default is no filter + $filter = match ($request->query('filter')) { + 'participant_role' => ['room_role.role', RoomUserRole::USER], + 'moderator_role' => ['room_role.role', RoomUserRole::MODERATOR], + 'co_owner_role' => ['room_role.role', RoomUserRole::CO_OWNER], + default => null, + }; + + // Get all role members of the room and sort them + $resource = $room->roleMembers()->withCount('users')->orderByRaw($sortBy.' '.$sortOrder)->orderBy('roles.id'); + + // count all before applying filters + $additional['meta']['total_no_filter'] = $resource->count(); + + // Apply search query if set + if ($request->has('query')) { + $resource = $resource->where(function ($query) use ($request) { + $query->whereLike('name', '%'.$request->query('query').'%'); + }); + } + + // Apply filter if set + if ($filter) { + $resource = $resource->wherePivot($filter[0], $filter[1]); + } + + return RoomRoleMember::collection($resource->paginate(app(GeneralSettings::class)->pagination_page_size))->additional($additional); + } + + /** + * Add a role as a room member + * + * @return \Illuminate\Http\Response + */ + public function store(Room $room, AddRoomRoleMember $request) + { + $room->roleMembers()->attach($request->role_id, ['role' => $request->role]); + + $role = Role::find($request->role_id); + + Log::info('Added role {role} with room role {roomRole} to room {room}', ['room' => $room->getLogLabel(), 'roomRole' => RoomUserRole::from($request->role)->label(), 'role' => $role->getLogLabel()]); + + return response()->noContent(); + } + + /** + * Update a role's room membership role + * + * @return \Illuminate\Http\Response + */ + public function update(Room $room, Role $role, UpdateRoomRoleMember $request) + { + if (! $room->roleMembers()->where('role_id', $role->id)->exists()) { + abort(410, __('app.errors.role_not_member_of_room')); + } + $room->roleMembers()->updateExistingPivot($role, ['role' => $request->role]); + + Log::info('Changed room role for role {role} to {roomRole} in room {room}', ['room' => $room->getLogLabel(), 'roomRole' => RoomUserRole::from($request->role)->label(), 'role' => $role->getLogLabel()]); + + return response()->noContent(); + } + + /** + * Remove a role from room membership + * + * @return \Illuminate\Http\Response + */ + public function destroy(Room $room, Role $role) + { + if (! $room->roleMembers()->where('role_id', $role->id)->exists()) { + abort(410, __('app.errors.role_not_member_of_room')); + } + $room->roleMembers()->detach($role); + + Log::info('Removed role {role} from room {room}', ['room' => $room->getLogLabel(), 'role' => $role->getLogLabel()]); + + return response()->noContent(); + } +} diff --git a/app/Http/Middleware/RoomAuthenticate.php b/app/Http/Middleware/RoomAuthenticate.php index 40ff9de26..22d387977 100644 --- a/app/Http/Middleware/RoomAuthenticate.php +++ b/app/Http/Middleware/RoomAuthenticate.php @@ -40,7 +40,7 @@ public function handle($request, Closure $next, $allowUnAuthenticated = false) $token = null; // requested user is the owner or a member of the room - if (Auth::user() && ($room->owner->is(Auth::user()) || $room->members->contains(Auth::user()) || Auth::user()->can('viewAll', Room::class))) { + if (Auth::user() && ($room->owner->is(Auth::user()) || $room->members->contains(Auth::user()) || $room->roleMembers()->whereIn('role_id', Auth::user()->roles->pluck('id'))->exists() || Auth::user()->can('viewAll', Room::class))) { $authenticated = true; } diff --git a/app/Http/Requests/AddRoomRoleMember.php b/app/Http/Requests/AddRoomRoleMember.php new file mode 100644 index 000000000..41441840f --- /dev/null +++ b/app/Http/Requests/AddRoomRoleMember.php @@ -0,0 +1,23 @@ + ['required', 'integer', 'exists:App\Models\Role,id', + function ($attribute, $value, $fail) { + if ($this->room->roleMembers()->where('role_id', $value)->exists()) { + $fail(__('validation.custom.room.role_already_member')); + } + }], + 'role' => ['required', Rule::in([RoomUserRole::USER, RoomUserRole::MODERATOR, RoomUserRole::CO_OWNER])], + ]; + } +} diff --git a/app/Http/Requests/UpdateRoomRoleMember.php b/app/Http/Requests/UpdateRoomRoleMember.php new file mode 100644 index 000000000..293868f1f --- /dev/null +++ b/app/Http/Requests/UpdateRoomRoleMember.php @@ -0,0 +1,17 @@ + ['required', Rule::in([RoomUserRole::USER, RoomUserRole::MODERATOR, RoomUserRole::CO_OWNER])], + ]; + } +} diff --git a/app/Http/Resources/Room.php b/app/Http/Resources/Room.php index 6f07fd751..42215b590 100644 --- a/app/Http/Resources/Room.php +++ b/app/Http/Resources/Room.php @@ -59,6 +59,7 @@ public function getDetails($latestMeeting) 'description' => $this->when($this->authenticated, $this->description), 'allow_membership' => $this->getRoomSetting('allow_membership'), 'is_member' => $this->resource->isMember(Auth::user()), + 'is_role_member' => $this->resource->isRoleMember(Auth::user()), 'is_moderator' => $this->resource->isModerator(Auth::user(), $this->token), 'is_co_owner' => $this->resource->isCoOwner(Auth::user()), 'can_start' => Gate::inspect('start', [$this->resource, $this->token])->allowed(), diff --git a/app/Http/Resources/RoomRoleMember.php b/app/Http/Resources/RoomRoleMember.php new file mode 100644 index 000000000..b2d2bda41 --- /dev/null +++ b/app/Http/Resources/RoomRoleMember.php @@ -0,0 +1,24 @@ + $this->id, + 'name' => $this->name, + 'role' => $this->pivot->role, + 'user_count' => $this->users_count ?? $this->users()->count(), + ]; + } +} diff --git a/app/Models/Role.php b/app/Models/Role.php index 69c6a62d3..89d3862e5 100644 --- a/app/Models/Role.php +++ b/app/Models/Role.php @@ -93,6 +93,16 @@ public function roomTypes() return $this->belongsToMany(RoomType::class); } + /** + * Rooms that are shared with this role. + * + * @return BelongsToMany + */ + public function rooms() + { + return $this->belongsToMany(Room::class, 'room_role')->using(RoomRole::class)->withPivot('role'); + } + /** * Scope a query to only get roles that have a name like the passed one. * diff --git a/app/Models/Room.php b/app/Models/Room.php index 79ac8f437..e2549b6a0 100644 --- a/app/Models/Room.php +++ b/app/Models/Room.php @@ -229,6 +229,16 @@ public function members() return $this->belongsToMany(User::class)->using(RoomUser::class)->withPivot('role'); } + /** + * Roles that have membership in the room + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + public function roleMembers() + { + return $this->belongsToMany(Role::class, 'room_role')->using(RoomRole::class)->withPivot('role'); + } + /** * Meetings * @@ -282,7 +292,17 @@ public function isModerator(?User $user) return $token->room->is($this) && $token->role == RoomUserRole::MODERATOR; } - return $user == null ? false : $this->members()->wherePivot('role', RoomUserRole::MODERATOR)->get()->contains($user); + if ($user == null) { + return false; + } + + // Check individual membership + if ($this->members()->wherePivot('role', RoomUserRole::MODERATOR)->get()->contains($user)) { + return true; + } + + // Check role-based membership + return $this->getRoleMembershipRole($user) === RoomUserRole::MODERATOR; } /** Check if user is co owner of this room @@ -290,7 +310,17 @@ public function isModerator(?User $user) */ public function isCoOwner(?User $user) { - return $user == null ? false : $this->members()->wherePivot('role', RoomUserRole::CO_OWNER)->get()->contains($user); + if ($user == null) { + return false; + } + + // Check individual membership + if ($this->members()->wherePivot('role', RoomUserRole::CO_OWNER)->get()->contains($user)) { + return true; + } + + // Check role-based membership + return $this->getRoleMembershipRole($user) === RoomUserRole::CO_OWNER; } /** @@ -307,7 +337,17 @@ public function isMember(?User $user) return $token->room->is($this) && ($token->role == RoomUserRole::USER || $token->role == RoomUserRole::MODERATOR); } - return $user == null ? false : $this->members->contains($user); + if ($user == null) { + return false; + } + + // Check individual membership + if ($this->members->contains($user)) { + return true; + } + + // Check role-based membership + return $this->getRoleMembershipRole($user) !== null; } /** @@ -327,14 +367,62 @@ public function getRole(?User $user, ?RoomToken $token): RoomUserRole return RoomUserRole::OWNER; } + $individualRole = null; $member = $this->members()->find($user); if ($member) { - return $member->pivot->role; + $individualRole = $member->pivot->role; + } + + $roleBasedRole = $this->getRoleMembershipRole($user); + + // If both exist, take the higher value (higher = more privileges) + if ($individualRole !== null && $roleBasedRole !== null) { + return $individualRole->value >= $roleBasedRole->value ? $individualRole : $roleBasedRole; + } + + if ($individualRole !== null) { + return $individualRole; + } + + if ($roleBasedRole !== null) { + return $roleBasedRole; } return $this->getRoomSetting('default_role'); } + /** + * Get the highest room role granted to the user via role-based membership. + * Returns null if the user has no role-based membership. + */ + protected function getRoleMembershipRole(?User $user): ?RoomUserRole + { + if ($user === null) { + return null; + } + + $userRoleIds = $user->roles->pluck('id'); + + if ($userRoleIds->isEmpty()) { + return null; + } + + $highestRoleMember = $this->roleMembers() + ->whereIn('role_id', $userRoleIds) + ->orderByDesc('room_role.role') + ->first(); + + return $highestRoleMember?->pivot->role; + } + + /** + * Check if user is a member of this room via role-based membership only + */ + public function isRoleMember(?User $user): bool + { + return $user !== null && $this->getRoleMembershipRole($user) !== null; + } + /** * Generate message for moderators inside the meeting * diff --git a/app/Models/RoomRole.php b/app/Models/RoomRole.php new file mode 100644 index 000000000..0e7c640a7 --- /dev/null +++ b/app/Models/RoomRole.php @@ -0,0 +1,15 @@ + RoomUserRole::class, + ]; +} diff --git a/database/migrations/2026_02_07_000001_create_room_role_table.php b/database/migrations/2026_02_07_000001_create_room_role_table.php new file mode 100644 index 000000000..481ed88a8 --- /dev/null +++ b/database/migrations/2026_02_07_000001_create_room_role_table.php @@ -0,0 +1,34 @@ +foreignId('role_id')->constrained()->onDelete('cascade'); + $table->string('room_id', 15); + $table->foreign('room_id')->references('id')->on('rooms')->onDelete('cascade'); + $table->primary(['role_id', 'room_id']); + $table->tinyInteger('role')->default(\App\Enums\RoomUserRole::USER); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('room_role'); + } +}; diff --git a/lang/de-gender/app.php b/lang/de-gender/app.php index f80f331f3..41033cb3c 100644 --- a/lang/de-gender/app.php +++ b/lang/de-gender/app.php @@ -39,6 +39,7 @@ 'no_room_access' => 'Sie haben nicht die notwendigen Rechte, den Raum zu bearbeiten.', 'no_server_available' => 'Zur Zeit sind keine Server verfügbar.', 'not_member_of_room' => 'Die Person ist nicht (mehr) Mitglied dieses Raums.', + 'role_not_member_of_room' => 'Diese Rolle ist nicht (mehr) Mitglied dieses Raums.', 'not_running' => 'Der Beitritt zum Raum ist fehlgeschlagen, da er derzeit geschlossen ist.', 'record_agreement_missing' => 'Die Zustimmung zur Aufzeichnung ist erforderlich.', 'role_delete_linked_users' => 'Die Rolle ist mit Benutzern verknüpft und kann deshalb nicht gelöscht werden!', diff --git a/lang/de-gender/rooms.php b/lang/de-gender/rooms.php index def5bd4c7..5da14c9eb 100644 --- a/lang/de-gender/rooms.php +++ b/lang/de-gender/rooms.php @@ -229,6 +229,28 @@ ], 'nodata' => 'Keine Mitglieder vorhanden', 'remove_user' => 'Mitglied löschen', + 'role_members' => [ + 'add' => 'Rolle hinzufügen', + 'edit' => 'Rolle bearbeiten', + 'modals' => [ + 'add' => [ + 'add' => 'Hinzufügen', + 'placeholder' => 'Rolle auswählen', + 'title' => 'Rolle als Mitglied hinzufügen', + ], + 'edit' => [ + 'title' => ':name bearbeiten', + ], + 'remove' => [ + 'confirm' => 'Möchten Sie die Rolle :name aus diesem Raum entfernen?', + 'title' => 'Rolle aus dem Raum entfernen', + ], + ], + 'nodata' => 'Keine Rollen mit diesem Raum geteilt', + 'remove' => 'Rolle entfernen', + 'title' => 'Rollenmitglieder', + 'user_count' => ':count Benutzer:innen', + ], 'title' => 'Mitglieder', ], 'modals' => [ diff --git a/lang/de-gender/validation.php b/lang/de-gender/validation.php index bb8e29d73..dcaedae11 100644 --- a/lang/de-gender/validation.php +++ b/lang/de-gender/validation.php @@ -208,6 +208,7 @@ ], 'room' => [ 'already_member' => 'Der Benutzer ist bereits Mitglied des Raums.', + 'role_already_member' => 'Diese Rolle ist bereits Mitglied des Raums.', 'not_member' => 'Der Benutzer ":firstname :lastname" ist nicht Mitglied des Raums.', 'self_delete' => 'Sie dürfen sich nicht selbst löschen.', 'self_edit' => 'Sie dürfen sich nicht selbst bearbeiten.', diff --git a/lang/de/app.php b/lang/de/app.php index 602aefccd..3612a152d 100644 --- a/lang/de/app.php +++ b/lang/de/app.php @@ -42,6 +42,7 @@ 'no_room_access' => 'Sie haben nicht die notwendigen Rechte, den Raum zu bearbeiten.', 'no_server_available' => 'Zur Zeit sind keine Server verfügbar.', 'not_member_of_room' => 'Die Person ist nicht (mehr) Mitglied dieses Raums.', + 'role_not_member_of_room' => 'Diese Rolle ist nicht (mehr) Mitglied dieses Raums.', 'not_running' => 'Der Beitritt zum Raum ist fehlgeschlagen, da er derzeit geschlossen ist.', 'record_agreement_missing' => 'Die Zustimmung zur Aufzeichnung ist erforderlich.', 'role_delete_linked_users' => 'Die Rolle ist mit Benutzern verknüpft und kann deshalb nicht gelöscht werden!', diff --git a/lang/de/rooms.php b/lang/de/rooms.php index bb3667e96..a985e3922 100644 --- a/lang/de/rooms.php +++ b/lang/de/rooms.php @@ -246,6 +246,28 @@ ], 'nodata' => 'Keine Mitglieder vorhanden', 'remove_user' => 'Mitglied löschen', + 'role_members' => [ + 'add' => 'Rolle hinzufügen', + 'edit' => 'Rolle bearbeiten', + 'modals' => [ + 'add' => [ + 'add' => 'Hinzufügen', + 'placeholder' => 'Rolle auswählen', + 'title' => 'Rolle als Mitglied hinzufügen', + ], + 'edit' => [ + 'title' => ':name bearbeiten', + ], + 'remove' => [ + 'confirm' => 'Möchten Sie die Rolle :name aus diesem Raum entfernen?', + 'title' => 'Rolle aus dem Raum entfernen', + ], + ], + 'nodata' => 'Keine Rollen mit diesem Raum geteilt', + 'remove' => 'Rolle entfernen', + 'title' => 'Rollenmitglieder', + 'user_count' => ':count Benutzer', + ], 'title' => 'Mitglieder', ], 'modals' => [ diff --git a/lang/de/validation.php b/lang/de/validation.php index b52470847..82e3abc16 100644 --- a/lang/de/validation.php +++ b/lang/de/validation.php @@ -227,6 +227,7 @@ ], 'room' => [ 'already_member' => 'Der Benutzer ist bereits Mitglied des Raums.', + 'role_already_member' => 'Diese Rolle ist bereits Mitglied des Raums.', 'not_member' => 'Der Benutzer ":firstname :lastname" ist nicht Mitglied des Raums.', 'self_delete' => 'Sie dürfen sich nicht selbst löschen.', 'self_edit' => 'Sie dürfen sich nicht selbst bearbeiten.', diff --git a/lang/en/app.php b/lang/en/app.php index f50528ac5..664253d41 100644 --- a/lang/en/app.php +++ b/lang/en/app.php @@ -42,6 +42,7 @@ 'no_room_access' => 'You does not have the necessary permissions, to edit this room.', 'no_server_available' => 'Currently there are no servers available.', 'not_member_of_room' => 'The person is not a member of this room (anymore).', + 'role_not_member_of_room' => 'This role is not a member of this room (anymore).', 'not_running' => 'Joining the room has failed as it is currently closed.', 'record_agreement_missing' => 'Consent to the recording is required.', 'role_delete_linked_users' => 'The role is linked to users and therefore it can\'t be deleted!', diff --git a/lang/en/rooms.php b/lang/en/rooms.php index 142dd9db0..ccfba3400 100644 --- a/lang/en/rooms.php +++ b/lang/en/rooms.php @@ -246,6 +246,28 @@ ], 'nodata' => 'No members available', 'remove_user' => 'Remove member', + 'role_members' => [ + 'add' => 'Add role', + 'edit' => 'Edit role', + 'modals' => [ + 'add' => [ + 'add' => 'Add', + 'placeholder' => 'Select a role', + 'title' => 'Add role as member', + ], + 'edit' => [ + 'title' => 'Edit :name', + ], + 'remove' => [ + 'confirm' => 'Do you want to remove the role :name from this room?', + 'title' => 'Remove role from this room', + ], + ], + 'nodata' => 'No roles shared with this room', + 'remove' => 'Remove role', + 'title' => 'Role members', + 'user_count' => ':count users', + ], 'title' => 'Members', ], 'modals' => [ diff --git a/lang/en/validation.php b/lang/en/validation.php index 8022749dd..e0f0a9c73 100644 --- a/lang/en/validation.php +++ b/lang/en/validation.php @@ -227,6 +227,7 @@ ], 'room' => [ 'already_member' => 'The user is already member of the room.', + 'role_already_member' => 'This role is already a member of the room.', 'not_member' => 'The user ":firstname :lastname" isn\'t a member.', 'self_delete' => 'The user is not allowed to delete himself.', 'self_edit' => 'The user is not allowed to edit himself.', diff --git a/lang/fa/app.php b/lang/fa/app.php index 543f71838..5a88b9ba3 100644 --- a/lang/fa/app.php +++ b/lang/fa/app.php @@ -42,6 +42,7 @@ 'no_room_access' => 'شما مجوزهای لازم برای ویرایش این اتاق را ندارید.', 'no_server_available' => 'در حال حاضر هیچ سروری در دسترس نیست.', 'not_member_of_room' => 'این شخص عضو این اتاق نیست (یا دیگر نیست).', + 'role_not_member_of_room' => 'این نقش عضو این اتاق نیست (یا دیگر نیست).', 'not_running' => 'ورود به اتاق ناموفق بود زیرا در حال حاضر بسته است.', 'record_agreement_missing' => 'رضایت به ضبط مورد نیاز است.', 'role_delete_linked_users' => 'نقش به کاربران پیوند خورده است و بنابراین نمیتوان آن را حذف کرد!', diff --git a/lang/fa/rooms.php b/lang/fa/rooms.php index aa920f6d0..8db159baa 100644 --- a/lang/fa/rooms.php +++ b/lang/fa/rooms.php @@ -234,6 +234,28 @@ ], 'nodata' => 'هیچ عضوی موجود نیست', 'remove_user' => 'حذف عضو', + 'role_members' => [ + 'add' => 'افزودن نقش', + 'edit' => 'ویرایش نقش', + 'modals' => [ + 'add' => [ + 'add' => 'افزودن', + 'placeholder' => 'انتخاب نقش', + 'title' => 'افزودن نقش به عنوان عضو', + ], + 'edit' => [ + 'title' => 'ویرایش :name', + ], + 'remove' => [ + 'confirm' => 'آیا میخواهید نقش :name را از این اتاق حذف کنید؟', + 'title' => 'حذف نقش از این اتاق', + ], + ], + 'nodata' => 'هیچ نقشی با این اتاق به اشتراک گذاشته نشده است', + 'remove' => 'حذف نقش', + 'title' => 'اعضای نقش', + 'user_count' => ':count کاربر', + ], 'title' => 'اعضا', ], 'modals' => [ diff --git a/lang/fa/validation.php b/lang/fa/validation.php index f635a80dc..47e43efba 100644 --- a/lang/fa/validation.php +++ b/lang/fa/validation.php @@ -222,6 +222,7 @@ ], 'room' => [ 'already_member' => 'کاربر قبلاً عضو اتاق است.', + 'role_already_member' => 'این نقش قبلاً عضو اتاق است.', 'not_member' => 'کاربر ":firstname :lastname" عضو نیست.', 'self_delete' => 'کاربر مجاز به حذف خود نیست.', 'self_edit' => 'کاربر مجاز به ویرایش خود نیست.', diff --git a/lang/fr/app.php b/lang/fr/app.php index 7f515beed..9f093e358 100644 --- a/lang/fr/app.php +++ b/lang/fr/app.php @@ -42,6 +42,7 @@ 'no_room_access' => 'Vous ne disposez pas des autorisations nécessaires pour modifier cette salle.', 'no_server_available' => 'Actuellement, il n\'y a pas de serveurs disponibles.', 'not_member_of_room' => 'Cette personne n\'est pas (plus) membre de cette salle.', + 'role_not_member_of_room' => 'Ce rôle n\'est pas (plus) membre de cette salle.', 'not_running' => 'Accès échoué ! La salle est actuellement fermée.', 'record_agreement_missing' => 'Le consentement à l\'enregistrement est requis.', 'role_delete_linked_users' => 'Ce rôle est associé à certains utilisateurs et ne peut donc pas être supprimé !', diff --git a/lang/fr/rooms.php b/lang/fr/rooms.php index ca6da2d7c..f33ece837 100644 --- a/lang/fr/rooms.php +++ b/lang/fr/rooms.php @@ -246,6 +246,28 @@ ], 'nodata' => 'Aucun membre disponible', 'remove_user' => 'Enlever le membre', + 'role_members' => [ + 'add' => 'Ajouter un rôle', + 'edit' => 'Modifier le rôle', + 'modals' => [ + 'add' => [ + 'add' => 'Ajouter', + 'placeholder' => 'Sélectionner un rôle', + 'title' => 'Ajouter un rôle en tant que membre', + ], + 'edit' => [ + 'title' => 'Modifier :name', + ], + 'remove' => [ + 'confirm' => 'Voulez-vous retirer le rôle :name de cette salle ?', + 'title' => 'Retirer le rôle de cette salle', + ], + ], + 'nodata' => 'Aucun rôle partagé avec cette salle', + 'remove' => 'Retirer le rôle', + 'title' => 'Membres par rôle', + 'user_count' => ':count utilisateurs', + ], 'title' => 'Membres', ], 'modals' => [ diff --git a/lang/fr/validation.php b/lang/fr/validation.php index a6481a18f..0b6ede050 100644 --- a/lang/fr/validation.php +++ b/lang/fr/validation.php @@ -227,6 +227,7 @@ ], 'room' => [ 'already_member' => 'L\'utilisateur est déjà membre de la salle.', + 'role_already_member' => 'Ce rôle est déjà membre de la salle.', 'not_member' => 'L\'utilisateur ":firstname :lastname" n\'est pas membre.', 'self_delete' => 'L\'utilisateur n\'est pas autorisé à s\'auto-enlever.', 'self_edit' => 'L\'utilisateur n\'est pas autorisé à s\'auto-modifier.', diff --git a/resources/js/components/RoomTabMembers.vue b/resources/js/components/RoomTabMembers.vue index 184c344b0..4c2229647 100644 --- a/resources/js/components/RoomTabMembers.vue +++ b/resources/js/components/RoomTabMembers.vue @@ -266,6 +266,14 @@ + + +