├── config └── filament-permission.php ├── src ├── Resources │ ├── RoleResource │ │ └── Pages │ │ │ ├── ListRoles.php │ │ │ ├── CreateRole.php │ │ │ └── EditRole.php │ └── RoleResource.php ├── FilamentSpatieRolesPermissionsFacade.php ├── FilamentSpatieRolesPermissions.php ├── Console │ └── Commands │ │ └── PublishRoleResourceCommand.php └── FilamentSpatieRolesPermissionsServiceProvider.php ├── stubs ├── Resources │ ├── RoleResource │ │ └── Pages │ │ │ ├── EditRole.stub │ │ │ ├── ListRoles.stub │ │ │ └── CreateRole.stub │ └── RoleResource.stub ├── FilamentSpatieRolesPermissionsServiceProvider.stub └── database │ └── seeders │ └── RolesAndPermissionsSeeder.stub ├── CHANGELOG.md ├── resources └── lang │ ├── ar │ └── filament-spatie.php │ └── en │ └── filament-spatie.php ├── composer.json └── README.md /config/filament-permission.php: -------------------------------------------------------------------------------- 1 | [ 5 | // 6 | ] 7 | 8 | ]; 9 | -------------------------------------------------------------------------------- /src/Resources/RoleResource/Pages/ListRoles.php: -------------------------------------------------------------------------------- 1 | Pages\ListRoles::route('/'), 14 | 'create' => Pages\CreateRole::route('/create'), 15 | 'edit' => Pages\EditRole::route('/{record}/edit'), 16 | ]; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [v1.1.1] - 2022-01-12 4 | 5 | ### Added 6 | - Testing Workflow 7 | 8 | ## [v1.1.0] - 2022-01-12 9 | 10 | ## What's Changed 11 | 12 | - Feat: add publish resource command, move seeder to stubs by @YSRoot in https://github.com/reksmeysrey/filament-spatie-roles-permissions/pull/4 13 | 14 | **Full Changelog**: https://github.com/reksmeysrey/filament-spatie-roles-permissions/compare/main...YSRoot:main 15 | 16 | All notable changes to `filament-spatie-roles-permission` will be documented in this file. 17 | 18 | ## [v1.0.0] - 202X-XX-XX 19 | 20 | - initial release 21 | -------------------------------------------------------------------------------- /stubs/FilamentSpatieRolesPermissionsServiceProvider.stub: -------------------------------------------------------------------------------- 1 | setBasePath(app_path('../vendor/reksmey/filament-spatie-roles-permissions/src')); 12 | 13 | parent::configurePackage($package); 14 | } 15 | 16 | protected function getResources(): array 17 | { 18 | return []; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /stubs/database/seeders/RolesAndPermissionsSeeder.stub: -------------------------------------------------------------------------------- 1 | each(fn($permission) => Permission::findOrCreate($permission)); 15 | } 16 | 17 | public static function getPermissionNames(): ?array 18 | { 19 | return collect(FilamentSpatieRolesPermissionsFacade::getEntities()) 20 | ->map(function ($entity) { 21 | return collect(FilamentSpatieRolesPermissionsFacade::getPermissions()) 22 | ->map(function ($permission) use ($entity) { 23 | return $entity . '_' . $permission; 24 | }) 25 | ->all(); 26 | }) 27 | ->flatten() 28 | ->values() 29 | ->all(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Resources/RoleResource/Pages/CreateRole.php: -------------------------------------------------------------------------------- 1 | permissions = array_keys(array_filter(Arr::except($this->data, ['name', 'select_all', 'guard_name']))); 19 | } 20 | 21 | public function afterCreate(): void 22 | { 23 | $permissions = []; 24 | foreach ($this->permissions as $name) { 25 | $permissions[] = Permission::findOrCreate($name, $this->record->guard_name); 26 | } 27 | $this->record->syncPermissions($permissions); 28 | } 29 | 30 | protected function mutateFormDataBeforeCreate(array $data): array 31 | { 32 | return Arr::only($this->data, ['name', 'guard_name']); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/FilamentSpatieRolesPermissions.php: -------------------------------------------------------------------------------- 1 | merge($this->getSlugPermissions()) 14 | ->unique() 15 | ->reduce(function ($options, $resource) { 16 | $option = Str::before(Str::afterLast($resource, '\\'), 'Resource'); 17 | $options[$option] = $option; 18 | return $options; 19 | }, []); 20 | } 21 | 22 | public function getSlugPermissions(): array 23 | { 24 | return collect(config('filament-permission.permissions', [])) 25 | ->flatten() 26 | ->values() 27 | ->map(function (string $action) { 28 | return Str::slug($action, '_'); 29 | }) 30 | ->all(); 31 | } 32 | 33 | public function getPermissions(): array 34 | { 35 | return ['view', 'viewAny', 'create', 'delete', 'deleteAny', 'update']; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /resources/lang/ar/filament-spatie.php: -------------------------------------------------------------------------------- 1 | 'تاريخ الإنشاء', 11 | 'field.guard_name' => 'اسم الحارس', 12 | 'field.name' => 'الإسم', 13 | 'field.permissions' => 'الأذونات', 14 | 'field.roles' => 'الصلاحيات', 15 | 'field.select_all' => 'إختر الكل', 16 | 'field.updated_at' => 'آخر تحديث', 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Labels 21 | |-------------------------------------------------------------------------- 22 | */ 23 | 24 | 'section.permission' => 'إذن', 25 | 'section.permissions' => 'الأذونات', 26 | 'section.role' => 'صلاحية', 27 | 'section.roles' => 'الصلاحيات', 28 | 'section.roles_and_permissions' => 'الصلاحيات والأذونات', 29 | 30 | /* 31 | |-------------------------------------------------------------------------- 32 | | Message 33 | |-------------------------------------------------------------------------- 34 | */ 35 | 36 | 'message.select_all' => 'تفعيل كافة الأذونات لهذه الصلاحية.', 37 | ]; 38 | -------------------------------------------------------------------------------- /resources/lang/en/filament-spatie.php: -------------------------------------------------------------------------------- 1 | 'Created At', 11 | 'field.guard_name' => 'Guard Name', 12 | 'field.name' => 'Name', 13 | 'field.permissions' => 'Permissions', 14 | 'field.roles' => 'roles', 15 | 'field.select_all' => 'Select All', 16 | 'field.updated_at' => 'Last Update', 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Labels 21 | |-------------------------------------------------------------------------- 22 | */ 23 | 24 | 'section.permission' => 'Permission', 25 | 'section.permissions' => 'Permissions', 26 | 'section.role' => 'Role', 27 | 'section.roles' => 'Roles', 28 | 'section.roles_and_permissions' => 'Roles and Permissions', 29 | 30 | /* 31 | |-------------------------------------------------------------------------- 32 | | Message 33 | |-------------------------------------------------------------------------- 34 | */ 35 | 36 | 'message.select_all' => 'Enable all Permissions for this role.', 37 | ]; 38 | -------------------------------------------------------------------------------- /src/Resources/RoleResource/Pages/EditRole.php: -------------------------------------------------------------------------------- 1 | permissions = static::onlyPermissionsKeys($data); 19 | 20 | return Arr::only($data, ['name', 'guard_name']); 21 | } 22 | 23 | public function afterSave(): void 24 | { 25 | $permissions = []; 26 | foreach ($this->permissions as $name) { 27 | $permissions[] = Permission::findOrCreate($name, $this->record->guard_name); 28 | } 29 | 30 | $this->record->touch(); 31 | $this->record->syncPermissions($permissions); 32 | } 33 | 34 | public static function onlyPermissionsKeys($data): array 35 | { 36 | return array_keys(array_filter(Arr::except($data, ['guard_name', 'id', 'name', 'select_all', 'created_at', 'updated_at']))); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Console/Commands/PublishRoleResourceCommand.php: -------------------------------------------------------------------------------- 1 | exists($roleResourcePath)) { 23 | $confirmed = $this->confirm('RoleResource already exists. Overwrite?', false); 24 | if (!$confirmed) { 25 | return static::INVALID; 26 | } 27 | } 28 | 29 | // publish RoleResource 30 | $filesystem->ensureDirectoryExists($baseResourcePath); 31 | $filesystem->copy($resourceStubsDir . '/RoleResource.stub', $roleResourcePath); 32 | 33 | // publish RoleResource pages 34 | $filesystem->ensureDirectoryExists($pagesPath); 35 | foreach (['CreateRole', 'EditRole', 'ListRoles'] as $page) { 36 | $filesystem->copy( 37 | sprintf("%s/RoleResource/Pages/%s.stub", $resourceStubsDir, $page), 38 | sprintf('%s/%s.php', $pagesPath, $page) 39 | ); 40 | } 41 | 42 | $this->info('RoleResource have been published successfully!'); 43 | 44 | return static::SUCCESS; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/FilamentSpatieRolesPermissionsServiceProvider.php: -------------------------------------------------------------------------------- 1 | hasCommand(PublishRoleResourceCommand::class); 24 | 25 | parent::configurePackage($package); 26 | } 27 | 28 | public function registeringPackage(): void 29 | { 30 | $this->app->bind('filament-spatie-roles-permissions', function (): FilamentSpatieRolesPermissions { 31 | return new FilamentSpatieRolesPermissions(); 32 | }); 33 | } 34 | 35 | public function bootingPackage() 36 | { 37 | if ($this->app->runningInConsole()) { 38 | $this->publishes([ 39 | $this->package->basePath('/../stubs/database/seeders/RolesAndPermissionsSeeder.stub') => database_path('seeders/RolesAndPermissionsSeeder.php'), 40 | ], $this->package->shortName() . '-seeders'); 41 | 42 | $this->publishes([ 43 | $this->package->basePath('/../stubs/FilamentSpatieRolesPermissionsServiceProvider.stub') => app_path('Providers/FilamentSpatieRolesPermissionsServiceProvider.php'), 44 | ], $this->package->shortName() . '-provider'); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reksmey/filament-spatie-roles-permissions", 3 | "description": "filament-spatie-roles-permissions", 4 | "type": "library-filament-plugin", 5 | "homepage": "https://github.com/reksmey/filament-spatie-roles-permissions", 6 | "keywords": [ 7 | "reksmey", 8 | "filament-spatie-roles-permissions" 9 | ], 10 | "authors": [ 11 | { 12 | "name": "Reaksmey SREY", 13 | "email": "reksmeysrey@gmail.com", 14 | "role": "Developer" 15 | } 16 | ], 17 | "require": { 18 | "php": "^8.0", 19 | "filament/filament": "^2.0", 20 | "illuminate/support": "^8.0", 21 | "spatie/laravel-permission": "^5.4" 22 | }, 23 | "require-dev": { 24 | "nunomaduro/collision": "^5.10", 25 | "nunomaduro/larastan": "^1.0", 26 | "orchestra/testbench": "^6.22", 27 | "pestphp/pest": "^1.21", 28 | "pestphp/pest-plugin-laravel": "^1.1", 29 | "phpstan/extension-installer": "^1.1", 30 | "phpstan/phpstan-deprecation-rules": "^1.0", 31 | "phpstan/phpstan-phpunit": "^1.0", 32 | "phpunit/phpunit": "^9.5", 33 | "spatie/laravel-ray": "^1.26" 34 | }, 35 | "autoload": { 36 | "psr-4": { 37 | "Reksmey\\FilamentSpatieRolesPermissions\\": "src" 38 | } 39 | }, 40 | "autoload-dev": { 41 | "psr-4": { 42 | "Reksmey\\FilamentSpatieRolesPermissions\\Tests\\": "tests" 43 | } 44 | }, 45 | "scripts": { 46 | "test": "vendor/bin/pest", 47 | "test-coverage": "vendor/bin/phpunit --coverage-html coverage", 48 | "analyse": "vendor/bin/phpstan analyse" 49 | }, 50 | "config": { 51 | "sort-packages": true, 52 | "allow-plugins": { 53 | "pestphp/pest-plugin": true, 54 | "phpstan/extension-installer": true 55 | } 56 | }, 57 | "extra": { 58 | "laravel": { 59 | "providers": [ 60 | "Reksmey\\FilamentSpatieRolesPermissions\\FilamentSpatieRolesPermissionsServiceProvider" 61 | ], 62 | "aliases": { 63 | "FilamentSpatieRolesPermissions": "Reksmey\\FilamentSpatieRolesPermissions\\FilamentSpatieRolesPermissionsFacade" 64 | } 65 | } 66 | }, 67 | "license": "MIT", 68 | "minimum-stability": "dev" 69 | } 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Screenshot 2 | 3 | ![image](https://user-images.githubusercontent.com/35394133/147733275-4de97f59-243c-4b74-958b-534dd70ae4a9.png) 4 | 5 | # Description 6 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/reksmey/filament-spatie-roles-permissions.svg?style=flat-square)](https://packagist.org/packages/reksmey/filament-spatie-roles-permissions) 7 | [![Total Downloads](https://img.shields.io/packagist/dt/reskmeysrey/filament-spatie-roles-permissions.svg?style=flat-square)](https://packagist.org/packages/reskmey/filament-spatie-roles-permissions) 8 | ![GitHub Actions](https://github.com/reksmeysrey/filament-spatie-roles-permissions/actions/workflows/main.yml/badge.svg) 9 | 10 | This plugin is built on top of [Spatie's Permission](https://spatie.be/docs/laravel-permission/v5/introduction) package. 11 | 12 | ## Installation 13 | 14 | You can install the package via composer: 15 | 16 | ```bash 17 | composer require reksmey/filament-spatie-roles-permissions 18 | ``` 19 | 20 | Since the package depends on [Spatie's Permission](https://spatie.be/docs/laravel-permission/v5/introduction) package. You have to publish the migrations by running: 21 | ```bash 22 | php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" 23 | ``` 24 | 25 | Now you should add any other configurations needed for the Spatie-Permission package. 26 | 27 | ## Usage 28 | 29 | You can add this to your *form* method in your UserResource 30 | 31 | ```php 32 | return $form->schema([ 33 | ... 34 | BelongsToManyMultiSelect::make('roles')->relationship('roles', 'name') 35 | ... 36 | ... 37 | ]) 38 | 39 | ``` 40 | 41 | ## Advance Usage 42 | You can publish permissions seeders in additional permissions. Sometimes you may add more action such as download-pdf, export, etc. 43 | 44 | ```php 45 | php artisan vendor:publish --tag=filament-spatie-roles-and-permissions-seeders 46 | 47 | Then 48 | 49 | php artisan db:seed --class=RolesAndPermissionsSeeder 50 | 51 | ``` 52 | 53 | ## Customize RoleResource 54 | - publish service provider 55 | ```php 56 | php artisan vendor:publish --tag=filament-spatie-roles-and-permissions-provider 57 | ``` 58 | - add the package to the `extra.laravel.dont-discover` key in `composer.json`, e.g. 59 | ```json 60 | "extra": { 61 | "laravel": { 62 | "dont-discover": [ 63 | "reksmey/filament-spatie-roles-permissions" 64 | ] 65 | } 66 | } 67 | ``` 68 | - publish RoleResource 69 | ```php 70 | php artisan filament-spatie-roles-permissions:publish-role-resource 71 | ``` 72 | - you can customize RoleResource in App\Filament 73 | ##### [For authorization, Filament will observe any model policies that are registered in your app](https://filamentadmin.com/docs/2.x/admin/resources#authorization) 74 | 75 | Hope you enjoy it ❤️ 76 | 77 | ### Security 78 | 79 | If you discover any security related issues, please create an issue. 80 | 81 | ## Credits 82 | 83 | - [bezhanSalleh](https://gist.github.com/bezhanSalleh/bda7d8db237a0c45549b63dafaa387c1) 84 | 85 | ## License 86 | 87 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 88 | -------------------------------------------------------------------------------- /src/Resources/RoleResource.php: -------------------------------------------------------------------------------- 1 | schema([ 49 | Forms\Components\Grid::make() 50 | ->schema([ 51 | Forms\Components\Card::make() 52 | ->schema([ 53 | Forms\Components\TextInput::make('name') 54 | ->label(__('filament-spatie-roles-and-permissions::filament-spatie.field.name')) 55 | ->required() 56 | ->maxLength(255) 57 | ->unique( 58 | ignorable: fn(?Role $record): ?Role => $record, 59 | callback: function (Unique $rule, callable $get): Unique { 60 | $guardName = $get('guard_name') ?? config('auth.defaults.guard'); 61 | 62 | return $rule 63 | ->where('guard_name', $guardName); 64 | } 65 | ), 66 | Forms\Components\Select::make('guard_name') 67 | ->label(__('filament-spatie-roles-and-permissions::filament-spatie.field.guard_name')) 68 | ->nullable() 69 | ->options(function (): array { 70 | $guards = array_keys(config('auth.guards', [])); 71 | 72 | return array_combine($guards, $guards); 73 | }) 74 | ->default(config('auth.defaults.guard')), 75 | Forms\Components\Toggle::make('select_all') 76 | ->label(__('filament-spatie-roles-and-permissions::filament-spatie.field.select_all')) 77 | ->helperText(__('filament-spatie-roles-and-permissions::filament-spatie.message.select_all')) 78 | ->onIcon('heroicon-s-shield-check') 79 | ->offIcon('heroicon-s-shield-exclamation') 80 | ->reactive() 81 | ->afterStateUpdated(function (Closure $set, $state) { 82 | foreach (FilamentSpatieRolesPermissionsFacade::getEntities() as $entity) { 83 | $set($entity, $state); 84 | 85 | foreach (FilamentSpatieRolesPermissionsFacade::getPermissions() as $perm) { 86 | $set($entity . '_' . $perm, $state); 87 | } 88 | } 89 | }) 90 | ]), 91 | ]), 92 | Forms\Components\Grid::make([ 93 | 'sm' => 2, 94 | 'lg' => 3, 95 | ]) 96 | ->schema(static::getEntitySchema()) 97 | ->columns([ 98 | 'sm' => 2, 99 | 'lg' => 3 100 | ]) 101 | ]); 102 | } 103 | 104 | protected static function getEntitySchema(): array 105 | { 106 | return collect(FilamentSpatieRolesPermissionsFacade::getEntities())->reduce(function (array $entities, string $entity) { 107 | $entities[] = Forms\Components\Card::make() 108 | ->schema([ 109 | Forms\Components\Toggle::make($entity) 110 | ->label(__($entity)) 111 | ->onIcon('heroicon-s-lock-open') 112 | ->offIcon('heroicon-s-lock-closed') 113 | ->reactive() 114 | ->afterStateUpdated(function (Closure $set, Closure $get, bool $state) use ($entity) { 115 | collect(FilamentSpatieRolesPermissionsFacade::getPermissions()) 116 | ->each(function (string $permission) use ($set, $entity, $state) { 117 | $set($entity . '_' . $permission, $state); 118 | }); 119 | 120 | if (!$state) { 121 | $set('select_all', false); 122 | } 123 | 124 | static::freshSelectAll($get, $set); 125 | }), 126 | Forms\Components\Fieldset::make('Permissions') 127 | ->label(__('filament-spatie-roles-and-permissions::filament-spatie.field.permissions')) 128 | ->extraAttributes(['class' => 'text-primary-600', 'style' => 'border-color:var(--primary)']) 129 | ->columns([ 130 | 'default' => 2, 131 | 'xl' => 3 132 | ]) 133 | ->schema(static::getPermissionsSchema($entity)) 134 | ]) 135 | ->columns(2) 136 | ->columnSpan(1); 137 | return $entities; 138 | }, []); 139 | } 140 | 141 | protected static function getPermissionsSchema(string $entity): array 142 | { 143 | return collect(FilamentSpatieRolesPermissionsFacade::getPermissions())->reduce(function (array $permissions, string $permission) use ($entity) { 144 | $permissions[] = Forms\Components\Checkbox::make($entity . '_' . $permission) 145 | ->label(__($permission)) 146 | ->extraAttributes(['class' => 'text-primary-600']) 147 | ->afterStateHydrated(function (Closure $set, Closure $get, ?Role $record) use ($entity, $permission) { 148 | if (is_null($record)) return; 149 | 150 | $existed = $record->checkPermissionTo($entity . '_' . $permission); 151 | $existed_Module = $record->checkPermissionTo($entity); 152 | 153 | if ($existed) { 154 | $set($entity . '_' . $permission, $existed); 155 | } 156 | 157 | if ($existed_Module) { 158 | $set($entity, true); 159 | } else { 160 | $set($entity, false); 161 | $set('select_all', false); 162 | } 163 | 164 | static::freshSelectAll($get, $set); 165 | }) 166 | ->reactive() 167 | ->afterStateUpdated(function (Closure $set, Closure $get, bool $state) use ($entity) { 168 | $permissionStates = []; 169 | foreach (FilamentSpatieRolesPermissionsFacade::getPermissions() as $perm) { 170 | $permissionStates [] = $get($entity . '_' . $perm); 171 | } 172 | 173 | if (in_array(false, $permissionStates, true) === false) { 174 | $set($entity, true); // if all permissions true => turn toggle on 175 | } 176 | 177 | if (in_array(false, $permissionStates, true) === true) { 178 | $set($entity, false); // if even one false => turn toggle off 179 | } 180 | 181 | if (!$state) { 182 | $set($entity, false); 183 | $set('select_all', false); 184 | } 185 | 186 | static::freshSelectAll($get, $set); 187 | }); 188 | return $permissions; 189 | }, []); 190 | } 191 | 192 | protected static function freshSelectAll(Closure $get, Closure $set): void 193 | { 194 | $entityStates = collect(FilamentSpatieRolesPermissionsFacade::getEntities()) 195 | ->map(fn(string $entity): bool => (bool)$get($entity)); 196 | 197 | if ($entityStates->containsStrict(false) === false) { 198 | $set('select_all', true); // if all toggles on => turn select_all on 199 | } 200 | 201 | if ($entityStates->containsStrict(false) === true) { 202 | $set('select_all', false); // if even one toggle off => turn select_all off 203 | } 204 | } 205 | 206 | public static function table(Table $table): Table 207 | { 208 | return $table 209 | ->columns([ 210 | Columns\TextColumn::make('name') 211 | ->label(__('filament-spatie-roles-and-permissions::filament-spatie.field.name')) 212 | ->sortable() 213 | ->searchable(), 214 | Columns\TextColumn::make('created_at') 215 | ->label(__('filament-spatie-roles-and-permissions::filament-spatie.field.created_at')) 216 | ->sortable() 217 | ->searchable(), 218 | Columns\TextColumn::make('updated_at') 219 | ->label(__('filament-spatie-roles-and-permissions::filament-spatie.field.updated_at')) 220 | ->sortable() 221 | ->searchable(), 222 | ]) 223 | ->filters([ 224 | // 225 | ]); 226 | } 227 | 228 | public static function getRelations(): array 229 | { 230 | return []; 231 | } 232 | 233 | public static function getPages(): array 234 | { 235 | return [ 236 | 'index' => Pages\ListRoles::route('/'), 237 | 'create' => Pages\CreateRole::route('/create'), 238 | 'edit' => Pages\EditRole::route('/{record}/edit'), 239 | ]; 240 | } 241 | } 242 | --------------------------------------------------------------------------------