migration symfo

This commit is contained in:
2025-09-04 19:30:58 +02:00
parent efea97228b
commit 48781d086e
20 changed files with 515 additions and 65 deletions

View File

@ -2,23 +2,18 @@
namespace App\Controller;
use App\Entity\User;
use App\Repository\ProjectRepository;
use App\Service\EtherpadService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class HomeController extends AbstractController
{
#[Route('/', name: 'app_home')]
public function home(Request $request): Response
public function home(ProjectRepository $projectRepository): Response
{
$user = $this->getUser();
if (!$user instanceof User) {
throw new AccessDeniedException('Vous n\'avez pas accès à cette ressource.');
}
$projects = $user->getProjects();
$projects = $projectRepository->findAll();
return $this->render('home/home.html.twig', [
'usemenu' => true,
@ -35,4 +30,19 @@ class HomeController extends AbstractController
'usesidebar' => true,
]);
}
#[Route('/user/etherpad/{id}', name: 'app_ehterpad')]
public function etherpad(string $id, EtherpadService $etherpadService): Response
{
$padAccess = $etherpadService->preparePadAccess($this->getUser(), $id);
dump(vars: $padAccess);
$response = $this->render('etherpad/show.html.twig', [
'iframeUrl' => $padAccess['iframeUrl'],
]);
$response->headers->setCookie($padAccess['cookie']);
return $response;
}
}

View File

@ -121,6 +121,9 @@ class ProjectController extends AbstractController
$attribute = constant(ProjectVoter::class.'::MOVE'.$status);
$this->denyAccessUnlessGranted($attribute, $project);
if (Project::VOTED == $status) {
}
$project->setStatus(constant(Project::class.'::'.$status));
$em->flush();

View File

@ -42,6 +42,15 @@ class Project
#[ORM\Column(type: 'datetime', nullable: true)]
private ?\DateTimeInterface $dueDate = null;
#[ORM\Column(type: 'integer')]
private int $nbVoteWhite = 0;
#[ORM\Column(type: 'integer')]
private int $nbVoteNull = 0;
#[ORM\Column(type: 'string', nullable: true)]
private ?string $resultVote = null;
/**
* @var Collection<int, User>
*/
@ -51,14 +60,14 @@ class Project
/**
* @var Collection<int, ProjectTimeline>
*/
#[ORM\OneToMany(mappedBy: 'project', targetEntity: ProjectTimeline::class, cascade: ['remove'])]
#[ORM\OneToMany(mappedBy: 'project', targetEntity: ProjectTimeline::class, cascade: ['remove'], orphanRemoval: true)]
#[ORM\OrderBy(['createdAt' => 'DESC'])]
private Collection $timelines;
/**
* @var Collection<int, ProjectTimeline>
* @var Collection<int, ProjectOption>
*/
#[ORM\OneToMany(mappedBy: 'project', targetEntity: ProjectOption::class, cascade: ['remove'])]
#[ORM\OneToMany(mappedBy: 'project', targetEntity: ProjectOption::class, cascade: ['remove'], orphanRemoval: true)]
#[ORM\OrderBy(['title' => 'ASC'])]
private Collection $options;
@ -146,6 +155,42 @@ class Project
return $this;
}
public function getNbVoteWhite(): int
{
return $this->nbVoteWhite;
}
public function setNbVoteWhite(int $nbVoteWhite): self
{
$this->nbVoteWhite = $nbVoteWhite;
return $this;
}
public function getNbVoteNull(): int
{
return $this->nbVoteNull;
}
public function setNbVoteNull(int $nbVoteNull): self
{
$this->nbVoteNull = $nbVoteNull;
return $this;
}
public function getResultVote(): ?string
{
return $this->resultVote;
}
public function setResultVote(?string $resultVote): self
{
$this->resultVote = $resultVote;
return $this;
}
/**
* @return Collection<int, User>
*/
@ -182,4 +227,21 @@ class Project
{
return $this->options;
}
public function addOption(ProjectOption $option): self
{
if (!$this->options->contains($option)) {
$this->options->add($option);
$option->setProject($this);
}
return $this;
}
public function removeOption(ProjectOption $option): self
{
$this->options->removeElement($option);
return $this;
}
}

View File

@ -26,6 +26,9 @@ class ProjectOption
#[ORM\Column(type: 'text', nullable: true)]
private ?string $whyNot = null;
#[ORM\Column(type: 'integer')]
private int $nbVote = 0;
public function getId(): ?int
{
return $this->id;
@ -78,4 +81,16 @@ class ProjectOption
return $this;
}
public function getNbVote(): int
{
return $this->nbVote;
}
public function setNbVote(int $nbVote): self
{
$this->nbVote = $nbVote;
return $this;
}
}

View File

@ -8,6 +8,7 @@ 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\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
@ -28,6 +29,12 @@ class ProjectType extends AbstractType
'label' => 'Titre',
])
->add('dueDate', DateType::class, [
'label' => 'A Voter pour le',
'required' => false,
'html5' => true,
])
->add('summary', TextareaType::class, [
'label' => 'Résumé',
])

View File

@ -0,0 +1,40 @@
<?php
namespace App\Form;
use App\Entity\Project;
use App\Form\Type\ProjectOptionType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ProjectVotedType 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('options', CollectionType::class, [
'entry_type' => ProjectOptionType::class,
'entry_options' => ['label' => false],
'label' => false,
'allow_add' => false,
'allow_delete' => false,
'by_reference' => false,
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Project::class,
'mode' => 'submit',
]);
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Form\Type;
use App\Entity\ProjectOption;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ProjectOptionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', null, [
'disabled' => true,
'label' => 'Titre',
])
->add('nbVote', IntegerType::class, [
'label' => 'Nombre de votes',
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => ProjectOption::class,
]);
}
}

