From 423755b019a09111b971e36c53e2557e2f5a704f Mon Sep 17 00:00:00 2001 From: polo Date: Tue, 16 Dec 2025 22:41:57 +0100 Subject: page emails, application du RGPD: table email et nettoyeur, renommage de $id_email --- src/EmailService.php | 26 +++++++-- src/controller/ContactFormController.php | 16 ++++++ src/controller/UserController.php | 2 + src/controller/ViewController.php | 2 +- src/installation.php | 8 +++ src/model/Model.php | 17 +++++- src/model/entities/Email.php | 90 +++++++++++++++++++++++++++++--- src/model/entities/Node.php | 37 +------------ src/model/entities/NodeData.php | 10 ++++ src/model/entities/NodeDataAsset.php | 1 - src/router.php | 13 +++++ src/view/ShowEmailsBuilder.php | 69 ++++++++++++++++++++++++ src/view/templates/form.php | 6 ++- src/view/templates/form_admin.php | 3 ++ src/view/templates/show_emails.php | 27 ++++++++++ 15 files changed, 277 insertions(+), 50 deletions(-) create mode 100644 src/view/ShowEmailsBuilder.php create mode 100644 src/view/templates/show_emails.php (limited to 'src') diff --git a/src/EmailService.php b/src/EmailService.php index 1bcca0f..8671817 100644 --- a/src/EmailService.php +++ b/src/EmailService.php @@ -65,16 +65,36 @@ class EmailService // copie en BDD if(!$test_email){ - $db_email = new Email($email, Config::$email_dest, $message); + $db_email = new Email($name, $email, Config::$email_dest, $message); $entityManager->persist($db_email); + self::updateLastContactDate($entityManager, $email); $entityManager->flush(); } return true; } catch(Exception $e){ - return false; - //echo "Le message n'a pas pu être envoyé. Erreur : {$mail->ErrorInfo}"; + echo "Le message n'a pas pu être envoyé. Erreur : {$e}
{$mail->ErrorInfo}"; + return false; } } + + static public function updateLastContactDate(EntityManager $entityManager, string $sender): void + { + foreach($entityManager->getRepository('App\Entity\Email')->findAll() as $email){ + $email->getSenderAddress() === $sender ? $email->updateLastContactDate() : null; + } + } + + // peut être appelée par bin/clean_emails_cron.php + static public function cleanEmails(EntityManager $entityManager): void + { + $emails = $entityManager->getRepository('App\Entity\Email')->findAll(); + foreach($emails as $email){ + if($email->getDeletionDate() < new \DateTime()){ + $entityManager->remove($email); + } + } + $entityManager->flush(); + } } \ No newline at end of file diff --git a/src/controller/ContactFormController.php b/src/controller/ContactFormController.php index 181e93c..01faa72 100644 --- a/src/controller/ContactFormController.php +++ b/src/controller/ContactFormController.php @@ -80,4 +80,20 @@ class ContactFormController } die; } + static public function deleteEmail(EntityManager $entityManager, array $json): void + { + $email = $entityManager->find('App\Entity\Email', $json['id']); + $entityManager->remove($email); + $entityManager->flush(); + echo json_encode(['success' => true]); + die; + } + static public function toggleSensitiveEmail(EntityManager $entityManager, array $json): void + { + $email = $entityManager->find('App\Entity\Email', $json['id']); + $email->makeSensitive($json['checked']); + $entityManager->flush(); + echo json_encode(['success' => true, 'checked' => $json['checked'], 'deletion_date' => $email->getDeletionDate()->format('d/m/Y')]); + die; + } } \ No newline at end of file diff --git a/src/controller/UserController.php b/src/controller/UserController.php index a35b09e..f3c99e7 100644 --- a/src/controller/UserController.php +++ b/src/controller/UserController.php @@ -90,6 +90,8 @@ class UserController $_SESSION['user'] = $_POST['login']; $_SESSION['admin'] = true; + EmailService::cleanEmails($entityManager); + $url = new URL(isset($_GET['from']) ? ['page' => $_GET['from']] : []); isset($_GET['id']) ? $url->addParams(['id' => $_GET['id']]) : ''; } diff --git a/src/controller/ViewController.php b/src/controller/ViewController.php index 9139dd4..8c95526 100644 --- a/src/controller/ViewController.php +++ b/src/controller/ViewController.php @@ -55,7 +55,7 @@ class ViewController extends AbstractBuilder // ViewController est aussi le prem self::$root_node = $model->getNode(); - /* 3/ 2ème contrôle utilisant les données récupérées */ + /* 3/ 2ème contrôle des paramètres avec les données récupérées */ // article non trouvé en BDD if(CURRENT_PAGE === 'article' && !$_SESSION['admin'] && self::$root_node->getNodeByName('main')->getAdoptedChild() === null){ diff --git a/src/installation.php b/src/installation.php index 3d75449..78f1768 100644 --- a/src/installation.php +++ b/src/installation.php @@ -100,6 +100,9 @@ function fillStartingDatabase(EntityManager $entityManager){ $new_page = new Page('Nouvelle page', 'new_page', "Nouvelle page", true, false, false, NULL, NULL); $new_page->addCSS('new_page'); $new_page->addJS('new_page'); + $emails = new Page("Courriels", 'emails', "Consulter les courriels en base de données", true, false, false, NULL, NULL); + $emails->addCSS('show_emails'); + $emails->addJS('form'); /* -- table node -- */ // paramètres: name_node, article_timestamp, attributes, position, parent, page, article @@ -113,12 +116,14 @@ function fillStartingDatabase(EntityManager $entityManager){ $user_edit = new Node('user_edit', 1, $main, $my_account, NULL); $bloc_edit_menu = new Node('menu', 1, $main, $menu_paths, NULL); $bloc_new_page = new Node('new_page', 1, $main, $new_page, NULL); + $bloc_emails = new Node('show_emails', 1, $main, $emails, NULL); /* -- table node_data -- */ // paramètres: data, node, images $head_data = new NodeData([], $head); $header_data = new NodeData([], $header); $footer_data = new NodeData([], $footer); + $emails_data = new NodeData([], $bloc_emails); /* -- table page -- */ $entityManager->persist($accueil); @@ -127,6 +132,7 @@ function fillStartingDatabase(EntityManager $entityManager){ $entityManager->persist($my_account); $entityManager->persist($menu_paths); $entityManager->persist($new_page); + $entityManager->persist($emails); /* -- table node -- */ $entityManager->persist($head); @@ -139,11 +145,13 @@ function fillStartingDatabase(EntityManager $entityManager){ $entityManager->persist($user_edit); $entityManager->persist($bloc_edit_menu); $entityManager->persist($bloc_new_page); + $entityManager->persist($bloc_emails); /* -- table node_data -- */ $entityManager->persist($head_data); $entityManager->persist($header_data); $entityManager->persist($footer_data); + $entityManager->persist($emails_data); $entityManager->flush(); header('Location: ' . new URL); diff --git a/src/model/Model.php b/src/model/Model.php index b650183..de391ff 100644 --- a/src/model/Model.php +++ b/src/model/Model.php @@ -58,11 +58,16 @@ class Model ->setParameter('page', $this->page) ->getResult(); - // groupes d'articles triés par bloc, permet de paginer par bloc foreach($bulk_data as $parent_block){ + // groupes d'articles triés par bloc, permet de paginer par bloc if(Blocks::hasPresentation($parent_block->getName())){ // = post_block ou news_block $bulk_data = array_merge($bulk_data, $this->getNextArticles($parent_block, $request)[0]); } + + // emails + if($parent_block->getName() === 'show_emails'){ + $parent_block->getNodeData()->setEmails($this->getAllEmails()); + } } } else{ // page "article" @@ -283,4 +288,14 @@ class Model $this->node->addChild($child); } } + + private function getAllEmails(): array + { + $dql = 'SELECT e FROM App\Entity\Email e'; + return $this->entityManager + ->createQuery($dql) + //->setParameter('page', $this->page) + ->getResult(); + } + //private function getEmails(string $sender): array } diff --git a/src/model/entities/Email.php b/src/model/entities/Email.php index 9d87f1f..c66625f 100644 --- a/src/model/entities/Email.php +++ b/src/model/entities/Email.php @@ -11,13 +11,20 @@ use Doctrine\ORM\Mapping as ORM; #[ORM\Table(name: TABLE_PREFIX . "email")] class Email { + // en mois + const LEGAL_RETENTION_PERIOD = 36; // 3 ans, justification = prospection, durée "glissante", date de suppression remise à jour à chaque nouvel e-mail + const LEGAL_RETENTION_PERIOD_SENSITIVE = 60; // 5 ans pour données sensibles ou litige, durée de preuve légale, durée non glissante + #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column(type: "integer")] - private int $id_log; + private int $id_email; + + #[ORM\Column(type: "string", length: 255)] + private string $sender_name; #[ORM\Column(type: "string", length: 320)] - private string $sender; + private string $sender_address; #[ORM\Column(type: "string", length: 320)] private string $recipient; @@ -30,12 +37,81 @@ class Email private string $content; #[ORM\Column(type: 'datetime', options: ['default' => 'CURRENT_TIMESTAMP'])] - private ?\DateTime $date_time ; + private \DateTime $date_time; + + #[ORM\Column(type: 'boolean')] + private bool $is_sensitive; // "sensitive" tout court est un mot réservé + + #[ORM\Column(type: 'datetime', options: ['default' => 'CURRENT_TIMESTAMP'])] + private \DateTime $last_contact_date; + + #[ORM\Column(type: 'datetime', nullable: true)] + private ?\DateTime $is_sensitive_since; - public function __construct(string $sender, string $recipient, string $content){ - $this->sender = strtolower($sender); + public function __construct(string $sender_name, string $sender_address, string $recipient, string $content, bool $sensitive = false){ + $this->sender_name = strtolower($sender_name); + $this->sender_address = strtolower($sender_address); $this->recipient = strtolower($recipient); $this->content = $content; - $this->date_time = new \DateTime(); + $this->date_time = new \DateTime; + $this->last_contact_date = new \DateTime; + $this->makeSensitive($sensitive); + } + + public function getId(): int + { + return $this->id_email; + } + public function getSenderName(): string + { + return $this->sender_name; + } + public function getSenderAddress(): string + { + return $this->sender_address; + } + public function getRecipient(): string + { + return $this->recipient; + } + public function getContent(): string + { + return $this->content; + } + public function getDateTime(): \DateTime + { + return $this->date_time; + } + /*public function getLastContactDate(): \DateTime + { + return $this->last_contact_date; + }*/ + public function isSensitive(): bool + { + return $this->is_sensitive; + } + public function isSensitiveSince(): ?\DateTime + { + return $this->is_sensitive_since; + } + + public function makeSensitive(bool $sensitive = true): void + { + $this->is_sensitive = $sensitive; + if($sensitive && $this->is_sensitive_since === null){ + $this->is_sensitive_since = new \DateTime(); + } + } + + public function updateLastContactDate(): void + { + $this->last_contact_date = new \DateTime; + } + + public function getDeletionDate(): \DateTime + { + return $this->is_sensitive // oui durée 5 ans, non durée 3 ans "glissante" + ? (clone $this->is_sensitive_since)->modify('+ ' . (string)self::LEGAL_RETENTION_PERIOD_SENSITIVE . ' month') // erreur si vrai mais sans date (pas censé arriver) + : (clone $this->last_contact_date)->modify('+ ' . (string)self::LEGAL_RETENTION_PERIOD . ' month'); } -} +} \ No newline at end of file diff --git a/src/model/entities/Node.php b/src/model/entities/Node.php index fe3a1e5..71c159d 100644 --- a/src/model/entities/Node.php +++ b/src/model/entities/Node.php @@ -72,42 +72,7 @@ class Node { $this->name_node = $name; }*/ - - /*public function getAttributes(): array - { - return $this->attributes; - } - public function setDefaultAttributes(array $attributes): void - { - $this->attributes = $attributes; - } - public function useDefaultAttributes(): void - { - $this->attributes = self::$default_attributes; - } - public function addAttribute(string $key, string $value): void - { - if(!isset($this->attributes[$key])) { // sécurité $key inexistante - $this->attributes[$key] = []; - } - if(!in_array($value, $this->attributes[$key])){ - $this->attributes[$key][] = $value; - } - }*/ - /*public function removeAttribute(string $key, string $value): void - { - if(isset($this->attributes[$key])) // sécurité $key inexistante - { - // supprime et réindex avec un nouveau tableau - $tmp_array = $this->attributes[$key]; - $this->attributes[$key] = []; - foreach($tmp_array as $entry){ - if($entry !== $value){ - $this->attributes[$key][] = $entry; - } - } - } - }*/ + public function getParent(): ?self { return $this->parent; diff --git a/src/model/entities/NodeData.php b/src/model/entities/NodeData.php index b25b540..19670fe 100644 --- a/src/model/entities/NodeData.php +++ b/src/model/entities/NodeData.php @@ -45,6 +45,7 @@ class NodeData private Collection $nda_collection; private int $nb_pages = 1; + private array $emails = []; // noeud show_emails uniquement public function __construct(array $data, Node $node, Collection $nda_collection = new ArrayCollection, ?string $presentation = null, ?bool $chrono_order = null) { @@ -179,4 +180,13 @@ class NodeData $this->new_nda = new NodeDataAsset($this, $asset, $role); $this->addNodeDataAsset($this->new_nda); }*/ + + public function getEmails(): array + { + return $this->emails; + } + public function setEmails(array $emails): void + { + $this->emails = $emails; + } } \ No newline at end of file diff --git a/src/model/entities/NodeDataAsset.php b/src/model/entities/NodeDataAsset.php index 7f92fd1..d5eb141 100644 --- a/src/model/entities/NodeDataAsset.php +++ b/src/model/entities/NodeDataAsset.php @@ -15,7 +15,6 @@ use Doctrine\ORM\Mapping as ORM; class NodeDataAsset { // clé primaire double - // inconvénient: impossible d'utiliser deux fois la même paire node_data/asset, même pour des rôles différents #[ORM\Id] #[ORM\ManyToOne(targetEntity: NodeData::class, inversedBy: 'nda_collection')] #[ORM\JoinColumn(name: 'node_data_id', referencedColumnName: 'id_node_data', onDelete: 'CASCADE')] diff --git a/src/router.php b/src/router.php index 15d5a4c..d2eba18 100644 --- a/src/router.php +++ b/src/router.php @@ -38,6 +38,12 @@ if($request->getMethod() === 'GET'){ CalendarController::getData($entityManager); } + // pages interdites + if(!$_SESSION['admin'] && in_array(CURRENT_PAGE, ['menu_paths', 'new_page', 'user_edit', 'emails'])){ + header('Location: ' . new URL); + die; + } + if($_SESSION['admin'] === true){ // ... } @@ -113,6 +119,13 @@ elseif($request->getMethod() === 'POST'){ ContactFormController::sendTestEmail($entityManager, $json); } + /* -- page emails -- */ + elseif($_GET['action'] === 'delete_email'){ + ContactFormController::deleteEmail($entityManager, $json); + } + elseif($_GET['action'] === 'toggle_sensitive_email'){ + ContactFormController::toggleSensitiveEmail($entityManager, $json); + } /* -- upload d'image dans tinymce par copier-coller -- */ // collage de HTML contenant une ou plusieurs balises diff --git a/src/view/ShowEmailsBuilder.php b/src/view/ShowEmailsBuilder.php new file mode 100644 index 0000000..3d2d6a9 --- /dev/null +++ b/src/view/ShowEmailsBuilder.php @@ -0,0 +1,69 @@ +getName() . '.php'; + if(file_exists($viewFile)) + { + // objets Email groupés par destinataire + $emails_by_recipient = []; + foreach($node->getNodeData()->getEmails() as $email){ + $recipient = $email->getRecipient(); + $emails_by_recipient[$recipient][] = $email; + } + + // affiche une table par destinataire + $emails = ''; + foreach($emails_by_recipient as $recipient => $emails_list){ + $html = '

