diff --git a/config/packages/vich_uploader.yaml b/config/packages/vich_uploader.yaml index 31d5460..7cfcc53 100644 --- a/config/packages/vich_uploader.yaml +++ b/config/packages/vich_uploader.yaml @@ -82,6 +82,13 @@ vich_uploader: inject_on_load: true delete_on_update: true delete_on_remove: true + fiche: + uri_prefix: /fiche_candidat + upload_destination: '%kernel.project_dir%/public/storage/fiche_candidat' + namer: Vich\UploaderBundle\Naming\UniqidNamer # Replaced namer + inject_on_load: true + delete_on_update: true + delete_on_remove: true #mappings: # products: # uri_prefix: /images/products diff --git a/migrations/Version20251225001531.php b/migrations/Version20251225001531.php new file mode 100644 index 0000000..182d3ee --- /dev/null +++ b/migrations/Version20251225001531.php @@ -0,0 +1,53 @@ +addSql('ALTER TABLE "join" ADD address VARCHAR(255) NOT NULL'); + $this->addSql('ALTER TABLE "join" ADD zip_code VARCHAR(255) NOT NULL'); + $this->addSql('ALTER TABLE "join" ADD city VARCHAR(255) NOT NULL'); + $this->addSql('ALTER TABLE "join" ADD pronom VARCHAR(255) NOT NULL'); + $this->addSql('ALTER TABLE "join" ADD sexe VARCHAR(255) NOT NULL'); + $this->addSql('ALTER TABLE "join" ADD insta_link VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE "join" ADD facebook_link VARCHAR(255) NOT NULL'); + $this->addSql('ALTER TABLE "join" ADD tiktok_link VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE "join" ADD is_discord BOOLEAN NOT NULL'); + $this->addSql('ALTER TABLE "join" ADD discord_account VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE "join" ADD role TEXT NOT NULL'); + $this->addSql('COMMENT ON COLUMN "join".role IS \'(DC2Type:array)\''); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('ALTER TABLE "join" DROP address'); + $this->addSql('ALTER TABLE "join" DROP zip_code'); + $this->addSql('ALTER TABLE "join" DROP city'); + $this->addSql('ALTER TABLE "join" DROP pronom'); + $this->addSql('ALTER TABLE "join" DROP sexe'); + $this->addSql('ALTER TABLE "join" DROP insta_link'); + $this->addSql('ALTER TABLE "join" DROP facebook_link'); + $this->addSql('ALTER TABLE "join" DROP tiktok_link'); + $this->addSql('ALTER TABLE "join" DROP is_discord'); + $this->addSql('ALTER TABLE "join" DROP discord_account'); + $this->addSql('ALTER TABLE "join" DROP role'); + } +} diff --git a/migrations/Version20251225104342.php b/migrations/Version20251225104342.php new file mode 100644 index 0000000..65afa05 --- /dev/null +++ b/migrations/Version20251225104342.php @@ -0,0 +1,43 @@ +addSql('ALTER TABLE "join" ADD fiche_file_name VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE "join" ADD fiche_dimensions JSON DEFAULT NULL'); + $this->addSql('ALTER TABLE "join" ADD fiche_size VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE "join" ADD fiche_mine_type VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE "join" ADD fiche_original_name VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE "join" ADD update_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL'); + $this->addSql('COMMENT ON COLUMN "join".update_at IS \'(DC2Type:datetime_immutable)\''); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('ALTER TABLE "join" DROP fiche_file_name'); + $this->addSql('ALTER TABLE "join" DROP fiche_dimensions'); + $this->addSql('ALTER TABLE "join" DROP fiche_size'); + $this->addSql('ALTER TABLE "join" DROP fiche_mine_type'); + $this->addSql('ALTER TABLE "join" DROP fiche_original_name'); + $this->addSql('ALTER TABLE "join" DROP update_at'); + } +} diff --git a/src/Controller/JoinController.php b/src/Controller/JoinController.php index 9914014..c1dc3f2 100644 --- a/src/Controller/JoinController.php +++ b/src/Controller/JoinController.php @@ -11,14 +11,18 @@ use App\Entity\Join; use App\Form\RequestPasswordConfirmType; use App\Form\RequestPasswordRequestType; use App\Service\Mailer\Mailer; +use App\Service\Pdf\Candidat; use App\Service\ResetPassword\Event\ResetPasswordConfirmEvent; use App\Service\ResetPassword\Event\ResetPasswordEvent; use Doctrine\ORM\EntityManagerInterface; use Psr\EventDispatcher\EventDispatcherInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\KernelInterface; +use Symfony\Component\Mime\Part\DataPart; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Security\Http\Authentication\AuthenticationUtils; @@ -28,13 +32,47 @@ class JoinController extends AbstractController { #[Route(path: '/join', name: 'app_recruit', options: ['sitemap' => false], methods: ['GET','POST'])] - public function join(Request $request,Mailer $mailer): Response + public function join(Request $request,EntityManagerInterface $entityManager,Mailer $mailer,KernelInterface $kernel): Response { $j= new Join(); $form = $this->createForm(JoinType::class,$j); $form->handleRequest($request); if($form->isSubmitted() && $form->isValid()){ - dd($j); + $j->setState("create"); + $j->setCreateAt(new \DateTimeImmutable("now")); + + $cPdf = new Candidat(); + $cPdf->setData($j,$entityManager,$kernel); + $cPdf->AddPage(); + $cPdf->contentDetails(); + $content = $cPdf->Output('S'); + $fileName = 'fiche_candidat.pdf'; + $tempFilePath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $fileName; + file_put_contents($tempFilePath, $content); + $file = new UploadedFile($tempFilePath,$fileName,"application/pdf",0,true); + $j->setFiche($file); + $entityManager->persist($j); + $entityManager->flush(); + + $mailer->send('contact@e-cosplay.fr', + 'E-Cosplay', + '[E-Cosplay] - Nouvelle candidature', + 'candidat/new.twig', + ['joint'=>$j], + [new DataPart($content,'candidat.pdf','application/pdf')] + ); + + + $mailer->send($j->getEmail(), + $j->getSurname()." ".$j->getName(), + "[E-Cosplay] - Confirmation de votre candidature", + 'condidat/confirm.twig', + ['joint'=>$j], + [new DataPart($content,'candidat.pdf','application/pdf')] + ); + //send mail to contact@e-cosplay.fr + //send mail to candidat + } return $this->render('join.twig',[ 'form' => $form->createView(), diff --git a/src/Dto/Join/JoinType.php b/src/Dto/Join/JoinType.php index a232596..027c242 100644 --- a/src/Dto/Join/JoinType.php +++ b/src/Dto/Join/JoinType.php @@ -7,6 +7,7 @@ use PixelOpen\CloudflareTurnstileBundle\Type\TurnstileType; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\DateTimeType; +use Symfony\Component\Form\Extension\Core\Type\DateType; use Symfony\Component\Form\Extension\Core\Type\EmailType; use Symfony\Component\Form\Extension\Core\Type\TelType; use Symfony\Component\Form\Extension\Core\Type\TextareaType; @@ -36,9 +37,8 @@ class JoinType extends AbstractType 'label' => 'form.label.phone', 'required' => true, ]) - ->add('dateBirth', DateTimeType::class, [ + ->add('dateBirth', DateType::class, [ 'label' => 'form.label.birthdate', - 'widget' => 'single_text', 'required' => true, ]) ->add('address', TextType::class, [ diff --git a/src/Entity/Join.php b/src/Entity/Join.php index 24620c6..2a1eb3b 100644 --- a/src/Entity/Join.php +++ b/src/Entity/Join.php @@ -5,9 +5,12 @@ namespace App\Entity; use App\Repository\JoinRepository; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\HttpFoundation\File\File; +use Vich\UploaderBundle\Mapping\Annotation as Vich; #[ORM\Entity(repositoryClass: JoinRepository::class)] #[ORM\Table(name: '`join`')] +#[Vich\Uploadable()] class Join { #[ORM\Id] @@ -75,6 +78,143 @@ class Join #[ORM\Column(type: Types::ARRAY)] private array $role = []; + #[Vich\UploadableField(mapping: 'fiche',fileNameProperty: 'ficheFileName', size: 'ficheSize', mimeType: 'ficheMineType', originalName: 'ficheOriginalName',dimensions: 'ficheDimensions')] + private ?File $fiche = null; + #[ORM\Column(nullable: true)] + private ?string $ficheFileName = null; + #[ORM\Column(nullable: true)] + private ?array $ficheDimensions = []; + #[ORM\Column(length: 255,nullable: true)] + private ?string $ficheSize = null; + #[ORM\Column(length: 255,nullable: true)] + private ?string $ficheMineType = null; + #[ORM\Column(length: 255,nullable: true)] + private ?string $ficheOriginalName = null; + #[ORM\Column(nullable: true)] + private ?\DateTimeImmutable $updateAt; + + + /** + * @return \DateTimeImmutable|null + */ + public function getUpdateAt(): ?\DateTimeImmutable + { + return $this->updateAt; + } + + /** + * @return File|null + */ + public function getFiche(): ?File + { + return $this->fiche; + } + + /** + * @return array|null + */ + public function getFicheDimensions(): ?array + { + return $this->ficheDimensions; + } + + /** + * @return string|null + */ + public function getFicheFileName(): ?string + { + return $this->ficheFileName; + } + + /** + * @return string|null + */ + public function getFicheMineType(): ?string + { + return $this->ficheMineType; + } + + /** + * @return string|null + */ + public function getFicheOriginalName(): ?string + { + return $this->ficheOriginalName; + } + + /** + * @return string|null + */ + public function getFicheSize(): ?string + { + return $this->ficheSize; + } + + /** + * @return bool|null + */ + public function getIsDiscord(): ?bool + { + return $this->isDiscord; + } + + /** + * @param \DateTimeImmutable|null $updateAt + */ + public function setUpdateAt(?\DateTimeImmutable $updateAt): void + { + $this->updateAt = $updateAt; + } + + /** + * @param File|null $fiche + */ + public function setFiche(?File $fiche): void + { + $this->fiche = $fiche; + } + + /** + * @param array|null $ficheDimensions + */ + public function setFicheDimensions(?array $ficheDimensions): void + { + $this->ficheDimensions = $ficheDimensions; + } + + /** + * @param string|null $ficheFileName + */ + public function setFicheFileName(?string $ficheFileName): void + { + $this->ficheFileName = $ficheFileName; + } + + /** + * @param string|null $ficheMineType + */ + public function setFicheMineType(?string $ficheMineType): void + { + $this->ficheMineType = $ficheMineType; + } + + /** + * @param string|null $ficheOriginalName + */ + public function setFicheOriginalName(?string $ficheOriginalName): void + { + $this->ficheOriginalName = $ficheOriginalName; + } + + /** + * @param string|null $ficheSize + */ + public function setFicheSize(?string $ficheSize): void + { + $this->ficheSize = $ficheSize; + } + + public function getId(): ?int { return $this->id; diff --git a/src/Service/Pdf/Candidat.php b/src/Service/Pdf/Candidat.php new file mode 100644 index 0000000..c5a5e53 --- /dev/null +++ b/src/Service/Pdf/Candidat.php @@ -0,0 +1,176 @@ +AliasNbPages(); + $this->SetAutoPageBreak(true, 20); + } + + public function setData(Join $join, EntityManagerInterface $em, KernelInterface $kernel) + { + $this->join = $join; + $this->em = $em; + $this->kernel = $kernel; + } + + public function Header() + { + $yPos = 10; + $xPos = 10; + $logoWidth = 25; + $logoHeight = 15; + + $this->SetY($yPos); + $agDate = $this->join->getCreateAt() ? $this->join->getCreateAt()->format('d/m/Y') : date('d/m/Y'); + + $this->SetFont('Arial', 'B', 16); + $this->SetTextColor(0, 0, 0); + $this->Cell(0, 7, utf8_decode("FICHE CANDIDAT"), 0, 1, 'C'); + + $this->SetFont('Arial', '', 10); + $this->Cell(0, 6, utf8_decode("Générée le " . $agDate), 0, 1, 'C'); + + $logoPath = $this->kernel->getProjectDir() . '/public/assets/images/logo.jpg'; + if (file_exists($logoPath)) { + $this->Image($logoPath, $xPos, $yPos, $logoWidth, $logoHeight); + } + + $this->Ln(5); + + if ($this->PageNo() == 1) { + $this->writeAgContextBlock(); + } else { + $this->SetY(30); + } + } + + public function writeAgContextBlock() + { + $this->SetFont('Arial', 'B', 9); + $this->Cell(0, 1, '', 'T', 1, 'L'); + $this->Ln(2); + $this->Cell(0, 5, utf8_decode('INFORMATIONS LÉGALES'), 0, 1, 'C'); + $this->SetFont('Arial', '', 9); + $this->Cell(35, 4, utf8_decode('Association :'), 0, 0); + $this->SetFont('Arial', 'B', 9); + $this->Cell(0, 4, utf8_decode('E-Cosplay Association loi 1901 – RNA N°W022006988'), 0, 1); + $this->SetFont('Arial', '', 9); + $this->Cell(35, 4, utf8_decode('Siège social :'), 0, 0); + $this->Cell(0, 4, utf8_decode('42 rue de saint-quentin 02800 Beautor'), 0, 1); + $this->Ln(2); + $this->Cell(0, 1, '', 'T', 1, 'L'); + $this->Ln(5); + } + + public function contentDetails() + { + // --- SECTION 1 : ÉTAT CIVIL --- + $this->drawSectionTitle('IDENTITÉ DU CANDIDAT'); + + $this->infoRow('Nom / Prénom :', strtoupper($this->join->getSurname()) . ' ' . $this->join->getName(), true); + + // --- CALCUL ET AFFICHAGE DE L'ÂGE --- + $birthDate = $this->join->getDateBirth(); + $this->SetFont('Arial', '', 10); + $this->Cell(40, 7, utf8_decode('Date de naissance :'), 0, 0); + + if ($birthDate) { + $age = $this->calculateAge($birthDate); + $dateStr = $birthDate->format('d/m/Y'); + + // Texte de la date + $this->SetFont('Arial', 'B', 10); + $this->Cell(30, 7, $dateStr, 0, 0); + + // Affichage de l'âge avec couleur + if ($age >= 16) { + $this->SetTextColor(0, 150, 0); // Vert + } else { + $this->SetTextColor(200, 0, 0); // Rouge + } + + $this->Cell(0, 7, utf8_decode(" (" . $age . " ans)"), 0, 1); + $this->SetTextColor(0, 0, 0); // Reset noir + } else { + $this->Cell(0, 7, 'N/C', 0, 1); + } + + $genreInfo = sprintf("Sexe : %s | Pronom : %s", + $this->join->getSexe() ?? 'N/C', + $this->join->getPronom() ?? 'N/C' + ); + $this->infoRow('Genre :', $genreInfo); + $this->Ln(3); + + // --- SECTION 2 : CONTACT --- + $this->drawSectionTitle('COORDONNÉES'); + $this->infoRow('Email :', $this->join->getEmail(), true); + $this->infoRow('Téléphone :', $this->join->getPhone()); + $address = sprintf("%s, %s %s", $this->join->getAddress(), $this->join->getZipCode(), $this->join->getCity()); + $this->infoRow('Adresse :', $address); + $this->Ln(3); + + // --- SECTION 3 : PROFIL --- + $this->drawSectionTitle('PROFIL ET RÔLES'); + $roles = $this->join->getRole(); + $this->infoRow('Rôles visés :', is_array($roles) ? implode(', ', $roles) : ($roles ?? 'Aucun'), true); + + $this->Ln(2); + $this->SetFont('Arial', 'B', 10); + $this->Cell(0, 6, utf8_decode("Présentation / Qui est-ce ?"), 0, 1); + $this->SetFont('Arial', '', 10); + $this->MultiCell(0, 5, utf8_decode($this->join->getWho() ?? "Aucune description fournie."), 1, 'L'); + $this->Ln(5); + + // --- SECTION 4 : RÉSEAUX SOCIAUX --- + $this->drawSectionTitle('RÉSEAUX SOCIAUX'); + $this->infoRow('Discord :', $this->join->getDiscordAccount() ?? 'Non renseigné'); + $this->infoRow('Instagram :', $this->join->getInstaLink() ?? 'N/A'); + $this->infoRow('TikTok :', $this->join->getTiktokLink() ?? 'N/A'); + $this->infoRow('Facebook :', $this->join->getFacebookLink() ?? 'N/A'); + } + + private function calculateAge(\DateTimeInterface $birthDate): int + { + $now = new \DateTime(); + return $now->diff($birthDate)->y; + } + + private function infoRow($label, $value, $important = false) + { + $this->SetFont('Arial', '', 10); + $this->Cell(40, 7, utf8_decode($label), 0, 0); + if ($important) $this->SetFont('Arial', 'B', 10); + $this->MultiCell(0, 7, utf8_decode($value), 0, 'L'); + } + + private function drawSectionTitle($title) + { + $this->SetFillColor(240, 240, 240); + $this->SetFont('Arial', 'B', 11); + $this->Cell(0, 8, utf8_decode(' ' . $title), 0, 1, 'L', true); + $this->Ln(2); + } + + public function Footer() + { + $this->SetY(-15); + $this->SetFont('Arial', 'I', 8); + $this->SetTextColor(128, 128, 128); + $this->Cell(0, 10, 'Fiche Candidat E-Cosplay - Page ' . $this->PageNo() . '/{nb}', 0, 0, 'C'); + } +}