This commit is contained in:
2025-09-18 22:01:50 +02:00
parent bcdf788be3
commit 589a2eacb9
13 changed files with 139 additions and 5 deletions

View File

@@ -45,6 +45,7 @@ class GroupController extends AbstractController
$group = new Group();
$group->addUser($this->getUser());
$group->setStatus(Group::ACTIVE);
$group->setOpen(true);
$this->denyAccessUnlessGranted(GroupVoter::SUBMIT, $group);
@@ -130,6 +131,25 @@ class GroupController extends AbstractController
return $this->redirectToRoute($isAdmin ? 'app_admin_group' : 'app_user_group_view', ['id' => $group->getId()]);
}
#[Route('/admin/group/subscribe/{id}', name: 'app_admin_group_subscribe')]
#[Route('/user/group/subscribe/{id}', name: 'app_user_group_subscribe')]
public function subscribe(int $id, Request $request, GroupRepository $groupRepository, EntityManagerInterface $em): Response
{
$group = $groupRepository->find($id);
if (!$group) {
throw new NotFoundHttpException('La ressource demandée est introuvable.');
}
$this->denyAccessUnlessGranted(GroupVoter::CANSUBSCRIBE, $group);
$group->addUser($this->getUser());
$em->flush();
$isAdmin = str_starts_with($request->attributes->get('_route'), 'app_admin');
return $this->redirectToRoute($isAdmin ? 'app_admin_group' : 'app_user_group_view', ['id' => $group->getId()]);
}
#[Route('/admin/group/delete/{id}', name: 'app_admin_group_delete')]
#[Route('/user/group/delete/{id}', name: 'app_user_group_delete')]
public function delete(int $id, Request $request, GroupRepository $groupRepository, EntityManagerInterface $em): Response
@@ -174,6 +194,7 @@ class GroupController extends AbstractController
'routecancel' => $isAdmin ? 'app_admin_group' : 'app_home',
'routedelete' => $isAdmin ? 'app_admin_group_delete' : 'app_user_group_delete',
'routemove' => $isAdmin ? 'app_admin_group_move' : 'app_user_group_move',
'routesubscribe' => $isAdmin ? 'app_admin_group_subscribe' : 'app_user_group_subscribe',
'group' => $group,
]);
}

View File

@@ -47,6 +47,7 @@ class ProjectController extends AbstractController
$project = new Project();
$project->addUser($this->getUser());
$project->setStatus(Project::DRAFT);
$project->setOpen(true);
$this->denyAccessUnlessGranted(ProjectVoter::SUBMIT, $project);
@@ -161,6 +162,25 @@ class ProjectController extends AbstractController
return $this->redirectToRoute($isAdmin ? 'app_admin_project' : 'app_user_project_view', ['id' => $project->getId()]);
}
#[Route('/admin/project/subscribe/{id}', name: 'app_admin_project_subscribe')]
#[Route('/user/project/subscribe/{id}', name: 'app_user_project_subscribe')]
public function subscribe(int $id, Request $request, ProjectRepository $projectRepository, EntityManagerInterface $em): Response
{
$project = $projectRepository->find($id);
if (!$project) {
throw new NotFoundHttpException('La ressource demandée est introuvable.');
}
$this->denyAccessUnlessGranted(ProjectVoter::CANSUBSCRIBE, $project);
$project->addUser($this->getUser());
$em->flush();
$isAdmin = str_starts_with($request->attributes->get('_route'), 'app_admin');
return $this->redirectToRoute($isAdmin ? 'app_admin_project' : 'app_user_project_view', ['id' => $project->getId()]);
}
#[Route('/admin/project/delete/{id}', name: 'app_admin_project_delete')]
#[Route('/user/project/delete/{id}', name: 'app_user_project_delete')]
public function delete(int $id, Request $request, ProjectRepository $projectRepository, EntityManagerInterface $em): Response
@@ -205,6 +225,7 @@ class ProjectController extends AbstractController
'routecancel' => $isAdmin ? 'app_admin_project' : 'app_home',
'routedelete' => $isAdmin ? 'app_admin_project_delete' : 'app_user_project_delete',
'routemove' => $isAdmin ? 'app_admin_project_move' : 'app_user_project_move',
'routesubscribe' => $isAdmin ? 'app_admin_project_subscribe' : 'app_user_project_subscribe',
'project' => $project,
]);
}

View File