Destinataire: ' . $recipient . '

+ + + + + + + + + + + + + '; + + // insère les données + foreach($emails_list as $email){ + $html .= ' + + + + + + + + '; + } + + $html .= ' +
ExpéditeurAdresseContenuDateEffacement prévu leSensible
' . htmlspecialchars($email->getSenderName()) . '' . htmlspecialchars($email->getSenderAddress()) . '' . htmlspecialchars($email->getContent()) . '' . $email->getDateTime()->format('d/m/Y') . '' . $email->getDeletionDate()->format('d/m/Y') . 'isSensitive() ? 'checked' : '') . ' onclick="toggleSensitiveEmail(' . $email->getId() . ')">
'; + $emails .= $html; + } + + ob_start(); + require $viewFile; // insertion de $this->html généré par unfoldMenu + $this->html = ob_get_clean(); // pas de concaténation .= cette fois on écrase + } + else{ + header('Location: ' . new URL(['error' => 'show_emails_view_not_found'])); + die; + } + } +} \ No newline at end of file diff --git a/src/view/templates/form.php b/src/view/templates/form.php index 5c959a0..df1dd0f 100644 --- a/src/view/templates/form.php +++ b/src/view/templates/form.php @@ -7,7 +7,7 @@ - + @@ -30,4 +30,8 @@

