-
Notifications
You must be signed in to change notification settings - Fork 1
User profile and PDF export of memberships #42
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 10 commits
4d22a78
19c8c1b
58a7dbc
0e7b81e
b5b23ef
8d57d4e
7704b76
30dea9c
ffca066
0b96e6f
0223da5
664ef00
a0ef3b9
8277e4c
8c39e85
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,10 +19,18 @@ class Profile extends Component | |
| #[Rule('string|required')] | ||
| public string $fullName; | ||
|
|
||
| public function mount() | ||
| public $currentUsername; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Vorsicht! Public probs können via client api frei verändert werden, auch wenn du da kein Input feld hin machst. Macht das hier Probleme? Evtl das [#Url] oder [#Locked] Prop verwenden stattdessen |
||
|
|
||
| public function mount($username) | ||
| { | ||
| $username = Auth::user()->username; | ||
| $user = User::findOrFailByUsername($username); | ||
| if ($username == auth()->user()->username || auth()->user()->can('superadmin', User::class)) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. s.o. |
||
| $this->currentUsername = $username; | ||
| } elseif ($username == auth()->user()->username) { | ||
| $this->currentUsername = auth()->user()->username; | ||
| } else { | ||
| abort('403'); | ||
| } | ||
| $user = User::findOrFailByUsername($this->currentUsername); | ||
| $this->uid = $user->getFirstAttribute('uid'); | ||
| $this->fullName = $user->getFirstAttribute('cn'); | ||
| $this->email = $user->getFirstAttribute('mail'); | ||
|
|
@@ -36,9 +44,6 @@ public function render() | |
| public function save() | ||
| { | ||
| $this->validate(); | ||
| if (Auth::user()->username !== $this->uid) { | ||
| abort('500'); | ||
| } | ||
| $user = User::findOrFailByUsername($this->uid); | ||
| $user->setAttribute('mail', $this->email); | ||
| $user->setAttribute('cn', $this->fullName); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| <?php | ||
|
|
||
| namespace App\Livewire\Profile; | ||
|
|
||
| use App\Ldap\User; | ||
| use App\Ldap\Role; | ||
| use App\Models\RoleMembership; | ||
| use Barryvdh\DomPDF\Facade\Pdf; | ||
| use Livewire\Component; | ||
|
|
||
| class Memberships extends Component | ||
| { | ||
| public $currentUsername; | ||
| public bool $showOnlyActive = true; | ||
|
|
||
| public function mount($username) | ||
| { | ||
| if ($username == auth()->user()->username || auth()->user()->can('superadmin', User::class)) { | ||
| $this->currentUsername = $username; | ||
| } elseif ($username == auth()->user()->username) { | ||
| $this->currentUsername = auth()->user()->username; | ||
| } else { | ||
| abort('403'); | ||
| } | ||
| } | ||
|
|
||
| public function getMemberships(string $username, bool $onlyActive) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. public Methoden können bei livewire auch vom client aus aufgerufen werden. Wenn du das nicht machst (wie hier?) solltest du die private machen
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moderator:innen einer Studischaft haben keinen Zugriff auf den Profil-Bereich. Damit Moderator:innen trotzdem die Möglichkeit haben, die Tätigkeitsübersicht zu exportieren und es möglichst wenig Code-Dopplung gibt, greift auch der Members-Controller auf diese Funktion zu. |
||
| { | ||
| $query = RoleMembership::where('username', $username); | ||
| if ($onlyActive) { | ||
| $query->whereNull('until'); | ||
| } | ||
| $roleMemberships = $query->get(); | ||
| $memberships = []; | ||
| foreach ($roleMemberships as $row) { | ||
| $role = Role::findOrFail('cn=' . $row->role_cn . ',' . $row->committee_dn); | ||
| array_push($memberships, [ | ||
|
||
| 'role' => $role, | ||
| 'from' => $row->from, | ||
| 'until' => $row->until, | ||
| 'decided' => $row->decided, | ||
| 'comment' => $row->comment, | ||
| ]); | ||
| } | ||
| return $memberships; | ||
| } | ||
|
|
||
| public function render() | ||
| { | ||
| $memberships = $this->getMemberships($this->currentUsername, $this->showOnlyActive); | ||
|
|
||
| return view('livewire.profile.memberships', [ | ||
| 'memberships' => $memberships, | ||
| ])->title(__('Profile')); | ||
| } | ||
|
|
||
| public function exportPdf() | ||
| { | ||
| $memberships = $this->getMemberships($this->currentUsername, false); | ||
| $user = User::findOrFailByUsername($this->currentUsername); | ||
| $pdf = Pdf::loadView('pdfs.memberships', [ | ||
| 'fullName' => $user->cn[0], | ||
| 'community' => null, | ||
| 'memberships' => $memberships, | ||
| ]); | ||
|
|
||
| return response()->streamDownload(function () use ($pdf) { | ||
| echo $pdf->stream(); | ||
| }, 'memberships-' . $this->currentUsername . '.pdf');; | ||
|
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,7 @@ | |
|
|
||
| use App\Ldap\Community; | ||
| use App\Ldap\User; | ||
| use Barryvdh\DomPDF\Facade\Pdf; | ||
| use Livewire\Attributes\Rule; | ||
| use Livewire\Attributes\Url; | ||
| use Livewire\Component; | ||
|
|
@@ -87,4 +88,20 @@ public function close(): void | |
| $this->showDeleteModal = false; | ||
| unset($this->deleteMemberName, $this->deleteMemberUsername); | ||
| } | ||
|
|
||
| public function exportPdf($username) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sehe ich das Richtig, das es einmal das PDF pro Person und das andere mal das PDF pro Rolle / Realm ist?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Falls du dich auf den Zugriff aus unterschiedlichen Komponenten auf diese Funktion beziehst, dann ist der einzige Unterschied, dass im Profil kein Realm mitgegeben werden kann, da die Nutzer nicht nur einem einzigen Realm zugeordnet sein können. Beim Export über die Mitglieder-Liste eines Realms kann der aktuelle Realm für den PDF-Export mitgegeben werden, sodass auch die Studischaft im Dokument angezeigt werden kann. |
||
| { | ||
| $memberships = app('App\Livewire\Profile\Memberships')->getMemberships($username, false); | ||
| $user = User::findOrFailByUsername($username); | ||
| $community = Community::findOrFailByUid($this->community_name); | ||
| $pdf = Pdf::loadView('pdfs.memberships', [ | ||
| 'fullName' => $user->cn[0], | ||
| 'community' => $community->description[0], | ||
| 'memberships' => $memberships, | ||
| ]); | ||
|
|
||
| return response()->streamDownload(function () use ($pdf) { | ||
| echo $pdf->stream(); | ||
| }, 'memberships-' . $username . '.pdf');; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| <?php | ||
|
|
||
| return [ | ||
| 'activity' => 'Tätigkeit', | ||
| 'activities' => 'Tätigkeiten', | ||
| 'comment' => 'Bemerkung', | ||
| 'committee' => 'Gremium', | ||
| 'decision' => 'Beschluss', | ||
| 'exportAsPdf' => 'Als PDF exportieren', | ||
| 'from' => 'von', | ||
| 'in' => 'in', | ||
| 'memberships' => 'Mitgliedschaften', | ||
| 'membershipsAsPdf' => 'Mitgliedschaften als PDF', | ||
| 'role' => 'Rolle', | ||
| 'showOnlyActiveMemberships' => 'Zeige nur aktive Mitgliedschaften', | ||
| 'today' => 'heute', | ||
| 'until' => 'bis', | ||
| ]; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| <?php | ||
|
|
||
| return [ | ||
| 'activity' => 'Activity', | ||
| 'activities' => 'Activities', | ||
| 'comment' => 'Comment', | ||
| 'committee' => 'Committee', | ||
| 'decision' => 'Decision', | ||
| 'exportAsPdf' => 'Export as PDF', | ||
| 'from' => 'from', | ||
| 'in' => 'in', | ||
| 'memberships' => 'Memberships', | ||
| 'membershipsAsPdf' => 'Memberships as PDF', | ||
| 'role' => 'Role', | ||
| 'showOnlyActiveMemberships' => 'Show only active memberships', | ||
| 'today' => 'today', | ||
| 'until' => 'until', | ||
| ]; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| <div> | ||
| <div class="w-full"> | ||
| <div class="mb-4 -mx-6 -mt-6 px-6 flex border-b border-zinc-200 gap-3"> | ||
| <a wire:navigate href="{{ route('profile', ['username' => $currentUsername]) }}" class="inline-flex items-center gap-x-1.5 px-2 pt-4 pb-3 border-b-2 border-transparent font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out">{{ __('Profile') }}</a> | ||
| <a wire:navigate href="{{ route('profile.memberships', ['username' => $currentUsername]) }}" class="inline-flex items-center gap-x-1.5 px-2 pt-4 pb-3 border-b-2 border-indigo-400 font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-indigo-400 focus:outline-none focus:text-gray-700 focus:border-indigo-400 transition duration-150 ease-in-out">{{ __('profile.memberships') }}</a> | ||
| <a wire:navigate href="{{ route('password.change', ['username' => $currentUsername]) }}" class="inline-flex items-center gap-x-1.5 px-2 pt-4 pb-3 border-b-2 border-transparent font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out">{{ __('Change Password') }}</a> | ||
| </div> | ||
| </div> | ||
|
|
||
| <div> | ||
| <div class="mt-6 flex"> | ||
| <div class="ml-auto"> | ||
| <x-button.primary wire:click="exportPdf">{{ __('profile.exportAsPdf') }}</x-button.primary> | ||
| </div> | ||
| </div> | ||
| <div class="mt-6 mb-6"> | ||
| <input id="showOnlyActive" type="checkbox" wire:model.change="showOnlyActive" class="rounded-sm border-indigo-400"> | ||
| <label for="showOnlyActive" class="ml-4">{{ __('profile.showOnlyActiveMemberships') }}</label> | ||
| </div> | ||
| <x-table> | ||
| <x-slot name="head"> | ||
| <x-table.heading> | ||
| {{ __('profile.role') }} | ||
| </x-table.heading> | ||
| <x-table.heading> | ||
| {{ __('profile.committee') }} | ||
| </x-table.heading> | ||
| <x-table.heading> | ||
| {{ __('profile.from') }} | ||
| </x-table.heading> | ||
| <x-table.heading> | ||
| {{ __('profile.until') }} | ||
| </x-table.heading> | ||
| <x-table.heading> | ||
| {{ __('profile.decision') }} | ||
| </x-table.heading> | ||
| <x-table.heading> | ||
| {{ __('profile.comment') }} | ||
| </x-table.heading> | ||
| </x-slot> | ||
| @forelse($memberships as $row) | ||
| <x-table.row> | ||
| <x-table.cell> | ||
| {{ $row['role']->getFirstAttribute('description') }} | ||
| </x-table.cell> | ||
| <x-table.cell> | ||
| {{ $row['role']->committee()->getFirstAttribute('description') }} | ||
| </x-table.cell> | ||
| <x-table.cell> | ||
| {{ \Carbon\Carbon::parse($row['from'])->format('Y-m-d') }} | ||
| </x-table.cell> | ||
| <x-table.cell> | ||
| @if ($row['until'] != '') | ||
| {{ \Carbon\Carbon::parse($row['until'])->format('Y-m-d') }} | ||
| @else | ||
| {{ __('profile.today') }} | ||
| @endif | ||
| </x-table.cell> | ||
| <x-table.cell> | ||
| {{ \Carbon\Carbon::parse($row['decided'])->format('Y-m-d') }} | ||
| </x-table.cell> | ||
| <x-table.cell> | ||
| {{ $row['comment'] }} | ||
| </x-table.cell> | ||
| </x-table.row> | ||
| @empty | ||
| <x-table.row> | ||
| <x-table.cell colspan="4"> | ||
| <div class="flex justify-center item-center"> | ||
| <span class="text-gray-400 text-xl py-2 font-medium">{{ __('groups.no_roles_found') }}</span> | ||
| </div> | ||
| </x-table.cell> | ||
| </x-table.row> | ||
| @endforelse | ||
| </x-table> | ||
| </div> | ||
| </div> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,14 +32,31 @@ | |
| </x-slot> | ||
| @forelse($realm_members as $realm_member) | ||
| <x-table.row> | ||
| <x-table.cell>{{ $realm_member->cn[0] }}</x-table.cell> | ||
| <x-table.cell> | ||
| @if (auth()->user()->can('superadmin', \App\Ldap\User::class)) | ||
| <a wire:navigate href="{{ route('profile', ['username' => $realm_member->uid[0]]) }}"> | ||
|
||
| {{ $realm_member->cn[0] }} | ||
| </a> | ||
| @else | ||
| {{ $realm_member->cn[0] }} | ||
| @endif | ||
| </x-table.cell> | ||
| <x-table.cell>{{ $realm_member->uid[0] }}</x-table.cell> | ||
| <x-table.cell> | ||
| <x-button.link-danger | ||
| icon-leading="fas-triangle-exclamation" | ||
| :disabled="auth()->user()->cannot('remove_member', $community)" | ||
| wire:click="deletePrepare('{{ $realm_member->uid[0] }}')">{{ __('Remove Member') }} | ||
| </x-button.link-danger> | ||
| <div class="flex gap-3"> | ||
| <x-button.link-primary | ||
| wire:click="exportPdf('{{ $realm_member->uid[0] }}')" | ||
| :disabled="auth()->user()->cannot('edit', $community)" | ||
| class="ml-auto" | ||
| > | ||
| {{ __('profile.membershipsAsPdf') }} | ||
| </x-button.link-primary> | ||
| <x-button.link-danger | ||
| icon-leading="fas-triangle-exclamation" | ||
| :disabled="auth()->user()->cannot('remove_member', $community)" | ||
| wire:click="deletePrepare('{{ $realm_member->uid[0] }}')">{{ __('Remove Member') }} | ||
| </x-button.link-danger> | ||
| </div> | ||
| </x-table.cell> | ||
| </x-table.row> | ||
| @empty | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
=== statt ==
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in Livewire kann man auch $this->authorize() verwenden, dann liefert es aber auch direkt ein Permission denied wenns schief geht. Ich glaube ich fände es schöner eine Policy für PW Changes zu haben und auf die hier zu authorizen. Dann ist wer darf was schön an einem Platz (bei den Policies). Kannst du beim User mit rein nehmen.