@@ -29,6 +29,9 @@ class Group
#[ORM\Column(type: 'text', nullable: true)]
private ?string $description = null;
#[ORM\Column(type: 'boolean', nullable: false)]
private bool $open;
#[ORM\Column]
private string $status;
@@ -95,6 +98,18 @@ class Group
return $this;
}
public function isOpen(): bool
{
return $this->open;
}
public function setOpen(bool $open): static
{
$this->open = $open;
return $this;
}
public function getStatus(): string
{
return $this->status;

View File

@@ -36,6 +36,9 @@ class Project
#[ORM\Column(type: 'string', length: 20)]
private string $nature;
#[ORM\Column(type: 'boolean', nullable: false)]
private bool $open;
#[ORM\Column]
private string $status;
@@ -131,6 +134,18 @@ class Project
return $this;
}
public function isOpen(): bool
{
return $this->open;
}
public function setOpen(bool $open): static
{
$this->open = $open;
return $this;
}
public function getStatus(): string
{
return $this->status;

View File

@@ -7,6 +7,7 @@ use App\Entity\User;
use Bnine\MdEditorBundle\Form\Type\MarkdownType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
@@ -38,8 +39,13 @@ class GroupType extends AbstractType
'label' => 'Résumé',
])
->add('open', CheckboxType::class, [
'label' => 'Groupe Ouvert',
'required' => false,
])
->add('users', EntityType::class, [
'label' => 'Propriétaires',
'label' => 'Paticipants',
'class' => User::class,
'choice_label' => 'username',
'multiple' => true,

View File

@@ -7,6 +7,7 @@ use App\Entity\User;
use Bnine\MdEditorBundle\Form\Type\MarkdownType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
@@ -39,6 +40,11 @@ class ProjectType extends AbstractType
'label' => 'Résumé',
])
->add('open', CheckboxType::class, [
'label' => 'Projet Ouvert',
'required' => false,
])
->add('nature', ChoiceType::class, [
'label' => 'Nature',
'choices' => [
@@ -49,7 +55,7 @@ class ProjectType extends AbstractType
])
->add('users', EntityType::class, [
'label' => 'Propriétaires',
'label' => 'Paticipants',
'class' => User::class,
'choice_label' => 'username',
'multiple' => true,

View File

@@ -17,10 +17,11 @@ class GroupVoter extends Voter
public const MOVETOACTIVE = 'MOVETOACTIVE';
public const MOVETOINACTIVE = 'MOVETOINACTIVE';
public const TOME = 'TOME';
public const CANSUBSCRIBE = 'CANSUBSCRIBE';
protected function supports(string $attribute, $subject): bool
{
$attributes = [self::VIEW, self::SUBMIT, self::UPDATE, self::DELETE, self::MOVETOACTIVE, self::MOVETOINACTIVE, self::TOME];
$attributes = [self::VIEW, self::SUBMIT, self::UPDATE, self::DELETE, self::MOVETOACTIVE, self::MOVETOINACTIVE, self::TOME, self::CANSUBSCRIBE];
return in_array($attribute, $attributes) && $subject instanceof Group;
}
@@ -76,6 +77,11 @@ class GroupVoter extends Voter
return $group->getUsers()->contains($user);
}
private function canSubscribe(Group $group, User $user): bool
{
return $group->isOpen() && !$group->getUsers()->contains($user);
}
protected function voteOnAttribute(string $attribute, $group, TokenInterface $token): bool
{
$user = $token->getUser();
@@ -91,6 +97,8 @@ class GroupVoter extends Voter
self::MOVETOACTIVE => $this->canMoveToActive($group, $user),
self::MOVETOINACTIVE => $this->canMoveToInactive($group, $user),
self::TOME => $this->toMe($group, $user),
self::CANSUBSCRIBE => $this->canSubscribe($group, $user),
default => false,
};
}

View File

@@ -19,10 +19,11 @@ class ProjectVoter extends Voter
public const MOVEVOTED = 'MOVEVOTED';
public const MOVEARCHIVED = 'MOVEARCHIVED';
public const TOME = 'TOME';
public const CANSUBSCRIBE = 'CANSUBSCRIBE';
protected function supports(string $attribute, $subject): bool
{
$attributes = [self::VIEW, self::SUBMIT, self::UPDATE, self::DELETE, self::MOVEDRAFT, self::MOVETOVOTE, self::MOVEVOTED, self::MOVEARCHIVED, self::TOME];
$attributes = [self::VIEW, self::SUBMIT, self::UPDATE, self::DELETE, self::MOVEDRAFT, self::MOVETOVOTE, self::MOVEVOTED, self::MOVEARCHIVED, self::TOME, self::CANSUBSCRIBE];
return in_array($attribute, $attributes) && $subject instanceof Project;
}
@@ -97,6 +98,11 @@ class ProjectVoter extends Voter
return $project->getUsers()->contains($user);
}
private function canSubscribe(Project $project, User $user): bool
{
return $project->isOpen() && !$project->getUsers()->contains($user);
}
protected function voteOnAttribute(string $attribute, $project, TokenInterface $token): bool
{
$user = $token->getUser();
@@ -114,6 +120,7 @@ class ProjectVoter extends Voter
self::MOVEVOTED => $this->canMoveVoted($project, $user),
self::MOVEARCHIVED => $this->canMoveArchived($project, $user),
self::TOME => $this->toMe($project, $user),
self::CANSUBSCRIBE => $this->canSubscribe($project, $user),
default => false,
};
}