View File

@ -18,10 +18,11 @@ class ProjectVoter extends Voter
public const MOVETOVOTE = 'MOVETOVOTE';
public const MOVEVOTED = 'MOVEVOTED';
public const MOVEARCHIVED = 'MOVEARCHIVED';
public const TOME = 'TOME';
protected function supports(string $attribute, $subject): bool
{
$attributes = [self::VIEW, self::SUBMIT, self::UPDATE, self::DELETE, self::MOVEDRAFT, self::MOVETOVOTE, self::MOVEVOTED, self::MOVEARCHIVED];
$attributes = [self::VIEW, self::SUBMIT, self::UPDATE, self::DELETE, self::MOVEDRAFT, self::MOVETOVOTE, self::MOVEVOTED, self::MOVEARCHIVED, self::TOME];
return in_array($attribute, $attributes) && $subject instanceof Project;
}
@ -79,7 +80,6 @@ class ProjectVoter extends Voter
{
$hasUser = $user->hasRole('ROLE_ADMIN') || $user->hasRole('ROLE_MASTER');
$hasStatus = Project::TOVOTE === $project->getStatus() || Project::ARCHIVED === $project->getStatus();
dump($hasStatus);
return $hasUser && $hasStatus;
}
@ -92,13 +92,17 @@ class ProjectVoter extends Voter
return $hasUser && $hasStatus;
}
private function toMe(Project $project, User $user): bool
{
return $project->getUsers()->contains($user);
}
protected function voteOnAttribute(string $attribute, $project, TokenInterface $token): bool
{
$user = $token->getUser();
if (!$user instanceof User) {
return false;
}
dump($attribute);
return match ($attribute) {
self::VIEW => $this->canView($project, $user),
@ -109,6 +113,7 @@ class ProjectVoter extends Voter
self::MOVETOVOTE => $this->canMoveToVote($project, $user),
self::MOVEVOTED => $this->canMoveVoted($project, $user),
self::MOVEARCHIVED => $this->canMoveArchived($project, $user),
self::TOME => $this->toMe($project, $user),
default => false,
};
}

View File

@ -0,0 +1,117 @@
<?php
namespace App\Service;
use App\Entity\User;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Contracts\HttpClient\HttpClientInterface;
class EtherpadService
{
private HttpClientInterface $client;
private string $etherpadUrl;
private string $etherpadInternalUrl;
private string $etherpadApiKey;
public function __construct(HttpClientInterface $client, ParameterBagInterface $parameter)
{
$this->client = $client;
$this->etherpadUrl = $parameter->get('etherpadUrl');
$this->etherpadInternalUrl = $parameter->get('etherpadInternalUrl');
$this->etherpadApiKey = $parameter->get('etherpadApiKey');
}
public function preparePadAccess(User $user, string $padName): array
{
// 1. Vérifier/créer l'auteur
$authorId = $this->createAuthorIfNeeded($user);
// 2. Vérifier/créer le groupe lié à ce pad
$groupId = $this->createGroupIfNeeded($padName);
// 3. Créer le pad si pas encore créé
$this->createGroupPadIfNeeded($groupId, $padName);
// 4. Créer une session temporaire (ex: 1h)
$validUntil = time() + 3600;
$sessionId = $this->createSession($groupId, $authorId, $validUntil);
// 5. Construire le cookie à ajouter à la réponse
$cookie = Cookie::create('sessionID', $sessionId)
->withDomain(parse_url($this->etherpadInternalUrl, PHP_URL_HOST))
->withPath('/')
->withSecure(true)
->withHttpOnly(false)
->withExpires($validUntil);
// 6. Construire lURL iframe
$iframeUrl = sprintf(
'%s/p/%s?showControls=true&showChat=false&userName=%s',
rtrim($this->etherpadUrl, '/'),
$padName,
urlencode($user->getUsername())
);
return [
'iframeUrl' => $iframeUrl,
'cookie' => $cookie,
];
}
private function createAuthorIfNeeded(User $user): string
{
// Idéalement, stocker $authorId en base pour éviter de le recréer
$response = $this->client->request('GET', $this->etherpadInternalUrl.'/api/1.2.15/createAuthor', [
'query' => [
'apikey' => $this->etherpadApiKey,
'name' => $user->getUsername(),
],
]);
$data = $response->toArray(false);
return $data['data']['authorID'] ?? throw new \RuntimeException('Impossible de créer l\'author');
}
private function createGroupIfNeeded(string $padName): string
{
// Ici tu peux lier un groupID en base à ton padName
$response = $this->client->request('GET', $this->etherpadInternalUrl.'/api/1.2.15/createGroup', [
'query' => [
'apikey' => $this->etherpadApiKey,
],
]);
$data = $response->toArray(false);
return $data['data']['groupID'] ?? throw new \RuntimeException('Impossible de créer le group');
}
private function createGroupPadIfNeeded(string $groupId, string $padName): void
{
$this->client->request('GET', $this->etherpadInternalUrl.'/api/1.2.15/createGroupPad', [
'query' => [
'apikey' => $this->etherpadApiKey,
'groupID' => $groupId,
'padName' => $padName,
],
]);
}
private function createSession(string $groupId, string $authorId, int $validUntil): string
{
$response = $this->client->request('GET', $this->etherpadInternalUrl.'/api/1.2.15/createSession', [
'query' => [
'apikey' => $this->etherpadApiKey,
'groupID' => $groupId,
'authorID' => $authorId,
'validUntil' => $validUntil,
],
]);
$data = $response->toArray(false);
return $data['data']['sessionID'] ?? throw new \RuntimeException('Impossible de créer la session');
}
}