This commit is contained in:
2025-08-05 22:37:43 +02:00
parent fc7494be40
commit efea97228b
15 changed files with 486 additions and 64 deletions

View File

@ -56,12 +56,12 @@ class ProjectController extends AbstractController
$em->flush();
$this->fileService->init('project', $project->getId());
return $this->redirectToRoute($isAdmin ? 'app_admin_project' : 'app_user_update', ['id' => $project->getId()]);
return $this->redirectToRoute($isAdmin ? 'app_admin_project_update' : 'app_user_project_update', ['id' => $project->getId()]);
}
return $this->render('project/edit.html.twig', [
'usemenu' => true,
'usesidebar' => true,
'usesidebar' => $isAdmin,
'title' => 'Création Projet',
'routecancel' => $isAdmin ? 'app_admin_project' : 'app_home',
'routedelete' => $isAdmin ? 'app_admin_project_delete' : 'app_user_project_delete',
@ -87,7 +87,11 @@ class ProjectController extends AbstractController
if ($form->isSubmitted() && $form->isValid()) {
$em->flush();
return $this->redirectToRoute($isAdmin ? 'app_admin_project' : 'app_home');
if ($isAdmin) {
return $this->redirectToRoute('app_admin_project');
} else {
return $this->redirectToRoute('app_user_project_view', ['id' => $project->getId()]);
}
}
return $this->render('project/edit.html.twig', [
@ -95,7 +99,10 @@ class ProjectController extends AbstractController
'usesidebar' => $isAdmin,
'title' => 'Modification Projet = '.$project->getTitle(),
'routecancel' => $isAdmin ? 'app_admin_project' : 'app_home',
'routemove' => $isAdmin ? 'app_admin_project_move' : 'app_user_project_move',
'routedelete' => $isAdmin ? 'app_admin_project_delete' : 'app_user_project_delete',
'routesubmitoption' => $isAdmin ? 'app_admin_option_submit' : 'app_user_option_submit',
'routeupdateoption' => $isAdmin ? 'app_admin_option_update' : 'app_user_option_update',
'mode' => 'update',
'form' => $form,
'project' => $project,
@ -119,7 +126,7 @@ class ProjectController extends AbstractController
$isAdmin = str_starts_with($request->attributes->get('_route'), 'app_admin');
return $this->redirectToRoute($isAdmin ? 'app_admin_project' : 'app_home');
return $this->redirectToRoute($isAdmin ? 'app_admin_project' : 'app_user_project_view', ['id' => $project->getId()]);
}
#[Route('/admin/project/delete/{id}', name: 'app_admin_project_delete')]
@ -141,8 +148,6 @@ class ProjectController extends AbstractController
$em->flush();
} catch (\Exception $e) {
$this->addflash('error', $e->getMessage());
return $this->redirectToRoute($isAdmin ? 'app_admin_project' : 'app_user_update', ['id' => $project->getId()]);
}
return $this->redirectToRoute($isAdmin ? 'app_admin_project' : 'app_home');
@ -158,11 +163,16 @@ class ProjectController extends AbstractController
$this->denyAccessUnlessGranted(ProjectVoter::VIEW, $project);
$isAdmin = str_starts_with($request->attributes->get('_route'), 'app_admin');
return $this->render('project/view.html.twig', [
'usemenu' => true,
'usesidebar' => false,
'title' => 'Projet = '.$project->getTitle(),
'routecancel' => 'app_home',
'routeupdate' => $isAdmin ? 'app_admin_project_update' : 'app_user_project_update',
'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',
'project' => $project,
]);
}

View File

@ -0,0 +1,111 @@
<?php
namespace App\Controller;
use App\Entity\ProjectOption;
use App\Form\OptionType;
use App\Repository\ProjectOptionRepository;
use App\Repository\ProjectRepository;
use App\Security\ProjectVoter;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Attribute\Route;
class ProjectOptionController extends AbstractController
{
#[Route('/admin/option/submit/{idproject}', name: 'app_admin_option_submit')]
#[Route('/user/option/submit/{idproject}', name: 'app_user_option_submit')]
public function submit(int $idproject, Request $request, ProjectRepository $projectRepository, EntityManagerInterface $em): Response
{
$project = $projectRepository->find($idproject);
if (!$project) {
throw new NotFoundHttpException('La ressource demandée est introuvable.');
}
$this->denyAccessUnlessGranted(ProjectVoter::UPDATE, $project);
$option = new ProjectOption();
$option->setProject($project);
$isAdmin = str_starts_with($request->attributes->get('_route'), 'app_admin');
$form = $this->createForm(OptionType::class, $option, ['mode' => 'submit']);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em->persist($option);
$em->flush();
return $this->redirectToRoute($isAdmin ? 'app_admin_project_update' : 'app_user_project_update', ['id' => $idproject]);
}
return $this->render('option/edit.html.twig', [
'usemenu' => true,
'usesidebar' => $isAdmin,
'title' => 'Création Option',
'routecancel' => $isAdmin ? 'app_admin_project_update' : 'app_admin_project_update',
'routedelete' => $isAdmin ? 'app_admin_option_delete' : 'app_user_option_delete',
'mode' => 'submit',
'idproject' => $idproject,
'form' => $form,
]);
}
#[Route('/admin/option/update/{idproject}/{id}', name: 'app_admin_option_update')]
#[Route('/user/option/update/{idproject}/{id}', name: 'app_user_option_update')]
public function update(int $idproject, int $id, Request $request, ProjectOptionRepository $projectOptionResitory, EntityManagerInterface $em): Response
{
$option = $projectOptionResitory->find($id);
if (!$option) {
throw new NotFoundHttpException('La ressource demandée est introuvable.');
}
$this->denyAccessUnlessGranted(ProjectVoter::UPDATE, $option->getProject());
$isAdmin = str_starts_with($request->attributes->get('_route'), 'app_admin');
$form = $this->createForm(OptionType::class, $option, ['mode' => 'update']);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em->flush();
return $this->redirectToRoute($isAdmin ? 'app_admin_project_update' : 'app_user_project_update', ['id' => $idproject]);
}
return $this->render('option/edit.html.twig', [
'usemenu' => true,
'usesidebar' => $isAdmin,
'title' => 'Modification Option = '.$option->getTitle(),
'routecancel' => $isAdmin ? 'app_admin_project_update' : 'app_user_project_update',
'routedelete' => $isAdmin ? 'app_admin_option_delete' : 'app_user_option_delete',
'mode' => 'update',
'idproject' => $idproject,
'option' => $option,
'form' => $form,
]);
}
#[Route('/admin/option/delete/{idproject}/{id}', name: 'app_admin_option_delete')]
#[Route('/user/option/delete/{idproject}/{id}', name: 'app_user_option_delete')]
public function delete(int $idproject, int $id, Request $request, ProjectOptionRepository $projectOptionResitory, EntityManagerInterface $em): Response
{
$option = $projectOptionResitory->find($id);
if (!$option) {
throw new NotFoundHttpException('La ressource demandée est introuvable.');
}
$this->denyAccessUnlessGranted(ProjectVoter::UPDATE, $option->getProject());
$isAdmin = str_starts_with($request->attributes->get('_route'), 'app_admin');
// Tentative de suppression
try {
$em->remove($option);
$em->flush();
} catch (\Exception $e) {
$this->addflash('error', $e->getMessage());
}
return $this->redirectToRoute($isAdmin ? 'app_admin_project_update' : 'app_user_project_update', ['id' => $idproject]);
}
}