View File

@@ -20,6 +20,7 @@
<div class="card-header">Information</div>
<div class="card-body">
{{ form_row(form.title) }}
{{ form_row(form.open) }}
{{ form_row(form.dueDate) }}
{{ form_row(form.users) }}
{{ form_row(form.summary) }}

View File

@@ -15,6 +15,12 @@
<a href="{{ path(routecancel) }}" class="btn btn-secondary me-5">Retour</a>
{% if is_granted('CANSUBSCRIBE', group) %}
<a href="{{ path(routesubscribe,{id:group.id}) }}" class="btn btn-secondary me-1" onclick="return confirm('Participer à ce groupe ?')">
<i class="fas fa-users"></i> Participer à ce groupe
</a>
{% endif %}
{% if is_granted('MOVETOINACTIVE', group) and group.status=="Actif"%}
<a href="{{ path(routemove,{id:group.id, status:"INACTIVE"}) }}" class="btn btn-primary me-1" onclick="return confirm('Statut = Inactif ?')">
<i class="fas fa-angle-double-right"></i> Statut = Inactif
@@ -39,6 +45,7 @@
<div class="card-header">Information</div>
<div class="card-body">
<b>Titre</b> = {{ group.title }}<br>
<b>Groupe Ouvert</b> = {{ group.open ? "Oui" : "Non" }}<br>
<b>A Voter pour le</b> = {{ group.dueDate ? group.dueDate|date("d/m/Y"):"" }}
</div>
</div>
@@ -50,6 +57,15 @@
</div>
</div>
<div class="card mt-3">
<div class="card-header">Participants</div>
<div class="card-body">
{%for user in group.users%}
{{loop.first ? user.username : ' - '~user.username}}
{%endfor%}
</div>
</div>
{{ render(path("bninefiles_files",{domain:'group',id:group.id, editable:0})) }}
<div class="card mt-3">

View File

@@ -84,7 +84,7 @@
<b>A Voter pour le</b> = {{ project.dueDate|date("d/m/Y") }}<br>
{% endif %}
<b>Nature</b> = {{ project.nature }}<br>
<b>Propriétaires</b> =
<b>Participants</b> =
{%for user in project.users%}
{{loop.first ? user.username : ' - '~user.username}}
{%endfor%}

View File

@@ -21,6 +21,8 @@
<div class="card-header">Information</div>
<div class="card-body">
{{ form_row(form.title) }}
{{ form_row(form.open) }}
{{ form_row(form.nature) }}
{{ form_row(form.dueDate) }}
{{ form_row(form.users) }}

View File

@@ -15,6 +15,12 @@
<a href="{{ path(routecancel) }}" class="btn btn-secondary me-5">Retour</a>
{% if is_granted('CANSUBSCRIBE', project) %}
<a href="{{ path(routesubscribe,{id:project.id}) }}" class="btn btn-secondary me-1" onclick="return confirm('Participer à ce projet ?')">
<i class="fas fa-users"></i> Participer à ce projet
</a>
{% endif %}
{% if is_granted('MOVETOVOTE', project) and project.status=="Brouillon"%}
<a href="{{ path(routemove,{id:project.id, status:"TOVOTE"}) }}" class="btn btn-primary me-1" onclick="return confirm('Statut = à Voter ?')">
<i class="fas fa-angle-double-right"></i> Statut = à Voter
@@ -59,6 +65,7 @@
<div class="card-header">Information</div>
<div class="card-body">
<b>Titre</b> = {{ project.title }}<br>
<b>Projet Ouvert</b> = {{ project.open ? "Oui" : "Non" }}<br>
<b>Nature</b> = {{ project.nature }}<br>
<b>A Voter pour le</b> = {{ project.dueDate ? project.dueDate|date("d/m/Y"):"" }}
</div>
@@ -71,6 +78,15 @@
</div>
</div>
<div class="card mt-3">
<div class="card-header">Participants</div>
<div class="card-body">
{%for user in project.users%}
{{loop.first ? user.username : ' - '~user.username}}
{%endfor%}
</div>
</div>
{{ render(path("bninefiles_files",{domain:'project',id:project.id, editable:0})) }}
<div class="card mt-3">