feat: Gestion des classes scolaires
Permet aux administrateurs de créer, modifier et supprimer des classes pour organiser les élèves par niveau. L'archivage soft-delete préserve l'historique tout en masquant les classes obsolètes. Inclut la validation des noms (2-50 caractères), les niveaux scolaires du CP à la Terminale, et les contrôles d'accès par rôle.
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Administration\Infrastructure\Security;
|
||||
|
||||
use App\Administration\Domain\Model\SchoolClass\SchoolClass;
|
||||
use App\Administration\Domain\Model\User\Role;
|
||||
use App\Administration\Infrastructure\Api\Resource\ClassResource;
|
||||
|
||||
use function in_array;
|
||||
|
||||
use Override;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\Vote;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
/**
|
||||
* Voter pour les autorisations sur les classes scolaires.
|
||||
*
|
||||
* Règles d'accès :
|
||||
* - ADMIN et SUPER_ADMIN : accès complet (CRUD)
|
||||
* - ENSEIGNANT : lecture seule (via affectations)
|
||||
* - ELEVE et PARENT : lecture de leur classe uniquement
|
||||
*
|
||||
* @extends Voter<string, SchoolClass|ClassResource>
|
||||
*/
|
||||
final class ClassVoter extends Voter
|
||||
{
|
||||
public const string VIEW = 'CLASS_VIEW';
|
||||
public const string CREATE = 'CLASS_CREATE';
|
||||
public const string EDIT = 'CLASS_EDIT';
|
||||
public const string DELETE = 'CLASS_DELETE';
|
||||
|
||||
private const array SUPPORTED_ATTRIBUTES = [
|
||||
self::VIEW,
|
||||
self::CREATE,
|
||||
self::EDIT,
|
||||
self::DELETE,
|
||||
];
|
||||
|
||||
#[Override]
|
||||
protected function supports(string $attribute, mixed $subject): bool
|
||||
{
|
||||
if (!in_array($attribute, self::SUPPORTED_ATTRIBUTES, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// CREATE and VIEW (for collections) don't require a subject
|
||||
if ($attribute === self::CREATE || ($attribute === self::VIEW && $subject === null)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $subject instanceof SchoolClass || $subject instanceof ClassResource;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool
|
||||
{
|
||||
$user = $token->getUser();
|
||||
|
||||
if (!$user instanceof UserInterface) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Récupérer le rôle depuis les rôles Symfony
|
||||
$roles = $user->getRoles();
|
||||
|
||||
return match ($attribute) {
|
||||
self::VIEW => $this->canView($roles),
|
||||
self::CREATE => $this->canCreate($roles),
|
||||
self::EDIT => $this->canEdit($roles),
|
||||
self::DELETE => $this->canDelete($roles),
|
||||
default => false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $roles
|
||||
*/
|
||||
private function canView(array $roles): bool
|
||||
{
|
||||
// Personnel de l'établissement uniquement
|
||||
// ELEVE et PARENT sont exclus car ils ne doivent voir que leur propre classe
|
||||
// via un endpoint dédié (non implémenté - nécessite le module Affectations)
|
||||
return $this->hasAnyRole($roles, [
|
||||
Role::SUPER_ADMIN->value,
|
||||
Role::ADMIN->value,
|
||||
Role::PROF->value,
|
||||
Role::VIE_SCOLAIRE->value,
|
||||
Role::SECRETARIAT->value,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $roles
|
||||
*/
|
||||
private function canCreate(array $roles): bool
|
||||
{
|
||||
// Seuls ADMIN et SUPER_ADMIN peuvent créer des classes
|
||||
return $this->hasAnyRole($roles, [
|
||||
Role::SUPER_ADMIN->value,
|
||||
Role::ADMIN->value,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $roles
|
||||
*/
|
||||
private function canEdit(array $roles): bool
|
||||
{
|
||||
// Seuls ADMIN et SUPER_ADMIN peuvent modifier des classes
|
||||
return $this->hasAnyRole($roles, [
|
||||
Role::SUPER_ADMIN->value,
|
||||
Role::ADMIN->value,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $roles
|
||||
*/
|
||||
private function canDelete(array $roles): bool
|
||||
{
|
||||
// Seuls ADMIN et SUPER_ADMIN peuvent supprimer des classes
|
||||
return $this->hasAnyRole($roles, [
|
||||
Role::SUPER_ADMIN->value,
|
||||
Role::ADMIN->value,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $userRoles
|
||||
* @param string[] $allowedRoles
|
||||
*/
|
||||
private function hasAnyRole(array $userRoles, array $allowedRoles): bool
|
||||
{
|
||||
foreach ($userRoles as $role) {
|
||||
if (in_array($role, $allowedRoles, true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user