svg
This commit is contained in:
34
composer.lock
generated
34
composer.lock
generated
@ -420,16 +420,16 @@
|
||||
},
|
||||
{
|
||||
"name": "doctrine/dbal",
|
||||
"version": "3.10.0",
|
||||
"version": "3.10.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/dbal.git",
|
||||
"reference": "1cf840d696373ea0d58ad0a8875c0fadcfc67214"
|
||||
"reference": "3626601014388095d3af9de7e9e958623b7ef005"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/dbal/zipball/1cf840d696373ea0d58ad0a8875c0fadcfc67214",
|
||||
"reference": "1cf840d696373ea0d58ad0a8875c0fadcfc67214",
|
||||
"url": "https://api.github.com/repos/doctrine/dbal/zipball/3626601014388095d3af9de7e9e958623b7ef005",
|
||||
"reference": "3626601014388095d3af9de7e9e958623b7ef005",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -514,7 +514,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/doctrine/dbal/issues",
|
||||
"source": "https://github.com/doctrine/dbal/tree/3.10.0"
|
||||
"source": "https://github.com/doctrine/dbal/tree/3.10.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -530,7 +530,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-07-10T21:11:04+00:00"
|
||||
"time": "2025-08-05T12:18:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/deprecations",
|
||||
@ -1221,16 +1221,16 @@
|
||||
},
|
||||
{
|
||||
"name": "doctrine/orm",
|
||||
"version": "3.5.0",
|
||||
"version": "3.5.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/orm.git",
|
||||
"reference": "6deec3655ba3e8f15280aac11e264225854d2369"
|
||||
"reference": "64444dcfd511089d526cd2c7f74b9d7ed583bdfc"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/orm/zipball/6deec3655ba3e8f15280aac11e264225854d2369",
|
||||
"reference": "6deec3655ba3e8f15280aac11e264225854d2369",
|
||||
"url": "https://api.github.com/repos/doctrine/orm/zipball/64444dcfd511089d526cd2c7f74b9d7ed583bdfc",
|
||||
"reference": "64444dcfd511089d526cd2c7f74b9d7ed583bdfc",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1305,9 +1305,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/doctrine/orm/issues",
|
||||
"source": "https://github.com/doctrine/orm/tree/3.5.0"
|
||||
"source": "https://github.com/doctrine/orm/tree/3.5.1"
|
||||
},
|
||||
"time": "2025-07-01T17:40:53+00:00"
|
||||
"time": "2025-08-05T06:05:51+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/persistence",
|
||||
@ -10086,16 +10086,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "2.1.21",
|
||||
"version": "2.1.22",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan.git",
|
||||
"reference": "1ccf445757458c06a04eb3f803603cb118fe5fa6"
|
||||
"reference": "41600c8379eb5aee63e9413fe9e97273e25d57e4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/1ccf445757458c06a04eb3f803603cb118fe5fa6",
|
||||
"reference": "1ccf445757458c06a04eb3f803603cb118fe5fa6",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/41600c8379eb5aee63e9413fe9e97273e25d57e4",
|
||||
"reference": "41600c8379eb5aee63e9413fe9e97273e25d57e4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -10140,7 +10140,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-07-28T19:35:08+00:00"
|
||||
"time": "2025-08-04T19:17:37+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan-doctrine",
|
||||
|
@ -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,
|
||||
]);
|
||||
}
|
||||
|
111
src/Controller/ProjectOptionController.php
Normal file
111
src/Controller/ProjectOptionController.php
Normal 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]);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
81
src/Entity/ProjectOption.php
Normal file
81
src/Entity/ProjectOption.php
Normal 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
45
src/Form/OptionType.php
Normal 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',
|
||||
]);
|
||||
}
|
||||
}
|
@ -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, [
|
||||
|
@ -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, [
|
||||
|
18
src/Repository/ProjectOptionRepository.php
Normal file
18
src/Repository/ProjectOptionRepository.php
Normal 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);
|
||||
}
|
||||
}
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
@ -4,30 +4,28 @@
|
||||
|
||||
{%block body%}
|
||||
<h2>Projets</h2>
|
||||
<a href="{{ path('app_user_project_submit') }}" class="btn btn-success mb-3">Ajouter</a>
|
||||
|
||||
<div class='d-flex flex-wrap' style='justify-content: left'>
|
||||
{% for project in projects %}
|
||||
{% if is_granted('VIEW', project) %}
|
||||
<div class='card' style='width:300px; margin-right:10px;'>
|
||||
<div class='card-header d-flex justify-content-between align-items-center'>
|
||||
{{project.status}}<br>
|
||||
{{project.title}}
|
||||
|
||||
<div>
|
||||
{% if is_granted('UPDATE', project) %}
|
||||
<a href="{{ path("app_user_project_update",{id:project.id}) }}" class="btn btn-primary btn-sm"><i class="fas fa-pencil"></i></a>
|
||||
{% endif %}
|
||||
<h5>{{project.title}}</h5>
|
||||
|
||||
<div>
|
||||
<a href="{{ path("app_user_project_view",{id:project.id}) }}" class="btn btn-secondary btn-sm"><i class="fas fa-eye"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class='card-body'>
|
||||
{{project.summary|raw}}
|
||||
|
||||
<br>
|
||||
{{project.summary|nl2br}}
|
||||
</div>
|
||||
<div class='card-footer'>
|
||||
<small><em>
|
||||
Propriétaires =
|
||||
<b>Statut</b> = {{ project.status }}<br>
|
||||
<b>Propriétaires</b> =
|
||||
{%for user in project.users%}
|
||||
{{' '~user.username}}
|
||||
{{loop.first ? user.username : ' - '~user.username}}
|
||||
{%endfor%}
|
||||
</em></small>
|
||||
</div>
|
||||
|
58
templates/option/edit.html.twig
Normal file
58
templates/option/edit.html.twig
Normal file
@ -0,0 +1,58 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %} = {{title}}{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<h1>{{title}}</h1>
|
||||
|
||||
|
||||
{{ form_start(form) }}
|
||||
{{ form_widget(form.submit) }}
|
||||
<a href="{{ path(routecancel,{id:idproject}) }}" class="btn btn-secondary me-5">Annuler</a>
|
||||
{% if mode=="update" %}
|
||||
<a href="{{ path(routedelete,{idproject:option.project.id,id:option.id}) }}" class="btn btn-danger float-end" onclick="return confirm('Confirmez-vous la suppression de cet enregistrement ?')">Supprimer</a>
|
||||
{% endif %}
|
||||
|
||||
{% include('include/error.html.twig') %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4 mx-auto">
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">Information</div>
|
||||
<div class="card-body">
|
||||
{{ form_row(form.title) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-2"></div>
|
||||
<div class="col-md-4">
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">Information</div>
|
||||
<div class="card-body">
|
||||
{{ form_row(form.whyYes) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">Information</div>
|
||||
<div class="card-body">
|
||||
{{ form_row(form.whyNot) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ form_end(form) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block localscript %}
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$("#project_title").focus();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
@ -8,18 +8,10 @@
|
||||
|
||||
{{ form_start(form) }}
|
||||
{{ form_widget(form.submit) }}
|
||||
<a href="{{ path(routecancel) }}" class="btn btn-secondary ms-1">Annuler</a>
|
||||
{%if mode=="update" %}
|
||||
{% if is_granted('MOVEDRAFT', project) %}
|
||||
<a href="{{ path("app_user_project_move",{id:form.vars.value.id, status:"DRAFT"}) }}" class="btn btn-primary" onclick="return confirm('Confirmez-vous le passage au vote ?')">Mettre en Brouillon</a>
|
||||
{% endif %}
|
||||
{% if is_granted('MOVETOVOTE', project) %}
|
||||
<a href="{{ path("app_user_project_move",{id:form.vars.value.id, status:"TOVOTE"}) }}" class="btn btn-primary" onclick="return confirm('Confirmez-vous le passage au vote ?')">Mettre au Vote</a>
|
||||
{% endif %}
|
||||
{% if is_granted('DELETE', project) %}
|
||||
<a href="{{ path(routedelete,{id:form.vars.value.id}) }}" class="btn btn-danger float-end" onclick="return confirm('Confirmez-vous la suppression de cet enregistrement ?')">Supprimer</a>
|
||||
{% endif %}
|
||||
{%endif%}
|
||||
<a href="{{ path(routecancel) }}" class="btn btn-secondary me-5">Annuler</a>
|
||||
{% if mode=="update" and is_granted('DELETE', project) %}
|
||||
<a href="{{ path(routedelete,{id:project.id}) }}" class="btn btn-danger float-end" onclick="return confirm('Confirmez-vous la suppression de cet enregistrement ?')">Supprimer</a>
|
||||
{% endif %}
|
||||
|
||||
{% include('include/error.html.twig') %}
|
||||
|
||||
@ -49,6 +41,34 @@
|
||||
|
||||
{% if mode=="update" %}
|
||||
<div class="col-md-8 mx-auto">
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">Options de Vote</div>
|
||||
<div class="card-body">
|
||||
<a href="{{ path(routesubmitoption,{idproject:project.id}) }}" class="btn btn-success btn-sm mb-3">Ajouter</a>
|
||||
|
||||
<div class="dataTable_wrapper">
|
||||
<table class="table table-striped table-bordered table-hover" id="dataTables" style="width:100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="100px" class="no-sort">Action</th>
|
||||
<th>Title</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for option in project.options %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{ path(routeupdateoption,{idproject:project.id,id:option.id}) }}" class="me-2"><i class="fas fa-file fa-2x"></i></a>
|
||||
</td>
|
||||
<td>{{option.title}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">Description du Projet</div>
|
||||
<div class="card-body">
|
||||
|
12
templates/project/status.html.twig
Normal file
12
templates/project/status.html.twig
Normal file
@ -0,0 +1,12 @@
|
||||
{% if is_granted('MOVEDRAFT', project) %}
|
||||
<a href="{{ path(routemove,{id:project.id, status:"DRAFT"}) }}" class="btn btn-primary me-1" onclick="return confirm('Confirmez-vous le passage au vote ?')">Mettre en Brouillon</a>
|
||||
{% endif %}
|
||||
{% if is_granted('MOVETOVOTE', project) %}
|
||||
<a href="{{ path(routemove,{id:project.id, status:"TOVOTE"}) }}" class="btn btn-primary me-1" onclick="return confirm('Confirmez-vous le passage au vote ?')">Mettre au Vote</a>
|
||||
{% endif %}
|
||||
{% if is_granted('MOVEVOTED', project) %}
|
||||
<a href="{{ path(routemove,{id:project.id, status:"VOTED"}) }}" class="btn btn-primary me-1" onclick="return confirm('Confirmez-vous le vote ?')">Voter</a>
|
||||
{% endif %}
|
||||
{% if is_granted('DELETE', project) %}
|
||||
<a href="{{ path(routedelete,{id:project.id}) }}" class="btn btn-danger float-end" onclick="return confirm('Confirmez-vous la suppression de cet enregistrement ?')">Supprimer</a>
|
||||
{% endif %}
|
@ -3,29 +3,44 @@
|
||||
{% block title %} = {{title}}{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<h1>{{title}}</h1>
|
||||
<h1 style="display:flex;">
|
||||
<div style="flex-grow:1">{{title}}</div>
|
||||
<div class="">{{project.status}}</div>
|
||||
</h1>
|
||||
|
||||
{% if is_granted('UPDATE', project) %}
|
||||
<a href="{{ path(routeupdate,{id:project.id}) }}" class="btn btn-success me-1">Modifier</a>
|
||||
{% endif %}
|
||||
|
||||
<a href="{{ path(routecancel) }}" class="btn btn-secondary ms-1">Annuler</a>
|
||||
<a href="{{ path(routecancel) }}" class="btn btn-secondary me-5">Retour</a>
|
||||
|
||||
{% if is_granted('MOVEDRAFT', project) %}
|
||||
<a href="{{ path("app_user_project_move",{id:project.id, status:"DRAFT"}) }}" class="btn btn-primary" onclick="return confirm('Confirmez-vous le passage au vote ?')">Mettre en Brouillon</a>
|
||||
<a href="{{ path(routemove,{id:project.id, status:"DRAFT"}) }}" class="btn btn-primary me-1" onclick="return confirm('Confirmez-vous le passage au vote ?')">Mettre en Brouillon</a>
|
||||
{% endif %}
|
||||
{% if is_granted('MOVETOVOTE', project) %}
|
||||
<a href="{{ path("app_user_project_move",{id:project.id, status:"TOVOTE"}) }}" class="btn btn-primary" onclick="return confirm('Confirmez-vous le passage au vote ?')">Mettre au Vote</a>
|
||||
<a href="{{ path(routemove,{id:project.id, status:"TOVOTE"}) }}" class="btn btn-primary me-1" onclick="return confirm('Confirmez-vous le passage au vote ?')">Mettre au Vote</a>
|
||||
{% endif %}
|
||||
{% if is_granted('MOVEVOTED', project) %}
|
||||
<a href="{{ path(routemove,{id:project.id, status:"VOTED"}) }}" class="btn btn-primary me-1" onclick="return confirm('Confirmez-vous le vote ?')">Voter</a>
|
||||
{% endif %}
|
||||
{% if is_granted('DELETE', project) %}
|
||||
<a href="{{ path(routedelete,{id:project.id}) }}" class="btn btn-danger float-end" onclick="return confirm('Confirmez-vous la suppression de cet enregistrement ?')">Supprimer</a>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4 mx-auto">
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">Information</div>
|
||||
<div class="card-body">
|
||||
{{ project.title }}
|
||||
{{ project.nature }}
|
||||
{{ project.summary }}
|
||||
<b>Titre</b> = {{ project.title }}<br>
|
||||
<b>Nature</b> = {{ project.nature }}<br><br>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">Résumé</div>
|
||||
<div class="card-body">
|
||||
{{ project.summary|nl2br }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -43,7 +58,7 @@
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">Description du Projet</div>
|
||||
<div class="card-body">
|
||||
{{ project.description }}
|
||||
{{ project.description ? project.description|markdown_to_html : "" }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user