View File

@ -55,10 +55,18 @@ class Project
#[ORM\OrderBy(['createdAt' => 'DESC'])]
private Collection $timelines;
/**
* @var Collection<int, ProjectTimeline>
*/
#[ORM\OneToMany(mappedBy: 'project', targetEntity: ProjectOption::class, cascade: ['remove'])]
#[ORM\OrderBy(['title' => 'ASC'])]
private Collection $options;
public function __construct()
{
$this->users = new ArrayCollection();
$this->timelines = new ArrayCollection();
$this->options = new ArrayCollection();
}
public function getId(): ?int
@ -169,4 +177,9 @@ class Project
{
return $this->timelines;
}
public function getOptions(): Collection
{
return $this->options;
}
}

View File

@ -0,0 +1,81 @@
<?php
namespace App\Entity;
use App\Repository\ProjectOptionRepository;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: ProjectOptionRepository::class)]
class ProjectOption
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\ManyToOne(targetEntity: Project::class, inversedBy: 'timelines')]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
private Project $project;
#[ORM\Column(length: 255)]
private string $title;
#[ORM\Column(type: 'text', nullable: true)]
private ?string $whyYes = null;
#[ORM\Column(type: 'text', nullable: true)]
private ?string $whyNot = null;
public function getId(): ?int
{
return $this->id;
}
public function getProject(): Project
{
return $this->project;
}
public function setProject(Project $project): self
{
$this->project = $project;
return $this;
}
public function getTitle(): string
{
return $this->title;
}
public function setTitle(string $title): self
{
$this->title = $title;
return $this;
}
public function getWhyYes(): ?string
{
return $this->whyYes;
}
public function setWhyYes(?string $whyYes): self
{
$this->whyYes = $whyYes;
return $this;
}
public function getWhyNot(): ?string
{
return $this->whyNot;
}
public function setWhyNot(?string $whyNot): self
{
$this->whyNot = $whyNot;
return $this;
}
}