+

+ Une copie de votre e-mail (nom, adresse et message) sera conservée dans notre base de données dans le but de pouvoir répondre à votre demande et et éventuellement dans un but de prospection. Ces données seront traitées automatiquement par notre serveur et conservées pendant au maximum 3 ans à compter de votre dernier message.
+ Ce traitement repose sur votre consentement. Vous pouvez consulter, modifier ou supprimer vos données en base de données sur simple demande. +

\ No newline at end of file diff --git a/src/view/templates/form_admin.php b/src/view/templates/form_admin.php index 3559d25..cabfeb0 100644 --- a/src/view/templates/form_admin.php +++ b/src/view/templates/form_admin.php @@ -3,6 +3,9 @@ declare(strict_types=1); // note: l'id ici n'est pas celui du noeud bloc mais celui de l'entrée dans node_data correspondante ?> +
+ +

Paramètres d'envoi

diff --git a/src/view/templates/show_emails.php b/src/view/templates/show_emails.php new file mode 100644 index 0000000..9954c6f --- /dev/null +++ b/src/view/templates/show_emails.php @@ -0,0 +1,27 @@ + +

+

Table "email" de la base de données

+

+ Les e-mails ci-dessous sont des copies de ceux arrivés dans votre boite de messagerie. Ils sont conservés dans un but pratique et éventuellement dans un but de prospection, ou dans tout autre but permettant de justifier leur conservation.
+ Ils sont "effacés" automatiquement au bout d'un certain temps comme le requièrt le RGPD. Un nettoyeur est exécuté à chaque connexion au mode admin ou éventuellement à l'aide d'une tâche CRON (le serveur doit pour ça être configuré pour exécuter periodiquement la commande "php /chemin/du/site/bin/cron.php"). +

+

Durées de conservation

+

+ Ce sont des durées maximales, les données peuvent être supprimées plus tôt ou même immédiatement. Le faire est d'ailleurs une obligation dans le cas où leur expéditeur le demande.
+ Théoriquement, ce même nettoyage des vieux messages devrait être également réalisé par vous-même dans votre boite de messagerie. +

+

+ Les e-mails ordinaires d'un même expéditeur (même adresse e-mail) sont tous supprimés simultanément lorsque le plus récent d'entre eux atteint les 3 ans (utilisateur "inactif").
+ Les e-mails sensibles quand à eux sont supprimés 5 ans après être devenus sensibles (durée juridique d'une preuve). +

+

Données sensibles

+

+ Un e-mail peut-être considéré comme "sensible". Vous pouvez rendre un e-mail sensible lorsqu'il possède une valeur de preuve dans le cas d'un litige.
+ Lorsqu'une personne demande la suppression de ses données personnelles du serveur, les e-mails sensibles peuvent être conservés, vous aurez noté que la durée de conservation est calculée différement. +

+

+ Les spams ne sont pas sensibles, c'est juste de la pollution, supprimez-les! +

+ + +
\ No newline at end of file -- cgit v1.2.3