mirror of
https://github.com/AnimeThemes/animethemes-server.git
synced 2026-07-11 01:24:46 +02:00
feat: prohibitions (#1053)
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Events\Auth\Prohibition;
|
||||
|
||||
use App\Constants\Config\ServiceConstants;
|
||||
use App\Contracts\Events\DiscordMessageEvent;
|
||||
use App\Contracts\Events\NotifiesUsersEvent;
|
||||
use App\Enums\Discord\EmbedColor;
|
||||
use App\Models\Auth\Prohibition;
|
||||
use App\Models\Auth\User;
|
||||
use App\Notifications\Auth\ProhibitionOrSanctionNotification;
|
||||
use DateTimeInterface;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Kyrch\Prohibition\Events\ModelProhibitionTriggered as BaseModelProhibitionTriggered;
|
||||
use NotificationChannels\Discord\DiscordMessage;
|
||||
|
||||
class ModelProhibitionTriggered extends BaseModelProhibitionTriggered implements DiscordMessageEvent, NotifiesUsersEvent
|
||||
{
|
||||
/**
|
||||
* @param User $model
|
||||
* @param Prohibition $prohibition
|
||||
* @param User $moderator
|
||||
*/
|
||||
public function __construct(
|
||||
public Model $model,
|
||||
public mixed $prohibition,
|
||||
public ?DateTimeInterface $expiresAt = null,
|
||||
public ?string $reason = null,
|
||||
public ?Model $moderator = null,
|
||||
) {
|
||||
parent::__construct($model, $prohibition, $expiresAt, $reason, $moderator);
|
||||
}
|
||||
|
||||
public function getDiscordMessage(): DiscordMessage
|
||||
{
|
||||
return DiscordMessage::create('', [
|
||||
'description' => "Prohibition '**{$this->prohibition->name}**' triggered for user '**{$this->model->getName()}**'. Reason: {$this->reason}",
|
||||
'color' => EmbedColor::GREEN->value,
|
||||
]);
|
||||
}
|
||||
|
||||
public function getDiscordChannel(): string
|
||||
{
|
||||
return Config::get(ServiceConstants::ADMIN_DISCORD_CHANNEL_QUALIFIED);
|
||||
}
|
||||
|
||||
public function shouldSendDiscordMessage(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function notify(): void
|
||||
{
|
||||
$this->model->notify(new ProhibitionOrSanctionNotification($this->prohibition, $this->reason, $this->expiresAt));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Events\Auth\Prohibition;
|
||||
|
||||
use App\Constants\Config\ServiceConstants;
|
||||
use App\Contracts\Events\DiscordMessageEvent;
|
||||
use App\Contracts\Events\NotifiesUsersEvent;
|
||||
use App\Enums\Discord\EmbedColor;
|
||||
use App\Models\Auth\Sanction;
|
||||
use App\Models\Auth\User;
|
||||
use App\Notifications\Auth\ProhibitionOrSanctionNotification;
|
||||
use DateTimeInterface;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Kyrch\Prohibition\Events\ModelSanctionTriggered as BaseModelSanctionTriggered;
|
||||
use NotificationChannels\Discord\DiscordMessage;
|
||||
|
||||
class ModelSanctionTriggered extends BaseModelSanctionTriggered implements DiscordMessageEvent, NotifiesUsersEvent
|
||||
{
|
||||
/**
|
||||
* @param User $model
|
||||
* @param Sanction $sanction
|
||||
* @param User $moderator
|
||||
*/
|
||||
public function __construct(
|
||||
public Model $model,
|
||||
public mixed $sanction,
|
||||
public ?DateTimeInterface $expiresAt = null,
|
||||
public ?string $reason = null,
|
||||
public ?Model $moderator = null,
|
||||
) {
|
||||
parent::__construct($model, $sanction, $expiresAt, $reason, $moderator);
|
||||
}
|
||||
|
||||
public function getDiscordMessage(): DiscordMessage
|
||||
{
|
||||
return DiscordMessage::create('', [
|
||||
'description' => "Sanction '**{$this->sanction->name}**' triggered for user '**{$this->model->getName()}**'. Reason: {$this->reason}",
|
||||
'color' => EmbedColor::GREEN->value,
|
||||
]);
|
||||
}
|
||||
|
||||
public function getDiscordChannel(): string
|
||||
{
|
||||
return Config::get(ServiceConstants::ADMIN_DISCORD_CHANNEL_QUALIFIED);
|
||||
}
|
||||
|
||||
public function shouldSendDiscordMessage(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function notify(): void
|
||||
{
|
||||
$this->model->notify(new ProhibitionOrSanctionNotification($this->sanction, $this->reason, $this->expiresAt));
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ namespace App\Filament\Actions\Models\Auth\Permission;
|
||||
|
||||
use App\Filament\Actions\BaseAction;
|
||||
use App\Filament\Components\Fields\Select;
|
||||
use App\Filament\Resources\Auth\Role as RoleResource;
|
||||
use App\Models\Auth\Permission;
|
||||
use App\Models\Auth\Role;
|
||||
use Filament\Schemas\Schema;
|
||||
@@ -14,7 +15,7 @@ use Illuminate\Support\Arr;
|
||||
|
||||
class GiveRoleAction extends BaseAction
|
||||
{
|
||||
final public const string FIELD_ROLE = 'role';
|
||||
final public const string FIELD_ROLES = 'roles';
|
||||
|
||||
public static function getDefaultName(): ?string
|
||||
{
|
||||
@@ -27,6 +28,8 @@ class GiveRoleAction extends BaseAction
|
||||
|
||||
$this->label(__('filament.actions.permission.give_role.name'));
|
||||
|
||||
$this->icon(RoleResource::getNavigationIcon());
|
||||
|
||||
$this->action(fn (Permission $record, array $data) => $this->handle($record, $data));
|
||||
}
|
||||
|
||||
@@ -35,9 +38,9 @@ class GiveRoleAction extends BaseAction
|
||||
*/
|
||||
public function handle(Permission $permission, array $data): void
|
||||
{
|
||||
$role = Role::findById(intval(Arr::get($data, self::FIELD_ROLE)));
|
||||
$roles = Arr::get($data, self::FIELD_ROLES);
|
||||
|
||||
$permission->assignRole($role);
|
||||
$permission->assignRole($roles);
|
||||
}
|
||||
|
||||
public function getSchema(Schema $schema): Schema
|
||||
@@ -51,9 +54,10 @@ class GiveRoleAction extends BaseAction
|
||||
|
||||
return $schema
|
||||
->components([
|
||||
Select::make(self::FIELD_ROLE)
|
||||
->label(__('filament.resources.singularLabel.role'))
|
||||
Select::make(self::FIELD_ROLES)
|
||||
->label(__('filament.resources.label.roles'))
|
||||
->searchable()
|
||||
->multiple()
|
||||
->required()
|
||||
->options($roles),
|
||||
]);
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace App\Filament\Actions\Models\Auth\Permission;
|
||||
|
||||
use App\Filament\Actions\BaseAction;
|
||||
use App\Filament\Components\Fields\Select;
|
||||
use App\Filament\Resources\Auth\Role as RoleResource;
|
||||
use App\Models\Auth\Permission;
|
||||
use App\Models\Auth\Role;
|
||||
use Filament\Schemas\Schema;
|
||||
@@ -14,7 +15,7 @@ use Illuminate\Support\Arr;
|
||||
|
||||
class RevokeRoleAction extends BaseAction
|
||||
{
|
||||
final public const string FIELD_ROLE = 'role';
|
||||
final public const string FIELD_ROLES = 'roles';
|
||||
|
||||
public static function getDefaultName(): ?string
|
||||
{
|
||||
@@ -27,6 +28,8 @@ class RevokeRoleAction extends BaseAction
|
||||
|
||||
$this->label(__('filament.actions.permission.revoke_role.name'));
|
||||
|
||||
$this->icon(RoleResource::getNavigationIcon());
|
||||
|
||||
$this->action(fn (Permission $record, array $data) => $this->handle($record, $data));
|
||||
}
|
||||
|
||||
@@ -35,9 +38,9 @@ class RevokeRoleAction extends BaseAction
|
||||
*/
|
||||
public function handle(Permission $permission, array $data): void
|
||||
{
|
||||
$role = Role::findById(intval(Arr::get($data, self::FIELD_ROLE)));
|
||||
$roles = Arr::get($data, self::FIELD_ROLES);
|
||||
|
||||
$permission->removeRole($role);
|
||||
$permission->removeRole($roles);
|
||||
}
|
||||
|
||||
public function getSchema(Schema $schema): Schema
|
||||
@@ -51,9 +54,10 @@ class RevokeRoleAction extends BaseAction
|
||||
|
||||
return $schema
|
||||
->components([
|
||||
Select::make(self::FIELD_ROLE)
|
||||
->label(__('filament.resources.singularLabel.role'))
|
||||
Select::make(self::FIELD_ROLES)
|
||||
->label(__('filament.resources.label.roles'))
|
||||
->searchable()
|
||||
->multiple()
|
||||
->required()
|
||||
->options($roles),
|
||||
]);
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace App\Filament\Actions\Models\Auth\Role;
|
||||
|
||||
use App\Filament\Actions\BaseAction;
|
||||
use App\Filament\Components\Fields\Select;
|
||||
use App\Filament\Resources\Auth\Permission as PermissionResource;
|
||||
use App\Models\Auth\Permission;
|
||||
use App\Models\Auth\Role;
|
||||
use Filament\Schemas\Schema;
|
||||
@@ -14,7 +15,7 @@ use Illuminate\Support\Arr;
|
||||
|
||||
class GivePermissionAction extends BaseAction
|
||||
{
|
||||
final public const string FIELD_PERMISSION = 'permission';
|
||||
final public const string FIELD_PERMISSIONS = 'permissions';
|
||||
|
||||
public static function getDefaultName(): ?string
|
||||
{
|
||||
@@ -27,6 +28,8 @@ class GivePermissionAction extends BaseAction
|
||||
|
||||
$this->label(__('filament.actions.role.give_permission.name'));
|
||||
|
||||
$this->icon(PermissionResource::getNavigationIcon());
|
||||
|
||||
$this->action(fn (Role $record, array $data) => $this->handle($record, $data));
|
||||
}
|
||||
|
||||
@@ -37,9 +40,9 @@ class GivePermissionAction extends BaseAction
|
||||
*/
|
||||
public function handle(Role $role, array $data): void
|
||||
{
|
||||
$permission = Permission::findById(intval(Arr::get($data, self::FIELD_PERMISSION)));
|
||||
$permissions = Arr::get($data, self::FIELD_PERMISSIONS);
|
||||
|
||||
$role->givePermissionTo($permission);
|
||||
$role->givePermissionTo($permissions);
|
||||
}
|
||||
|
||||
public function getSchema(Schema $schema): Schema
|
||||
@@ -53,10 +56,11 @@ class GivePermissionAction extends BaseAction
|
||||
|
||||
return $schema
|
||||
->components([
|
||||
Select::make(self::FIELD_PERMISSION)
|
||||
->label(__('filament.resources.singularLabel.permission'))
|
||||
Select::make(self::FIELD_PERMISSIONS)
|
||||
->label(__('filament.resources.label.permissions'))
|
||||
->searchable()
|
||||
->required()
|
||||
->multiple()
|
||||
->options($permissions),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace App\Filament\Actions\Models\Auth\Role;
|
||||
|
||||
use App\Filament\Actions\BaseAction;
|
||||
use App\Filament\Components\Fields\Select;
|
||||
use App\Filament\Resources\Auth\Permission as PermissionResource;
|
||||
use App\Models\Auth\Permission;
|
||||
use App\Models\Auth\Role;
|
||||
use Filament\Schemas\Schema;
|
||||
@@ -14,7 +15,7 @@ use Illuminate\Support\Arr;
|
||||
|
||||
class RevokePermissionAction extends BaseAction
|
||||
{
|
||||
final public const string FIELD_PERMISSION = 'permission';
|
||||
final public const string FIELD_PERMISSIONS = 'permissions';
|
||||
|
||||
public static function getDefaultName(): ?string
|
||||
{
|
||||
@@ -27,6 +28,8 @@ class RevokePermissionAction extends BaseAction
|
||||
|
||||
$this->label(__('filament.actions.role.revoke_permission.name'));
|
||||
|
||||
$this->icon(PermissionResource::getNavigationIcon());
|
||||
|
||||
$this->action(fn (Role $record, array $data) => $this->handle($record, $data));
|
||||
}
|
||||
|
||||
@@ -37,9 +40,9 @@ class RevokePermissionAction extends BaseAction
|
||||
*/
|
||||
public function handle(Role $role, array $data): void
|
||||
{
|
||||
$permission = Permission::findById(intval(Arr::get($data, self::FIELD_PERMISSION)));
|
||||
$permissions = Arr::get($data, self::FIELD_PERMISSIONS);
|
||||
|
||||
$role->revokePermissionTo($permission);
|
||||
$role->revokePermissionTo($permissions);
|
||||
}
|
||||
|
||||
public function getSchema(Schema $schema): Schema
|
||||
@@ -53,9 +56,10 @@ class RevokePermissionAction extends BaseAction
|
||||
|
||||
return $schema
|
||||
->components([
|
||||
Select::make(self::FIELD_PERMISSION)
|
||||
->label(__('filament.resources.singularLabel.permission'))
|
||||
Select::make(self::FIELD_PERMISSIONS)
|
||||
->label(__('filament.resources.label.permissions'))
|
||||
->searchable()
|
||||
->multiple()
|
||||
->required()
|
||||
->options($permissions),
|
||||
]);
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Actions\Models\Auth\Sanction;
|
||||
|
||||
use App\Filament\Actions\BaseAction;
|
||||
use App\Filament\Components\Fields\Select;
|
||||
use App\Filament\Resources\Auth\Prohibition as ProhibitionResource;
|
||||
use App\Models\Auth\Prohibition;
|
||||
use App\Models\Auth\Sanction;
|
||||
use Filament\Schemas\Schema;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class GiveProhibitionAction extends BaseAction
|
||||
{
|
||||
final public const string FIELD_PROHIBITIONS = 'prohibitions';
|
||||
|
||||
public static function getDefaultName(): ?string
|
||||
{
|
||||
return 'sanction-give-prohibition';
|
||||
}
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->label(__('filament.actions.sanction.give_prohibition.name'));
|
||||
|
||||
$this->icon(ProhibitionResource::getNavigationIcon());
|
||||
|
||||
$this->action(fn (Sanction $record, array $data) => $this->handle($record, $data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the action on the given model.
|
||||
*
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public function handle(Sanction $sanction, array $data): void
|
||||
{
|
||||
$prohibitions = Arr::get($data, self::FIELD_PROHIBITIONS);
|
||||
|
||||
$sanction->prohibitions()->attach($prohibitions);
|
||||
}
|
||||
|
||||
public function getSchema(Schema $schema): Schema
|
||||
{
|
||||
$prohibitions = Prohibition::query()
|
||||
->whereDoesntHave(Prohibition::RELATION_SANCTIONS, fn (Builder $query) => $query->whereKey($this->getRecord()->getKey()))
|
||||
->get([Prohibition::ATTRIBUTE_ID, Prohibition::ATTRIBUTE_NAME])
|
||||
->keyBy(Prohibition::ATTRIBUTE_ID)
|
||||
->map(fn (Prohibition $prohibition) => $prohibition->name)
|
||||
->toArray();
|
||||
|
||||
return $schema
|
||||
->components([
|
||||
Select::make(self::FIELD_PROHIBITIONS)
|
||||
->label(__('filament.resources.label.prohibitions'))
|
||||
->searchable()
|
||||
->required()
|
||||
->multiple()
|
||||
->options($prohibitions),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Actions\Models\Auth\Sanction;
|
||||
|
||||
use App\Filament\Actions\BaseAction;
|
||||
use App\Filament\Components\Fields\Select;
|
||||
use App\Filament\Resources\Auth\Prohibition as ProhibitionResource;
|
||||
use App\Models\Auth\Prohibition;
|
||||
use App\Models\Auth\Sanction;
|
||||
use Filament\Schemas\Schema;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class RevokeProhibitionAction extends BaseAction
|
||||
{
|
||||
final public const string FIELD_PROHIBITIONS = 'prohibitions';
|
||||
|
||||
public static function getDefaultName(): ?string
|
||||
{
|
||||
return 'sanction-revoke-prohibition';
|
||||
}
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->label(__('filament.actions.sanction.revoke_prohibition.name'));
|
||||
|
||||
$this->icon(ProhibitionResource::getNavigationIcon());
|
||||
|
||||
$this->action(fn (Sanction $record, array $data) => $this->handle($record, $data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the action on the given model.
|
||||
*
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public function handle(Sanction $sanction, array $data): void
|
||||
{
|
||||
$prohibitions = Arr::get($data, self::FIELD_PROHIBITIONS);
|
||||
|
||||
$sanction->prohibitions()->detach($prohibitions);
|
||||
}
|
||||
|
||||
public function getSchema(Schema $schema): Schema
|
||||
{
|
||||
$prohibitions = Prohibition::query()
|
||||
->whereHas(Prohibition::RELATION_SANCTIONS, fn (Builder $query) => $query->whereKey($this->getRecord()->getKey()))
|
||||
->get([Prohibition::ATTRIBUTE_ID, Prohibition::ATTRIBUTE_NAME])
|
||||
->keyBy(Prohibition::ATTRIBUTE_ID)
|
||||
->map(fn (Prohibition $prohibition) => $prohibition->name)
|
||||
->toArray();
|
||||
|
||||
return $schema
|
||||
->components([
|
||||
Select::make(self::FIELD_PROHIBITIONS)
|
||||
->label(__('filament.resources.label.prohibitions'))
|
||||
->searchable()
|
||||
->multiple()
|
||||
->required()
|
||||
->options($prohibitions),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ namespace App\Filament\Actions\Models\Auth\User;
|
||||
|
||||
use App\Filament\Actions\BaseAction;
|
||||
use App\Filament\Components\Fields\Select;
|
||||
use App\Filament\Resources\Auth\Permission as PermissionResource;
|
||||
use App\Models\Auth\Permission;
|
||||
use App\Models\Auth\User;
|
||||
use Filament\Schemas\Schema;
|
||||
@@ -27,6 +28,8 @@ class GivePermissionAction extends BaseAction
|
||||
|
||||
$this->label(__('filament.actions.user.give_permission.name'));
|
||||
|
||||
$this->icon(PermissionResource::getNavigationIcon());
|
||||
|
||||
$this->action(fn (User $record, array $data) => $this->handle($record, $data));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Actions\Models\Auth\User;
|
||||
|
||||
use App\Events\Auth\Prohibition\ModelProhibitionTriggered;
|
||||
use App\Filament\Actions\BaseAction;
|
||||
use App\Filament\Components\Fields\Select;
|
||||
use App\Filament\Components\Fields\TextInput;
|
||||
use App\Filament\Resources\Auth\Prohibition as ProhibitionResource;
|
||||
use App\Models\Auth\Prohibition;
|
||||
use App\Models\Auth\User;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Schemas\Schema;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Date;
|
||||
|
||||
class GiveProhibitionAction extends BaseAction
|
||||
{
|
||||
final public const string FIELD_PROHIBITION = 'prohibition';
|
||||
|
||||
public static function getDefaultName(): ?string
|
||||
{
|
||||
return 'user-give-prohibition';
|
||||
}
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->label(__('filament.actions.user.give_prohibition.name'));
|
||||
|
||||
$this->icon(ProhibitionResource::getNavigationIcon());
|
||||
|
||||
$this->action(fn (User $record, array $data) => $this->handle($record, $data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the action on the given model.
|
||||
*
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public function handle(User $user, array $data): void
|
||||
{
|
||||
/** @var Prohibition $prohibition */
|
||||
$prohibition = Prohibition::query()->find(intval(Arr::get($data, self::FIELD_PROHIBITION)));
|
||||
|
||||
$reason = Arr::get($data, 'reason');
|
||||
|
||||
$expiresAt = Date::createFromFormat('Y-m-d H:i:s', Arr::get($data, 'expires_at'));
|
||||
|
||||
$user->prohibit($prohibition, $expiresAt, $reason, Auth::user());
|
||||
|
||||
event(new ModelProhibitionTriggered($user, $prohibition, $expiresAt, $reason, Auth::user()));
|
||||
}
|
||||
|
||||
public function getSchema(Schema $schema): Schema
|
||||
{
|
||||
$prohibitions = Prohibition::query()
|
||||
->get([Prohibition::ATTRIBUTE_ID, Prohibition::ATTRIBUTE_NAME])
|
||||
->keyBy(Prohibition::ATTRIBUTE_ID)
|
||||
->map(fn (Prohibition $prohibition) => $prohibition->name)
|
||||
->toArray();
|
||||
|
||||
return $schema
|
||||
->components([
|
||||
Select::make(self::FIELD_PROHIBITION)
|
||||
->label(__('filament.resources.singularLabel.prohibition'))
|
||||
->searchable()
|
||||
->required()
|
||||
->options($prohibitions),
|
||||
|
||||
DateTimePicker::make('expires_at')
|
||||
->label(__('filament.actions.user.give_prohibition.expires_at.name'))
|
||||
->helperText(__('filament.actions.user.give_prohibition.expires_at.help'))
|
||||
->nullable(),
|
||||
|
||||
TextInput::make('reason')
|
||||
->label(__('filament.actions.user.give_prohibition.reason.name'))
|
||||
->helperText(__('filament.actions.user.give_prohibition.reason.help'))
|
||||
->required(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ namespace App\Filament\Actions\Models\Auth\User;
|
||||
|
||||
use App\Filament\Actions\BaseAction;
|
||||
use App\Filament\Components\Fields\Select;
|
||||
use App\Filament\Resources\Auth\Role as RoleResource;
|
||||
use App\Models\Auth\Role;
|
||||
use App\Models\Auth\User;
|
||||
use Filament\Schemas\Schema;
|
||||
@@ -27,6 +28,8 @@ class GiveRoleAction extends BaseAction
|
||||
|
||||
$this->label(__('filament.actions.user.give_role.name'));
|
||||
|
||||
$this->icon(RoleResource::getNavigationIcon());
|
||||
|
||||
$this->action(fn (User $record, array $data) => $this->handle($record, $data));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Actions\Models\Auth\User;
|
||||
|
||||
use App\Events\Auth\Prohibition\ModelSanctionTriggered;
|
||||
use App\Filament\Actions\BaseAction;
|
||||
use App\Filament\Components\Fields\Select;
|
||||
use App\Filament\Components\Fields\TextInput;
|
||||
use App\Filament\Resources\Auth\Sanction as SanctionResource;
|
||||
use App\Models\Auth\Sanction;
|
||||
use App\Models\Auth\User;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Schemas\Schema;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Date;
|
||||
|
||||
class GiveSanctionAction extends BaseAction
|
||||
{
|
||||
final public const string FIELD_SANCTION = 'sanction';
|
||||
|
||||
public static function getDefaultName(): ?string
|
||||
{
|
||||
return 'user-give-sanction';
|
||||
}
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->label(__('filament.actions.user.give_sanction.name'));
|
||||
|
||||
$this->icon(SanctionResource::getNavigationIcon());
|
||||
|
||||
$this->action(fn (User $record, array $data) => $this->handle($record, $data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the action on the given model.
|
||||
*
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public function handle(User $user, array $data): void
|
||||
{
|
||||
$sanction = Sanction::query()->find(intval(Arr::get($data, self::FIELD_SANCTION)));
|
||||
|
||||
$reason = Arr::get($data, 'reason');
|
||||
|
||||
$expiresAt = Date::createFromFormat('Y-m-d H:i:s', Arr::get($data, 'expires_at'));
|
||||
|
||||
$user->applySanction($sanction, $expiresAt, $reason, Auth::user());
|
||||
|
||||
event(new ModelSanctionTriggered($user, $sanction, $expiresAt, $reason, Auth::user()));
|
||||
}
|
||||
|
||||
public function getSchema(Schema $schema): Schema
|
||||
{
|
||||
$sanctions = Sanction::query()
|
||||
->get([Sanction::ATTRIBUTE_ID, Sanction::ATTRIBUTE_NAME])
|
||||
->keyBy(Sanction::ATTRIBUTE_ID)
|
||||
->map(fn (Sanction $sanction) => $sanction->name)
|
||||
->toArray();
|
||||
|
||||
return $schema
|
||||
->components([
|
||||
Select::make(self::FIELD_SANCTION)
|
||||
->label(__('filament.resources.singularLabel.sanction'))
|
||||
->searchable()
|
||||
->required()
|
||||
->options($sanctions),
|
||||
|
||||
DateTimePicker::make('expires_at')
|
||||
->label(__('filament.actions.user.give_sanction.expires_at.name'))
|
||||
->helperText(__('filament.actions.user.give_sanction.expires_at.help'))
|
||||
->nullable(),
|
||||
|
||||
TextInput::make('reason')
|
||||
->label(__('filament.actions.user.give_sanction.reason.name'))
|
||||
->helperText(__('filament.actions.user.give_sanction.reason.help'))
|
||||
->required(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ namespace App\Filament\Actions\Models\Auth\User;
|
||||
|
||||
use App\Filament\Actions\BaseAction;
|
||||
use App\Filament\Components\Fields\Select;
|
||||
use App\Filament\Resources\Auth\Permission as PermissionResource;
|
||||
use App\Models\Auth\Permission;
|
||||
use App\Models\Auth\User;
|
||||
use Filament\Schemas\Schema;
|
||||
@@ -27,6 +28,8 @@ class RevokePermissionAction extends BaseAction
|
||||
|
||||
$this->label(__('filament.actions.user.revoke_permission.name'));
|
||||
|
||||
$this->icon(PermissionResource::getNavigationIcon());
|
||||
|
||||
$this->action(fn (User $record, array $data) => $this->handle($record, $data));
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace App\Filament\Actions\Models\Auth\User;
|
||||
|
||||
use App\Filament\Actions\BaseAction;
|
||||
use App\Filament\Components\Fields\Select;
|
||||
use App\Filament\Resources\Auth\Role as RoleResource;
|
||||
use App\Models\Auth\Role;
|
||||
use App\Models\Auth\User;
|
||||
use Filament\Schemas\Schema;
|
||||
@@ -27,6 +28,8 @@ class RevokeRoleAction extends BaseAction
|
||||
|
||||
$this->label(__('filament.actions.user.revoke_role.name'));
|
||||
|
||||
$this->icon(RoleResource::getNavigationIcon());
|
||||
|
||||
$this->action(fn (User $record, array $data) => $this->handle($record, $data));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\RelationManagers\Auth;
|
||||
|
||||
use App\Filament\RelationManagers\BaseRelationManager;
|
||||
use App\Filament\Resources\Auth\Prohibition as ProhibitionResource;
|
||||
use App\Filament\Resources\BaseResource;
|
||||
use App\Models\Auth\Prohibition;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
abstract class ProhibitionRelationManager extends BaseRelationManager
|
||||
{
|
||||
/**
|
||||
* The resource of the relation manager.
|
||||
*
|
||||
* @var class-string<BaseResource>|null
|
||||
*/
|
||||
protected static ?string $relatedResource = ProhibitionResource::class;
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return parent::table(
|
||||
$table
|
||||
->recordTitleAttribute(Prohibition::ATTRIBUTE_NAME)
|
||||
->defaultSort(Prohibition::TABLE.'.'.Prohibition::ATTRIBUTE_ID, 'desc')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Filament\Actions\Action[]
|
||||
*/
|
||||
public static function getHeaderActions(): array
|
||||
{
|
||||
return ProhibitionResource::getTableActions();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\RelationManagers\Auth;
|
||||
|
||||
use App\Filament\RelationManagers\BaseRelationManager;
|
||||
use App\Filament\Resources\Auth\Sanction as SanctionResource;
|
||||
use App\Filament\Resources\BaseResource;
|
||||
use App\Models\Auth\Sanction;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
abstract class SanctionRelationManager extends BaseRelationManager
|
||||
{
|
||||
/**
|
||||
* The resource of the relation manager.
|
||||
*
|
||||
* @var class-string<BaseResource>|null
|
||||
*/
|
||||
protected static ?string $relatedResource = SanctionResource::class;
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return parent::table(
|
||||
$table
|
||||
->recordTitleAttribute(Sanction::ATTRIBUTE_NAME)
|
||||
->defaultSort(Sanction::TABLE.'.'.Sanction::ATTRIBUTE_ID, 'desc')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Filament\Actions\Action[]
|
||||
*/
|
||||
public static function getHeaderActions(): array
|
||||
{
|
||||
return SanctionResource::getTableActions();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Auth;
|
||||
|
||||
use App\Enums\Filament\NavigationGroup;
|
||||
use App\Filament\Components\Columns\TextColumn;
|
||||
use App\Filament\Components\Fields\TextInput;
|
||||
use App\Filament\Components\Infolist\TextEntry;
|
||||
use App\Filament\Components\Infolist\TimestampSection;
|
||||
use App\Filament\Resources\Auth\Prohibition\Pages\ListProhibitions;
|
||||
use App\Filament\Resources\Auth\Prohibition\Pages\ViewProhibition;
|
||||
use App\Filament\Resources\Auth\Prohibition\RelationManagers\SanctionProhibitionRelationManager;
|
||||
use App\Filament\Resources\Auth\Prohibition\RelationManagers\UserProhibitionRelationManager;
|
||||
use App\Filament\Resources\BaseResource;
|
||||
use App\Models\Auth\Prohibition as ProhibitionModel;
|
||||
use Filament\Resources\RelationManagers\RelationGroup;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Prohibition extends BaseResource
|
||||
{
|
||||
/**
|
||||
* The model the resource corresponds to.
|
||||
*
|
||||
* @var class-string<Model>|null
|
||||
*/
|
||||
protected static ?string $model = ProhibitionModel::class;
|
||||
|
||||
public static function getModelLabel(): string
|
||||
{
|
||||
return __('filament.resources.singularLabel.prohibition');
|
||||
}
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return __('filament.resources.label.prohibitions');
|
||||
}
|
||||
|
||||
public static function getNavigationGroup(): NavigationGroup
|
||||
{
|
||||
return NavigationGroup::AUTH;
|
||||
}
|
||||
|
||||
public static function getNavigationIcon(): Heroicon
|
||||
{
|
||||
return Heroicon::OutlinedExclamationTriangle;
|
||||
}
|
||||
|
||||
public static function getRecordSlug(): string
|
||||
{
|
||||
return 'prohibitions';
|
||||
}
|
||||
|
||||
public static function getRecordTitleAttribute(): string
|
||||
{
|
||||
return ProhibitionModel::ATTRIBUTE_NAME;
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make(ProhibitionModel::ATTRIBUTE_NAME)
|
||||
->label(__('filament.fields.prohibition.name'))
|
||||
->required()
|
||||
->maxLength(192)
|
||||
->disabled(),
|
||||
])
|
||||
->columns(1);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return parent::table($table)
|
||||
->recordUrl(fn (ProhibitionModel $record): string => static::getUrl('view', ['record' => $record]))
|
||||
->columns([
|
||||
TextColumn::make(ProhibitionModel::ATTRIBUTE_ID)
|
||||
->label(__('filament.fields.base.id')),
|
||||
|
||||
TextColumn::make(ProhibitionModel::ATTRIBUTE_NAME)
|
||||
->label(__('filament.fields.prohibition.name'))
|
||||
->searchable()
|
||||
->copyableWithMessage(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function infolist(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Section::make(static::getRecordTitle($schema->getRecord()))
|
||||
->schema([
|
||||
TextEntry::make(ProhibitionModel::ATTRIBUTE_ID)
|
||||
->label(__('filament.fields.base.id')),
|
||||
|
||||
TextEntry::make(ProhibitionModel::ATTRIBUTE_NAME)
|
||||
->label(__('filament.fields.prohibition.name'))
|
||||
->copyableWithMessage(),
|
||||
]),
|
||||
|
||||
TimestampSection::make(),
|
||||
])
|
||||
->columns(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, RelationGroup|class-string<\Filament\Resources\RelationManagers\RelationManager>>
|
||||
*/
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
RelationGroup::make(static::getModelLabel(), [
|
||||
SanctionProhibitionRelationManager::class,
|
||||
UserProhibitionRelationManager::class,
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, \Filament\Resources\Pages\PageRegistration>
|
||||
*/
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListProhibitions::route('/'),
|
||||
'view' => ViewProhibition::route('/{record:id}'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Auth\Prohibition\Pages;
|
||||
|
||||
use App\Filament\Resources\Auth\Prohibition;
|
||||
use App\Filament\Resources\Base\BaseListResources;
|
||||
|
||||
class ListProhibitions extends BaseListResources
|
||||
{
|
||||
protected static string $resource = Prohibition::class;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Auth\Prohibition\Pages;
|
||||
|
||||
use App\Filament\Resources\Auth\Prohibition;
|
||||
use App\Filament\Resources\Base\BaseViewResource;
|
||||
|
||||
class ViewProhibition extends BaseViewResource
|
||||
{
|
||||
protected static string $resource = Prohibition::class;
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Auth\Prohibition\RelationManagers;
|
||||
|
||||
use App\Filament\RelationManagers\Auth\SanctionRelationManager;
|
||||
use App\Models\Auth\Prohibition;
|
||||
use App\Models\Auth\Sanction;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class SanctionProhibitionRelationManager extends SanctionRelationManager
|
||||
{
|
||||
/**
|
||||
* The relationship the relation manager corresponds to.
|
||||
*/
|
||||
protected static string $relationship = Prohibition::RELATION_SANCTIONS;
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return parent::table(
|
||||
$table
|
||||
->inverseRelationship(Sanction::RELATION_PROHIBITIONS)
|
||||
);
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Auth\Prohibition\RelationManagers;
|
||||
|
||||
use App\Filament\RelationManagers\Auth\UserRelationManager;
|
||||
use App\Models\Auth\Prohibition;
|
||||
use App\Models\Auth\User;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class UserProhibitionRelationManager extends UserRelationManager
|
||||
{
|
||||
/**
|
||||
* The relationship the relation manager corresponds to.
|
||||
*/
|
||||
protected static string $relationship = Prohibition::RELATION_USERS;
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return parent::table(
|
||||
$table
|
||||
->inverseRelationship(User::RELATION_SANCTIONS)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Auth;
|
||||
|
||||
use App\Enums\Filament\NavigationGroup;
|
||||
use App\Filament\Actions\Models\Auth\Sanction\GiveProhibitionAction;
|
||||
use App\Filament\Actions\Models\Auth\Sanction\RevokeProhibitionAction;
|
||||
use App\Filament\Components\Columns\TextColumn;
|
||||
use App\Filament\Components\Fields\TextInput;
|
||||
use App\Filament\Components\Infolist\TextEntry;
|
||||
use App\Filament\Components\Infolist\TimestampSection;
|
||||
use App\Filament\Resources\Auth\Sanction\Pages\ListSanctions;
|
||||
use App\Filament\Resources\Auth\Sanction\Pages\ViewSanction;
|
||||
use App\Filament\Resources\Auth\Sanction\RelationManagers\ProhibitionSanctionRelationManager;
|
||||
use App\Filament\Resources\Auth\Sanction\RelationManagers\UserSanctionRelationManager;
|
||||
use App\Filament\Resources\BaseResource;
|
||||
use App\Models\Auth\Sanction as SanctionModel;
|
||||
use Filament\Resources\RelationManagers\RelationGroup;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Sanction extends BaseResource
|
||||
{
|
||||
/**
|
||||
* The model the resource corresponds to.
|
||||
*
|
||||
* @var class-string<Model>|null
|
||||
*/
|
||||
protected static ?string $model = SanctionModel::class;
|
||||
|
||||
public static function getModelLabel(): string
|
||||
{
|
||||
return __('filament.resources.singularLabel.sanction');
|
||||
}
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return __('filament.resources.label.sanctions');
|
||||
}
|
||||
|
||||
public static function getNavigationGroup(): NavigationGroup
|
||||
{
|
||||
return NavigationGroup::AUTH;
|
||||
}
|
||||
|
||||
public static function getNavigationIcon(): Heroicon
|
||||
{
|
||||
return Heroicon::OutlinedShieldExclamation;
|
||||
}
|
||||
|
||||
public static function getRecordSlug(): string
|
||||
{
|
||||
return 'sanctions';
|
||||
}
|
||||
|
||||
public static function getRecordTitleAttribute(): string
|
||||
{
|
||||
return SanctionModel::ATTRIBUTE_NAME;
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make(SanctionModel::ATTRIBUTE_NAME)
|
||||
->label(__('filament.fields.sanction.name'))
|
||||
->required()
|
||||
->maxLength(192),
|
||||
])
|
||||
->columns(1);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return parent::table($table)
|
||||
->columns([
|
||||
TextColumn::make(SanctionModel::ATTRIBUTE_ID)
|
||||
->label(__('filament.fields.base.id')),
|
||||
|
||||
TextColumn::make(SanctionModel::ATTRIBUTE_NAME)
|
||||
->label(__('filament.fields.sanction.name'))
|
||||
->searchable()
|
||||
->copyableWithMessage(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function infolist(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Section::make(static::getRecordTitle($schema->getRecord()))
|
||||
->schema([
|
||||
TextEntry::make(SanctionModel::ATTRIBUTE_ID)
|
||||
->label(__('filament.fields.base.id')),
|
||||
|
||||
TextEntry::make(SanctionModel::ATTRIBUTE_NAME)
|
||||
->label(__('filament.fields.sanction.name'))
|
||||
->copyableWithMessage(),
|
||||
])
|
||||
->columns(3),
|
||||
|
||||
TimestampSection::make(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, RelationGroup|class-string<\Filament\Resources\RelationManagers\RelationManager>>
|
||||
*/
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
RelationGroup::make(static::getModelLabel(), [
|
||||
ProhibitionSanctionRelationManager::class,
|
||||
UserSanctionRelationManager::class,
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, \Filament\Tables\Filters\BaseFilter>
|
||||
*/
|
||||
public static function getFilters(): array
|
||||
{
|
||||
return [
|
||||
...parent::getFilters(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, \Filament\Actions\Action|\Filament\Actions\ActionGroup>
|
||||
*/
|
||||
public static function getRecordActions(): array
|
||||
{
|
||||
return [
|
||||
GiveProhibitionAction::make(),
|
||||
|
||||
RevokeProhibitionAction::make(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, \Filament\Resources\Pages\PageRegistration>
|
||||
*/
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListSanctions::route('/'),
|
||||
'view' => ViewSanction::route('/{record:id}'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Auth\Sanction\Pages;
|
||||
|
||||
use App\Filament\Resources\Auth\Sanction;
|
||||
use App\Filament\Resources\Base\BaseListResources;
|
||||
|
||||
class ListSanctions extends BaseListResources
|
||||
{
|
||||
protected static string $resource = Sanction::class;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Auth\Sanction\Pages;
|
||||
|
||||
use App\Filament\Resources\Auth\Sanction;
|
||||
use App\Filament\Resources\Base\BaseViewResource;
|
||||
|
||||
class ViewSanction extends BaseViewResource
|
||||
{
|
||||
protected static string $resource = Sanction::class;
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Auth\Sanction\RelationManagers;
|
||||
|
||||
use App\Filament\RelationManagers\Auth\ProhibitionRelationManager;
|
||||
use App\Models\Auth\Prohibition;
|
||||
use App\Models\Auth\Sanction;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class ProhibitionSanctionRelationManager extends ProhibitionRelationManager
|
||||
{
|
||||
/**
|
||||
* The relationship the relation manager corresponds to.
|
||||
*/
|
||||
protected static string $relationship = Sanction::RELATION_PROHIBITIONS;
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return parent::table(
|
||||
$table
|
||||
->inverseRelationship(Prohibition::RELATION_SANCTIONS)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Auth\Sanction\RelationManagers;
|
||||
|
||||
use App\Filament\RelationManagers\Auth\UserRelationManager;
|
||||
use App\Models\Auth\Sanction;
|
||||
use App\Models\Auth\User;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class UserSanctionRelationManager extends UserRelationManager
|
||||
{
|
||||
/**
|
||||
* The relationship the relation manager corresponds to.
|
||||
*/
|
||||
protected static string $relationship = Sanction::RELATION_USERS;
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return parent::table(
|
||||
$table
|
||||
->inverseRelationship(User::RELATION_SANCTIONS)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,9 @@ namespace App\Filament\Resources\Auth;
|
||||
|
||||
use App\Enums\Filament\NavigationGroup;
|
||||
use App\Filament\Actions\Models\Auth\User\GivePermissionAction;
|
||||
use App\Filament\Actions\Models\Auth\User\GiveProhibitionAction;
|
||||
use App\Filament\Actions\Models\Auth\User\GiveRoleAction;
|
||||
use App\Filament\Actions\Models\Auth\User\GiveSanctionAction;
|
||||
use App\Filament\Actions\Models\Auth\User\RevokePermissionAction;
|
||||
use App\Filament\Actions\Models\Auth\User\RevokeRoleAction;
|
||||
use App\Filament\Components\Columns\TextColumn;
|
||||
@@ -17,7 +19,9 @@ use App\Filament\Resources\Auth\User\Pages\ListUsers;
|
||||
use App\Filament\Resources\Auth\User\Pages\ViewUser;
|
||||
use App\Filament\Resources\Auth\User\RelationManagers\PermissionUserRelationManager;
|
||||
use App\Filament\Resources\Auth\User\RelationManagers\PlaylistUserRelationManager;
|
||||
use App\Filament\Resources\Auth\User\RelationManagers\ProhibitionUserRelationManager;
|
||||
use App\Filament\Resources\Auth\User\RelationManagers\RoleUserRelationManager;
|
||||
use App\Filament\Resources\Auth\User\RelationManagers\SanctionUserRelationManager;
|
||||
use App\Filament\Resources\BaseResource;
|
||||
use App\Models\Auth\User as UserModel;
|
||||
use Filament\Infolists\Components\ImageEntry;
|
||||
@@ -147,6 +151,8 @@ class User extends BaseResource
|
||||
RelationGroup::make(static::getModelLabel(), [
|
||||
RoleUserRelationManager::class,
|
||||
PermissionUserRelationManager::class,
|
||||
SanctionUserRelationManager::class,
|
||||
ProhibitionUserRelationManager::class,
|
||||
PlaylistUserRelationManager::class,
|
||||
|
||||
...parent::getBaseRelations(),
|
||||
@@ -167,6 +173,10 @@ class User extends BaseResource
|
||||
GivePermissionAction::make(),
|
||||
|
||||
RevokePermissionAction::make(),
|
||||
|
||||
GiveProhibitionAction::make(),
|
||||
|
||||
GiveSanctionAction::make(),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Auth\User\RelationManagers;
|
||||
|
||||
use App\Filament\Components\Columns\TextColumn;
|
||||
use App\Filament\Components\Fields\TextInput;
|
||||
use App\Filament\RelationManagers\Auth\ProhibitionRelationManager;
|
||||
use App\Models\Auth\Prohibition;
|
||||
use App\Models\Auth\User;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class ProhibitionUserRelationManager extends ProhibitionRelationManager
|
||||
{
|
||||
/**
|
||||
* The relationship the relation manager corresponds to.
|
||||
*/
|
||||
protected static string $relationship = User::RELATION_PROHIBITIONS;
|
||||
|
||||
public function getPivotComponents(): array
|
||||
{
|
||||
return [
|
||||
DateTimePicker::make('expires_at')
|
||||
->label(__('filament.actions.user.give_prohibition.expires_at.name'))
|
||||
->helperText(__('filament.actions.user.give_prohibition.expires_at.help'))
|
||||
->nullable(),
|
||||
|
||||
TextInput::make('reason')
|
||||
->label(__('filament.actions.user.give_prohibition.reason.name'))
|
||||
->helperText(__('filament.actions.user.give_prohibition.reason.help'))
|
||||
->disabled(),
|
||||
];
|
||||
}
|
||||
|
||||
public function getPivotColumns(): array
|
||||
{
|
||||
return [
|
||||
TextColumn::make('expires_at')
|
||||
->date('M j, Y H:i:s')
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('reason')
|
||||
->sortable(),
|
||||
];
|
||||
}
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return parent::table(
|
||||
$table
|
||||
->inverseRelationship(Prohibition::RELATION_USERS)
|
||||
);
|
||||
}
|
||||
|
||||
public static function getBulkActions(?array $actionsIncludedInGroup = []): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ namespace App\Filament\Resources\Auth\User\RelationManagers;
|
||||
use App\Filament\RelationManagers\Auth\RoleRelationManager;
|
||||
use App\Models\Auth\Role;
|
||||
use App\Models\Auth\User;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class RoleUserRelationManager extends RoleRelationManager
|
||||
@@ -32,8 +31,11 @@ class RoleUserRelationManager extends RoleRelationManager
|
||||
{
|
||||
return [
|
||||
...parent::getRecordActions(),
|
||||
|
||||
EditAction::make(),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getBulkActions(?array $actionsIncludedInGroup = []): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Auth\User\RelationManagers;
|
||||
|
||||
use App\Filament\Components\Columns\TextColumn;
|
||||
use App\Filament\Components\Fields\TextInput;
|
||||
use App\Filament\RelationManagers\Auth\SanctionRelationManager;
|
||||
use App\Models\Auth\Sanction;
|
||||
use App\Models\Auth\User;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class SanctionUserRelationManager extends SanctionRelationManager
|
||||
{
|
||||
/**
|
||||
* The relationship the relation manager corresponds to.
|
||||
*/
|
||||
protected static string $relationship = User::RELATION_SANCTIONS;
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return parent::table(
|
||||
$table
|
||||
->inverseRelationship(Sanction::RELATION_USERS)
|
||||
);
|
||||
}
|
||||
|
||||
public function getPivotComponents(): array
|
||||
{
|
||||
return [
|
||||
DateTimePicker::make('expires_at')
|
||||
->label(__('filament.actions.user.give_sanction.expires_at.name'))
|
||||
->helperText(__('filament.actions.user.give_sanction.expires_at.help'))
|
||||
->nullable(),
|
||||
|
||||
TextInput::make('reason')
|
||||
->label(__('filament.actions.user.give_sanction.reason.name'))
|
||||
->helperText(__('filament.actions.user.give_sanction.reason.help'))
|
||||
->disabled(),
|
||||
];
|
||||
}
|
||||
|
||||
public function getPivotColumns(): array
|
||||
{
|
||||
return [
|
||||
TextColumn::make('expires_at')
|
||||
->label(__('filament.actions.user.give_sanction.expires_at.name'))
|
||||
->date('M j, Y H:i:s')
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('reason')
|
||||
->label(__('filament.actions.user.give_sanction.reason.name'))
|
||||
->sortable(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, \Filament\Actions\Action>
|
||||
*/
|
||||
public static function getRecordActions(): array
|
||||
{
|
||||
return [
|
||||
...parent::getRecordActions(),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getBulkActions(?array $actionsIncludedInGroup = []): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models\Auth;
|
||||
|
||||
use App\Contracts\Models\Nameable;
|
||||
use Kyrch\Prohibition\Models\Prohibition as BaseProhibition;
|
||||
|
||||
class Prohibition extends BaseProhibition implements Nameable
|
||||
{
|
||||
final public const string TABLE = 'prohibitions';
|
||||
|
||||
final public const string ATTRIBUTE_ID = 'id';
|
||||
final public const string ATTRIBUTE_NAME = 'name';
|
||||
|
||||
final public const string RELATION_SANCTIONS = 'sanctions';
|
||||
final public const string RELATION_USERS = 'users';
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models\Auth;
|
||||
|
||||
use App\Contracts\Models\Nameable;
|
||||
use Kyrch\Prohibition\Models\Sanction as BaseSanction;
|
||||
|
||||
class Sanction extends BaseSanction implements Nameable
|
||||
{
|
||||
final public const string TABLE = 'sanctions';
|
||||
|
||||
final public const string ATTRIBUTE_ID = 'id';
|
||||
final public const string ATTRIBUTE_NAME = 'name';
|
||||
|
||||
final public const string RELATION_PROHIBITIONS = 'prohibitions';
|
||||
final public const string RELATION_USERS = 'users';
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,7 @@ use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use Kyrch\Prohibition\Traits\HasSanctions;
|
||||
use Laravel\Fortify\TwoFactorAuthenticatable;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
use Laravel\Sanctum\PersonalAccessToken;
|
||||
@@ -69,6 +70,7 @@ class User extends Authenticatable implements FilamentUser, HasAvatar, HasSubtit
|
||||
use HasApiTokens;
|
||||
use HasFactory;
|
||||
use HasRoles;
|
||||
use HasSanctions;
|
||||
use Notifiable;
|
||||
use SoftDeletes;
|
||||
use TwoFactorAuthenticatable;
|
||||
@@ -89,7 +91,9 @@ class User extends Authenticatable implements FilamentUser, HasAvatar, HasSubtit
|
||||
final public const string RELATION_MANAGED_SUBMISSIONS = 'managedsubmissions';
|
||||
final public const string RELATION_NOTIFICATIONS = 'notifications';
|
||||
final public const string RELATION_PERMISSIONS = 'permissions';
|
||||
final public const string RELATION_PROHIBITIONS = 'prohibitions';
|
||||
final public const string RELATION_PLAYLISTS = 'playlists';
|
||||
final public const string RELATION_SANCTIONS = 'sanctions';
|
||||
final public const string RELATION_SUBMISSIONS = 'submissions';
|
||||
final public const string RELATION_ROLES = 'roles';
|
||||
final public const string RELATION_ROLES_PERMISSIONS = 'roles.permissions';
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Notifications\Auth;
|
||||
|
||||
use App\Models\Auth\Prohibition;
|
||||
use App\Models\Auth\Sanction;
|
||||
use DateTimeInterface;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class ProhibitionOrSanctionNotification extends Notification
|
||||
{
|
||||
public function __construct(
|
||||
public Prohibition|Sanction $issued,
|
||||
public string $reason,
|
||||
public ?DateTimeInterface $expiresAt = null,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get the notification's channels.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
*/
|
||||
public function via($notifiable): array
|
||||
{
|
||||
return ['mail'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the mail representation of the notification.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return MailMessage
|
||||
*/
|
||||
public function toMail($notifiable)
|
||||
{
|
||||
$type = $this->issued instanceof Prohibition ? 'prohibition' : 'sanction';
|
||||
|
||||
return new MailMessage()
|
||||
->subject('Account Prohibition or Sanction Issued')
|
||||
->greeting("Dear {$notifiable->getName()},")
|
||||
->line("Your account has been issued a {$type} ({$this->issued->name}).")
|
||||
->line($this->getExpiresAtLine($type))
|
||||
->line("Reason: {$this->reason}");
|
||||
}
|
||||
|
||||
protected function getExpiresAtLine(string $type): string
|
||||
{
|
||||
if (! $this->expiresAt instanceof DateTimeInterface) {
|
||||
return "This {$type} is permanent.";
|
||||
}
|
||||
|
||||
$expiresFormat = $this->expiresAt->format('Y-m-d H:i:s');
|
||||
|
||||
return "This {$type} will expire on {$expiresFormat}.";
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ namespace App\Policies\Auth;
|
||||
|
||||
use App\Enums\Auth\CrudPermission;
|
||||
use App\Enums\Auth\ExtendedCrudPermission;
|
||||
use App\Enums\Auth\Role;
|
||||
use App\Models\Auth\Permission;
|
||||
use App\Models\Auth\User;
|
||||
use App\Policies\BasePolicy;
|
||||
@@ -61,43 +62,59 @@ class PermissionPolicy extends BasePolicy
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function attachAnyRole(): Response
|
||||
public function attachAnyRole(User $user): Response
|
||||
{
|
||||
return Response::deny();
|
||||
return $user->hasRole(Role::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function attachRole(): Response
|
||||
public function attachRole(User $user): Response
|
||||
{
|
||||
return Response::deny();
|
||||
return $user->hasRole(Role::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function detachAnyRole(): Response
|
||||
public function detachAnyRole(User $user): Response
|
||||
{
|
||||
return Response::deny();
|
||||
return $user->hasRole(Role::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function detachRole(): Response
|
||||
public function detachRole(User $user): Response
|
||||
{
|
||||
return Response::deny();
|
||||
return $user->hasRole(Role::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function attachAnyUser(): Response
|
||||
public function attachAnyUser(User $user): Response
|
||||
{
|
||||
return Response::deny();
|
||||
return $user->hasRole(Role::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function attachUser(): Response
|
||||
public function attachUser(User $user): Response
|
||||
{
|
||||
return Response::deny();
|
||||
return $user->hasRole(Role::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function detachAnyUser(): Response
|
||||
public function detachAnyUser(User $user): Response
|
||||
{
|
||||
return Response::deny();
|
||||
return $user->hasRole(Role::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function detachUser(): Response
|
||||
public function detachUser(User $user): Response
|
||||
{
|
||||
return Response::deny();
|
||||
return $user->hasRole(Role::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Policies\Auth;
|
||||
|
||||
use App\Enums\Auth\CrudPermission;
|
||||
use App\Enums\Auth\ExtendedCrudPermission;
|
||||
use App\Enums\Auth\Role;
|
||||
use App\Models\Auth\Prohibition;
|
||||
use App\Models\Auth\User;
|
||||
use App\Policies\BasePolicy;
|
||||
use Illuminate\Auth\Access\Response;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ProhibitionPolicy extends BasePolicy
|
||||
{
|
||||
public function viewAny(?User $user, mixed $value = null): Response
|
||||
{
|
||||
return $user instanceof User && $user->can(CrudPermission::VIEW->format(Prohibition::class))
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Prohibition $Prohibition
|
||||
*/
|
||||
public function view(?User $user, Model $Prohibition): Response
|
||||
{
|
||||
return $user instanceof User && $user->can(CrudPermission::VIEW->format(Prohibition::class))
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Prohibition $Prohibition
|
||||
*/
|
||||
public function update(User $user, Model $Prohibition): Response
|
||||
{
|
||||
return $user->can(CrudPermission::UPDATE->format(Prohibition::class))
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Prohibition $Prohibition
|
||||
*/
|
||||
public function delete(User $user, Model $Prohibition): Response
|
||||
{
|
||||
return $user->can(CrudPermission::DELETE->format(Prohibition::class))
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Prohibition $Prohibition
|
||||
*/
|
||||
public function restore(User $user, Model $Prohibition): Response
|
||||
{
|
||||
return $user->can(ExtendedCrudPermission::RESTORE->format(Prohibition::class))
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function attachAnySanction(User $user): Response
|
||||
{
|
||||
return $user->hasRole(Role::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function attachSanction(User $user): Response
|
||||
{
|
||||
return $user->hasRole(Role::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function detachAnySanction(User $user): Response
|
||||
{
|
||||
return $user->hasRole(Role::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function detachSanction(User $user): Response
|
||||
{
|
||||
return $user->hasRole(Role::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function attachAnyUser(User $user): Response
|
||||
{
|
||||
return $user->hasRole(Role::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function attachUser(User $user): Response
|
||||
{
|
||||
return $user->hasRole(Role::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function detachAnyUser(User $user): Response
|
||||
{
|
||||
return $user->hasRole(Role::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function detachUser(User $user): Response
|
||||
{
|
||||
return $user->hasRole(Role::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace App\Policies\Auth;
|
||||
|
||||
use App\Enums\Auth\CrudPermission;
|
||||
use App\Enums\Auth\Role as RoleEnum;
|
||||
use App\Models\Auth\Role;
|
||||
use App\Models\Auth\User;
|
||||
use App\Policies\BasePolicy;
|
||||
@@ -30,43 +31,59 @@ class RolePolicy extends BasePolicy
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function attachAnyPermission(): Response
|
||||
public function attachAnyPermission(User $user): Response
|
||||
{
|
||||
return Response::deny();
|
||||
return $user->hasRole(RoleEnum::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function attachPermission(): Response
|
||||
public function attachPermission(User $user): Response
|
||||
{
|
||||
return Response::deny();
|
||||
return $user->hasRole(RoleEnum::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function detachAnyPermission(): Response
|
||||
public function detachAnyPermission(User $user): Response
|
||||
{
|
||||
return Response::deny();
|
||||
return $user->hasRole(RoleEnum::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function detachPermission(): Response
|
||||
public function detachPermission(User $user): Response
|
||||
{
|
||||
return Response::deny();
|
||||
return $user->hasRole(RoleEnum::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function attachAnyUser(): Response
|
||||
public function attachAnyUser(User $user): Response
|
||||
{
|
||||
return Response::deny();
|
||||
return $user->hasRole(RoleEnum::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function attachUser(): Response
|
||||
public function attachUser(User $user): Response
|
||||
{
|
||||
return Response::deny();
|
||||
return $user->hasRole(RoleEnum::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function detachAnyUser(): Response
|
||||
public function detachAnyUser(User $user): Response
|
||||
{
|
||||
return Response::deny();
|
||||
return $user->hasRole(RoleEnum::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function detachUser(): Response
|
||||
public function detachUser(User $user): Response
|
||||
{
|
||||
return Response::deny();
|
||||
return $user->hasRole(RoleEnum::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Policies\Auth;
|
||||
|
||||
use App\Enums\Auth\CrudPermission;
|
||||
use App\Enums\Auth\Role;
|
||||
use App\Models\Auth\Sanction;
|
||||
use App\Models\Auth\User;
|
||||
use App\Policies\BasePolicy;
|
||||
use Illuminate\Auth\Access\Response;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class SanctionPolicy extends BasePolicy
|
||||
{
|
||||
public function viewAny(?User $user, mixed $value = null): Response
|
||||
{
|
||||
return $user instanceof User && $user->can(CrudPermission::VIEW->format(Sanction::class))
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Sanction $Sanction
|
||||
*/
|
||||
public function view(?User $user, Model $Sanction): Response
|
||||
{
|
||||
return $user instanceof User && $user->can(CrudPermission::VIEW->format(Sanction::class))
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function attachAnyProhibition(User $user): Response
|
||||
{
|
||||
return $user->hasRole(Role::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function attachProhibition(User $user): Response
|
||||
{
|
||||
return $user->hasRole(Role::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function detachAnyProhibition(User $user): Response
|
||||
{
|
||||
return $user->hasRole(Role::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function detachProhibition(User $user): Response
|
||||
{
|
||||
return $user->hasRole(Role::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function attachAnyUser(User $user): Response
|
||||
{
|
||||
return $user->hasRole(Role::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function attachUser(User $user): Response
|
||||
{
|
||||
return $user->hasRole(Role::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function detachAnyUser(User $user): Response
|
||||
{
|
||||
return $user->hasRole(Role::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function detachUser(User $user): Response
|
||||
{
|
||||
return $user->hasRole(Role::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
}
|
||||
@@ -30,44 +30,60 @@ class UserPolicy extends BasePolicy
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function attachAnyRole(): Response
|
||||
public function attachAnyRole(User $user): Response
|
||||
{
|
||||
return Response::deny();
|
||||
return $user->hasRole(RoleEnum::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function attachRole(): Response
|
||||
public function attachRole(User $user): Response
|
||||
{
|
||||
return Response::deny();
|
||||
return $user->hasRole(RoleEnum::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function detachAnyRole(): Response
|
||||
public function detachAnyRole(User $user): Response
|
||||
{
|
||||
return Response::deny();
|
||||
return $user->hasRole(RoleEnum::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function detachRole(): Response
|
||||
public function detachRole(User $user): Response
|
||||
{
|
||||
return Response::deny();
|
||||
return $user->hasRole(RoleEnum::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function attachAnyPermission(): Response
|
||||
public function attachAnyPermission(User $user): Response
|
||||
{
|
||||
return Response::deny();
|
||||
return $user->hasRole(RoleEnum::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function attachPermission(): Response
|
||||
public function attachPermission(User $user): Response
|
||||
{
|
||||
return Response::deny();
|
||||
return $user->hasRole(RoleEnum::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function detachAnyPermission(): Response
|
||||
public function detachAnyPermission(User $user): Response
|
||||
{
|
||||
return Response::deny();
|
||||
return $user->hasRole(RoleEnum::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function detachPermission(): Response
|
||||
public function detachPermission(User $user): Response
|
||||
{
|
||||
return Response::deny();
|
||||
return $user->hasRole(RoleEnum::ADMIN->value)
|
||||
? Response::allow()
|
||||
: Response::deny();
|
||||
}
|
||||
|
||||
public function addPlaylist(User $user): Response
|
||||
|
||||
@@ -33,12 +33,25 @@ abstract class BasePolicy
|
||||
->__toString();
|
||||
}
|
||||
|
||||
protected static function format(string $ability): string
|
||||
{
|
||||
return Str::of($ability)
|
||||
->remove('Any')
|
||||
->append(class_basename(static::getModel()))
|
||||
->snake(' ')
|
||||
->__toString();
|
||||
}
|
||||
|
||||
public function before(User $user, string $ability): ?Response
|
||||
{
|
||||
if ($user->can(SpecialPermission::BYPASS_AUTHORIZATION->value)) {
|
||||
return Response::allow();
|
||||
}
|
||||
|
||||
if ($user->isProhibitedFrom(static::format($ability))) {
|
||||
return Response::deny('You have been banned from performing this action. Please check your email for more details.');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ use App\Pivots\Wiki\AnimeThemeEntryVideo;
|
||||
use App\Pivots\Wiki\ArtistMember;
|
||||
use App\Pivots\Wiki\ArtistSong;
|
||||
use Database\Seeders\Auth\Permission\PermissionSeeder;
|
||||
use Database\Seeders\Auth\Prohibition\ProhibitionSeeder;
|
||||
use Database\Seeders\Auth\Role\AdminSeeder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
@@ -55,6 +56,7 @@ class AppServiceProvider extends ServiceProvider
|
||||
ParallelTesting::setUpTestDatabase(function (string $database, int $token): void {
|
||||
Artisan::call('db:seed', ['--class' => PermissionSeeder::class]);
|
||||
Artisan::call('db:seed', ['--class' => AdminSeeder::class]);
|
||||
Artisan::call('db:seed', ['--class' => ProhibitionSeeder::class]);
|
||||
});
|
||||
|
||||
DB::listen(function (QueryExecuted $query): void {
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"filament/filament": "^4.4.0",
|
||||
"flowframe/laravel-trend": ">=0.4",
|
||||
"guzzlehttp/guzzle": "^7.10.0",
|
||||
"kyrch/laravel-prohibitions": "^1.1.0",
|
||||
"larastan/larastan": "^3.8.1",
|
||||
"laravel-notification-channels/discord": "^1.7",
|
||||
"laravel/fortify": "^1.33.0",
|
||||
|
||||
Generated
+81
-1
@@ -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": "e17368d2e59c27ecf9080634a686d848",
|
||||
"content-hash": "ad7bbfdc96fcdca090892cc0ee25a07f",
|
||||
"packages": [
|
||||
{
|
||||
"name": "anourvalar/eloquent-serialize",
|
||||
@@ -3342,6 +3342,86 @@
|
||||
},
|
||||
"time": "2025-12-17T00:37:48+00:00"
|
||||
},
|
||||
{
|
||||
"name": "kyrch/laravel-prohibitions",
|
||||
"version": "v1.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Kyrch/laravel-prohibitions.git",
|
||||
"reference": "8874f07051b40994fd069950e28aa2f7ce39b3da"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Kyrch/laravel-prohibitions/zipball/8874f07051b40994fd069950e28aa2f7ce39b3da",
|
||||
"reference": "8874f07051b40994fd069950e28aa2f7ce39b3da",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"illuminate/contracts": "^11.0||^12.0",
|
||||
"php": "^8.4",
|
||||
"spatie/laravel-package-tools": "^1.16"
|
||||
},
|
||||
"require-dev": {
|
||||
"driftingly/rector-laravel": "^2.1",
|
||||
"larastan/larastan": "^3.0",
|
||||
"laravel/pint": "^1.14",
|
||||
"nunomaduro/collision": "^8.8",
|
||||
"orchestra/testbench": "^10.0.0||^9.0.0",
|
||||
"pestphp/pest": "^4.0",
|
||||
"pestphp/pest-plugin-arch": "^4.0",
|
||||
"pestphp/pest-plugin-laravel": "^4.0",
|
||||
"phpstan/extension-installer": "^1.4",
|
||||
"phpstan/phpstan-deprecation-rules": "^2.0",
|
||||
"phpstan/phpstan-phpunit": "^2.0",
|
||||
"rector/rector": "^2.2"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"aliases": {
|
||||
"Prohibition": "Kyrch\\Prohibition\\Facades\\Prohibition"
|
||||
},
|
||||
"providers": [
|
||||
"Kyrch\\Prohibition\\ProhibitionServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Kyrch\\Prohibition\\": "src/",
|
||||
"Kyrch\\Prohibition\\Database\\Factories\\": "database/factories/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Kyrch",
|
||||
"email": "kyrchk5@gmail.com",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "Prohibit your models from execute an action in a way similar to spatie/laravel-permission.",
|
||||
"homepage": "https://github.com/kyrch/laravel-prohibitions",
|
||||
"keywords": [
|
||||
"Kyrch",
|
||||
"laravel",
|
||||
"laravel-prohibitions"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Kyrch/laravel-prohibitions/issues",
|
||||
"source": "https://github.com/Kyrch/laravel-prohibitions/tree/v1.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/Kyrch",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2026-01-21T19:03:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laragraph/utils",
|
||||
"version": "v2.2.0",
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Models\Auth\Prohibition;
|
||||
use App\Models\Auth\Sanction;
|
||||
use App\Models\Auth\User;
|
||||
use Kyrch\Prohibition\Pivots\ModelProhibition;
|
||||
use Kyrch\Prohibition\Pivots\ModelSanction;
|
||||
|
||||
return [
|
||||
'events_enabled' => false,
|
||||
|
||||
'models' => [
|
||||
'user' => User::class,
|
||||
'prohibition' => Prohibition::class,
|
||||
'sanction' => Sanction::class,
|
||||
'model_prohibition' => ModelProhibition::class,
|
||||
'model_sanction' => ModelSanction::class,
|
||||
],
|
||||
|
||||
'table_names' => [
|
||||
'prohibition' => 'prohibitions',
|
||||
'sanction' => 'sanctions',
|
||||
'sanction_prohibition' => 'sanction_prohibition',
|
||||
'model_sanctions' => 'model_sanctions',
|
||||
'model_prohibitions' => 'model_prohibitions',
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
/** @var array<string, string> $tableNames */
|
||||
$tableNames = Config::array('prohibition.table_names');
|
||||
|
||||
Schema::create($tableNames['prohibition'], function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique('name');
|
||||
});
|
||||
|
||||
Schema::create($tableNames['sanction'], function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique('name');
|
||||
});
|
||||
|
||||
Schema::create($tableNames['sanction_prohibition'], function (Blueprint $table) use ($tableNames): void {
|
||||
$table->unsignedBigInteger('sanction_id');
|
||||
$table->foreign('sanction_id')->references('id')->on($tableNames['sanction'])->cascadeOnDelete();
|
||||
|
||||
$table->unsignedBigInteger('prohibition_id');
|
||||
$table->foreign('prohibition_id')->references('id')->on($tableNames['prohibition'])->cascadeOnDelete();
|
||||
$table->timestamps();
|
||||
|
||||
$table->primary(['sanction_id', 'prohibition_id']);
|
||||
});
|
||||
|
||||
Schema::create($tableNames['model_sanctions'], function (Blueprint $table) use ($tableNames): void {
|
||||
$table->morphs('model');
|
||||
|
||||
$table->unsignedBigInteger('sanction_id');
|
||||
$table->foreign('sanction_id')->references('id')->on($tableNames['sanction'])->cascadeOnDelete();
|
||||
|
||||
$table->nullableMorphs('moderator');
|
||||
$table->timestamp('expires_at')->nullable();
|
||||
$table->text('reason')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->primary(['model_type', 'model_id', 'sanction_id']);
|
||||
$table->index('expires_at');
|
||||
});
|
||||
|
||||
Schema::create($tableNames['model_prohibitions'], function (Blueprint $table) use ($tableNames): void {
|
||||
$table->morphs('model');
|
||||
|
||||
$table->unsignedBigInteger('prohibition_id');
|
||||
$table->foreign('prohibition_id')->references('id')->on($tableNames['prohibition'])->cascadeOnDelete();
|
||||
|
||||
$table->nullableMorphs('moderator');
|
||||
$table->timestamp('expires_at')->nullable();
|
||||
$table->text('reason')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->primary(['model_type', 'model_id', 'prohibition_id']);
|
||||
$table->index('expires_at');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -12,7 +12,9 @@ use App\Models\Admin\Dump;
|
||||
use App\Models\Admin\Feature;
|
||||
use App\Models\Admin\FeaturedTheme;
|
||||
use App\Models\Auth\Permission;
|
||||
use App\Models\Auth\Prohibition;
|
||||
use App\Models\Auth\Role;
|
||||
use App\Models\Auth\Sanction;
|
||||
use App\Models\Auth\User;
|
||||
use App\Models\Discord\DiscordThread;
|
||||
use App\Models\Document\Page;
|
||||
@@ -61,7 +63,9 @@ class PermissionSeeder extends Seeder
|
||||
|
||||
// Auth Resources
|
||||
$this->registerResource(Permission::class, [CrudPermission::VIEW]);
|
||||
$this->registerResource(Prohibition::class, [CrudPermission::VIEW, CrudPermission::UPDATE]);
|
||||
$this->registerResource(Role::class, CrudPermission::cases());
|
||||
$this->registerResource(Sanction::class, CrudPermission::cases());
|
||||
$this->registerResource(User::class, $extendedCrudPermissions);
|
||||
|
||||
// Discord Resources
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Database\Seeders\Auth\Prohibition;
|
||||
|
||||
use App\Models\Auth\Permission;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Kyrch\Prohibition\Models\Prohibition;
|
||||
|
||||
class ProhibitionSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
Permission::query()->get([Permission::ATTRIBUTE_NAME])->each(
|
||||
function (Permission $permission): void {
|
||||
Prohibition::query()->firstOrCreate([
|
||||
'name' => $permission->name,
|
||||
]);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,9 @@ use App\Models\Admin\Dump;
|
||||
use App\Models\Admin\Feature;
|
||||
use App\Models\Admin\FeaturedTheme;
|
||||
use App\Models\Auth\Permission;
|
||||
use App\Models\Auth\Prohibition;
|
||||
use App\Models\Auth\Role;
|
||||
use App\Models\Auth\Sanction;
|
||||
use App\Models\Auth\User;
|
||||
use App\Models\Discord\DiscordThread;
|
||||
use App\Models\Document\Page;
|
||||
@@ -69,7 +71,9 @@ class AdminSeeder extends RoleSeeder
|
||||
|
||||
// Auth Resources
|
||||
$this->configureResource($role, Permission::class, [CrudPermission::VIEW]);
|
||||
$this->configureResource($role, Prohibition::class, [CrudPermission::VIEW, CrudPermission::UPDATE]);
|
||||
$this->configureResource($role, Role::class, CrudPermission::cases());
|
||||
$this->configureResource($role, Sanction::class, CrudPermission::cases());
|
||||
$this->configureResource($role, User::class, $extendedCrudPermissions);
|
||||
|
||||
// Discord Resources
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace Database\Seeders;
|
||||
|
||||
use Database\Seeders\Admin\Feature\FeatureSeeder;
|
||||
use Database\Seeders\Auth\Permission\PermissionSeeder;
|
||||
use Database\Seeders\Auth\Prohibition\ProhibitionSeeder;
|
||||
use Database\Seeders\Auth\Role\RoleSeeder;
|
||||
use Database\Seeders\Scout\ImportModelsSeeder;
|
||||
use Database\Seeders\Wiki\Audio\AudioSeeder;
|
||||
@@ -21,6 +22,7 @@ class DatabaseSeeder extends Seeder
|
||||
{
|
||||
$this->call(PermissionSeeder::class);
|
||||
$this->call(RoleSeeder::class);
|
||||
$this->call(ProhibitionSeeder::class);
|
||||
$this->call(VideoSeeder::class);
|
||||
$this->call(AudioSeeder::class);
|
||||
$this->call(HashidsSeeder::class);
|
||||
|
||||
@@ -340,6 +340,14 @@ return [
|
||||
'name' => 'Revoke Permission',
|
||||
],
|
||||
],
|
||||
'sanction' => [
|
||||
'give_prohibition' => [
|
||||
'name' => 'Give Prohibition',
|
||||
],
|
||||
'revoke_prohibition' => [
|
||||
'name' => 'Revoke Prohibition',
|
||||
],
|
||||
],
|
||||
'storage' => [
|
||||
'delete' => [
|
||||
'confirmButtonText' => 'Remove',
|
||||
@@ -405,12 +413,35 @@ return [
|
||||
'give_role' => [
|
||||
'name' => 'Give Role',
|
||||
],
|
||||
'give_prohibition' => [
|
||||
'name' => 'Give Prohibition',
|
||||
'expires_at' => [
|
||||
'help' => 'The date and time the prohibition expires. Leave blank for a permanent prohibition',
|
||||
'name' => 'Expires At',
|
||||
],
|
||||
'reason' => [
|
||||
'help' => 'The reason for the prohibition. It will be sent to the email address associated with the user account.',
|
||||
'name' => 'Reason',
|
||||
],
|
||||
],
|
||||
'give_sanction' => [
|
||||
'name' => 'Give Sanction',
|
||||
'expires_at' => [
|
||||
'help' => 'The date and time the sanction expires. Leave blank for a permanent sanction',
|
||||
'name' => 'Expires At',
|
||||
],
|
||||
'reason' => [
|
||||
'help' => 'The reason for the sanction. It will be sent to the email address associated with the user account.',
|
||||
'name' => 'Reason',
|
||||
],
|
||||
],
|
||||
'revoke_permission' => [
|
||||
'name' => 'Revoke Permission',
|
||||
],
|
||||
'revoke_role' => [
|
||||
'name' => 'Revoke Role',
|
||||
],
|
||||
|
||||
],
|
||||
'video_script' => [
|
||||
'delete' => [
|
||||
@@ -850,6 +881,9 @@ return [
|
||||
'name' => 'Visibility',
|
||||
],
|
||||
],
|
||||
'prohibition' => [
|
||||
'name' => 'Name',
|
||||
],
|
||||
'submission' => [
|
||||
'finished_at' => 'Finished At',
|
||||
'locked' => 'Locked',
|
||||
@@ -884,6 +918,9 @@ return [
|
||||
'name' => 'Priority',
|
||||
],
|
||||
],
|
||||
'sanction' => [
|
||||
'name' => 'Name',
|
||||
],
|
||||
'series' => [
|
||||
'name' => [
|
||||
'help' => 'The display title of the Series. Ex: "Monogatari", "Code Geass", "Dungeon ni Deai wo Motomeru no wa Machigatteiru Darou ka".',
|
||||
@@ -1007,9 +1044,11 @@ return [
|
||||
'permissions' => 'Permissions',
|
||||
'playlist_tracks' => 'Playlist Tracks',
|
||||
'playlists' => 'Playlists',
|
||||
'prohibitions' => 'Prohibitions',
|
||||
'submissions' => 'Submissions',
|
||||
'submission_stages' => 'Submission Stages',
|
||||
'roles' => 'Roles',
|
||||
'sanctions' => 'Sanctions',
|
||||
'series' => 'Series',
|
||||
'songs' => 'Songs',
|
||||
'studios' => 'Studios',
|
||||
@@ -1043,6 +1082,8 @@ return [
|
||||
'permission' => 'Permission',
|
||||
'playlist_track' => 'Playlist Track',
|
||||
'playlist' => 'Playlist',
|
||||
'prohibition' => 'Prohibition',
|
||||
'sanction' => 'Sanction',
|
||||
'submission' => 'Submission',
|
||||
'submission_stage' => 'Submission Stage',
|
||||
'role' => 'Role',
|
||||
|
||||
Reference in New Issue
Block a user