45
src/Form/OptionType.php Normal file
View File

@ -0,0 +1,45 @@
<?php
namespace App\Form;
use App\Entity\ProjectOption;
use Bnine\MdEditorBundle\Form\Type\MarkdownType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class OptionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('submit', SubmitType::class, [
'label' => 'Valider',
'attr' => ['class' => 'btn btn-success no-print me-1'],
])
->add('title', TextType::class, [
'label' => 'Titre',
])
->add('whyYes', MarkdownType::class, [
'label' => 'Pourquoi Voter Pour',
'markdown_height' => 200,
])
->add('whyNot', MarkdownType::class, [
'label' => 'Pourquoi Voter Contre',
'markdown_height' => 200,
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => ProjectOption::class,
'mode' => 'submit',
]);
}
}

View File

@ -21,7 +21,7 @@ class ProjectType extends AbstractType
$builder
->add('submit', SubmitType::class, [
'label' => 'Valider',
'attr' => ['class' => 'btn btn-success no-print'],
'attr' => ['class' => 'btn btn-success no-print me-1'],
])
->add('title', TextType::class, [

View File

@ -24,7 +24,7 @@ class UserType extends AbstractType
$builder
->add('submit', SubmitType::class, [
'label' => 'Valider',
'attr' => ['class' => 'btn btn-success no-print'],
'attr' => ['class' => 'btn btn-success no-print me-1'],
])
->add('username', TextType::class, [

View File

@ -0,0 +1,18 @@
<?php
namespace App\Repository;
use App\Entity\ProjectOption;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<ProjectOption>
*/
class ProjectOptionRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, ProjectOption::class);
}
}

View File

@ -16,10 +16,12 @@ class ProjectVoter extends Voter
public const DELETE = 'DELETE';
public const MOVEDRAFT = 'MOVEDRAFT';
public const MOVETOVOTE = 'MOVETOVOTE';
public const MOVEVOTED = 'MOVEVOTED';
public const MOVEARCHIVED = 'MOVEARCHIVED';
protected function supports(string $attribute, $subject): bool
{
$attributes = [self::VIEW, self::SUBMIT, self::UPDATE, self::DELETE, self::MOVEDRAFT, self::MOVETOVOTE];
$attributes = [self::VIEW, self::SUBMIT, self::UPDATE, self::DELETE, self::MOVEDRAFT, self::MOVETOVOTE, self::MOVEVOTED, self::MOVEARCHIVED];
return in_array($attribute, $attributes) && $subject instanceof Project;
}
@ -36,22 +38,58 @@ class ProjectVoter extends Voter
private function canUpdate(Project $project, User $user): bool
{
return $user->hasRole('ROLE_ADMIN') || (Project::DRAFT === $project->getStatus() && $project->getUsers()->contains($user));
$hasMaster = $user->hasRole('ROLE_ADMIN') || $user->hasRole('ROLE_MASTER');
$hasUser = $project->getUsers()->contains($user);
$hasStatus = Project::DRAFT === $project->getStatus();
return $hasMaster || ($hasUser && $hasStatus);
}
private function canDelete(Project $project, User $user): bool
{
return $user->hasRole('ROLE_ADMIN') || (Project::DRAFT === $project->getStatus() && $project->getUsers()->contains($user));
$hasMaster = $user->hasRole('ROLE_ADMIN') || $user->hasRole('ROLE_MASTER');
$hasUser = $project->getUsers()->contains($user);
$hasStatus = Project::DRAFT === $project->getStatus();
return $hasMaster || ($hasUser && $hasStatus);
}
private function canMoveDraft(Project $project, User $user): bool
{
return $user->hasRole('ROLE_ADMIN') || (Project::TOVOTE === $project->getStatus() && $project->getUsers()->contains($user));
$hasUser = $user->hasRole('ROLE_ADMIN') || $user->hasRole('ROLE_MASTER') || $project->getUsers()->contains($user);
$hasStatus = Project::TOVOTE === $project->getStatus();
return $hasUser && $hasStatus;
}
private function canMoveToVote(Project $project, User $user): bool
{
return $user->hasRole('ROLE_ADMIN') || (Project::DRAFT === $project->getStatus() && $project->getUsers()->contains($user));
$hasMaster = $user->hasRole('ROLE_ADMIN') || $user->hasRole('ROLE_MASTER');
$hasUser = $project->getUsers()->contains($user);
if (Project::VOTED === $project->getStatus()) {
return $hasMaster;
} elseif (Project::DRAFT === $project->getStatus()) {
return $hasMaster || $hasUser;
} else {
return false;
}
}
private function canMoveVoted(Project $project, User $user): bool
{
$hasUser = $user->hasRole('ROLE_ADMIN') || $user->hasRole('ROLE_MASTER');
$hasStatus = Project::TOVOTE === $project->getStatus() || Project::ARCHIVED === $project->getStatus();
dump($hasStatus);
return $hasUser && $hasStatus;
}
private function canMoveArchived(Project $project, User $user): bool
{
$hasUser = $user->hasRole('ROLE_ADMIN') || $user->hasRole('ROLE_MASTER');
$hasStatus = Project::VOTED === $project->getStatus();
return $hasUser && $hasStatus;
}
protected function voteOnAttribute(string $attribute, $project, TokenInterface $token): bool
@ -60,6 +98,7 @@ class ProjectVoter extends Voter
if (!$user instanceof User) {
return false;
}
dump($attribute);
return match ($attribute) {
self::VIEW => $this->canView($project, $user),
@ -68,6 +107,8 @@ class ProjectVoter extends Voter
self::DELETE => $this->canDelete($project, $user),
self::MOVEDRAFT => $this->canMoveDraft($project, $user),
self::MOVETOVOTE => $this->canMoveToVote($project, $user),
self::MOVEVOTED => $this->canMoveVoted($project, $user),
self::MOVEARCHIVED => $this->canMoveArchived($project, $user),
default => false,
};
}