<?php
declare(strict_types=1);
/*
* Copyright (C) Office National de Publication et de Communication - All Rights Reserved
*
* Unauthorized copying of this file, via any medium is strictly prohibited by law
* This file is proprietary and confidential
*/
namespace App\Controller;
use DateTime;
use Swift_Mailer;
use App\Entity\JPO;
use App\Entity\Trace;
use Twig\Environment;
use App\Service\Tools;
use App\Service\Slugify;
use App\Service\Traceur;
use App\Entity\QuoiMotCle;
use App\Entity\Utilisateur;
use App\Service\Historique;
use Detection\MobileDetect;
use App\Service\PathBuilder;
use App\Elasticsearch\Moteur;
use App\Entity\Etablissement;
use App\Entity\FicheProspect;
use App\Entity\ItemsInsertion;
use App\Service\UserTransient;
use App\Elasticsearch\Contexte;
use App\Form\FicheProspectType;
use App\Elasticsearch\Parametres;
use App\Entity\DemandeInformation;
use App\Entity\TexteReferencement;
use App\Service\MailMessageFactory;
use App\Entity\DonneeComplementaire;
use App\Form\DemandeInformationType;
use Doctrine\Persistence\ManagerRegistry;
use App\Entity\DemandeInformationBlacklist;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Session\Session;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
final class FicheController extends AbstractController
{
private const MAX_RECOMMANDATION = 6; // Pour les non-participants
private const MAX_RECOMMANDATION_PARTICIPANT = 8;
/** @var Slugify */
private $slugify;
/** @var Moteur */
private $moteur;
/** @var Historique */
private $historique;
/** @var Traceur */
private $traceur;
/** @var PathBuilder */
private $pathBuilder;
/** @var Swift_Mailer */
private $mailer;
/** @var MailMessageFactory */
private $mailMessageFactory;
/** @var UserTransient */
private $userTransient;
/**
* @var Environment
*/
private $twig;
private ManagerRegistry $doctrine;
public function __construct(
Slugify $slugify,
Moteur $moteur,
Historique $historique,
Traceur $traceur,
PathBuilder $pathBuilder,
Swift_Mailer $mailer,
MailMessageFactory $mailMessageFactory,
UserTransient $userTransient,
Environment $twig,
ManagerRegistry $doctrine
) {
$this->slugify = $slugify;
$this->moteur = $moteur;
$this->historique = $historique;
$this->traceur = $traceur;
$this->pathBuilder = $pathBuilder;
$this->mailer = $mailer;
$this->mailMessageFactory = $mailMessageFactory;
$this->userTransient = $userTransient;
$this->twig = $twig;
$this->doctrine = $doctrine;
}
/**
* @Route("/fiche-organisme/{id}", name="fiche-organisme",methods={"GET", "POST"}, requirements={"id"="^[A-Za-z0-9-]*$"})
* @Route("/fiche-fournisseur/{id}", name="fiche-fournisseur",methods={"GET", "POST"}, requirements={"id"="^[A-Za-z0-9-]*$"})
* @Route("/fiche/{slug}/{id}", name="fiche", methods={"GET", "POST"}, requirements={"id"="^[A-Za-z0-9-]*$"})
* @Route("/fiche/{slug}/{id}/internats", name="fiche-internat", methods={"GET", "POST"}, requirements={"id"="^[A-Za-z0-9-]*$"})
* @Route("/fiche/{id}", methods={"GET", "POST"}, requirements={"id"="^[A-Za-z0-9-]*$"})
* @Cache(
* lastModified="etablissement ? etablissement.getDateDerniereModification() : null",
* ETag="'Fiche' ~ (etablissement ? etablissement.getId() ~ etablissement.getDateDerniereModification().getTimestamp() : '')",
* expires="+5 minutes",
* public=false
* )
*/
public function index(Request $request, ?Etablissement $etablissement): Response
{
//
// N'affiche pas établissement supprimé
//
if (!$etablissement || !$etablissement->isActif()) {
return $this->redirectToRoute('homepage', [], 301);
}
//
// Canonical URL
//
$urlCanonical = null;
$path = $this->pathBuilder->buildFiche($etablissement);
if ($request->getPathInfo() !== $path || \count($request->query)) {
$urlCanonical = $this->pathBuilder->buildFiche($etablissement, [], UrlGeneratorInterface::ABSOLUTE_URL);
}
//
// Force l'établissement avoir un texte référencement
//
$repositoryTexteReferencement = $this->getDoctrine()->getRepository(TexteReferencement::class);
$repositoryTexteReferencement->assureEtablissementDetientTexte($etablissement, true);
//$quoi = new QuoiMotCle( $etablissement->getType() );
$quoi = new QuoiMotCle( \App\Service\Tools::typeEtablissement2MotCle( $etablissement->getType() ) );
$lieu = $etablissement->getVille();
//
// Cherche les établissements à proximité (recommendations)
//
$proches = [];
if ($quoi && $lieu) {
$paramsProches = new Parametres();
$paramsProches->setQuoiLieu($quoi, $lieu);
$paramsProches->pageActuele = 1;
$paramsProches->maxParPage = $etablissement->isParticipant() ? static::MAX_RECOMMANDATION_PARTICIPANT : static::MAX_RECOMMANDATION;
$paramsProches->etablissementsExclues = [$etablissement->getId()];
$proches = $this->moteur->cherche($paramsProches);
}
//
// Contexte de recherche
//
$precedent = null;
$suivant = null;
$contexte = $this->userTransient->get(UserTransient::CONTEXTE_RECHERCHE);
$isContextValid =
$contexte instanceof Contexte &&
$contexte->hasEstablishment($etablissement);
$isQuoiLieuValid =
$quoi && $quoi->getSlug() &&
$lieu && $lieu->getSlug();
if (!$isContextValid && $isQuoiLieuValid) {
$paramsContexte = new Parametres();
$paramsContexte->setQuoiLieu($quoi, $lieu);
$this->moteur->count($paramsContexte); // pour éteindre si necessaire
$contexte = $this->moteur->createContext($paramsContexte);
$this->userTransient->set(UserTransient::CONTEXTE_RECHERCHE, $contexte);
}
if ($contexte instanceof Contexte) {
list($precedent, $suivant) = $contexte->getBounds($etablissement);
$repository = $this->getDoctrine()->getRepository(Etablissement::class);
if ($precedent) {
$precedent = $repository->find($precedent);
}
if ($suivant) {
$suivant = $repository->find($suivant);
}
}
//recupère les insertions
// TODO : trop de requêtes SQL sur une meme table (ItemsInsertion) !! Il faut optimiser ce bloc
$items = $this->getDoctrine()->getRepository(ItemsInsertion::class)->findBy([ 'etablissement' => $etablissement->getId() ], ['tri' => 'ASC']);
$formations = [];
$presentation = [];
$specificites = [];
$accueil = [];
$activites = [];
$hebergement = [];
$accompagnement = [];
$equipement = [];
$infos = [];
$classes= [];
$complement = [];
$tarifs = [];
$langues = [];
$prices = [];
foreach ($items as $item) {
if ( $item->getOnglet() == 'Formations' )
$formations[] = $item;
if ( $item->getOnglet() == 'Présentation' )
$presentation[] = $item;
if ( $item->getOnglet() == 'Spécificités' )
$specificites[] = $item;
if ( $item->getOnglet() == 'Accueil/accès' )
$accueil[] = $item;
if ( $item->getOnglet() == 'Activité(s) périscolaire(s)' )
$activites[] = $item;
if ( $item->getOnglet() == 'Hébergement' )
$hebergement[] = $item;
if ( $item->getOnglet() == 'Accompagnement scolaire' )
$accompagnement[] = $item;
if ( $item->getOnglet() == 'Équipements complémentaires' )
$equipement[] = $item;
if ( $item->getOnglet() == 'Informations pratiques' )
$infos[] = $item;
if ( $item->getOnglet() == 'Classes' )
$classes[] = $item;
if ( $item->getOnglet() == 'COMPLEMENT' )
$complement[] = $item;
if ( $item->getOnglet() == 'Tarifs' )
$tarifs[] = $item;
if ( $item->getOnglet() == 'Langues' )
$langues[] = $item;
// if ( strpos($item->getIdentifiant(), 'ECOLE-TARIFS-') )
// if ( strtolower($item->getOnglet()) == strtolower('Frais de scolarité') )
if ( $item->getOnglet() == 'Frais de scolarité' )
$prices[] = $item;
}
//
// Met à jour le historique
//
$this->historique->addFiche($etablissement);
//
// Enregistre les traces
//
$this->traceur->add(Trace::FICHE, $etablissement);
$this->traceur->add(Trace::RECOMMANDATION, $proches);
$this->traceur->add(Trace::RECOMMANDATION, [$precedent, $suivant]);
//
// Retrouver le lien vers la page précédente
// Si on arrive depuis une page de liste on récupère la var de session, sinon on construit une URL générique
// la plus simple possible et qui est un des lien de l'onglet (en partant sur les données de la fiche en cours : type et/ou ville|dpt|region)
//
$session = new Session();
if ($session->has('ROUTE_ANNUAIRE'))
$urlPagePrecedente = $session->get('ROUTE_ANNUAIRE');
else {
$urlPagePrecedente = $this->pathBuilder->buildDefaultPath($etablissement);
}
// La toute première JPO à venir
$jpo = $this->getDoctrine()->getRepository(JPO::class)->getLastByEtablissement($etablissement, 1);
if (!empty($jpo))
$jpo = $jpo[0];
// Liste JPO à venir
$jpoListe = [];
$now = new DateTime('NOW');
if(count($etablissement->getJPOs()) > 0){
foreach ($etablissement->getJPOs() as $item){
if($item->getDate() > $now ){
$jpoListe[] = $item;
}
}
}
// Template texte SEO - aléatoire entre 1 et 2
$type = $etablissement->getTypeClient() == "internat" ? "internat" : Tools::typeEtablissement($etablissement->getType());
if ($type == "" || $type == null)
$type = "default"; // On ne sait jamais ...
$number = crc32($etablissement->getId());
$rand = $number % 2 == 0 ? 1 : 2;
$template = $type . "-" . $rand;
// Récupérer les données complémentaires (pour les diplomes et les modules (accueil|présentation|Spécificité))
$dcs = $this->getDoctrine()->getManager()->getRepository(DonneeComplementaire::class)->findByEtablissement($etablissement);
// Trouver l'ets/internat lié (dans le cas d'un internant autonome)
$etsLie = $internatLie = null;
if ($etablissement->getTypeClient() == "internat") {
$etsLie = $this->getDoctrine()->getRepository(Etablissement::class)->findOneBy(
['internat2' => $etablissement,
'actif' => 1]
);
} elseif ($etablissement->getTypeClient() == "établissement") {
$internatLie = $etablissement->getInternat2();
}
// Savoir s'il a payé certains textes complémentairs
$moduleAccueil = false;
foreach ($items as $item)
if ( $item->getOnglet() == 'Accueil/accès' && $item->getLigneSecours() == 'non' )
$moduleAccueil = true;
$modulePresentation = false;
foreach ($items as $item)
if ( $item->getOnglet() == 'Présentation' && $item->getLigneSecours() == 'non' )
$modulePresentation = true;
$moduleSpecificite = false;
foreach ($items as $item)
if ( $item->getOnglet() == 'Spécificités' && $item->getLigneSecours() == 'non' )
$moduleSpecificite = true;
// Texte de présentation spéciali lycée-pro (dans les items) - Exp : et057-55
$textePresentation = null;
if ( $etablissement->isClient() ) {
$textePresentation = $this->getDoctrine()->getRepository(ItemsInsertion::class)->findOneBy([ 'etablissement' => $etablissement->getId(), 'identifiant' => 'LYCPRO-PRESLIBRE-TXTS_IS']);
if ($textePresentation)
$textePresentation = $textePresentation->getTexte();
}
// Formlaire INLINE de contact
$demande = $this->createDemande();
$demande->setEtablissement($etablissement);
// Get route params
$routeParams = $request->attributes->get('_route_params');
if (isset($routeParams['slug']) && isset($routeParams['id'])) {
$form = $this->createForm(DemandeInformationType::class, $demande, [
/*
Pb : on arrive sur la fiche depuis "contacter etab" de la liste, ça fait un smoothscroll vers le form
on submit le form : le hash est gardé et on scroll de nv ver sle form, et on zappe le flash en haut !
Solution : focer le form à regénérer l'url de submit, il ne prend plus celle en cours
*/
'action' => $this->generateUrl('fiche', ['slug' => $routeParams['slug'], 'id' => $routeParams['id']]),
]);
} else {
$form = $this->createForm(DemandeInformationType::class, $demande);
}
$form->handleRequest($request);
$diEnvoyee = false;
if ($form->isSubmitted() && $form->isValid() && empty($form->get("email2")->getData())) {
// Vérifier si le gars est blacklisté en global ou seulement pour l'étab en cours
$email = $demande->getInterlocuteurEmail();
$etabbId = $etablissement->getId();
$n1 = $this->doctrine->getRepository(DemandeInformationBlacklist::class)->getCountByEmail($email, $etabbId);
$n2 = $this->doctrine->getRepository(DemandeInformationBlacklist::class)->getCountByEmail($email);
$bypass = false;
if ($n1 > 0 || $n2 >= 3) {
$this->addFlash('success', "Votre demande d'information a été envoyée !");
$bypass = true;
}
// Persiste dans la base de données
if (!$bypass) {
$entityManager = $this->getDoctrine()->getManager();
$demande->setProfil("");
$demande->setNiveau([Tools::typeEtablissement($etablissement->getType())]);
$detect = new MobileDetect();
$device = $detect->isMobile() ? 'mobile' : ($detect->isTablet() ? 'tablet' : 'desktop');
$demande->setSujet($device);
$demande->setDate(new \DateTime()); // Forcer la maj de la date, sinon on aura tjr la meme !!!!
$entityManager->persist($demande);
$entityManager->flush();
$etab = $demande->getEtablissement();
$salt = $_SERVER['APP_SECRET'];
$hashedDemandeId = md5($demande->getId() . $salt);
$html = $this->twig->render('emails/demande-information.html.twig', ['demande' => $demande, 'hashedDemandeId' => $hashedDemandeId]);
$plain = $this->twig->render('emails/demande-information.txt.twig', ['demande' => $demande, 'hashedDemandeId' => $hashedDemandeId]);
$plain = str_replace("\n", "\r\n", $plain);
$tempTo =
$this->getDestinatairesDemandeInformation($etab, 'ROLE_CONTACT_ADMISSION') ??
$etab->getEmailPrincipal() ??
$this->getDestinatairesDemandeInformation($etab, 'ROLE_RESPONSABLE_ETABLISSEMENT', true) ;
$mail = $this
->mailMessageFactory
->create()
->setSubject("Demande d'information")
// Envoyer dans l'ordre au(x) contact(s) di, sinon au mail principale sinon au(x) responsable(s) étabs, même ci ce dernier a décoché son @ en tant que destinataire par défaut des Di (depuis son espace étab > gestion des mails > 1er bloc > icône enveloppe )
->setTo(
$tempTo
)
->setReplyTo($demande->getInterlocuteurEmail(), $demande->getInterlocuteurNom())
->setBody($html, 'text/html')
->addPart($plain, 'text/plain');
$this->mailer->send($mail);
// Garde la demande pour être réutilisé pour une prochaine fois
$this->userTransient->set(UserTransient::DEMANDE_INFORMATION, $demande->getId());
$this->addFlash('success', "Votre demande d'information a été envoyée");
$diEnvoyee = true;
} // -- if (!$bypass)
}
// Pour utilisation Twig seulement
$typeEtab = $etablissement->getType() != "" ? $etablissement->getType() : $etablissement->getTypeClient();
$typeEtab = Tools::typeEtablissement($typeEtab);
return $this->render('fiche/index.html.twig', [
'urlCanonical' => $urlCanonical,
'etablissement' => $etablissement,
'formations' => $formations,
'presentation' => $presentation,
'langues' => $langues,
'specificites' => $specificites,
'accueil' => $accueil,
'activites' => $activites,
'prices' => $prices,
'hebergement' => $hebergement,
'accompagnement' => $accompagnement,
'equipement' => $equipement,
'infos' => $infos,
'classes' => $classes,
'etablissementsProches' => $proches,
'complement' => $complement,
'moduleAccueil' => $moduleAccueil,
'modulePresentation' => $modulePresentation,
'moduleSpecificite' => $moduleSpecificite,
'contexte' => [
'etablissement' => $etablissement,
'quoi' => $contexte->quoi ?? null,
'lieu' => $contexte->lieu ?? null,
'precedent' => $precedent,
'suivant' => $suivant,
'urlPagePrecedente' => $urlPagePrecedente
],
'jpo' => $jpo,
'jpoListe' => $jpoListe,
'template' => $template,
'dc' => $dcs, // Données complémentaires (pour les diplomes/formations)
'tarifs' => $tarifs,
'etsLie' => $etsLie,
'internatLie' => $internatLie,
'textePresentation' => $textePresentation,
'form' => $form->createView(),
'type_etab' => $typeEtab,
'noindex' => $etablissement->getTypeClient() == "organisme" ? true : false,
'diEnvoyee' => $diEnvoyee
]);
}
/**
* @Route(
* "/montrer-telephone/{id}",
* name="montrer-telephone",
* condition="request.isXmlHttpRequest()",
* methods={"POST"},
* options = { "expose" = true }
* )
*/
public function traceTelephone(Etablissement $etablissement): Response
{
$this->traceur->add(Trace::TELEPHONE, $etablissement);
return new Response();
}
/**
* @Route("/completer-fiche/{id}", name="completer-fiche", methods={"GET", "POST"})
*/
public function formProspect(Request $request, Etablissement $etablissement): Response
{
// Évite formulaire de prospect pour les clients
if ($etablissement->isClient()) {
return $this->redirect(
$this->pathBuilder->buildFiche($etablissement),
Response::HTTP_FOUND
);
}
$prospect = new FicheProspect();
$prospect->setEtablissement($etablissement);
$form = $this->createForm(FicheProspectType::class, $prospect);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// Enregistre offre dans la base de données
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($prospect);
$entityManager->flush();
// Envoi email
$mailHtml = $this->renderView('emails/fiche-prospect.html.twig', ['prospect' => $prospect]);
$mailTxt = $this->renderView('emails/fiche-prospect.txt.twig', ['prospect' => $prospect]);
$mailTo = $prospect->getAutoriseContact()
? $to = 'infos_commerciales@onpc.fr'
: $to = 'infos_complementaires@onpc.fr';
$mail = $this
->mailMessageFactory
->create()
->setSubject(sprintf('Infos complémentaires (%s)', $etablissement->getId()))
->setTo($mailTo)
->setReplyTo($prospect->getEmail(), $prospect->getResponsable())
->setBody($mailHtml, 'text/html')
->addPart(str_replace("\n", "\r\n", $mailTxt), 'text/plain');
$this->mailer->send($mail);
$this->addFlash('success', 'Vos informations ont été envoyées');
return $this->redirect(
$this->pathBuilder->buildFiche($etablissement),
Response::HTTP_FOUND
);
}
return $this->render('fiche/completer-fiche.html.twig', [
'etablissement' => $etablissement,
'form' => $form->createView(),
]);
}
/**
* @Route("/fiche-maj/{id}", name="fiche-maj", methods={"GET"})
*/
public function maj(Etablissement $etablissement): Response
{
return new Response($etablissement->getMaj()->format('c'), Response::HTTP_OK, [
'Content-Type' => 'text/plain',
'X-Robots-Tag' => 'noindex, nofollow',
]);
}
private function createDemande(): DemandeInformation
{
$idTransient = $this->userTransient->get(UserTransient::DEMANDE_INFORMATION);
if (!$idTransient) {
return new DemandeInformation();
}
$repository = $this->getDoctrine()->getRepository(DemandeInformation::class);
$transient = $repository->find($idTransient);
if (!$transient) {
return new DemandeInformation();
}
return clone $transient;
}
/**
* Renvoi la liste (tableau) de destinataires DI, par défaut (si aucun argument) toutes les @ du module contact (DI et gestionnaire)
*
* @param string $role renvoyer uniquement les @ ayant le role indiqué, éventuellement si c'est un responsable, on se limite à ceux ayant accepté leur @ comme défaut
* @param bool $forceResponsable ignorer le dernier pt du param précédent : c-a-d on ignore si le responsable a décoché son adr en tant que destinataire par défaut
*/
private function getDestinatairesDemandeInformation($etablissement, string $role = null, bool $forceResponsable = null)
{
$etabs = [];
$emails = [];
$responsables = $this->getDoctrine()
->getRepository(Utilisateur::class)
->findResponsablesByEtablissement($etablissement);
foreach ($responsables as $responsable) {
$etabs = $responsable->getEtablissementsResponsable();
}
$responsables = $this->getDoctrine()
->getRepository(Utilisateur::class)
->findResponsablesByEtablissements($etabs);
foreach ($responsables as $responsable) {
if (
(!$role || $role == "ROLE_RESPONSABLE_ETABLISSEMENT") and
$responsable->hasRole('ROLE_RESPONSABLE_ETABLISSEMENT') and
($forceResponsable or $responsable->isMailDemandeAdmission())
) {
$emails[] = $responsable->getEmail();
}
if (
(!$role || $role == "ROLE_CONTACT_ADMISSION") and
$responsable->hasRole('ROLE_CONTACT_ADMISSION')
) {
$emails[] = $responsable->getEmail();
}
}
return !empty($emails) ? $emails : null;
}
}