src/Controller/EclacController.php line 105

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use Doctrine\ORM\EntityManagerInterface;
  4. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  5. use Symfony\Component\HttpFoundation\Request;
  6. use Symfony\Component\HttpFoundation\Response;
  7. use Symfony\Component\HttpFoundation\RedirectResponse;
  8. use Symfony\Component\Routing\Annotation\Route;
  9. use Symfony\Component\HttpFoundation\RequestStack;
  10. use Psr\Log\LoggerInterface;
  11. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  12. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
  13. use Symfony\Component\HttpKernel\KernelInterface;
  14. // Security
  15. use Symfony\Component\Security\Core\Security;
  16. // PDF
  17. use Knp\Snappy\Pdf;
  18. // DateTime
  19. use DateTime;
  20. // Files
  21. use Symfony\Component\String\Slugger\SluggerInterface;
  22. // Controller
  23. // Classes
  24. use App\Entity\Utilisateur;
  25. use App\Entity\Eleve;
  26. use App\Entity\Enseignant;
  27. use App\Entity\Enseignantcours;
  28. use App\Entity\Cursus;
  29. use App\Entity\Anneeuniversitaire;
  30. use App\Entity\Semestre;
  31. use App\Entity\Typecours;
  32. use App\Entity\Qualification;
  33. use App\Entity\Competence;
  34. use App\Entity\Composante;
  35. use App\Entity\Diplome;
  36. use App\Entity\Note;
  37. use App\Entity\Niveau;
  38. use App\Entity\Fichier;
  39. use App\Entity\Statutimport;
  40. use App\Entity\Cours;
  41. use App\Entity\Epreuve;
  42. use App\Entity\Evaluation;
  43. use App\Entity\Evaluationeleve;
  44. use App\Entity\Inscription;
  45. use App\Entity\Typeimport;
  46. use App\Entity\Modelenotif;
  47. use App\Entity\Parametre;
  48. #[Route('/secured')]
  49. class EclacController extends AbstractController
  50. {
  51. public string $versionapp = "v1.4.0";
  52. public string $app_env;
  53. public bool $app_public;
  54. public string $php_version;
  55. public string $projectDir;
  56. public function __construct(
  57. public Security $security,
  58. private Pdf $knpSnappyPdf,
  59. private EntityManagerInterface $entityManager,
  60. private SluggerInterface $slugger,
  61. private RequestStack $requestStack,
  62. private UrlGeneratorInterface $urlGenerator,
  63. private ParameterBagInterface $params,
  64. private KernelInterface $kernel,
  65. private LoggerInterface $compassLogger,
  66. ) {
  67. $this->app_env = $params->get('app_env');
  68. $this->app_public = $params->get('app_public');
  69. $this->php_version = $params->get('php_version');
  70. $this->projectDir = $kernel->getProjectDir();
  71. if ($this->requestStack->getCurrentRequest() != null) {
  72. if ($this->requestStack->getCurrentRequest()->hasSession()) {
  73. $session = $this->requestStack->getSession();
  74. // Variable session parametre
  75. $parametre = $this->entityManager->getRepository(Parametre::class)->findOneBy(array(
  76. 'idparametre' => 1
  77. ));
  78. $session->set('parametre', $parametre);
  79. // Variable session élève DEMO
  80. $jeanneDupont = $this->entityManager->getRepository(Eleve::class)->findOneBy(array(
  81. 'numeroidentification' => "DEMO"
  82. ));
  83. if ($jeanneDupont != null) {
  84. $session->set('jeanneDupont', true);
  85. $session->set('jeanneDupontNom', $jeanneDupont->getPrenomeleve() . ' ' . $jeanneDupont->getNomeleve());
  86. } else {
  87. $session->set('jeanneDupont', false);
  88. $session->set('jeanneDupontNom', null);
  89. }
  90. // Variable session élève DEMOFINALE
  91. $jeanDupont = $this->entityManager->getRepository(Eleve::class)->findOneBy(array(
  92. 'numeroidentification' => "DEMOFINALE"
  93. ));
  94. if ($jeanDupont != null) {
  95. $session->set('jeanDupont', true);
  96. $session->set('jeanDupontNom', $jeanDupont->getPrenomeleve() . ' ' . $jeanDupont->getNomeleve());
  97. } else {
  98. $session->set('jeanDupont', false);
  99. $session->set('jeanDupontNom', null);
  100. }
  101. // Variable session anneeUniversitaire
  102. $now = new DateTime('now');
  103. if ($session->get('anneeUniversitaire') == null) {
  104. $anneesuniversitaire = $entityManager->getRepository(Anneeuniversitaire::class)->findAll();
  105. // On recherche si une année existe pour la date actuelle
  106. $query = $entityManager
  107. ->createQuery(
  108. 'SELECT a
  109. FROM App\Entity\Anneeuniversitaire a
  110. WHERE a.datedebut <= :now
  111. AND a.datefin >= :now'
  112. )
  113. ->setParameter('now', $now);
  114. $anneeUniversitaireQuery = $query->getResult();
  115. // Si oui, on la définit en session
  116. if (!empty($anneeUniversitaireQuery)) {
  117. $session->set('anneeUniversitaire', $anneeUniversitaireQuery[0]);
  118. } // Sinon on choisit la dernière année créée
  119. else {
  120. $session->set('anneeUniversitaire', $anneesuniversitaire[0]);
  121. }
  122. }
  123. // Variable session cursus
  124. if ($session->get('cursus') == null) {
  125. $cursus = $entityManager->getRepository(Cursus::class)->findAll();
  126. $query = $entityManager
  127. ->createQuery(
  128. 'SELECT c
  129. FROM App\Entity\Cursus c
  130. ORDER BY c.libellecursus ASC'
  131. );
  132. $cursus = $query->getResult();
  133. $session->set('cursus', $cursus[0]);
  134. }
  135. $importsEnErreur = $this->entityManager->getRepository(Fichier::class)->findBy(array(
  136. "statutimport" => 3,
  137. ));
  138. if (!empty($importsEnErreur)) {
  139. $session->set('importEnErreur', true);
  140. } else {
  141. $session->set('importEnErreur', false);
  142. }
  143. if ($parametre->isModemaintenance()) {
  144. $session->set('modeMaintenance', true);
  145. if ($session->get('droitadmin') != null) {
  146. if (str_contains($this->requestStack->getMainRequest()->getRequestUri(), '/secured/')) {
  147. if ($session->get('droitadmin') != true) {
  148. if ($this->requestStack->getMainRequest()->attributes->get('_route') != "app_eclac_maintenance"
  149. && $this->requestStack->getMainRequest()->attributes->get('_route') != "app_eclac_secure-index"
  150. && str_contains($this->requestStack->getMainRequest()->getRequestUri(), '/secured/')) {
  151. $response = new RedirectResponse($this->urlGenerator->generate('app_eclac_maintenance'));
  152. $response->send();
  153. exit;
  154. }
  155. }
  156. }
  157. }
  158. } else {
  159. $session->set('modeMaintenance', false);
  160. }
  161. if ($this->versionapp != $parametre->getVersionapp()) {
  162. $session->set('afficheNavbarre', false);
  163. $session->set('messageMaintenance', 'messages');
  164. // On ne redirige que les utilisateurs déjà connectés : sinon, le formulaire de
  165. // connexion (app_public_login) déclenche lui aussi cette redirection avant même
  166. // d'avoir pu authentifier l'utilisateur, ce qui crée une boucle avec le SSO Entra
  167. // (ré-authentification silencieuse -> maintenance -> ré-authentification -> ...)
  168. if ($this->security->getUser() !== null
  169. && $this->requestStack->getMainRequest()->attributes->get('_route') != "app_eclac_maintenance") {
  170. $session->set('afficheNavbarre', false);
  171. $response = new RedirectResponse($this->urlGenerator->generate('app_eclac_maintenance'));
  172. $response->send();
  173. exit;
  174. }
  175. } else {
  176. $session->set('afficheNavbarre', true);
  177. $session->set('messageMaintenance', 'libelles');
  178. }
  179. }
  180. }
  181. }
  182. #[Route('/maintenance', name: 'app_eclac_maintenance', methods: ['GET'])]
  183. public function maintenance(Request $request): Response
  184. {
  185. return $this->render('eclac/maintenance.html.twig', []);
  186. }
  187. #[Route('/', name: 'app_eclac_secure-index', methods: ['GET'])]
  188. public function secureIndex(Request $request): Response
  189. {
  190. // Variables communes
  191. $entityManager = $this->entityManager;
  192. $session = $request->getSession();
  193. // On récupère l'utilisateur
  194. $utilisateur = $entityManager->getRepository(Utilisateur::class)->findOneBy(array(
  195. 'idutilisateur' => $session->get('utilisateurconnecte')->getIdutilisateur()
  196. ));
  197. // On regarde si la personne connectée est un élève
  198. $eleve = $entityManager->getRepository(Eleve::class)->findOneBy(
  199. array(
  200. 'utilisateur' => $utilisateur->getIdutilisateur()
  201. )
  202. );
  203. if ($eleve != null) {
  204. return $this->redirectToRoute('app_ecleleve_index', [], Response::HTTP_SEE_OTHER);
  205. } else {
  206. return $this->redirectToRoute('app_public_index', [], Response::HTTP_SEE_OTHER);
  207. }
  208. }
  209. // Permet de se connecter à l'application
  210. #[Route('/choix-espace/{switch}', name: 'app_eclac_choix-espace', methods: ['GET', 'POST'], defaults: ['switch' => 'switch'])]
  211. public function choixEspace(Request $request, ?string $switch): Response
  212. {
  213. // La personne a besoin d'être identifiée pour accéder à la ressource
  214. $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
  215. // Variables communes
  216. $entityManager = $this->entityManager;
  217. $session = $request->getSession();
  218. // RAZ des variables
  219. $session->set('droitadmin', false);
  220. $session->set('utilisateurconnecte', null);
  221. $eleve = null;
  222. //echo "ON est la ";die();
  223. // error_log($token->getUser()->getUserIdentifier());
  224. /*
  225. $utilisateurCAS = $this->security->getUser();
  226. // On regarde si la personne connectée est un élève
  227. $utilisateur = $entityManager->getRepository(Utilisateur::class)->findOneBy(
  228. array(
  229. 'login' => $utilisateurCAS->getUserIdentifier()
  230. )
  231. );
  232. */
  233. $utilisateurCAS = $this->security->getUser();
  234. $identifier = $utilisateurCAS->getUserIdentifier();
  235. // Entra renvoie un email (ex: ext-bruyas@em-lyon.com) ; CAS/LDAP renvoie déjà le login
  236. $login = str_contains($identifier, '@')
  237. ? strtolower(explode('@', $identifier)[0])
  238. : $identifier;
  239. $utilisateur = $entityManager->getRepository(Utilisateur::class)->findOneBy(
  240. array(
  241. 'login' => $login
  242. )
  243. );
  244. if ($utilisateur == null) {
  245. // Login non trouvé dans UTILISATEUR : on déconnecte l'utilisateur de Symfony. Sinon il reste
  246. // authentifié et la page de connexion le renvoie ici en boucle (choix-espace -> index ->
  247. // form_login -> choix-espace...). Il retrouve la page de connexion avec le message.
  248. $this->logError("Connexion refusée, login non trouvé : " . $login . " (identifiant reçu : " . $identifier . ")", "connexion");
  249. $this->container->get('security.token_storage')->setToken(null);
  250. $session->invalidate();
  251. // Après invalidate() : le message est stocké dans la nouvelle session
  252. $this->addFlash('error', 'Votre compte (' . $login . ') n\'est pas autorisé dans ComPass. Contactez l\'administrateur.');
  253. return $this->redirectToRoute('app_public_login', [], Response::HTTP_SEE_OTHER);
  254. } else {
  255. $session->set('utilisateurconnecte', $utilisateur);
  256. // On regarde en priorité si la personne connectée est enseignant (si elle a aussi un profil élève, l'espace enseignant prime)
  257. $enseignant = $entityManager->getRepository(Enseignant::class)->findOneBy(
  258. array(
  259. 'utilisateur' => $utilisateur->getIdutilisateur()
  260. )
  261. );
  262. if ($enseignant != null) {
  263. $session->set('utilisateur', $enseignant);
  264. $session->set('typeUtilisateur', 'Enseignant');
  265. $session->set('enseignant', $enseignant);
  266. // On vérifie si l'enseignant est un administrateur
  267. if ($enseignant->isAdministrateur()) {
  268. $session->set('droitadmin', true);
  269. if($switch == 'switch') {
  270. if ($session->get('vueAdministrateur') == true) {
  271. $session->set('vueAdministrateur', false);
  272. } else {
  273. $session->set('vueAdministrateur', true);
  274. }
  275. }
  276. } else {
  277. $session->set('droitadmin', false);
  278. }
  279. return $this->redirectToRoute('app_eclenseignant_index', [], Response::HTTP_SEE_OTHER);
  280. }
  281. // Sinon on regarde si c'est un élève
  282. else {
  283. $eleve = $entityManager->getRepository(Eleve::class)->findOneBy(
  284. array(
  285. 'utilisateur' => $utilisateur->getIdutilisateur()
  286. )
  287. );
  288. if ($eleve != null) {
  289. $session->set('utilisateur', $eleve);
  290. $session->set('typeUtilisateur', 'Élève');
  291. $session->set('droitadmin', false);
  292. $session->set('vueAdministrateur', false);
  293. return $this->redirectToRoute('app_ecleleve_index', [], Response::HTTP_SEE_OTHER);
  294. }
  295. // Sinon, il n'est ni enseignant, ni élève, il faut le déconnecter et contacter un administrateur
  296. else {
  297. return $this->redirectToRoute('app_logout', [], Response::HTTP_SEE_OTHER);
  298. }
  299. }
  300. }
  301. }
  302. // Paramètres
  303. #[Route('/parametre', name: 'app_eclac_parametre', methods: ['GET', 'POST'])]
  304. public function parametre(Request $request): Response
  305. {
  306. // La personne a besoin d'être identifiée pour accéder à la ressource
  307. $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
  308. // Variables communes
  309. $entityManager = $this->entityManager;
  310. $session = $request->getSession();
  311. if ($session->get('vueAdministrateur') != true) {
  312. $this->addFlash('error', 'Vous n\'avez pas les droits d\'accéder à cette ressource');
  313. return $this->redirectToRoute('app_eclac_choix-espace', [], Response::HTTP_SEE_OTHER);
  314. }
  315. $parametre = $entityManager->getRepository(Parametre::class)->findOneBy(array(
  316. 'idparametre' => 1
  317. ));
  318. if (isset($_POST['activeMaintenance'])) {
  319. $parametre->setModemaintenance(true);
  320. $entityManager->persist($parametre);
  321. $entityManager->flush();
  322. $session->set('modeMaintenance', true);
  323. }
  324. if (isset($_POST['desactiveMaintenance'])) {
  325. $parametre->setModemaintenance(false);
  326. $entityManager->persist($parametre);
  327. $entityManager->flush();
  328. $session->set('modeMaintenance', false);
  329. }
  330. $session->set('parametre', $parametre);
  331. $notif = $entityManager->getRepository(Modelenotif::class)->findOneBy(array(
  332. 'idmodelenotif' => 1
  333. ));
  334. return $this->renderForm('eclac/parametre.html.twig', [
  335. 'modelnotif' => $notif,
  336. ]);
  337. }
  338. public static function overrideFile(string $file, string $contenu): void
  339. {
  340. // On vérifie que le fichier existe
  341. if (!file_exists($file)) {
  342. touch($file);
  343. }
  344. // On vérifie que le fichier est accessible en écriture
  345. if (is_writable($file)) {
  346. // On vérifie qu'on arrive à ouvrir le fichier
  347. if (!$fp = fopen($file, 'w+')) {
  348. throw new \Exception();
  349. }
  350. // On vérifie qu'on arrive à écrire le yaml dans le fichier
  351. if (fwrite($fp, $contenu) === false) {
  352. throw new \Exception();
  353. }
  354. fclose($fp);
  355. } else {
  356. throw new \Exception();
  357. }
  358. }
  359. // Permet de récupérer les composantes d'un élève
  360. public function getComposantesEleve(Eleve $eleve, bool $all)
  361. {
  362. $entityManager = $this->entityManager;
  363. // On récupère les inscriptions de l'élève
  364. $inscriptions = $entityManager->getRepository(Inscription::class)->findBy(array(
  365. "eleve" => $eleve
  366. ));
  367. // Puis tous les cours auquel il est inscrit
  368. $arrayIdcours = array();
  369. foreach ($inscriptions as $inscription) {
  370. if (!in_array($inscription->getCours()->getIdcours(), $arrayIdcours)) {
  371. array_push($arrayIdcours, $inscription->getCours()->getIdcours());
  372. }
  373. }
  374. $cours = $entityManager->getRepository(Cours::class)->findBy(array(
  375. 'idcours' => $arrayIdcours
  376. ));
  377. // On récupère toutes les épreuves de ces cours
  378. $epreuves = $entityManager->getRepository(Epreuve::class)->findBy(array('cours' => $cours));
  379. // On récupère toutes les composantes de ces épreuves
  380. $arrayIdcomposantes = array();
  381. foreach ($epreuves as $epreuve) {
  382. // Épreuve pas encore qualifiée (critères non importés) : pas de composante
  383. if ($epreuve->getComposante() === null) {
  384. continue;
  385. }
  386. if ($all) {
  387. array_push($arrayIdcomposantes, $epreuve->getComposante()->getIdcomposante());
  388. } else {
  389. $arrayIdcomposantes[$epreuve->getComposante()->getCodecomposante()] = $epreuve->getComposante()->getIdcomposante();
  390. }
  391. }
  392. $composantes = $entityManager->getRepository(Composante::class)->findBy(
  393. array(
  394. 'idcomposante' => $arrayIdcomposantes,
  395. ),
  396. array(
  397. 'codecomposante' => 'ASC',
  398. 'idcomposante' => 'ASC',
  399. )
  400. );
  401. return $composantes;
  402. }
  403. // Rend propre une chaîne de caractère
  404. public static function sanitizeString(string $string, bool $file = false): string
  405. {
  406. $array = [' ' => "_",
  407. '.' => "_",
  408. '\'' => "_",
  409. '"' => "_",
  410. '…' => "",
  411. '«' => "",
  412. '»' => "",
  413. ];
  414. if ($file) {
  415. $array = [' ' => "_",
  416. '\'' => "_",
  417. '"' => "_",
  418. '…' => "",
  419. '«' => "",
  420. '»' => "",
  421. ];
  422. }
  423. return strtolower(
  424. EclacController::replaceAccent(
  425. strtr($string, $array)
  426. )
  427. );
  428. }
  429. // Remet le contenu d'un CSV en UTF-8 propre : les anciens exports du client étaient en
  430. // Latin-1/Windows-1252, mais les exports récents sont déjà en UTF-8 (parfois avec BOM).
  431. // utf8_encode() suppose à tort du Latin-1 : appliqué à un fichier déjà en UTF-8, il double-
  432. // encode chaque octet non-ASCII (le BOM devient un artefact "" qui n'est plus reconnu comme
  433. // BOM par le lecteur CSV et reste collé au nom de la première colonne, cassant sa détection).
  434. public static function reencodeUtf8(string $contents): string
  435. {
  436. $contents = preg_replace('/^\xEF\xBB\xBF/', '', $contents);
  437. if (!mb_check_encoding($contents, 'UTF-8')) {
  438. $contents = mb_convert_encoding($contents, 'UTF-8', 'ISO-8859-1');
  439. }
  440. return $contents;
  441. }
  442. // Décrit pourquoi l'écriture d'un fichier d'import a échoué (droits, disque plein...),
  443. // pour distinguer ce cas d'un vrai type d'import non reconnu dans les logs et messages d'erreur.
  444. public static function diagnosticEcritureFichier(string $file): string
  445. {
  446. $dir = dirname($file);
  447. $uid = function_exists('posix_geteuid') ? posix_geteuid() : null;
  448. $user = function_exists('posix_getpwuid') && $uid !== null ? (posix_getpwuid($uid)['name'] ?? 'inconnu') : get_current_user();
  449. return sprintf(
  450. "Échec écriture %s | uid=%s user=%s | fichier: exists=%s writable=%s perms=%s owner=%s | dossier: exists=%s writable=%s perms=%s | disque libre=%s octets",
  451. $file,
  452. $uid ?? 'n/a',
  453. $user,
  454. file_exists($file) ? 'oui' : 'non',
  455. is_writable($file) ? 'oui' : 'non',
  456. file_exists($file) ? substr(sprintf('%o', fileperms($file)), -4) : 'n/a',
  457. file_exists($file) && function_exists('posix_getpwuid') ? (posix_getpwuid(fileowner($file))['name'] ?? fileowner($file)) : 'n/a',
  458. is_dir($dir) ? 'oui' : 'non',
  459. is_writable($dir) ? 'oui' : 'non',
  460. is_dir($dir) ? substr(sprintf('%o', fileperms($dir)), -4) : 'n/a',
  461. (string) (disk_free_space($dir) ?: 'n/a')
  462. );
  463. }
  464. // Remplace les accents
  465. public static function replaceAccent(string $string): string
  466. {
  467. $unwanted_array = array(
  468. 'À' => 'A',
  469. 'Á' => 'A',
  470. 'Â' => 'A',
  471. 'Ã' => 'A',
  472. 'Ä' => 'A',
  473. 'Å' => 'A',
  474. 'Æ' => 'A',
  475. 'à' => 'a',
  476. 'á' => 'a',
  477. 'â' => 'a',
  478. 'ã' => 'a',
  479. 'ä' => 'a',
  480. 'å' => 'a',
  481. 'æ' => 'a',
  482. 'Ç' => 'C',
  483. 'ç' => 'c',
  484. 'È' => 'E',
  485. 'É' => 'E',
  486. 'Ê' => 'E',
  487. 'Ë' => 'E',
  488. 'è' => 'e',
  489. 'é' => 'e',
  490. 'ê' => 'e',
  491. 'ë' => 'e',
  492. 'Ì' => 'I',
  493. 'Í' => 'I',
  494. 'Î' => 'I',
  495. 'Ï' => 'I',
  496. 'ì' => 'i',
  497. 'í' => 'i',
  498. 'î' => 'i',
  499. 'ï' => 'i',
  500. 'Ñ' => 'N',
  501. 'ñ' => 'n',
  502. 'Ò' => 'O',
  503. 'Ó' => 'O',
  504. 'Ô' => 'O',
  505. 'Õ' => 'O',
  506. 'Ö' => 'O',
  507. 'Ø' => 'O',
  508. 'ð' => 'o',
  509. 'ò' => 'o',
  510. 'ó' => 'o',
  511. 'ô' => 'o',
  512. 'õ' => 'o',
  513. 'ö' => 'o',
  514. 'Š' => 'S',
  515. 'š' => 's',
  516. 'Ù' => 'U',
  517. 'Ú' => 'U',
  518. 'Û' => 'U',
  519. 'Ü' => 'U',
  520. 'ù' => 'u',
  521. 'ú' => 'u',
  522. 'û' => 'u',
  523. 'Ý' => 'Y',
  524. 'ý' => 'y',
  525. 'Ž' => 'Z',
  526. 'ž' => 'z',
  527. 'Þ' => 'B',
  528. 'ß' => 'Ss',
  529. 'þ' => 'b',
  530. 'ÿ' => 'y',
  531. "\n" => ' '
  532. );
  533. $newString = strtr($string, $unwanted_array);
  534. return $newString;
  535. }
  536. public function createTempDoc(string $path, string $docName): string
  537. {
  538. $now = date_format(new DateTime(), ('Ymd'));
  539. $outputPathBase = "";
  540. $file = $path . $docName;
  541. if (file_exists($file)) {
  542. // On génère un random
  543. $random = "";
  544. for ($i = 1; $i < 15; $i++) {
  545. $random .= mt_rand(1, 10);
  546. }
  547. if (!is_dir($this->projectDir . "/public/docs/tmp/")) {
  548. if (!mkdir($this->projectDir . "/public/docs/tmp/", 0775, true)) {
  549. $this->addFlash('error', 'Erreur à la création du dossier');
  550. }
  551. }
  552. $pathRoot = $this->projectDir;
  553. $outputPath = "/public/docs/tmp/";
  554. $outputFile = "temp-" . $now . "-" . $random . "-" . $docName;
  555. $outputComplet = $pathRoot . $outputPath . $outputFile;
  556. $outputPathBase = "/docs/tmp/" . $outputFile;
  557. if (!copy($file, $outputComplet)) {
  558. $this->addFlash('error', 'Document non trouvé');
  559. }
  560. } else {
  561. $this->addFlash('error', 'Document non trouvé');
  562. }
  563. return $outputPathBase;
  564. }
  565. // Supprime les copies publiques de /docs/tmp/ plus anciennes que $ageMaxSecondes. Sans risque pour
  566. // les liens : getCheminTempCertificat() recrée la copie à la demande depuis l'original.
  567. public function purgeDocsTemp(int $ageMaxSecondes = 86400): int
  568. {
  569. $limite = time() - $ageMaxSecondes;
  570. $nbSupprimes = 0;
  571. foreach(glob($this->projectDir . "/public/docs/tmp/temp-*") ?: [] as $fichier)
  572. {
  573. if(is_file($fichier) && filemtime($fichier) < $limite && unlink($fichier))
  574. {
  575. $nbSupprimes++;
  576. }
  577. }
  578. if($nbSupprimes > 0)
  579. {
  580. $this->logError($nbSupprimes . " fichier(s) temporaire(s) supprimé(s) de /docs/tmp/", "purgeDocsTemp");
  581. }
  582. return $nbSupprimes;
  583. }
  584. // Retourne le chemin public (dans /docs/tmp/) du certificat de ce diplôme, en recréant la copie
  585. // temporaire à partir de l'original (docs/certificat-competence/, non accessible) si elle a été purgée.
  586. // Retourne NULL si l'original n'existe pas.
  587. public function getCheminTempCertificat(Diplome $diplome): ?string
  588. {
  589. $pathRoot = $this->projectDir . '/public';
  590. if($diplome->getChemintemp() != NULL && file_exists($pathRoot . $diplome->getChemintemp()))
  591. {
  592. return $diplome->getChemintemp();
  593. }
  594. $cheminTemp = $this->createTempDoc($pathRoot . '/../docs/certificat-competence/', 'certif_competences_' . $diplome->getNumerodiplome() . '.pdf');
  595. if($cheminTemp == "")
  596. {
  597. return NULL;
  598. }
  599. $diplome->setChemintemp($cheminTemp);
  600. $this->entityManager->persist($diplome);
  601. $this->entityManager->flush();
  602. return $cheminTemp;
  603. }
  604. // Journal applicatif unique : logs/compass-AAAA-MM-JJ.log (canal Monolog "compass", voir monolog.yaml).
  605. // $typeLog (ex. "cronPreImport") n'est plus un nom de fichier mais une étiquette en tête du message ;
  606. // $contexte (ex. ['fichier' => 1068]) est ajouté en fin de ligne au format JSON.
  607. // logError() était historiquement utilisé pour tous les messages : le niveau est déduit du texte
  608. // tant que les appels n'ont pas été remplacés par logInfo()/logWarning().
  609. public function logError(string $string, string $typeLog, array $contexte = []): void
  610. {
  611. $niveau = preg_match('/erreur|impossible|échou|echou|exception|introuvable|non trouvé|refus|rejet|orphelin|stoppé/iu', $string) ? 'error' : 'info';
  612. $this->journaliser($niveau, $string, $typeLog, $contexte);
  613. }
  614. public function logWarning(string $string, string $typeLog, array $contexte = []): void
  615. {
  616. $this->journaliser('warning', $string, $typeLog, $contexte);
  617. }
  618. public function logInfo(string $string, string $typeLog, array $contexte = []): void
  619. {
  620. $this->journaliser('info', $string, $typeLog, $contexte);
  621. }
  622. private function journaliser(string $niveau, string $string, string $typeLog, array $contexte): void
  623. {
  624. if ($string == '') {
  625. $string = "Message d'erreur non défini";
  626. }
  627. if ($typeLog == '') {
  628. $typeLog = "NC";
  629. }
  630. $this->compassLogger->log($niveau, '[' . $typeLog . '] ' . $string, $contexte);
  631. }
  632. // Recrée l'utilisateur système (id=3) utilisé par les commandes cron si jamais il a été supprimé
  633. public function ensureUtilisateurSysteme(): void
  634. {
  635. $conn = $this->entityManager->getConnection();
  636. $conn->executeStatement(
  637. "INSERT IGNORE INTO UTILISATEUR (idutilisateur, actifutilisateur, datecrea, datemaj, login, password, pers_id)
  638. VALUES (3, 0, NOW(), NOW(), ?, NULL, NULL)",
  639. ['Automatique']
  640. );
  641. }
  642. public static function dateToMySQL(string $date): DateTime
  643. {
  644. $tabDate = explode('/', $date);
  645. $date = $tabDate[2].'-'.$tabDate[1].'-'.$tabDate[0];
  646. $date = new DateTime(date('Y-m-d H:i:s', strtotime($date)));
  647. return $date;
  648. }
  649. // Permet de générer un PDF à partir d'une vue twig
  650. public function generatePDF(string $html, string $path, string $url): bool
  651. {
  652. $knpSnappyPdf = $this->knpSnappyPdf;
  653. $parametre = $this->entityManager->getRepository(Parametre::class)->findOneBy(array(
  654. "idparametre" => 1,
  655. ));
  656. $header = $this->renderView('pdf/header' . $parametre->getNompdf() . '.html.twig', [
  657. "url" => $url,
  658. 'parametre' => $parametre,
  659. ]);
  660. $footer = $this->renderView('pdf/footer' . $parametre->getNompdf() . '.html.twig', [
  661. 'parametre' => $parametre,
  662. ]);
  663. try {
  664. $knpSnappyPdf->setOptions(
  665. [
  666. 'page-size' => 'A4',
  667. 'margin-top' => '1.5in',
  668. 'margin-right' => '0in',
  669. 'margin-bottom' => '0.35in',
  670. 'margin-left' => '0in',
  671. 'header-html' => $header,
  672. 'footer-html' => $footer,
  673. 'disable-smart-shrinking' => true,
  674. 'enable-local-file-access' => true,
  675. 'enable-external-links' => true,
  676. 'enable-internal-links' => true,
  677. 'enable-javascript' => true,
  678. 'debug-javascript' => true,
  679. // Le graphique (Chart.js, animations désactivées) est dessiné dès le chargement de la page :
  680. // 200 ms suffisent. Le délai est payé à chaque certificat (il était de 2 s).
  681. 'javascript-delay' => 200,
  682. 'no-stop-slow-scripts' => true,
  683. 'encoding' => 'UTF-8',
  684. 'toc' => false,
  685. 'dpi' => 96,
  686. 'zoom' => 1,
  687. 'viewport-size' => '1920x1080',
  688. ]
  689. )
  690. ->generateFromHtml($html, $path);
  691. return true;
  692. } catch (\Exception $e) {
  693. $this->logError($e->getMessage(), "generateAllDegrees");
  694. return false;
  695. }
  696. }
  697. }