diff --git a/app/Core/LogsActivity.php b/app/Core/LogsActivity.php index 26811e91..4314b0fb 100644 --- a/app/Core/LogsActivity.php +++ b/app/Core/LogsActivity.php @@ -22,7 +22,7 @@ public function getActivitylogOptions(): LogOptions ->logOnly($this->getFillable()) ->setDescriptionForEvent(fn(string $eventName) => new HtmlString( '
' - . auth()->user()->name . " " . $eventName . " " . $this->fromCamelCase((new \ReflectionClass($this))->getShortName()) . " " . $this + . (auth()->user()->name ?? '') . " " . $eventName . " " . $this->fromCamelCase((new \ReflectionClass($this))->getShortName()) . " " . $this . ' ' . __('See details') . '' . '
' )); diff --git a/app/Http/Livewire/Administration/Companies.php b/app/Http/Livewire/Administration/Companies.php new file mode 100644 index 00000000..2f82beea --- /dev/null +++ b/app/Http/Livewire/Administration/Companies.php @@ -0,0 +1,184 @@ +user()->can('View own companies') && !auth()->user()->can('View all companies')) { + $query->where('responsible_id', auth()->user()->id); + } elseif (!auth()->user()->can('View all companies')) { + // Get empty list + $query->whereNull('id'); + } + return $query; + } + + /** + * Table definition + * + * @return array + */ + protected function getTableColumns(): array + { + return [ + ImageColumn::make('logo') + ->label(__('Logo')) + ->height(30), + + TextColumn::make('name') + ->label(__('Company name')) + ->searchable() + ->sortable(), + + UserColumn::make('responsible') + ->label(__('Responsible')) + ->searchable() + ->sortable(), + + BooleanColumn::make('is_disabled') + ->label(__('Company activated')) + ->trueIcon('heroicon-o-x-circle') + ->falseIcon('heroicon-o-check-circle') + ->trueColor('danger') + ->falseColor('success') + ->searchable() + ->sortable(), + + TagsColumn::make('users.name') + ->label(__('Company users')) + ->limit(1) + ->searchable() + ->sortable(), + + TextColumn::make('created_at') + ->label(__('Created at')) + ->sortable() + ->searchable() + ->dateTime(), + ]; + } + + /** + * Table actions definition + * + * @return array + */ + protected function getTableActions(): array + { + return [ + Action::make('edit') + ->icon('heroicon-o-pencil') + ->link() + ->label(__('Edit company')) + ->visible(fn () => auth()->user()->can('Update companies')) + ->action(fn(Company $record) => $this->updateCompany($record->id)) + ]; + } + + /** + * Table default sort column definition + * + * @return string|null + */ + protected function getDefaultTableSortColumn(): ?string + { + return 'created_at'; + } + + /** + * Table default sort direction definition + * + * @return string|null + */ + protected function getDefaultTableSortDirection(): ?string + { + return 'desc'; + } + + /** + * Show update company dialog + * + * @param $id + * @return void + */ + public function updateCompany($id) + { + $this->selectedCompany = Company::find($id); + $this->dispatchBrowserEvent('toggleCompanyModal'); + } + + /** + * Show create company dialog + * + * @return void + */ + public function createCompany() + { + $this->selectedCompany = new Company(); + $this->dispatchBrowserEvent('toggleCompanyModal'); + } + + /** + * Cancel and close company create / update dialog + * + * @return void + */ + public function cancelCompany() + { + $this->selectedCompany = null; + $this->dispatchBrowserEvent('toggleCompanyModal'); + } + + /** + * Event launched after a company is created / updated + * + * @return void + */ + public function companySaved() + { + $this->cancelCompany(); + } + + /** + * Event launched after a company is deleted + * + * @return void + */ + public function companyDeleted() + { + $this->companySaved(); + } +} diff --git a/app/Http/Livewire/Administration/CompaniesDialog.php b/app/Http/Livewire/Administration/CompaniesDialog.php new file mode 100644 index 00000000..8eec6a5b --- /dev/null +++ b/app/Http/Livewire/Administration/CompaniesDialog.php @@ -0,0 +1,217 @@ +form->fill([ + 'name' => $this->company->name, + 'logo' => $this->company->logo, + 'description' => $this->company->description, + 'is_disabled' => $this->company->is_disabled, + 'responsible_id' => $this->company->responsible_id, + 'users' => $this->company->users->pluck('id')->toArray() + ]); + } + + + public function render() + { + return view('livewire.administration.companies-dialog'); + } + + /** + * Form schema definition + * + * @return array + */ + protected function getFormSchema(): array + { + return [ + + Grid::make(5) + ->schema([ + + Grid::make(1) + ->columnSpan(2) + ->schema([ + FileUpload::make('logo') + ->image() + ->maxSize(10240) + ->label(__('Logo')), + ]), + + Grid::make(1) + ->columnSpan(3) + ->schema([ + + TextInput::make('name') + ->label(__('Company name')) + ->maxLength(255) + ->unique(table: Company::class, column: 'name', ignorable: fn() => $this->company, callback: function (Unique $rule) { + return $rule->withoutTrashed(); + }) + ->required(), + + Select::make('responsible_id') + ->label(__('Responsible')) + ->searchable() + ->required() + ->options(User::all()->pluck('name', 'id')->toArray()), + ]), + + ]), + + RichEditor::make('description') + ->label(__('Description')) + ->fileAttachmentsDisk(config('filesystems.default')) + ->fileAttachmentsDirectory('companies') + ->fileAttachmentsVisibility('private'), + + Toggle::make('is_disabled') + ->label(__('Disable access to this company')), + + MultiSelect::make('users') + ->label(__('Company users')) + ->options(User::all()->pluck('name', 'id')->toArray()) + ]; + } + + /** + * Create / Update the company + * + * @return void + */ + public function save(): void + { + $data = $this->form->getState(); + if (!$this->company?->id) { + $company = Company::create([ + 'name' => $data['name'], + 'logo' => $data['logo'] ?? null, + 'description' => $data['description'] ?? null, + 'is_disabled' => $data['is_disabled'] ?? false, + 'responsible_id' => $data['responsible_id'], + ]); + foreach ($data['users'] as $user) { + CompanyUser::create([ + 'company_id' => $company->id, + 'user_id' => $user + ]); + } + Notification::make() + ->success() + ->title(__('Company created')) + ->body(__('The company has been created')) + ->send(); + } else { + $this->company->name = $data['name']; + $this->company->description = $data['description']; + $this->company->logo = $data['logo']; + $this->company->is_disabled = $data['is_disabled']; + $this->company->responsible_id = $data['responsible_id']; + $this->company->save(); + CompanyUser::where('company_id', $this->company->id)->delete(); + foreach ($data['users'] as $user) { + CompanyUser::create([ + 'company_id' => $this->company->id, + 'user_id' => $user + ]); + } + Notification::make() + ->success() + ->title(__('Company updated')) + ->body(__("The company's details has been updated")) + ->send(); + } + $this->emit('companySaved'); + } + + /** + * Delete an existing company + * + * @return void + */ + public function doDeleteCompany(): void + { + $this->company->delete(); + $this->deleteConfirmationOpened = false; + $this->emit('companyDeleted'); + Notification::make() + ->success() + ->title(__('Company deleted')) + ->body(__('The company has been deleted')) + ->send(); + } + + /** + * Cancel the deletion of a company + * + * @return void + */ + public function cancelDeleteCompany(): void + { + $this->deleteConfirmationOpened = false; + } + + /** + * Show the delete company confirmation dialog + * + * @return void + * @throws \Exception + */ + public function deleteCompany(): void + { + $this->deleteConfirmationOpened = true; + Notification::make() + ->warning() + ->title(__('Company deletion')) + ->body(__('Are you sure you want to delete this company?')) + ->actions([ + Action::make('confirm') + ->label(__('Confirm')) + ->color('danger') + ->button() + ->close() + ->emit('doDeleteCompany'), + Action::make('cancel') + ->label(__('Cancel')) + ->close() + ->emit('cancelDeleteCompany') + ]) + ->persistent() + ->send(); + } +} diff --git a/app/Http/Livewire/Administration/Users.php b/app/Http/Livewire/Administration/Users.php index 6db099f5..0d5642fd 100644 --- a/app/Http/Livewire/Administration/Users.php +++ b/app/Http/Livewire/Administration/Users.php @@ -2,15 +2,19 @@ namespace App\Http\Livewire\Administration; +use App\Models\Company; use App\Models\User; use App\Notifications\UserCreatedNotification; +use Filament\Forms\Components\TagsInput; use Filament\Notifications\Notification; use Filament\Tables\Actions\Action; use Filament\Tables\Columns\BadgeColumn; use Filament\Tables\Columns\BooleanColumn; +use Filament\Tables\Columns\TagsColumn; use Filament\Tables\Columns\TextColumn; use Filament\Tables\Concerns\InteractsWithTable; use Filament\Tables\Contracts\HasTable; +use Filament\Tables\Filters\SelectFilter; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Relations\Relation; use Livewire\Component; @@ -35,7 +39,18 @@ public function render() */ protected function getTableQuery(): Builder|Relation { - return User::query(); + $query = User::query(); + if (auth()->user()->can('View company users') && !auth()->user()->can('View all users')) { + $query->whereHas('companies', fn ($query) => + $query->whereIn('companies.id', + auth()->user()->ownCompanies->pluck('id')->toArray() + ) + ); + } elseif (!auth()->user()->can('View all users')) { + // Get empty list + $query->whereNull('id'); + } + return $query; } /** @@ -51,18 +66,24 @@ protected function getTableColumns(): array ->searchable() ->sortable(), - BadgeColumn::make('role') - ->label(__('Role')) - ->searchable() - ->sortable() - ->enum(roles_list()) - ->colors(roles_list_badges()), - BooleanColumn::make('isAccountActivated') ->label(__('Account activated')) ->sortable() ->searchable(), + TagsColumn::make('permissions.name') + ->label(__('Permissions')) + ->limit(1) + ->visible(fn () => auth()->user()->can('Assign permissions')) + ->searchable() + ->sortable(), + + TagsColumn::make('companies.name') + ->label(__('Companies')) + ->limit(1) + ->searchable() + ->sortable(), + TextColumn::make('created_at') ->label(__('Created at')) ->sortable() @@ -84,13 +105,14 @@ protected function getTableActions(): array ->link() ->color('warning') ->label(__('Resend activation email')) - ->visible(fn(User $record) => $record->register_token) + ->visible(fn(User $record) => $record->register_token && auth()->user()->can('Update users')) ->action(fn(User $record) => $this->resendActivationEmail($record->id)), Action::make('edit') ->icon('heroicon-o-pencil') ->link() ->label(__('Edit user')) + ->visible(fn () => auth()->user()->can('Update users')) ->action(fn(User $record) => $this->updateUser($record->id)) ]; } @@ -115,6 +137,32 @@ protected function getDefaultTableSortDirection(): ?string return 'desc'; } + /** + * Table filters definition + * + * @return array + */ + protected function getTableFilters(): array + { + return [ + SelectFilter::make('isAccountActivated') + ->label(__('Account activated')) + ->placeholder(__('All users')) + ->options([ + 'yes' => __('Yes'), + 'no' => __('No'), + ]) + ->query(function ($state, $query) { + if ($state['value'] === 'yes') { + $query->whereNull('register_token'); + } + if ($state['value'] === 'no') { + $query->whereNotNull('register_token'); + } + }) + ]; + } + /** * Show update user dialog * diff --git a/app/Http/Livewire/Administration/UsersDialog.php b/app/Http/Livewire/Administration/UsersDialog.php index 89666ad8..7a0992c6 100644 --- a/app/Http/Livewire/Administration/UsersDialog.php +++ b/app/Http/Livewire/Administration/UsersDialog.php @@ -2,16 +2,28 @@ namespace App\Http\Livewire\Administration; +use App\Models\Company; +use App\Models\CompanyUser; use App\Models\User; use App\Notifications\UserCreatedNotification; +use Filament\Forms\Components\Checkbox; +use Filament\Forms\Components\CheckboxList; +use Filament\Forms\Components\Grid; +use Filament\Forms\Components\MultiSelect; +use Filament\Forms\Components\Placeholder; +use Filament\Forms\Components\Radio; use Filament\Forms\Components\Select; +use Filament\Forms\Components\TagsInput; use Filament\Forms\Components\TextInput; use Filament\Forms\Concerns\InteractsWithForms; use Filament\Forms\Contracts\HasForms; use Filament\Notifications\Actions\Action; use Filament\Notifications\Notification; +use Illuminate\Support\HtmlString; use Livewire\Component; use Ramsey\Uuid\Uuid; +use Spatie\Permission\Models\Permission; +use Closure; class UsersDialog extends Component implements HasForms { @@ -22,11 +34,15 @@ class UsersDialog extends Component implements HasForms protected $listeners = ['doDeleteUser', 'cancelDeleteUser']; - public function mount(): void { + public array $permissions; + + public function mount(): void + { $this->form->fill([ 'name' => $this->user->name, 'email' => $this->user->email, - 'role' => $this->user->role, + 'locale' => $this->user->locale ?? config('app.locale'), + 'permissions' => $this->user->permissions->pluck('id')->toArray(), ]); } @@ -44,56 +60,148 @@ public function render() protected function getFormSchema(): array { return [ - TextInput::make('name') - ->label(__('Full name')) - ->maxLength(255) - ->required(), - - TextInput::make('email') - ->label(__('Email address')) - ->email() - ->unique(table: User::class, column: 'email', ignorable: fn () => $this->user->id ? $this->user : null) - ->required(), - - Select::make('role') - ->label(__('Role')) - ->required() - ->searchable() - ->options(roles_list()) + Grid::make() + ->schema([ + TextInput::make('name') + ->label(__('Full name')) + ->maxLength(255) + ->required(), + + TextInput::make('email') + ->label(__('Email address')) + ->email() + ->unique(table: User::class, column: 'email', ignorable: fn() => $this->user->id ? $this->user : null) + ->required(), + ]), + + Grid::make(2) + ->schema([ + Radio::make('locale') + ->label(__('Default language')) + ->options(locales()) + ->columnSpan(2) + ->required(), + + Select::make('company') + ->label(__('Company')) + ->columnSpan(1) + ->visible(fn() => !$this->user?->id && (auth()->user()->can('View own companies') && !auth()->user()->can('View all companies'))) + ->options(fn() => auth()->user()->ownCompanies->pluck('name', 'id')->toArray()), + ]), + + Grid::make() + ->extraAttributes([ + 'class' => 'border-t border-gray-200 pt-5 mt-5' + ]) + ->schema([ + Select::make('same_permissions_as') + ->label(__('Use same permissions of')) + ->helperText(__("Update the permissions of this user based on another user's permissions")) + ->searchable() + ->options(function () { + $query = User::query(); + if (auth()->user()->can('View own companies') && !auth()->user()->can('View all companies')) { + $query->whereHas('companies', fn($query) => $query->whereIn('companies.id', + auth()->user()->ownCompanies->pluck('id')->toArray() + ) + ); + } + return $query->get()->pluck('name', 'id')->toArray(); + }) + ->reactive() + ->afterStateUpdated(function (Closure $set, Closure $get) { + if ($get('same_permissions_as')) { + $user = User::find($get('same_permissions_as')); + $set('permissions', $user->permissions->pluck('id')->toArray()); + } + }) + ]), + + CheckboxList::make('permissions') + ->label(__('Permissions')) + ->hint(new HtmlString(' +
+ + | + +
+ ')) + ->visible(fn() => auth()->user()->can('Assign permissions')) + ->columns(3) + ->options(Permission::orderBy('name')->get()->pluck('name', 'id')->toArray()) ]; } + /** + * Assign all permissions + * + * @return void + */ + public function assignAllPermissions(): void + { + $this->permissions = Permission::all()->pluck('id')->toArray(); + } + + /** + * Remove all assigned permissions + * + * @return void + */ + public function removeAllPermissions(): void + { + $this->permissions = []; + } + /** * Create / Update the user * * @return void */ - public function save(): void { + public function save(): void + { $data = $this->form->getState(); if (!$this->user?->id) { $user = User::create([ 'name' => $data['name'], 'email' => $data['email'], - 'role' => $data['role'], + 'locale' => $data['locale'], 'password' => bcrypt(uniqid()), 'register_token' => Uuid::uuid4()->toString() ]); + $user->syncPermissions($this->permissions); $user->notify(new UserCreatedNotification($user)); Notification::make() ->success() ->title(__('User created')) ->body(__('An email has been sent to the user')) ->send(); + if (isset($data['company'])) { + CompanyUser::create([ + 'user_id' => $user->id, + 'company_id' => $data['company'] + ]); + } } else { $this->user->name = $data['name']; $this->user->email = $data['email']; - $this->user->role = $data['role']; + $this->user->locale = $data['locale']; $this->user->save(); + $this->user->syncPermissions($this->permissions); Notification::make() ->success() ->title(__('User updated')) ->body(__('The user\'s details has been updated')) ->send(); + + if ($this->user->id == auth()->user()->id) { + session()->put('locale', $this->user->locale); + } + if (isset($data['company'])) { + CompanyUser::create([ + 'user_id' => $this->user->id, + 'company_id' => $data['company'] + ]); + } } $this->emit('userSaved'); } @@ -103,7 +211,8 @@ public function save(): void { * * @return void */ - public function doDeleteUser(): void { + public function doDeleteUser(): void + { $this->user->delete(); $this->deleteConfirmationOpened = false; $this->emit('userDeleted'); @@ -119,7 +228,8 @@ public function doDeleteUser(): void { * * @return void */ - public function cancelDeleteUser(): void { + public function cancelDeleteUser(): void + { $this->deleteConfirmationOpened = false; } @@ -129,7 +239,8 @@ public function cancelDeleteUser(): void { * @return void * @throws \Exception */ - public function deleteUser(): void { + public function deleteUser(): void + { $this->deleteConfirmationOpened = true; Notification::make() ->warning() diff --git a/app/Http/Livewire/Analytics.php b/app/Http/Livewire/Analytics.php index fb0b5d67..e8df96a7 100644 --- a/app/Http/Livewire/Analytics.php +++ b/app/Http/Livewire/Analytics.php @@ -55,7 +55,7 @@ private function loadNotAssignedTickets(): void private function loadTicketsAssignments(): void { $query = Ticket::query(); - if (has_all_permissions(auth()->user(), 'view-own-tickets') && !has_all_permissions(auth()->user(), 'view-all-tickets')) { + if (auth()->user()->can('View own tickets') && !auth()->user()->can('View all tickets')) { $query->where(function ($query) { $query->where('owner_id', auth()->user()->id) ->orWhere('responsible_id', auth()->user()->id); @@ -78,7 +78,7 @@ private function loadTicketsAssignments(): void private function loadTicketsByStatuses(): void { $query = Ticket::query(); - if (has_all_permissions(auth()->user(), 'view-own-tickets') && !has_all_permissions(auth()->user(), 'view-all-tickets')) { + if (auth()->user()->can('View own tickets') && !auth()->user()->can('View all tickets')) { $query->where(function ($query) { $query->where('owner_id', auth()->user()->id) ->orWhere('responsible_id', auth()->user()->id); diff --git a/app/Http/Livewire/Auth/ActivateAccount.php b/app/Http/Livewire/Auth/ActivateAccount.php index 1def97de..f1e0a85d 100644 --- a/app/Http/Livewire/Auth/ActivateAccount.php +++ b/app/Http/Livewire/Auth/ActivateAccount.php @@ -63,6 +63,7 @@ public function activate(): void $this->user->save(); $this->user->notify(new UserActivatedNotification()); Auth::login($this->user); + session()->put('locale', $this->user->locale); redirect()->to(route('home')); } } diff --git a/app/Http/Livewire/Kanban.php b/app/Http/Livewire/Kanban.php index 4d24157a..b458bdaf 100644 --- a/app/Http/Livewire/Kanban.php +++ b/app/Http/Livewire/Kanban.php @@ -38,7 +38,7 @@ protected function records(): Collection { $query = Ticket::query(); $query->withCount('comments'); - if (has_all_permissions(auth()->user(), 'view-own-tickets') && !has_all_permissions(auth()->user(), 'view-all-tickets')) { + if (auth()->user()->can('View own tickets') && !auth()->user()->can('View all tickets')) { $query->where(function ($query) { $query->where('owner_id', auth()->user()->id) ->orWhere('responsible_id', auth()->user()->id); @@ -116,7 +116,7 @@ protected function styles(): array public function onStatusChanged($recordId, $statusId, $fromOrderedIds, $toOrderedIds): void { $ticket = Ticket::find($recordId); - if ((has_all_permissions(auth()->user(), 'update-all-tickets') || (has_all_permissions(auth()->user(), 'update-own-tickets') && ($ticket->owner_id === auth()->user() || $ticket->responsible_id === auth()->user()->id))) && has_all_permissions(auth()->user(), 'change-status-tickets')) { + if ((auth()->user()->can('Update all tickets') || (auth()->user()->can('Update own tickets') && ($ticket->owner_id === auth()->user() || $ticket->responsible_id === auth()->user()->id))) && auth()->user()->can('Change status tickets')) { $before = __(config('system.statuses.' . $ticket->status . '.title')) ?? '-'; $ticket->status = $statusId; $ticket->save(); diff --git a/app/Http/Livewire/Projects.php b/app/Http/Livewire/Projects.php index 58eb7ec0..d5886a62 100644 --- a/app/Http/Livewire/Projects.php +++ b/app/Http/Livewire/Projects.php @@ -38,7 +38,7 @@ protected function getTableQuery(): Builder|Relation { $query = Project::query(); $query->withCount('tickets'); - if (has_all_permissions(auth()->user(), 'view-own-projects') && !has_all_permissions(auth()->user(), 'view-all-projects')) { + if (auth()->user()->can('View own projects') && !auth()->user()->can('View all projects')) { $query->where(function ($query) { $query->where('owner_id', auth()->user()->id) ->orWhereHas('tickets', function ($query) { diff --git a/app/Http/Livewire/Tickets.php b/app/Http/Livewire/Tickets.php index 1af5692e..12ee4733 100644 --- a/app/Http/Livewire/Tickets.php +++ b/app/Http/Livewire/Tickets.php @@ -49,7 +49,7 @@ public function render() { $query = Ticket::query(); $query->withCount('comments'); - if (has_all_permissions(auth()->user(), 'view-own-tickets') && !has_all_permissions(auth()->user(), 'view-all-tickets')) { + if (auth()->user()->can('View own tickets') && !auth()->user()->can('View all tickets')) { $query->where(function ($query) { $query->where('owner_id', auth()->user()->id) ->orWhere('responsible_id', auth()->user()->id); @@ -118,7 +118,7 @@ protected function getFormSchema(): array ->placeholder(__('Project')) ->options(function () { $query = Project::query(); - if (has_all_permissions(auth()->user(), 'view-own-projects') && !has_all_permissions(auth()->user(), 'view-all-projects')) { + if (auth()->user()->can('View own projects') && !auth()->user()->can('View all projects')) { $query->where('owner_id', auth()->user()->id); } return $query->get()->pluck('name', 'id'); diff --git a/app/Http/Livewire/TicketsDialog.php b/app/Http/Livewire/TicketsDialog.php index 86a163ab..aed4d9fb 100644 --- a/app/Http/Livewire/TicketsDialog.php +++ b/app/Http/Livewire/TicketsDialog.php @@ -52,7 +52,7 @@ protected function getFormSchema(): array ->searchable() ->options(function () { $query = Project::query(); - if (has_all_permissions(auth()->user(), 'view-own-projects') && !has_all_permissions(auth()->user(), 'view-all-projects')) { + if (auth()->user()->can('View own projects') && !auth()->user()->can('View all projects')) { $query->where('owner_id', auth()->user()->id); } return $query->get()->pluck('name', 'id'); diff --git a/app/Http/Middleware/CanAccessTicket.php b/app/Http/Middleware/CanAccessTicket.php index 76597bf9..f7bee674 100644 --- a/app/Http/Middleware/CanAccessTicket.php +++ b/app/Http/Middleware/CanAccessTicket.php @@ -19,10 +19,10 @@ public function handle(Request $request, Closure $next) { $ticket = $request->route('ticket'); if (!( - has_all_permissions(auth()->user(), 'view-all-tickets') + auth()->user()->can('View all tickets') || ( - has_all_permissions(auth()->user(), 'view-own-tickets') + auth()->user()->can('View own tickets') && in_array(auth()->user()->id, [$ticket->owner_id, $ticket->responsible_id]) ) )) { diff --git a/app/Jobs/CommentCreatedJob.php b/app/Jobs/CommentCreatedJob.php index de21ee59..8c18a22e 100644 --- a/app/Jobs/CommentCreatedJob.php +++ b/app/Jobs/CommentCreatedJob.php @@ -39,9 +39,9 @@ public function handle() $users = User::whereNull('register_token')->get(); foreach ($users as $user) { if ( - (has_all_permissions($user, 'view-all-tickets') && $this->comment->owner_id !== $user->id) + (auth()->user()->can('View all tickets') && $this->comment->owner_id !== $user->id) || - (has_all_permissions($user, 'view-own-tickets') && ($this->comment->ticket->owner_id === $user->id || $this->comment->ticket->responsible_id === $user->id) && $this->comment->owner_id !== $user->id) + (auth()->user()->can('View own tickets') && ($this->comment->ticket->owner_id === $user->id || $this->comment->ticket->responsible_id === $user->id) && $this->comment->owner_id !== $user->id) ) { $user->notify(new CommentCreateNotification($this->comment, $user)); } diff --git a/app/Jobs/TicketCreatedJob.php b/app/Jobs/TicketCreatedJob.php index b345cf78..3c1525de 100644 --- a/app/Jobs/TicketCreatedJob.php +++ b/app/Jobs/TicketCreatedJob.php @@ -37,7 +37,7 @@ public function handle() { $users = User::whereNull('register_token')->get(); foreach ($users as $user) { - if (has_all_permissions($user, 'view-all-tickets') && $this->ticket->owner_id !== $user->id) { + if (auth()->user()->can('View all tickets') && $this->ticket->owner_id !== $user->id) { $user->notify(new TicketCreatedNotification($this->ticket, $user)); } } diff --git a/app/Jobs/TicketUpdatedJob.php b/app/Jobs/TicketUpdatedJob.php index 30692b18..2bad3bca 100644 --- a/app/Jobs/TicketUpdatedJob.php +++ b/app/Jobs/TicketUpdatedJob.php @@ -49,9 +49,9 @@ public function handle() $users = User::whereNull('register_token')->where('id', '<>', $this->user->id)->get(); foreach ($users as $u) { if ( - (has_all_permissions($u, 'view-all-tickets') && $this->ticket->owner_id !== $u->id) + (auth()->user()->can('View all tickets') && $this->ticket->owner_id !== $u->id) || - (has_all_permissions($u, 'view-own-tickets') && ($this->ticket->owner_id === $u->id || $this->ticket->responsible_id === $u->id) && $this->ticket->owner_id !== $u->id) + (auth()->user()->can('View own tickets') && ($this->ticket->owner_id === $u->id || $this->ticket->responsible_id === $u->id) && $this->ticket->owner_id !== $u->id) ) { $u->notify(new TicketUpdatedNotification($this->ticket, $this->field, $this->before, $this->after, $this->user)); } diff --git a/app/Models/Company.php b/app/Models/Company.php new file mode 100644 index 00000000..87d93c92 --- /dev/null +++ b/app/Models/Company.php @@ -0,0 +1,32 @@ +belongsTo(User::class, 'responsible_id'); + } + + public function users(): BelongsToMany + { + return $this->belongsToMany(User::class, 'company_users', 'company_id', 'user_id'); + } +} diff --git a/app/Models/CompanyUser.php b/app/Models/CompanyUser.php new file mode 100644 index 00000000..d828e3d3 --- /dev/null +++ b/app/Models/CompanyUser.php @@ -0,0 +1,27 @@ +belongsTo(User::class); + } + + public function company(): BelongsTo + { + return $this->belongsTo(Company::class); + } +} diff --git a/app/Models/Project.php b/app/Models/Project.php index 6bffc23a..3f7edb8e 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -44,7 +44,7 @@ public function tickets(): HasMany public function favoriteUsers(): BelongsToMany { $query = $this->belongsToMany(User::class, 'favorite_projects', 'project_id', 'user_id'); - if (has_all_permissions(auth()->user(), 'view-own-projects') && !has_all_permissions(auth()->user(), 'view-all-projects')) { + if (auth()->user()->can('View own projects') && !auth()->user()->can('View all projects')) { $query->where('user_id', auth()->user()->id); } return $query; diff --git a/app/Models/User.php b/app/Models/User.php index 0a141460..3adbf15f 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -16,10 +16,11 @@ use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Laravel\Sanctum\HasApiTokens; +use Spatie\Permission\Traits\HasRoles; class User extends Authenticatable implements HasLogsActivity { - use HasApiTokens, HasFactory, Notifiable, CanResetPassword, SoftDeletes, LogsActivity, HasAvatarUrl; + use HasApiTokens, HasFactory, Notifiable, CanResetPassword, SoftDeletes, LogsActivity, HasAvatarUrl, HasRoles; /** * The attributes that are mass assignable. @@ -30,7 +31,6 @@ class User extends Authenticatable implements HasLogsActivity 'name', 'email', 'password', - 'role', 'register_token', 'locale', ]; @@ -80,7 +80,7 @@ public function assignedTickets(): HasMany public function favoriteProjects(): BelongsToMany { $query = $this->belongsToMany(Project::class, 'favorite_projects', 'user_id', 'project_id'); - if (has_all_permissions(auth()->user(), 'view-own-projects') && !has_all_permissions(auth()->user(), 'view-all-projects')) { + if (auth()->user()->can('View own projects') && !auth()->user()->can('View all projects')) { $query->where('user_id', auth()->user()->id); } return $query; @@ -107,4 +107,14 @@ public function isAccountActivated(): Attribute get: fn() => $this->register_token == null ); } + + public function ownCompanies(): HasMany + { + return $this->hasMany(Company::class, 'responsible_id'); + } + + public function companies(): BelongsToMany + { + return $this->belongsToMany(Company::class, 'company_users', 'user_id', 'company_id'); + } } diff --git a/app/View/Components/MainMenu.php b/app/View/Components/MainMenu.php index 7c35769e..ac012a76 100644 --- a/app/View/Components/MainMenu.php +++ b/app/View/Components/MainMenu.php @@ -26,37 +26,43 @@ public function __construct() 'title' => 'Overview', 'icon' => 'fa-table-columns', 'always_shown' => true, - 'show_notification_indicator' => false + 'show_notification_indicator' => false, + 'permission' => '' ], 'analytics' => [ 'title' => 'Analytics', 'icon' => 'fa-chart-bar', 'always_shown' => false, - 'show_notification_indicator' => false + 'show_notification_indicator' => false, + 'permission' => 'Can view Analytics page' ], 'tickets' => [ 'title' => 'Tickets', 'icon' => 'fa-ticket', 'always_shown' => false, - 'show_notification_indicator' => false + 'show_notification_indicator' => false, + 'permission' => 'Can view Tickets page' ], 'kanban' => [ 'title' => 'Kanban Board', 'icon' => 'fa-clipboard-check', 'always_shown' => false, - 'show_notification_indicator' => false + 'show_notification_indicator' => false, + 'permission' => 'Can view Kanban page' ], 'administration' => [ 'title' => 'Administration', 'icon' => 'fa-cogs', 'always_shown' => false, - 'show_notification_indicator' => false + 'show_notification_indicator' => false, + 'permission' => 'Can view Administration page' ], 'notifications' => [ 'title' => 'Notifications', 'icon' => 'fa-bell', 'always_shown' => true, - 'show_notification_indicator' => true + 'show_notification_indicator' => true, + 'permission' => '' ], ]; } diff --git a/app/helpers.php b/app/helpers.php index 190f6517..84922c6e 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -6,101 +6,6 @@ use App\Models\User; use Illuminate\Contracts\Auth\Authenticatable; -if (!function_exists('roles_list')) { - /** - * Return roles list as an array of KEY (role id) => VALUE (role title) - * - * @return array - */ - function roles_list(): array - { - $roles = []; - foreach (config('system.roles') as $key => $value) { - $roles[$key] = __($value['title']); - } - return $roles; - } -} - -if (!function_exists('roles_list_badges')) { - /** - * Return roles list as an array of KEY (role id) => VALUE (role title) - * - * @return array - */ - function roles_list_badges(): array - { - $roles = []; - foreach (config('system.roles') as $key => $value) { - $roles[$value['badge-color']] = $key; - } - return $roles; - } -} - -if (!function_exists('has_all_permissions')) { - /** - * Check if the user has all the permissions passed as parameters - * - * @param User|Authenticatable $user - * @param ...$permissions - * @return bool - */ - function has_all_permissions(User|Authenticatable $user, ...$permissions): bool - { - if ($user->role) { - $role = config('system.roles.' . $user->role); - if ($role) { - return sizeof(array_intersect($role['permissions']['functions'], $permissions)) === sizeof($permissions); - } - return false; - } - return false; - } -} - -if (!function_exists('has_any_permissions')) { - /** - * Check if the user has any of the permissions passed as parameters - * - * @param User|Authenticatable $user - * @param ...$permissions - * @return bool - */ - function has_any_permissions(User|Authenticatable $user, ...$permissions): bool - { - if ($user->role) { - $role = config('system.roles.' . $user->role); - if ($role) { - return sizeof(array_intersect($role['permissions']['functions'], $permissions)); - } - return false; - } - return false; - } -} - -if (!function_exists('can_access_page')) { - /** - * Check if the user can access the pages passed as parameters - * - * @param User|Authenticatable $user - * @param ...$pages - * @return bool - */ - function can_access_page(User|Authenticatable $user, ...$pages): bool - { - if ($user->role) { - $role = config('system.roles.' . $user->role); - if ($role) { - return sizeof(array_intersect($role['permissions']['pages'], $pages)); - } - return false; - } - return false; - } -} - if (!function_exists('statuses_list')) { /** * Return statuses list as an array of KEY (status id) => VALUE (status title) @@ -177,10 +82,10 @@ function types_list(): array */ function locales(): array { - $roles = []; + $locales = []; foreach (config('system.locales') as $key => $value) { - $roles[$key] = __($value); + $locales[$key] = __($value); } - return $roles; + return $locales; } } diff --git a/composer.json b/composer.json index 45958a28..36bc3a59 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,8 @@ "laravel/tinker": "^2.7", "livewire/livewire": "^2.10", "phpsa/filament-password-reveal": "^1.1", - "spatie/laravel-activitylog": "^4.5" + "spatie/laravel-activitylog": "^4.5", + "spatie/laravel-permission": "^5.5" }, "require-dev": { "fakerphp/faker": "^1.9.1", diff --git a/composer.lock b/composer.lock index 6787d447..0421e44a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "66b739442c43a1297dbf8d7c2d2d7cd2", + "content-hash": "354575329f6cd81ed3fde350bff1670f", "packages": [ { "name": "akaunting/laravel-money", @@ -4204,6 +4204,88 @@ ], "time": "2022-09-07T14:31:31+00:00" }, + { + "name": "spatie/laravel-permission", + "version": "5.5.5", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-permission.git", + "reference": "f2303a70be60919811ca8afc313e8244fda00974" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-permission/zipball/f2303a70be60919811ca8afc313e8244fda00974", + "reference": "f2303a70be60919811ca8afc313e8244fda00974", + "shasum": "" + }, + "require": { + "illuminate/auth": "^7.0|^8.0|^9.0", + "illuminate/container": "^7.0|^8.0|^9.0", + "illuminate/contracts": "^7.0|^8.0|^9.0", + "illuminate/database": "^7.0|^8.0|^9.0", + "php": "^7.3|^8.0|^8.1" + }, + "require-dev": { + "orchestra/testbench": "^5.0|^6.0|^7.0", + "phpunit/phpunit": "^9.4", + "predis/predis": "^1.1" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\Permission\\PermissionServiceProvider" + ] + }, + "branch-alias": { + "dev-main": "5.x-dev", + "dev-master": "5.x-dev" + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Spatie\\Permission\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Permission handling for Laravel 6.0 and up", + "homepage": "https://github.com/spatie/laravel-permission", + "keywords": [ + "acl", + "laravel", + "permission", + "permissions", + "rbac", + "roles", + "security", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-permission/issues", + "source": "https://github.com/spatie/laravel-permission/tree/5.5.5" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2022-06-29T23:11:42+00:00" + }, { "name": "symfony/console", "version": "v6.1.4", @@ -9296,5 +9378,5 @@ "php": "^8.0.2" }, "platform-dev": [], - "plugin-api-version": "2.1.0" + "plugin-api-version": "2.2.0" } diff --git a/config/permission.php b/config/permission.php new file mode 100644 index 00000000..5b6e184c --- /dev/null +++ b/config/permission.php @@ -0,0 +1,161 @@ + [ + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * Eloquent model should be used to retrieve your permissions. Of course, it + * is often just the "Permission" model but you may use whatever you like. + * + * The model you want to use as a Permission model needs to implement the + * `Spatie\Permission\Contracts\Permission` contract. + */ + + 'permission' => Spatie\Permission\Models\Permission::class, + + /* + * When using the "HasRoles" trait from this package, we need to know which + * Eloquent model should be used to retrieve your roles. Of course, it + * is often just the "Role" model but you may use whatever you like. + * + * The model you want to use as a Role model needs to implement the + * `Spatie\Permission\Contracts\Role` contract. + */ + + 'role' => Spatie\Permission\Models\Role::class, + + ], + + 'table_names' => [ + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your roles. We have chosen a basic + * default value but you may easily change it to any table you like. + */ + + 'roles' => 'roles', + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * table should be used to retrieve your permissions. We have chosen a basic + * default value but you may easily change it to any table you like. + */ + + 'permissions' => 'permissions', + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * table should be used to retrieve your models permissions. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'model_has_permissions' => 'model_has_permissions', + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your models roles. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'model_has_roles' => 'model_has_roles', + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your roles permissions. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'role_has_permissions' => 'role_has_permissions', + ], + + 'column_names' => [ + /* + * Change this if you want to name the related pivots other than defaults + */ + 'role_pivot_key' => null, //default 'role_id', + 'permission_pivot_key' => null, //default 'permission_id', + + /* + * Change this if you want to name the related model primary key other than + * `model_id`. + * + * For example, this would be nice if your primary keys are all UUIDs. In + * that case, name this `model_uuid`. + */ + + 'model_morph_key' => 'model_id', + + /* + * Change this if you want to use the teams feature and your related model's + * foreign key is other than `team_id`. + */ + + 'team_foreign_key' => 'team_id', + ], + + /* + * When set to true, the method for checking permissions will be registered on the gate. + * Set this to false, if you want to implement custom logic for checking permissions. + */ + + 'register_permission_check_method' => true, + + /* + * When set to true the package implements teams using the 'team_foreign_key'. If you want + * the migrations to register the 'team_foreign_key', you must set this to true + * before doing the migration. If you already did the migration then you must make a new + * migration to also add 'team_foreign_key' to 'roles', 'model_has_roles', and + * 'model_has_permissions'(view the latest version of package's migration file) + */ + + 'teams' => false, + + /* + * When set to true, the required permission names are added to the exception + * message. This could be considered an information leak in some contexts, so + * the default setting is false here for optimum safety. + */ + + 'display_permission_in_exception' => false, + + /* + * When set to true, the required role names are added to the exception + * message. This could be considered an information leak in some contexts, so + * the default setting is false here for optimum safety. + */ + + 'display_role_in_exception' => false, + + /* + * By default wildcard permission lookups are disabled. + */ + + 'enable_wildcard_permission' => false, + + 'cache' => [ + + /* + * By default all permissions are cached for 24 hours to speed up performance. + * When permissions or roles are updated the cache is flushed automatically. + */ + + 'expiration_time' => \DateInterval::createFromDateString('24 hours'), + + /* + * The cache key used to store all permissions. + */ + + 'key' => 'spatie.permission.cache', + + /* + * You may optionally indicate a specific cache driver to use for permission and + * role caching using any of the `store` drivers listed in the cache.php config + * file. Using 'default' here means to use the `default` set in cache.php. + */ + + 'store' => 'default', + ], +]; diff --git a/config/system.php b/config/system.php index bd7807ed..b1f3bd9d 100644 --- a/config/system.php +++ b/config/system.php @@ -15,82 +15,4 @@ 'fr' => 'Français' ], - /* - |-------------------------------------------------------------------------- - | Users statuses configuration - |-------------------------------------------------------------------------- - | - | This value is the configured roles assigned to users while creating / - | updating them, you can change it as you like - | - | 'text-color' and 'bg-color' are tailwindcss based css classes - | 'badge-color' uses the colors defined on 'tailwind.config.js' - | 'permissions' contains the rights to access "pages" and "functions" - | 'pages' contains the pages accessible by the role - | -> Same declared in App\View\Components\MainMenu component) - | 'functions' contains the functionnalities accessible by the role - | - | Available permissions: - | - Pages: analytics, tickets, administration - | - Functions: - | - view-all-projects: View all configured projects - | - update-all-projects: Update all configured projects - | - delete-all-projects: Delete all configured projects - | - create-projects: Create a new project - | - view-own-projects: View assigned projects - | - update-own-projects: Update assigned projects - | - delete-own-projects: Delete assigned projects - | - view-all-tickets: View all configured tickets - | - update-all-tickets: Update all configured tickets - | - delete-all-tickets: Delete all configured tickets - | - create-tickets: Create a new ticket - | - view-own-tickets: View assigned tickets - | - update-own-tickets: Update assigned tickets - | - delete-own-tickets: Delete assigned tickets - | - assign-tickets: Assign tickets to responsibles - | - change-status-tickets: Change tickets status - | - */ - 'roles' => [ - 'administrator' => [ - 'title' => 'Administrator', - 'text-color' => 'bg-red-50', - 'bg-color' => 'text-red-500', - 'badge-color' => 'danger', - 'permissions' => [ - 'pages' => ['analytics', 'tickets', 'kanban', 'administration'], - 'functions' => [ - 'view-all-projects', 'update-all-projects', 'delete-all-projects', 'create-projects', - 'view-all-tickets', 'update-all-tickets', 'delete-all-tickets', 'create-tickets', 'assign-tickets', 'change-status-tickets' - ] - ] - ], - 'employee' => [ - 'title' => 'Employee', - 'text-color' => 'bg-gray-50', - 'bg-color' => 'text-gray-500', - 'badge-color' => 'warning', - 'permissions' => [ - 'pages' => ['analytics', 'tickets', 'kanban'], - 'functions' => [ - 'view-own-projects', - 'view-own-tickets', 'update-own-tickets', 'delete-own-tickets', 'create-tickets', 'assign-tickets', 'change-status-tickets' - ] - ] - ], - 'customer' => [ - 'title' => 'Customer', - 'text-color' => 'bg-blue-50', - 'bg-color' => 'text-blue-500', - 'badge-color' => 'primary', - 'permissions' => [ - 'pages' => ['analytics', 'tickets', 'kanban'], - 'functions' => [ - 'view-own-projects', - 'view-own-tickets', 'update-own-tickets', 'delete-own-tickets', 'create-tickets' - ] - ] - ], - ], - ]; diff --git a/database/help_desk.pgsql.sql b/database/help_desk.pgsql.sql index e9276d66..6f682d21 100644 --- a/database/help_desk.pgsql.sql +++ b/database/help_desk.pgsql.sql @@ -81,4 +81,6 @@ VALUES (1, 'Improvement', '#dbeafe', '#3b82f6', 'fa-arrow-up', NULL, '2022-09-19 (5, 'Bug', '#ef4444', '#fee2e2', 'fa-bug', NULL, '2022-09-19 10:37:37', '2022-09-19 11:31:04', 'bug'); - +INSERT INTO companies (id, name, logo, description, is_disabled, responsible_id, deleted_at, created_at, updated_at) VALUES +(1, 'Google', null, '

Google is an American technology services company founded in 1998 in Silicon Valley, California, by Larry Page and Sergey Brin, creators of the Google search engine. It has been a subsidiary of the Alphabet company since August 2015.

', false, 4, NULL, '2022-09-24 23:31:50', '2022-09-24 23:44:50'), +(2, 'Meta', null, '

Meta Platforms, Inc., better known by the trade name Meta, is an American company created in 2004 by Mark Zuckerberg. It is one of the giants of the Web, grouped under the acronym GAFAM, alongside Google, Apple, Amazon and Microsoft.

', true, 5, NULL, '2022-09-24 23:46:26', '2022-09-24 23:46:47'); diff --git a/database/help_desk.sql b/database/help_desk.sql index 7eef3af7..424bdd46 100644 --- a/database/help_desk.sql +++ b/database/help_desk.sql @@ -82,4 +82,8 @@ VALUES (1, 'Improvement', '#dbeafe', '#3b82f6', 'fa-arrow-up', NULL, '2022-09-19 'task'), (5, 'Bug', '#ef4444', '#fee2e2', 'fa-bug', NULL, '2022-09-19 10:37:37', '2022-09-19 11:31:04', 'bug'); +INSERT INTO `companies` (`id`, `name`, `logo`, `description`, `is_disabled`, `responsible_id`, `deleted_at`, `created_at`, `updated_at`) VALUES +(1, 'Google', null, '

Google is an American technology services company founded in 1998 in Silicon Valley, California, by Larry Page and Sergey Brin, creators of the Google search engine. It has been a subsidiary of the Alphabet company since August 2015.

', 0, 4, NULL, '2022-09-24 23:31:50', '2022-09-24 23:44:50'), +(2, 'Meta', null, '

Meta Platforms, Inc., better known by the trade name Meta, is an American company created in 2004 by Mark Zuckerberg. It is one of the giants of the Web, grouped under the acronym GAFAM, alongside Google, Apple, Amazon and Microsoft.

', 1, 5, NULL, '2022-09-24 23:46:26', '2022-09-24 23:46:47'); + SET foreign_key_checks = 1; diff --git a/database/migrations/2022_09_24_230950_create_companies_table.php b/database/migrations/2022_09_24_230950_create_companies_table.php new file mode 100644 index 00000000..36332555 --- /dev/null +++ b/database/migrations/2022_09_24_230950_create_companies_table.php @@ -0,0 +1,37 @@ +id(); + $table->string('name'); + $table->string('logo')->nullable(); + $table->longText('description')->nullable(); + $table->boolean('is_disabled')->default(false); + $table->foreignId('responsible_id')->constrained('users'); + $table->softDeletes(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('companies'); + } +}; diff --git a/database/migrations/2022_09_25_154331_create_permission_tables.php b/database/migrations/2022_09_25_154331_create_permission_tables.php new file mode 100644 index 00000000..04c3278b --- /dev/null +++ b/database/migrations/2022_09_25_154331_create_permission_tables.php @@ -0,0 +1,141 @@ +bigIncrements('id'); // permission id + $table->string('name'); // For MySQL 8.0 use string('name', 125); + $table->string('guard_name'); // For MySQL 8.0 use string('guard_name', 125); + $table->timestamps(); + + $table->unique(['name', 'guard_name']); + }); + + Schema::create($tableNames['roles'], function (Blueprint $table) use ($teams, $columnNames) { + $table->bigIncrements('id'); // role id + if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing + $table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable(); + $table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index'); + } + $table->string('name'); // For MySQL 8.0 use string('name', 125); + $table->string('guard_name'); // For MySQL 8.0 use string('guard_name', 125); + $table->timestamps(); + if ($teams || config('permission.testing')) { + $table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']); + } else { + $table->unique(['name', 'guard_name']); + } + }); + + Schema::create($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames, $columnNames, $teams) { + $table->unsignedBigInteger(PermissionRegistrar::$pivotPermission); + + $table->string('model_type'); + $table->unsignedBigInteger($columnNames['model_morph_key']); + $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index'); + + $table->foreign(PermissionRegistrar::$pivotPermission) + ->references('id') // permission id + ->on($tableNames['permissions']) + ->onDelete('cascade'); + if ($teams) { + $table->unsignedBigInteger($columnNames['team_foreign_key']); + $table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index'); + + $table->primary([$columnNames['team_foreign_key'], PermissionRegistrar::$pivotPermission, $columnNames['model_morph_key'], 'model_type'], + 'model_has_permissions_permission_model_type_primary'); + } else { + $table->primary([PermissionRegistrar::$pivotPermission, $columnNames['model_morph_key'], 'model_type'], + 'model_has_permissions_permission_model_type_primary'); + } + + }); + + Schema::create($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames, $columnNames, $teams) { + $table->unsignedBigInteger(PermissionRegistrar::$pivotRole); + + $table->string('model_type'); + $table->unsignedBigInteger($columnNames['model_morph_key']); + $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index'); + + $table->foreign(PermissionRegistrar::$pivotRole) + ->references('id') // role id + ->on($tableNames['roles']) + ->onDelete('cascade'); + if ($teams) { + $table->unsignedBigInteger($columnNames['team_foreign_key']); + $table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index'); + + $table->primary([$columnNames['team_foreign_key'], PermissionRegistrar::$pivotRole, $columnNames['model_morph_key'], 'model_type'], + 'model_has_roles_role_model_type_primary'); + } else { + $table->primary([PermissionRegistrar::$pivotRole, $columnNames['model_morph_key'], 'model_type'], + 'model_has_roles_role_model_type_primary'); + } + }); + + Schema::create($tableNames['role_has_permissions'], function (Blueprint $table) use ($tableNames) { + $table->unsignedBigInteger(PermissionRegistrar::$pivotPermission); + $table->unsignedBigInteger(PermissionRegistrar::$pivotRole); + + $table->foreign(PermissionRegistrar::$pivotPermission) + ->references('id') // permission id + ->on($tableNames['permissions']) + ->onDelete('cascade'); + + $table->foreign(PermissionRegistrar::$pivotRole) + ->references('id') // role id + ->on($tableNames['roles']) + ->onDelete('cascade'); + + $table->primary([PermissionRegistrar::$pivotPermission, PermissionRegistrar::$pivotRole], 'role_has_permissions_permission_id_role_id_primary'); + }); + + app('cache') + ->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null) + ->forget(config('permission.cache.key')); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + $tableNames = config('permission.table_names'); + + if (empty($tableNames)) { + throw new \Exception('Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.'); + } + + Schema::drop($tableNames['role_has_permissions']); + Schema::drop($tableNames['model_has_roles']); + Schema::drop($tableNames['model_has_permissions']); + Schema::drop($tableNames['roles']); + Schema::drop($tableNames['permissions']); + } +} diff --git a/database/migrations/2022_09_25_163436_remove_role_from_users.php b/database/migrations/2022_09_25_163436_remove_role_from_users.php new file mode 100644 index 00000000..0895bd4f --- /dev/null +++ b/database/migrations/2022_09_25_163436_remove_role_from_users.php @@ -0,0 +1,32 @@ +dropColumn('role'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function (Blueprint $table) { + $table->string('role'); + }); + } +}; diff --git a/database/migrations/2022_09_25_165452_create_company_users_table.php b/database/migrations/2022_09_25_165452_create_company_users_table.php new file mode 100644 index 00000000..77f712a3 --- /dev/null +++ b/database/migrations/2022_09_25_165452_create_company_users_table.php @@ -0,0 +1,33 @@ +id(); + $table->foreignId('user_id')->constrained('users'); + $table->foreignId('company_id')->constrained('companies'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('company_users'); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 7873482a..ac587451 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -15,5 +15,6 @@ class DatabaseSeeder extends Seeder public function run() { $this->call(FontAwesomeFreeSeeder::class); + $this->call(PermissionsSeeder::class); } } diff --git a/database/seeders/FontAwesomeFreeSeeder.php b/database/seeders/FontAwesomeFreeSeeder.php index bfe87ab6..451d2fa5 100644 --- a/database/seeders/FontAwesomeFreeSeeder.php +++ b/database/seeders/FontAwesomeFreeSeeder.php @@ -1476,7 +1476,9 @@ class FontAwesomeFreeSeeder extends Seeder public function run() { foreach (self::icons as $icon) { - Icon::create(['icon' => $icon]); + if (!Icon::where('icon', $icon)->count()) { + Icon::create(['icon' => $icon]); + } } } } diff --git a/database/seeders/PermissionsSeeder.php b/database/seeders/PermissionsSeeder.php new file mode 100644 index 00000000..c60ae1c0 --- /dev/null +++ b/database/seeders/PermissionsSeeder.php @@ -0,0 +1,62 @@ +count()) { + Permission::create(['name' => $permission]); + } + } + } +} diff --git a/lang/fr.json b/lang/fr.json index 196af923..7c18c3e6 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -285,5 +285,32 @@ "Use the section below to chat with the speakers of this ticket": "Utilisez la section ci-dessous pour échanger avec les intervenants de ce ticket", "Type a message..": "Tapez un message..", "Send": "Envoyer", - "No messages yet!": "Aucun message pour le moment !" + "No messages yet!": "Aucun message pour le moment !", + "Company name": "Nom de l'entreprise", + "Logo": "Logo", + "Companies": "Entreprises", + "Below is the list of configured companies in :app": "Ci-dessous est la liste des entreprises configurées sur :app", + "Create a new company": "Créer une nouvelle entreprise", + "Disable access to this company": "Désactiver l'accès à cette entreprise", + "Company created": "Entreprise créée", + "The company has been created": "L'entreprise est créée avec succès", + "Company updated": "Entreprise mise à jour", + "The company's details has been updated": "L'entreprise est mise à jour avec succès", + "Company deleted": "Entreprise supprimée", + "The company has been deleted": "L'entreprise est supprimée avec succès", + "Company deletion": "Suppression de l'entreprise", + "Are you sure you want to delete this company?": "Êtes-vous sûr de vouloir supprimer cette entreprise ?", + "Companies Management": "Gestion des entreprises", + "Here you can show and manage the companies list configured on :app": "Ici vous pouvez visualiser et gérer la liste des entreprises de :app", + "Manage companies": "Gérer les entreprises", + "Company activated": "Entreprise activée", + "Edit company": "Modifier l'entreprise", + "Use same permissions of": "Utiliser les mêmes permission que", + "Update the permissions of this user based on another user's permissions": "Mettre à jour les permissions de cet utilisateur en se basant sur les permissions d'un autre utilisateur", + "Assign all permissions": "Assigner toutes les permissions", + "Remove all permissions": "Enlever toutes les permissions", + "Company users": "Utilisateurs de l'entreprise", + "Yes": "Oui", + "No": "Non", + "All users": "Tous les utilisateurs" } diff --git a/public/images/administration/companies.jpeg b/public/images/administration/companies.jpeg new file mode 100644 index 00000000..a7f3d7fe Binary files /dev/null and b/public/images/administration/companies.jpeg differ diff --git a/resources/css/app.scss b/resources/css/app.scss index c996c870..6315e790 100644 --- a/resources/css/app.scss +++ b/resources/css/app.scss @@ -85,3 +85,9 @@ table { } } } + +.modal-container { + @apply w-full overflow-y-auto max-h-screen p-5; + + max-height: calc(100vh - 200px); +} diff --git a/resources/views/administration/companies.blade.php b/resources/views/administration/companies.blade.php new file mode 100644 index 00000000..9e0c231c --- /dev/null +++ b/resources/views/administration/companies.blade.php @@ -0,0 +1,7 @@ + + + Administration - Companies + + @livewire('administration.companies') + + diff --git a/resources/views/components/administration.blade.php b/resources/views/components/administration.blade.php index 8ba1b980..172a296a 100644 --- a/resources/views/components/administration.blade.php +++ b/resources/views/components/administration.blade.php @@ -1,83 +1,111 @@ -
+
-
-
-
-
@lang('User Management')
-

@lang('Here you can show and manage the users list configured on :app', [ + @if(auth()->user()->hasAnyPermission(['View all users', 'View company users'])) +

+
+
+
@lang('User Management')
+

@lang('Here you can show and manage the users list configured on :app', [ 'app' => config('app.name') ])

-
-
+ @endif -
-
-
-
@lang('Statuses Management')
-

@lang('Here you can show and manage the tickets statuses list configured on :app', [ - 'app' => config('app.name') - ])

-
- - @lang('Manage statuses') - - + @if(auth()->user()->hasAnyPermission(['View all companies', 'View own companies'])) +
+
+
+
@lang('Companies Management')
+

@lang('Here you can show and manage the companies list configured on :app', [ + 'app' => config('app.name') + ])

+
-
+ @endcan -
-
-
-
@lang('Priorities Management')
-

@lang('Here you can show and manage the tickets priorities list configured on :app', [ - 'app' => config('app.name') - ])

-
- - @lang('Manage priorities') - - + @can('Manage ticket statuses') +
+
+
+
@lang('Statuses Management')
+

@lang('Here you can show and manage the tickets statuses list configured on :app', [ + 'app' => config('app.name') + ])

+
-
+ @endcan -
-
-
-
@lang('Types Management')
-

@lang('Here you can show and manage the tickets types list configured on :app', [ - 'app' => config('app.name') - ])

-
- - @lang('Manage types') - - + @can('Manage ticket priorities') +
+
+
+
@lang('Priorities Management')
+

@lang('Here you can show and manage the tickets priorities list configured on :app', [ + 'app' => config('app.name') + ])

+
-
+ @endcan -
-
-
-
@lang('Activity logs')
-

@lang('Here you can see all activity logs of :app', [ - 'app' => config('app.name') - ])

-
- - @lang('See details') - - + @can('Manage ticket types') +
+
+
+
@lang('Types Management')
+

@lang('Here you can show and manage the tickets types list configured on :app', [ + 'app' => config('app.name') + ])

+ +
+
+ @endcan + + @can('View activity log') +
+
+
+
@lang('Activity logs')
+

@lang('Here you can see all activity logs of :app', [ + 'app' => config('app.name') + ])

+
-
+ @endcan
diff --git a/resources/views/components/main-menu.blade.php b/resources/views/components/main-menu.blade.php index 768df9b5..e2031544 100644 --- a/resources/views/components/main-menu.blade.php +++ b/resources/views/components/main-menu.blade.php @@ -34,7 +34,7 @@