aboutsummaryrefslogtreecommitdiff
path: root/src/model
diff options
context:
space:
mode:
authorpolo <ordipolo@gmx.fr>2026-10-23 00:47:32 +0200
committerpolo <ordipolo@gmx.fr>2026-10-23 00:47:32 +0200
commitf27cca08ff786ceff1870147f6d6bad14d71117f (patch)
treed36597a72025313d6faf4ed228755fa267ebe2f6 /src/model
parentc7309f900ef05bd5cb313110ec159b761af394ec (diff)
downloadcms-f27cca08ff786ceff1870147f6d6bad14d71117f.tar.gz
cms-f27cca08ff786ceff1870147f6d6bad14d71117f.tar.bz2
cms-f27cca08ff786ceff1870147f6d6bad14d71117f.zip
dossiers avec majuscule, namespace pour toutes les classes, autoload psr-4, UserDataService::createZip déplacé dans Backup
Diffstat (limited to 'src/model')
-rw-r--r--src/model/DatabaseSettingUp.php155
-rw-r--r--src/model/EventDTO.php51
-rw-r--r--src/model/Menu.php42
-rw-r--r--src/model/Model.php312
-rw-r--r--src/model/Path.php76
-rw-r--r--src/model/Position.php50
-rw-r--r--src/model/doctrine-bootstrap.php33
-rw-r--r--src/model/entities/AppMetadata.php46
-rw-r--r--src/model/entities/Article.php94
-rw-r--r--src/model/entities/Asset.php68
-rw-r--r--src/model/entities/AssetEmployment.php54
-rw-r--r--src/model/entities/Email.php131
-rw-r--r--src/model/entities/EmailForm.php57
-rw-r--r--src/model/entities/Event.php129
-rw-r--r--src/model/entities/Image.php89
-rw-r--r--src/model/entities/Log.php38
-rw-r--r--src/model/entities/Node.php166
-rw-r--r--src/model/entities/NodeData.php102
-rw-r--r--src/model/entities/Page.php221
-rw-r--r--src/model/entities/Presentation.php135
-rw-r--r--src/model/entities/User.php67
21 files changed, 0 insertions, 2116 deletions
diff --git a/src/model/DatabaseSettingUp.php b/src/model/DatabaseSettingUp.php
deleted file mode 100644
index 4086d5a..0000000
--- a/src/model/DatabaseSettingUp.php
+++ /dev/null
@@ -1,155 +0,0 @@
1<?php
2// src/model/DatabaseSettingUp.php
3
4declare(strict_types=1);
5
6use App\Entity\AppMetadata;
7use App\Entity\Page;
8use App\Entity\Node;
9use App\Entity\NodeData;
10use Doctrine\ORM\EntityManager;
11
12/* création d'un site minimal avec une page d'accueil à la toute 1ère visite du site, ne doit surtout pas être exécutée une seconde fois */
13class DatabaseSettingUp
14{
15 static public function run(EntityManager $entityManager): void
16 {
17 if(!self::isFirstRun($entityManager)){
18 return;
19 }
20
21 // la BDD n'est pas vierge, on ne touche à rien
22 if(!self::areTablesEmpty($entityManager)){
23 return;
24 }
25
26 self::fillStartingDatabase($entityManager);
27
28 // empêcher la réutilisation de cette fonction
29 self::preventReinstallation($entityManager);
30
31 // recharger la page?
32 //header('Location: ' . new URL);
33 }
34
35 // protection 1 utilisé à chaque requête
36 static private function isFirstRun(EntityManager $entityManager): bool
37 {
38 $metadata = $entityManager->getRepository(AppMetadata::class)->find('installed');
39 return !$metadata || $metadata->getValue() !== '1';
40 }
41
42 // protection 2, qui vérifie vraiment que les tables concernées sont vides
43 static private function areTablesEmpty(EntityManager $entityManager): bool
44 {
45 $empty = true;
46 $entities = ['Page', 'Node', 'NodeData'];
47 foreach($entities as $entity){
48 $entity = 'App\Entity\\' . $entity; // nécéssaire quand on insère le nom avec une variable
49
50 if($entityManager
51 ->createQuery("SELECT e FROM $entity e")
52 ->setMaxResults(1)
53 ->getOneOrNullResult()){
54 $empty = false;
55 }
56 }
57
58 // cas anormal détecté, on remet en place la clé "installed"
59 if(!$empty){
60 self::preventReinstallation($entityManager);
61 }
62
63 return $empty;
64 }
65
66 static private function fillStartingDatabase(EntityManager $entityManager): void
67 {
68 /* -- table page -- */
69 // paramètres: name_page, end_of_path, reachable, in_menu, hidden, position, parent
70 $accueil = new Page('Accueil', 'accueil', "Page d'accueil", true, true, false, 1, NULL);
71 $article = new Page('Article', 'article', "", true, false, false, NULL, NULL);
72 $connection = new Page('Connexion', 'connection', "Connexion", true, false, false, NULL, NULL);
73 $my_account = new Page('Mon compte', 'user_edit', "Mon compte", true, false, false, NULL, NULL);
74 $menu_paths = new Page("Menu et chemins", 'menu_paths', "Menu et chemins", true, false, false, NULL, NULL);
75 $menu_paths->addCSS('menu');
76 $menu_paths->addJS('menu');
77 $new_page = new Page('Nouvelle page', 'new_page', "Nouvelle page", true, false, false, NULL, NULL);
78 $new_page->addCSS('new_page');
79 $new_page->addJS('new_page');
80 $emails = new Page("Courriels", 'emails', "Consulter les courriels en base de données", true, false, false, NULL, NULL);
81 $emails->addCSS('show_emails');
82 $emails->addJS('form');
83 $maintenance = new Page("Maintenance", 'maintenance', "Opérations de maintenance, installation, sauvegarde, restauration", true, false, false, NULL, NULL);
84 $maintenance->addCSS('maintenance');
85 $maintenance->addJS('maintenance');
86
87 /* -- table node -- */
88 // paramètres: name_node, article_timestamp, attributes, position, parent, page, article
89 $head = new Node('head', 1, NULL, NULL, NULL);
90 $header = new Node('header', 2, NULL, NULL, NULL);
91 $nav = new Node('nav', 1, $header, NULL, NULL);
92 $main = new Node('main', 3, NULL, NULL, NULL);
93 $footer = new Node('footer', 4, NULL, NULL, NULL);
94 $breadcrumb = new Node('breadcrumb', 2, $header, NULL, NULL);
95 $login = new Node('login', 1, $main, $connection, NULL);
96 $user_edit = new Node('user_edit', 1, $main, $my_account, NULL);
97 $bloc_edit_menu = new Node('menu', 1, $main, $menu_paths, NULL);
98 $bloc_new_page = new Node('new_page', 1, $main, $new_page, NULL);
99 $bloc_emails = new Node('show_emails', 1, $main, $emails, NULL);
100 $bloc_maintenance = new Node('maintenance', 1, $main, $maintenance, NULL);
101
102 /* -- table node_data -- */
103 // paramètres: data, node, images
104 $head_data = new NodeData([], $head);
105 $header_data = new NodeData([], $header);
106 $footer_data = new NodeData([], $footer);
107 $emails_data = new NodeData([], $bloc_emails);
108
109 /* -- table page -- */
110 $entityManager->persist($accueil);
111 $entityManager->persist($article);
112 $entityManager->persist($connection);
113 $entityManager->persist($my_account);
114 $entityManager->persist($menu_paths);
115 $entityManager->persist($new_page);
116 $entityManager->persist($emails);
117 $entityManager->persist($maintenance);
118
119 /* -- table node -- */
120 $entityManager->persist($head);
121 $entityManager->persist($header);
122 $entityManager->persist($nav);
123 $entityManager->persist($main);
124 $entityManager->persist($footer);
125 $entityManager->persist($breadcrumb);
126 $entityManager->persist($login);
127 $entityManager->persist($user_edit);
128 $entityManager->persist($bloc_edit_menu);
129 $entityManager->persist($bloc_new_page);
130 $entityManager->persist($bloc_emails);
131 $entityManager->persist($bloc_maintenance);
132
133 /* -- table node_data -- */
134 $entityManager->persist($head_data);
135 $entityManager->persist($header_data);
136 $entityManager->persist($footer_data);
137 $entityManager->persist($emails_data);
138
139 $entityManager->flush();
140 }
141
142 // met en place la protection
143 static private function preventReinstallation(EntityManager $entityManager): void
144 {
145 $metadata = $entityManager->getRepository(AppMetadata::class)->find('installed');
146 if($metadata){
147 $metadata->setValue('1');
148 }
149 else{
150 $metadata = new AppMetadata('installed', '1');
151 $entityManager->persist($metadata);
152 }
153 $entityManager->flush();
154 }
155} \ No newline at end of file
diff --git a/src/model/EventDTO.php b/src/model/EventDTO.php
deleted file mode 100644
index 6dd3722..0000000
--- a/src/model/EventDTO.php
+++ /dev/null
@@ -1,51 +0,0 @@
1<?php
2// src/model/EventDTO.php
3//
4// classe de données JSONifiable compatible avec fullcalendar
5// servira éventuellement pour l'import/export de fichiers .ics
6
7declare(strict_types=1);
8
9use App\Entity\Event;
10
11class EventDTO
12{
13 public int $id;
14 public string $title;
15 public string $start;
16 public string $end;
17 public bool $allDay;
18 public ?string $color;
19 public string $description;
20
21 public function __construct(Event $event)
22 {
23 $this->id = $event->getId();
24 $this->title = $event->getTitle();
25 $this->description = $event->getDescription() ?? ''; // renvoie $event->getDescription() si existe et ne vaut pas "null"
26 $this->allDay = $event->isAllDay();
27 $this->color = $event->getColor();
28
29 if($this->allDay){
30 $this->start = $event->getStart()->format('Y-m-d');
31 $this->end = $event->getEnd()->format('Y-m-d');
32 }
33 else{
34 $this->start = $event->getStart()->format('Y-m-d\TH:i:s\Z');
35 $this->end = $event->getEnd()->format('Y-m-d\TH:i:s\Z');
36 }
37 }
38
39 public function toArray(): array
40 {
41 return [
42 'id' => $this->id,
43 'title' => $this->title,
44 'description' => $this->description,
45 'start' => $this->start,
46 'end' => $this->end,
47 'allDay' => $this->allDay,
48 'color' => $this->color,
49 ];
50 }
51}
diff --git a/src/model/Menu.php b/src/model/Menu.php
deleted file mode 100644
index d277259..0000000
--- a/src/model/Menu.php
+++ /dev/null
@@ -1,42 +0,0 @@
1<?php
2// src/model/Menu.php
3
4declare(strict_types=1);
5
6use Doctrine\ORM\EntityManager;
7use App\Entity\Page;
8use Doctrine\Common\Collections\ArrayCollection;
9
10class Menu extends Page
11{
12 private array $other_pages = []; // pages n'apparaissant pas dans le menu
13
14 public function __construct(EntityManager $entityManager){
15 $this->children = new ArrayCollection();
16
17 $bulk_data = $entityManager
18 ->createQuery('SELECT n FROM App\Entity\Page n WHERE n.parent IS null') // :Doctrine\ORM\Query
19 ->getResult(); // :array de Page
20
21 foreach($bulk_data as $first_level_entries){
22 // dans le menu
23 if($first_level_entries->isInMenu()){
24 $this->addChild($first_level_entries);
25 }
26 // autres pages
27 else{
28 // attention, seul le premier élément du chemin est pris en compte
29 $this->other_pages[] = $first_level_entries;
30 }
31 }
32
33 foreach($this->getChildren() as $page){
34 $page->fillChildrenPagePath();
35 }
36 }
37
38 public function getOtherPages(): array
39 {
40 return $this->other_pages;
41 }
42} \ No newline at end of file
diff --git a/src/model/Model.php b/src/model/Model.php
deleted file mode 100644
index 15ff12a..0000000
--- a/src/model/Model.php
+++ /dev/null
@@ -1,312 +0,0 @@
1<?php
2// src/model/Model.php
3//
4// à l'occaz, faire des classes métiers: NodeModel = celle-ci
5// puis PageModel, puis éventuellement UserModel, EmailModel (le calendrier a déjà EventDTO)
6
7declare(strict_types=1);
8
9use Doctrine\ORM\EntityManager;
10use App\Entity\Page;
11use App\Entity\Node;
12use App\Entity\Presentation;
13use Doctrine\ORM\QueryBuilder;
14use Symfony\Component\HttpFoundation\Request;
15
16class Model
17{
18 private EntityManager $entityManager;
19 static public Menu $menu; // pour NavBuilder
20 static public ?Path $page_path = null; // pour $current dans NavBuilder et pour BreadcrumbBuilder
21 private Page $page;
22 private ?Node $node;
23 private Node $article;
24
25 public function __construct(EntityManager $entityManager)
26 {
27 $this->entityManager = $entityManager;
28 $this->node = new Node; // instance mère "vide" ne possédant rien d'autre que des enfants
29 }
30
31 // à déplacer dans Path ou un truc comme ça?
32 // couper Model en deux classes NodeModel et PageModel?
33 public function makeMenuAndPaths(): void // lit la table "page"
34 {
35 self::$menu = new Menu($this->entityManager);
36 self::$page_path = new Path();
37 $this->page = self::$page_path->getLast();
38 }
39
40 public function getNode(): Node
41 {
42 return $this->node;
43 }
44 public function getArticleNode(): Node
45 {
46 return $this->article;
47 }
48
49 // affichage d'une page ordinaire
50 public function getWholePageData(Request $request): void // lit la table "node" + jointures
51 {
52 $id = CURRENT_PAGE === 'article' ? htmlspecialchars($request->query->get('id')) : '';
53
54 if($id === ''){ // page "normale"
55 // récupérer tous les noeuds sauf les articles
56 $dql = "SELECT n FROM App\Entity\Node n WHERE n.name_node != 'new' AND n.name_node != 'post' AND (n.page = :page OR n.page IS null)";
57 $bulk_data = $this->entityManager
58 ->createQuery($dql)
59 ->setParameter('page', $this->page)
60 ->getResult();
61
62 foreach($bulk_data as $parent_block){
63 // groupes d'articles triés par bloc, permet de paginer par bloc
64 if(Presentation::hasPresentation($parent_block->getName())){ // = post_block ou news_block
65 $bulk_data = array_merge($bulk_data, $this->getNextArticles($parent_block, $request)[0]);
66 }
67
68 // emails
69 if($parent_block->getName() === 'show_emails'){ // show_emails = l'afficheur
70 $parent_block->getNodeData()->setEmails($this->getAllEmails());
71
72 // aller chercher les formulaires à la place (future MAJ?)
73 //$this->getFormsNodeData();
74 }
75 }
76 }
77 else{ // page "article"
78 $dql = 'SELECT n FROM App\Entity\Node n WHERE n.page = :page OR n.page IS null OR n.id_node = :id';
79 $bulk_data = $this->entityManager
80 ->createQuery($dql)
81 ->setParameter('page', $this->page)
82 ->setParameter('id', $id)
83 ->getResult();
84 }
85
86 $this->makeNodeTree($bulk_data);
87 }
88
89 // récupération d'articles
90 public function getNextArticles(Node $parent_block, Request $request): array
91 {
92 $qb = $this->entityManager->createQueryBuilder();
93 $qb->select('n')
94 ->from('App\Entity\Node', 'n')
95 ->where('n.parent = :parent')
96 ->setParameter('parent', $parent_block);
97
98 if($parent_block->getName() === 'post_block'){
99 $qb->orderBy('n.position');
100 }
101 elseif($parent_block->getName() === 'news_block'){
102 $qb->join('n.article', 'a');
103 if($parent_block->getNodeData()->getChronoOrder() ?? false){ // ordre antichrono par défaut
104 $qb->orderBy('a.date_time', 'ASC');
105 }
106 else{
107 $qb->orderBy('a.date_time', 'DESC');
108 }
109 }
110
111 // pagination
112 $limit = $parent_block->getNodeData()->getPaginationLimit(); // = 12 par défaut si = null en BDD
113 $this->paginateWithCursor($qb, $parent_block, $request->query->get('last_article'));
114 $result = $qb->getQuery()->getResult();
115
116 // il reste des articles à récupérer SI on vient d'en récupérer trop
117 // ET on gère le cas particulier de $limit <= 0
118 $truncated = false;
119 if(count($result) > $limit && $limit > 0){ // si nb résultat > limit > 0
120 $truncated = true;
121 array_pop($result); // compenser le $limit + 1 dans paginateWithCursor
122 }
123
124 return [$result, $truncated]; // besoin exceptionnel de retourner deux valeurs
125 }
126
127 private function paginateWithCursor(QueryBuilder $qb, Node $parent_block, ?string $last_article): void
128 {
129 //var_dump($last_article);
130 $limit = $parent_block->getNodeData()->getPaginationLimit(); // = 12 par défaut si = null en BDD
131
132 if($limit > 0){ // si 0 ou moins pas de pagination
133 // nombres de "pages" d'articles
134 $nb_pages = $this->getNumberOfPages($parent_block, $limit);
135 $parent_block->getNodeData()->setNumberOfPages($nb_pages > 1 ? $nb_pages : 1);
136
137 // adaptation de la requête
138 if($parent_block->getName() === 'post_block'){
139 $qb->andWhere('n.position > :last_position')
140 ->setParameter('last_position', empty($last_article) ? 0 : $last_article)
141 ->setMaxResults($limit + 1);
142 }
143 elseif($parent_block->getName() === 'news_block'){
144 $cursor_start = $parent_block->getNodeData()->getChronoOrder() ? '1970-01-01' : '9999-12-31';
145 $qb->andWhere($parent_block->getNodeData()->getChronoOrder() ? 'a.date_time > :date_time' : 'a.date_time < :date_time')
146 ->setParameter('date_time', empty($last_article) ? $cursor_start : $last_article)
147 ->setMaxResults($limit + 1);
148 }
149 }
150 }
151
152 // le Paginator de doctrine le fait aussi si on décidait de s'en servir
153 private function getNumberOfPages(Node $parent_block, int $limit): int
154 {
155 $dql = 'SELECT COUNT(n.id_node) FROM App\Entity\Node n WHERE n.parent = :parent';
156 $nb_articles = $this->entityManager
157 ->createQuery($dql)
158 ->setParameter('parent', $parent_block)
159 ->getSingleScalarResult();
160 return (int)ceil($nb_articles / $limit); // que PHP fasse une division non euclidienne (pas comme en C) nous arrange ici
161 }
162
163 private function makeNodeTree(array $bulk_data): void // $bulk_data = tableau de Node
164 {
165 // puis on les range
166 // (attention, risque de disfonctionnement si les noeuds de 1er niveau ne sont pas récupérés en 1er dans la BDD)
167 foreach($bulk_data as $node){
168 // premier niveau
169 if($node->getParent() == null){
170 $this->node->addChild($node);
171
172 // spécifique page article
173 if($node->getName() === 'main' && $this->page->getEndOfPath() == 'article'){
174 $main = $node;
175 }
176 }
177 // autres niveaux
178 else{
179 $node->getParent()->addChild($node);
180
181 // spécifique page article
182 if($this->page->getEndOfPath() == 'article'){
183 if($node->getName() === 'new'){
184 $new = $node;
185 }
186 }
187 }
188 }
189 if(isset($new)){
190 $main->setAdoptedChild($new);
191 }
192 }
193
194 // le basique
195 public function findNodeById(int $id): bool
196 {
197 $this->node = $this->entityManager->find('App\Entity\Node', $id);
198 return $this->node === null ? false : true;
199 }
200 public function findWhateverNode(string $field, string $value): bool
201 {
202 $queryBuilder = $this->entityManager->createQueryBuilder();
203 $queryBuilder
204 ->select('n')
205 ->from('App\Entity\Node', 'n')
206 ->where("n.$field = :value") // avec le querybuilder, ce truc sale reste sécurisé
207 ->setParameter('value', $value);
208 $result = $queryBuilder->getQuery()->getOneOrNullResult();
209
210 if($result === null){
211 return false;
212 }
213 else{
214 $this->node = $result;
215 return true;
216 }
217 }
218 public function getWhatever(string $class, string $field, string $value): array
219 {
220 // penser au entityManager "repository"
221 $queryBuilder = $this->entityManager->createQueryBuilder();
222 $queryBuilder
223 ->select('n')
224 ->from($class, 'n')
225 ->where("n.$field = :value")
226 ->setParameter('value', $value);
227 return $queryBuilder->getQuery()->getResult();
228 }
229
230 // récupération d'un article pour modification
231 public function makeArticleNode(string $id = '', bool $get_section = false): bool
232 {
233 if($get_section){
234 $dql = 'SELECT n, p FROM App\Entity\Node n LEFT JOIN n.parent p WHERE n.id_node = :id';
235 }
236 else{
237 $dql = 'SELECT n FROM App\Entity\Node n WHERE n.id_node = :id';
238 }
239 // n est l'article et p son $parent
240 $bulk_data = $this->entityManager
241 ->createQuery($dql)
242 ->setParameter('id', $id)
243 ->getResult();
244
245 if(count($bulk_data) === 0){
246 return false;
247 }
248
249 if($get_section){
250 $this->article = $bulk_data[0];
251 $this->findNodeById($bulk_data[0]->getParent()->getId());
252 $this->makeSectionNode();
253 }
254 else{
255 $this->article = $bulk_data[0];
256 }
257
258 return true;
259 }
260
261 // récupération des articles d'un bloc <section> à la création d'un article
262 public function makeSectionNode(): bool
263 {
264 $bulk_data = $this->entityManager
265 ->createQuery('SELECT n FROM App\Entity\Node n WHERE n.parent = :parent')
266 ->setParameter('parent', $this->node)
267 ->getResult();
268
269 foreach($bulk_data as $article){
270 $this->node->addChild($article); // pas de flush, on ne va pas écrire dans la BDD à chaque nouvelle page
271 }
272 return true;
273 }
274
275 public function findUniqueNodeByName(string $name): void // = unique en BDD, donc sans "page" associée
276 {
277 $bulk_data = $this->entityManager
278 ->createQuery('SELECT n FROM App\Entity\Node n WHERE n.name_node = :name')
279 ->setParameter('name', $name)
280 ->getResult();
281 $this->node = $bulk_data[0];
282 }
283
284 public function findItsChildren(): void
285 {
286 $bulk_data = $this->entityManager
287 ->createQuery('SELECT n FROM App\Entity\Node n WHERE n.parent = :parent AND n.page = :page')
288 ->setParameter('parent', $this->node)
289 ->setParameter('page', $this->page)
290 ->getResult();
291 foreach($bulk_data as $child){
292 $this->node->addChild($child);
293 }
294 }
295
296 private function getAllEmails(): array
297 {
298 $dql = 'SELECT e FROM App\Entity\Email e';
299 return $this->entityManager
300 ->createQuery($dql)
301 ->getResult();
302 }
303 /*private function getFormsNodeData(): array
304 {
305 $dql = 'SELECT fd FROM App\Entity\NodeData fd WHERE node.name_node = form';
306 return $this->entityManager
307 ->createQuery($dql)
308 //->setParameter('page', $this->page)
309 ->getResult();
310 }*/
311 //private function getEmailsBySender(string $sender): array
312}
diff --git a/src/model/Path.php b/src/model/Path.php
deleted file mode 100644
index 4303952..0000000
--- a/src/model/Path.php
+++ /dev/null
@@ -1,76 +0,0 @@
1<?php
2// src/model/Path.php
3//
4// attention, cette classe dépend de src/model/Menu.php
5
6declare(strict_types=1);
7
8use Doctrine\ORM\EntityManager;
9use App\Entity\Page;
10
11class Path extends Page
12{
13 private array $current_page = []; // tableau d'objets Page
14
15 public function __construct()
16 {
17 $path_array = explode('/', CURRENT_PAGE);
18 try{
19 // parcourir le menu de haut en bas pour obtenir un ou plusieurs objets Page
20 $this->findPage(Model::$menu, $path_array); // remplit $this->current_page
21 }
22 catch(Exception $e){}
23 }
24
25 // produit un tableau de Page en comparant le chemin demandé avec les données dans Menu
26 // succès => une exception est lancée pour sortir des fonctions imbriquées
27 // echec => redirection vers la page erreur 404
28 private function findPage(Page|Menu $menu, array $path_array): void
29 {
30 // recherche dans les autres pages
31 if($menu instanceof Menu){
32 foreach($menu->getOtherPages() as $page){
33 if($path_array[0] === $page->getEndOfPath()){
34 $this->current_page[] = $page;
35 throw new Exception();
36 }
37 }
38 }
39 // recherche dans le menu
40 foreach($menu->getChildren() as $page){
41 if($path_array[0] === $page->getEndOfPath()){
42 $this->current_page[] = $page;
43 if(count($path_array) > 1){
44 array_shift($path_array); // $this->path_array n'est pas modifié, un tableau PHP est passé à une fonction par copie
45 $this->findPage($page, $path_array);
46 }
47 else{
48 throw new Exception(); // sortir de tous les findPage() en même temps
49 }
50 }
51 }
52
53 // rien trouvé
54 header('Location: '. new URL(['page' => Model::$menu->getChildren()[0]->getEndOfPath()]));
55 die;
56 }
57
58 public function getString(): string
59 {
60 $path_string = "";
61 foreach($this->current_page as $one_page){
62 $path_string .= $one_page->getEndOfPath() . '/';
63 }
64 return rtrim($path_string, '/');
65 }
66 public function getArray(): array
67 {
68 return $this->current_page;
69 }
70
71 // c'est là qu'on est quoi
72 public function getLast(): Page
73 {
74 return $this->current_page[count($this->current_page) - 1];
75 }
76} \ No newline at end of file
diff --git a/src/model/Position.php b/src/model/Position.php
deleted file mode 100644
index fdba271..0000000
--- a/src/model/Position.php
+++ /dev/null
@@ -1,50 +0,0 @@
1<?php
2// src/modele/Position.php
3//
4// pour Node et Page
5
6declare(strict_types=1);
7
8trait Position
9{
10 // tri par insertion du tableau des enfants
11 public function sortChildren(bool $reindexation = false): void
12 {
13 for($i = 1; $i < count($this->children); $i++)
14 {
15 $tmp = $this->children[$i];
16 $j = $i - 1;
17
18 // Déplacez les éléments du tableau qui sont plus grands que la clé à une position devant leur position actuelle
19 while($j >= 0 && $this->children[$j]->getPosition() > $tmp->getPosition()) {
20 $this->children[$j + 1] = $this->children[$j];
21 $j--;
22 }
23 $this->children[$j + 1] = $tmp;
24 }
25
26 // récursivité (utile dans Menu et chemin)
27 // appel dans Page et dans MenuAndPathController (un tri supplémentaire à faire?)
28 if(self::class === 'App\Entity\Page'){
29 foreach($this->children as $child) {
30 if(count($child->children) > 0) {
31 $child->sortChildren($reindexation);
32 }
33 }
34 }
35
36 if($reindexation){
37 $this->reindexPositions();
38 }
39 }
40
41 // nouvelles positions (tableau $children => BDD)
42 public function reindexPositions(): void
43 {
44 $i = 1;
45 foreach($this->children as $child){
46 $child->setPosition($i);
47 $i++;
48 }
49 }
50} \ No newline at end of file
diff --git a/src/model/doctrine-bootstrap.php b/src/model/doctrine-bootstrap.php
deleted file mode 100644
index 127f72f..0000000
--- a/src/model/doctrine-bootstrap.php
+++ /dev/null
@@ -1,33 +0,0 @@
1<?php
2// src/model/doctrine-bootstrap.php
3
4declare(strict_types=1);
5
6use Doctrine\ORM\ORMSetup;
7use Doctrine\DBAL\DriverManager;
8use Doctrine\ORM\EntityManager;
9
10require_once "../vendor/autoload.php";
11
12// Create a simple "default" Doctrine ORM configuration for Attributes
13$config = ORMSetup::createAttributeMetadataConfiguration(
14 //paths: array(__DIR__.'/entities'),
15 paths: array('../src/model/entities'),
16 isDevMode: true,
17 // true: cache en mémoire vive
18 // false: utilisation de APCu ou redis ou memcache
19);
20
21// configuring the database connection
22$connection = DriverManager::getConnection([
23 'driver' => Config::$db_driver,
24 'user' => Config::$user,
25 'password' => Config::$password,
26 'host' => Config::$db_host,
27 'dbname' => Config::$database,
28], $config);
29
30// obtaining the entity manager
31$entityManager = new EntityManager($connection, $config);
32
33foreach($entityManager->getMetadataFactory()->getAllMetadata() as $class){} \ No newline at end of file
diff --git a/src/model/entities/AppMetadata.php b/src/model/entities/AppMetadata.php
deleted file mode 100644
index ae42ad9..0000000
--- a/src/model/entities/AppMetadata.php
+++ /dev/null
@@ -1,46 +0,0 @@
1<?php
2// src/model/entities/AppMetadata.php
3
4// comme dans AppMode, prévoir d'ajouter des champs "since" et "by" (qui a changé quoi quel jour?)
5
6declare(strict_types=1);
7
8namespace App\Entity;
9
10use Doctrine\ORM\Mapping as ORM;
11
12#[ORM\Entity]
13#[ORM\Table(name: TABLE_PREFIX . "app_metadata")]
14class AppMetadata
15{
16 #[ORM\Id]
17 #[ORM\Column(type: "string", length: 100)]
18 private string $key_name;
19
20 #[ORM\Column(type: "string")]
21 private string $value;
22
23 public function __construct(string $key, string $value)
24 {
25 $this->key_name = $key;
26 $this->value = $value;
27 }
28
29 public function getKey(): string
30 {
31 return $this->key_name;
32 }
33 public function getValue(): string
34 {
35 return $this->value;
36 }
37
38 public function setKey(string $key): void
39 {
40 $this->key_name = $key;
41 }
42 public function setValue(string $value): void
43 {
44 $this->value = $value;
45 }
46} \ No newline at end of file
diff --git a/src/model/entities/Article.php b/src/model/entities/Article.php
deleted file mode 100644
index b9cb4bb..0000000
--- a/src/model/entities/Article.php
+++ /dev/null
@@ -1,94 +0,0 @@
1<?php
2// src/model/entities/Article.php
3//
4// entité commune pour les "post" (articles simples) et les "news" (complexes avec titre, aperçu et contenu)
5
6declare(strict_types=1);
7
8namespace App\Entity;
9
10use Doctrine\ORM\Mapping as ORM;
11use Doctrine\Common\Collections\ArrayCollection;
12use Doctrine\Common\Collections\Collection;
13
14#[ORM\Entity]
15#[ORM\Table(name: TABLE_PREFIX . "article")]
16class Article
17{
18 #[ORM\Id]
19 #[ORM\GeneratedValue]
20 #[ORM\Column(type: "integer")]
21 private int $id_article;
22
23 // datetime_immutable permet à la base de toujours gérer cette clé primaire correctement
24 #[ORM\Column(type: 'datetime', options: ['default' => 'CURRENT_TIMESTAMP'], unique: true)]
25 private ?\DateTime $date_time; // le type datetime de doctrine convertit en type \DateTime de PHP
26
27 #[ORM\Column(type: "text")] // "string" => varchar(255) => risque HTML non valide
28 private string $title;
29
30 #[ORM\Column(type: "text")]
31 private string $preview; // une simple textarea
32
33 #[ORM\Column(type: "text")]
34 private string $content; // de l'éditeur html
35
36 #[ORM\ManyToMany(targetEntity: Image::class, inversedBy: "article")] // cascade: ['remove'] = très dangereux!
37 #[ORM\JoinTable(
38 name: TABLE_PREFIX . "article_image",
39 joinColumns: [new ORM\JoinColumn(name: "article_id", referencedColumnName: "id_article", onDelete: "CASCADE")], // onDelete: "CASCADE": très utile
40 inverseJoinColumns: [new ORM\JoinColumn(name: "image_id", referencedColumnName: "id_image", onDelete: "CASCADE")]
41 )]
42 private Collection $images;
43
44 public function __construct(string $content, \DateTime $date_time = null, string $title = '', string $preview = '')
45 {
46 $this->date_time = $date_time;
47 $this->title = $title;
48 $this->preview = $preview;
49 $this->content = $content;
50 $this->images = new ArrayCollection(); // initialisation nécessaire
51 }
52
53 public function getDateTime(): \DateTime
54 {
55 return $this->date_time;
56 }
57 public function setDateTime(\DateTime $date_time): void
58 {
59 $this->date_time = $date_time;
60 }
61 public function getTimestamp(): int
62 {
63 return $this->date_time->getTimestamp();
64 }
65 public function getTitle(): string
66 {
67 return $this->title;
68 }
69 public function setTitle(string $title): void
70 {
71 $this->title = $title;
72 }
73 public function getPreview(): string
74 {
75 return $this->preview;
76 }
77 public function setPreview(string $preview): void
78 {
79 $this->preview = $preview;
80 }
81 public function getContent(): string
82 {
83 return $this->content;
84 }
85 public function setContent(string $data): void
86 {
87 $this->content = $data;
88 }
89
90 public function getImages(): Collection
91 {
92 return $this->images;
93 }
94}
diff --git a/src/model/entities/Asset.php b/src/model/entities/Asset.php
deleted file mode 100644
index d76ba08..0000000
--- a/src/model/entities/Asset.php
+++ /dev/null
@@ -1,68 +0,0 @@
1<?php
2// src/model/entities/Asset.php
3
4declare(strict_types=1);
5
6namespace App\Entity;
7
8use Doctrine\ORM\Mapping as ORM;
9use Doctrine\Common\Collections\Collection;
10
11#[ORM\Entity]
12#[ORM\Table(name: TABLE_PREFIX . "asset")]
13class Asset
14{
15 const PATH = 'assets/';
16 const USER_PATH = 'user_data/assets/';
17 // choisir un répertoire du genre /var/www/html/uploads/? ou au moins hors de /src? j'en sais rien
18
19 #[ORM\Id]
20 #[ORM\GeneratedValue]
21 #[ORM\Column(type: "integer")]
22 private int $id_asset;
23
24 #[ORM\Column(type: "string", length: 255)] // nom de fichier modifié avec uniqid (fichiers différents de même nom)
25 private string $file_name;
26
27 #[ORM\Column(type: "string", length: 255, nullable: true)]
28 private string $mime_type; // image/jpeg, image/png, etc
29
30 #[ORM\Column(type: "string", length: 64, unique: true)] // doctrine n'a pas d'équivalent au type CHAR des BDD (on voulait CHAR(64)), c'est pas grave!
31 private string $hash; // pour détecter deux fichiers identiques, même si leurs noms et les métadonnées changent
32
33 #[ORM\OneToMany(mappedBy: 'asset', targetEntity: AssetEmployment::class)]
34 private Collection $asset_employment;
35
36 public function __construct(string $name, string $mime_type, string $hash)
37 {
38 $this->file_name = $name;
39 $this->mime_type = $mime_type;
40 $this->hash = $hash;
41 }
42
43 public function getFileName(): string
44 {
45 return $this->file_name;
46 }
47 public function setFileName(string $name): void
48 {
49 $this->file_name = $name;
50 }
51 public function getMimeType(): string
52 {
53 return $this->mime_type;
54 }
55 public function setMimeType(string $mime_type): void
56 {
57 $this->mime_type = $mime_type;
58 }
59 public function getHash(): string
60 {
61 return $this->hash;
62 }
63
64 public function getAssetEmployments(): Collection
65 {
66 return $this->asset_employment;
67 }
68}
diff --git a/src/model/entities/AssetEmployment.php b/src/model/entities/AssetEmployment.php
deleted file mode 100644
index 04bbd9a..0000000
--- a/src/model/entities/AssetEmployment.php
+++ /dev/null
@@ -1,54 +0,0 @@
1<?php
2// src/model/entities/NodeDataAsset.php
3//
4// entité intermédiaire 3 colonnes conçue selon le principe "slot <=> ressource unique" (paires node_data/role uniques)
5// doctrine gère mal les clés primaires triples, j'ai donc choisi une clé primaire double node_data_id/role
6
7declare(strict_types=1);
8
9namespace App\Entity;
10
11use Doctrine\ORM\Mapping as ORM;
12
13#[ORM\Entity]
14#[ORM\Table(name: TABLE_PREFIX . 'asset_employment')]
15class AssetEmployment
16{
17 // clé primaire double
18 #[ORM\Id]
19 #[ORM\ManyToOne(targetEntity: NodeData::class, inversedBy: 'asset_employment')]
20 #[ORM\JoinColumn(name: 'node_data_id', referencedColumnName: 'id_node_data', onDelete: 'CASCADE')]
21 private NodeData $node_data;
22
23 #[ORM\Id]
24 #[ORM\Column(type: 'string', length: 50)]
25 private string $role;
26
27 #[ORM\ManyToOne(targetEntity: Asset::class, inversedBy: 'asset_employment')]
28 #[ORM\JoinColumn(name: 'asset_id', referencedColumnName: 'id_asset', onDelete: 'CASCADE')]
29 private Asset $asset;
30
31 public function __construct(NodeData $node_data, Asset $asset, string $role){
32 $this->node_data = $node_data;
33 $this->asset = $asset;
34 $this->role = $role;
35 }
36
37 /*public function getNodeData(): NodeData
38 {
39 return $this->node_data;
40 }*/
41 public function getAsset(): Asset
42 {
43 return $this->asset;
44 }
45 public function setAsset(Asset $asset): self
46 {
47 $this->asset = $asset;
48 return $this;
49 }
50 public function getRole(): string
51 {
52 return $this->role;
53 }
54}
diff --git a/src/model/entities/Email.php b/src/model/entities/Email.php
deleted file mode 100644
index 60fcc1f..0000000
--- a/src/model/entities/Email.php
+++ /dev/null
@@ -1,131 +0,0 @@
1<?php
2// src/model/entities/Email.php
3
4declare(strict_types=1);
5
6namespace App\Entity;
7
8use Doctrine\ORM\Mapping as ORM;
9
10#[ORM\Entity]
11#[ORM\Table(name: TABLE_PREFIX . "email")]
12class Email
13{
14 // en mois
15 const DEFAULT_RETENTION_PERIOD = 36; // 3 ans, justification = prospection, durée "glissante", date de suppression remise à jour à chaque nouvel e-mail
16 const DEFAULT_RETENTION_PERIOD_SENSITIVE = 60; // 5 ans pour données sensibles ou litige, durée de preuve légale, durée non glissante
17
18 #[ORM\Id]
19 #[ORM\GeneratedValue]
20 #[ORM\Column(type: "integer")]
21 private int $id_email;
22
23 #[ORM\Column(type: "string", length: 255)]
24 private string $sender_name;
25
26 #[ORM\Column(type: "string", length: 320)]
27 private string $sender_address;
28
29 #[ORM\Column(type: "string", length: 320)]
30 private string $recipient;
31
32 // inutile, objet = 'Message envoyé par ' . $name . ' (' . $email . ') depuis le site web'
33 /*#[ORM\Column(type: "text")]
34 private string $subject;*/
35
36 #[ORM\Column(type: "text")]
37 private string $content;
38
39 #[ORM\Column(type: 'datetime', options: ['default' => 'CURRENT_TIMESTAMP'])]
40 private \DateTime $date_time;
41
42 #[ORM\Column(type: 'boolean')]
43 private bool $is_sensitive; // "sensitive" tout court est un mot réservé
44
45 #[ORM\Column(type: 'datetime', options: ['default' => 'CURRENT_TIMESTAMP'])]
46 private \DateTime $last_contact_date;
47
48 #[ORM\Column(type: 'datetime', nullable: true)]
49 private ?\DateTime $is_sensitive_since;
50
51 #[ORM\ManyToOne(targetEntity: EmailForm::class)]
52 #[ORM\JoinColumn(name: "email_form_id", referencedColumnName: "id_email_form", nullable: true)]
53 private ?EmailForm $email_form;
54
55 public function __construct(string $sender_name, string $sender_address, string $recipient, string $content, EmailForm $email_form, bool $sensitive = false){
56 $this->sender_name = strtolower($sender_name);
57 $this->sender_address = strtolower($sender_address);
58 $this->recipient = strtolower($recipient);
59 $this->content = $content;
60 $this->date_time = new \DateTime;
61 $this->last_contact_date = new \DateTime;
62 $this->email_form = $email_form;
63 $this->makeSensitive($sensitive);
64 }
65
66 public function getId(): int
67 {
68 return $this->id_email;
69 }
70 public function getSenderName(): string
71 {
72 return $this->sender_name;
73 }
74 public function getSenderAddress(): string
75 {
76 return $this->sender_address;
77 }
78 public function getRecipient(): string
79 {
80 return $this->recipient;
81 }
82 public function getContent(): string
83 {
84 return $this->content;
85 }
86 public function getDateTime(): \DateTime
87 {
88 return $this->date_time;
89 }
90 /*public function getLastContactDate(): \DateTime
91 {
92 return $this->last_contact_date;
93 }*/
94 public function isSensitive(): bool
95 {
96 return $this->is_sensitive;
97 }
98 public function isSensitiveSince(): ?\DateTime
99 {
100 return $this->is_sensitive_since;
101 }
102
103 public function makeSensitive(bool $sensitive = true): void
104 {
105 $this->is_sensitive = $sensitive;
106 if($sensitive && $this->is_sensitive_since === null){
107 $this->is_sensitive_since = new \DateTime();
108 }
109 }
110
111 public function updateLastContactDate(): void
112 {
113 $this->last_contact_date = new \DateTime;
114 }
115
116 public function getDeletionDate(): \DateTime // utilise une durée de conservation $period qui est propre au bloc formulaire (à son EmailForm)
117 {
118 // tests appliqués:
119 // => e-mail associé à un formulaire?
120 // => ce formulaire dispose d'une durée de stockage spécifique?
121 // => cette donnée est un entier > 0
122 $key = $this->is_sensitive ? 'retention_period_sensible' : 'retention_period';
123 $period = $this->email_form ? (int)($this->email_form->getData()[$key] ?? null) : null;
124
125 $default = $this->is_sensitive ? self::DEFAULT_RETENTION_PERIOD_SENSITIVE : self::DEFAULT_RETENTION_PERIOD;
126 $period = ($period === null || $period <= 0) ? $default : $period;
127
128 $date = clone ($this->is_sensitive ? $this->is_sensitive_since : $this->last_contact_date); // erreur si "sensible" mais sans date disponible (pas censé arriver)
129 return $date->modify('+ ' . (string)$period . ' month');
130 }
131} \ No newline at end of file
diff --git a/src/model/entities/EmailForm.php b/src/model/entities/EmailForm.php
deleted file mode 100644
index 0dcdeec..0000000
--- a/src/model/entities/EmailForm.php
+++ /dev/null
@@ -1,57 +0,0 @@
1<?php
2// src/model/entities/EmailForm.php
3
4declare(strict_types=1);
5
6namespace App\Entity;
7
8use Doctrine\ORM\Mapping as ORM;
9
10#[ORM\Entity]
11#[ORM\Table(name: TABLE_PREFIX . "email_form")]
12class EmailForm{
13 #[ORM\Id]
14 #[ORM\GeneratedValue]
15 #[ORM\Column(type: "integer")]
16 private int $id_email_form;
17
18 // inverseBy fait le lien avec $email_form dans Node (qui a "mappedBy")
19 #[ORM\OneToOne(targetEntity: Node::class, inversedBy: "email_form")]
20 #[ORM\JoinColumn(name: "node_id", referencedColumnName: "id_node")]
21 private ?Node $node;
22
23 #[ORM\Column(type: "json")]
24 private array $data;
25
26 public function __construct(array $data, Node $node){
27 $this->data = $data;
28 $this->node = $node;
29 }
30
31 public function getId(): int
32 {
33 return $this->id_email_form;
34 }
35
36 // getData et updateData sont indentiques au code dans NodeData
37 // plutôt qu'une interface, pourquoi pas une classe abstraite? ou peut-être un trait?
38 public function getData(): array
39 {
40 return $this->data;
41 }
42 public function updateData(string $key, string|int|bool|array $value = ''): void
43 {
44 if($value !== ''){
45 $this->data[$key] = $value;
46 }
47 // si $value est vide, supprime la clé
48 elseif(isset($this->data[$key])){
49 unset($this->data[$key]);
50 }
51 }
52
53 public function setNode(?Node $node): void
54 {
55 $this->node = $node;
56 }
57} \ No newline at end of file
diff --git a/src/model/entities/Event.php b/src/model/entities/Event.php
deleted file mode 100644
index ae0d396..0000000
--- a/src/model/entities/Event.php
+++ /dev/null
@@ -1,129 +0,0 @@
1<?php
2// src/model/entities/Event.php
3
4declare(strict_types=1);
5
6namespace App\Entity;
7
8use Doctrine\ORM\Mapping as ORM;
9
10#[ORM\Entity]
11#[ORM\Table(name: TABLE_PREFIX . 'event')]
12class Event
13{
14 #[ORM\Id]
15 #[ORM\GeneratedValue]
16 #[ORM\Column(type: 'integer')]
17 private int $id;
18
19 #[ORM\Column(type: 'string', length: 255)] // type varchar(255)
20 private string $title;
21 // contrôle JS: if(title.length > 255)
22
23 #[ORM\Column(type: 'text')]
24 private string $description = '';
25 // chatgpt: Dans un contexte API/Front comme FullCalendar,
26 // préférer une chaîne vide à une varaible "null" peut être plus pratique,
27 // car ça évite des contrôles côté JS.
28
29 #[ORM\Column(type: 'datetime')] // chatgpt: Doctrine suppose UTC si pas de configuration spécifique
30 private \DateTimeInterface $start; // typage possible avec une interface,
31 //chatgpt: choix \DateTime par défaut, autorise \DateTimeImmutable
32
33 #[ORM\Column(type: 'datetime')]
34 private \DateTimeInterface $end;
35
36 #[ORM\Column(type: 'boolean')]
37 private bool $all_day;
38
39 #[ORM\Column(type: 'string', length: 7, nullable: true)]
40 private ?string $color = null;
41
42 public function __construct(array $json){
43 $this->securedUpdateFromJSON($json);
44 }
45
46 public function securedUpdateFromJSON(array $json): void
47 {
48 $this->title = htmlspecialchars($json['title']);
49 $this->description = htmlspecialchars($json['description']);
50 try{
51 $this->start = new \Datetime($json['start']);
52 $this->end = new \Datetime($json['end']);
53 }
54 catch(\Exception $e){
55 throw new \InvalidArgumentException('Bad date input');
56 }
57 $all_day = filter_var($json['allDay'] ?? null, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
58 if(!is_bool($all_day)){
59 throw new \InvalidArgumentException('Bad checkbox input');
60 }
61 $this->all_day = $all_day;
62 $this->color = isset($json['color']) ? htmlspecialchars($json['color']) : null;
63 }
64
65 public function getId(): int
66 {
67 return $this->id;
68 }
69
70 public function getTitle(): string
71 {
72 return $this->title;
73 }
74 /*public function setTitle(string $title): self
75 {
76 $this->title = $title;
77 return $this;
78 }*/
79
80 public function getDescription(): string
81 {
82 return $this->description;
83 }
84 /*public function setDescription(string $description = ''): self
85 {
86 $this->description = $description;
87 return $this;
88 }*/
89
90 public function getStart(): \DateTimeInterface
91 {
92 return $this->start;
93 }
94 /*public function setStart(\DateTimeInterface $start): self
95 {
96 $this->start = $start;
97 return $this;
98 }*/
99
100 public function getEnd(): \DateTimeInterface
101 {
102 return $this->end;
103 }
104 /*public function setEnd(\DateTimeInterface $end): self
105 {
106 $this->end = $end;
107 return $this;
108 }*/
109
110 public function isAllDay(): bool
111 {
112 return $this->all_day;
113 }
114 /*public function setAllDay(bool $all_day): self
115 {
116 $this->all_day = $all_day;
117 return $this;
118 }*/
119
120 public function getColor(): ?string
121 {
122 return $this->color;
123 }
124 /*public function setColor(?string $color): self
125 {
126 $this->color = $color;
127 return $this;
128 }*/
129}
diff --git a/src/model/entities/Image.php b/src/model/entities/Image.php
deleted file mode 100644
index 2ee379a..0000000
--- a/src/model/entities/Image.php
+++ /dev/null
@@ -1,89 +0,0 @@
1<?php
2// src/model/entities/Image.php
3
4declare(strict_types=1);
5
6namespace App\Entity;
7
8use Doctrine\Common\Collections\Collection;
9use Doctrine\ORM\Mapping as ORM;
10
11#[ORM\Entity]
12#[ORM\Table(name: TABLE_PREFIX . "image")]
13class Image
14{
15 #[ORM\Id]
16 #[ORM\GeneratedValue]
17 #[ORM\Column(type: "integer")]
18 private int $id_image;
19
20 #[ORM\Column(type: "string", length: 255, unique: true)] // nom d'image UNIQUE
21 private string $file_name;
22
23 // choisir un répertoire du genre /var/www/html/uploads/ de préférence hors de /src
24 #[ORM\Column(type: "string", length: 255, unique: true, nullable: true)]
25 private ?string $file_path;
26
27 #[ORM\Column(type: "string", length: 255, unique: true, nullable: true)]
28 private ?string $file_path_mini;
29
30 #[ORM\Column(type: "string", length: 255, nullable: true)]
31 private string $mime_type; // image/jpeg, image/png, etc
32
33 #[ORM\Column(type: "string", length: 255, nullable: true)]
34 private string $alt; // texte alternatif
35
36 // autre champs optionnels: file_size, date (default current timestamp)
37
38 /* étapes au téléchargement:
39 => Validation du type de fichier : On vérifie que le fichier est bien une image en utilisant le type MIME. On peut aussi vérifier la taille du fichier.
40 => Création d'un répertoire structuré : On génère un chemin dynamique basé sur la date (uploads/2024/12/24/) pour organiser les images.
41 => Génération d'un nom de fichier unique : On utilise uniqid() pour générer un nom unique et éviter les conflits de nom.
42 => Déplacement du fichier sur le serveur : Le fichier est déplacé depuis son emplacement temporaire vers le répertoire uploads/.
43 => Enregistrement dans la base de données : On enregistre les informations de l'image dans la base de données. */
44
45 #[ORM\ManyToMany(targetEntity: Article::class, mappedBy: "images")]
46 private Collection $article;
47
48 public function __construct(string $name, ?string $path, ?string $path_mini, string $mime_type, string $alt)
49 {
50 $this->file_name = $name;
51 $this->file_path = $path;
52 $this->file_path_mini = $path_mini;
53 $this->mime_type = $mime_type;
54 $this->alt = $alt;
55 }
56
57 public function getFileName(): string
58 {
59 return $this->file_name;
60 }
61 public function getFilePath(): string
62 {
63 return $this->file_path;
64 }
65 public function getFilePathMini(): string
66 {
67 return $this->file_path_mini;
68 }
69 public function getAlt(): string
70 {
71 return $this->alt;
72 }
73
74
75 // pour ViewBuilderController?
76 /*public function displayImage($imageId): void
77 {
78 //$imageId = 1; // Exemple d'ID d'image
79 $stmt = $pdo->prepare("SELECT file_path FROM images WHERE id = ?");
80 $stmt->execute([$imageId]);
81 $image = $stmt->fetch();
82
83 if ($image) {
84 echo "<img src='" . $image['file_path'] . "' alt='Image'>";
85 } else {
86 echo "Image non trouvée.";
87 }
88 }*/
89}
diff --git a/src/model/entities/Log.php b/src/model/entities/Log.php
deleted file mode 100644
index 222b8db..0000000
--- a/src/model/entities/Log.php
+++ /dev/null
@@ -1,38 +0,0 @@
1<?php
2// src/model/entities/Log.php
3
4declare(strict_types=1);
5
6namespace App\Entity;
7
8use Doctrine\ORM\Mapping as ORM;
9
10#[ORM\Entity]
11#[ORM\Table(name: TABLE_PREFIX . "log")]
12class Log
13{
14 #[ORM\Id]
15 #[ORM\GeneratedValue]
16 #[ORM\Column(type: "integer")]
17 private int $id_log;
18
19 #[ORM\Column(type: 'datetime', options: ['default' => 'CURRENT_TIMESTAMP'])] // CURRENT_TIMESTAMP "inutile", date générée par PHP
20 private \DateTime $date_time; // type datetime de doctrine <=> type \DateTime de PHP
21
22 #[ORM\Column(type: "boolean")]
23 private bool $success;
24
25 public function __construct(bool $success){
26 $this->date_time = new \DateTime;
27 $this->success = $success;
28 }
29
30 public function getFormatedDate(): string
31 {
32 return $this->date_time->format('d/m/Y à H\hi');
33 }
34 public function getSuccess(): bool
35 {
36 return $this->success;
37 }
38}
diff --git a/src/model/entities/Node.php b/src/model/entities/Node.php
deleted file mode 100644
index 7932efc..0000000
--- a/src/model/entities/Node.php
+++ /dev/null
@@ -1,166 +0,0 @@
1<?php
2// src/model/entities/Node.php
3
4declare(strict_types=1);
5
6namespace App\Entity;
7
8use Doctrine\ORM\Mapping as ORM;
9
10#[ORM\Entity]
11#[ORM\Table(name: TABLE_PREFIX . "node")]
12class Node
13{
14 use \Position;
15
16 #[ORM\Id]
17 #[ORM\GeneratedValue]
18 #[ORM\Column(type: "integer")]
19 private ?int $id_node = null;
20
21 #[ORM\Column(type: "string", length: 255)]
22 private string $name_node;
23
24 #[ORM\Column(type: "integer")]
25 private int $position;
26
27 #[ORM\ManyToOne(targetEntity: self::class)]
28 //#[ORM\ManyToOne(targetEntity: self::class, fetch: 'EAGER')] // fetch: 'eager' désactive le mode fainéant de doctrine
29 #[ORM\JoinColumn(name: "parent_id", referencedColumnName: "id_node", onDelete: "SET NULL", nullable: true)]
30 private ?self $parent = null;
31
32 // un onDelete: "CASCADE" serait logique ici mais dangereux, on pourrait aussi faire en sorte que les noeuds soient récupérables si la page est brutalement supprimée
33 #[ORM\ManyToOne(targetEntity: Page::class)]
34 #[ORM\JoinColumn(name: "page_id", referencedColumnName: "id_page", nullable: true)]
35 private ?Page $page;
36
37 #[ORM\ManyToOne(targetEntity: Article::class, cascade: ['persist'])]
38 #[ORM\JoinColumn(name: "article_id", referencedColumnName: "id_article", onDelete: "CASCADE", nullable: true)] // supprimer le contenu d'un article supprime les noeuds associés, faux dans l'autre sens
39 private ?Article $article = null;
40
41 // propriété non mappée dans la table "node", la jointure est décrite dans NodeData
42 // elle sert à persister ou supprimer des données par cascade
43 // "mappedBy" permet de cibler $node dans l'autre classe, qui elle possède un "inversedBy"
44 #[ORM\OneToOne(targetEntity: NodeData::class, mappedBy: "node", cascade: ['persist', 'remove'])]
45 private ?NodeData $node_data = null;
46
47 #[ORM\OneToOne(targetEntity: EmailForm::class, mappedBy: "node", cascade: ['persist'])] // pas de remove, les e-mails sont associés au EmailForm
48 private ?EmailForm $email_form = null;
49
50 #[ORM\OneToOne(targetEntity: Presentation::class, mappedBy: "node", cascade: ['persist', 'remove'])]
51 private ?Presentation $presentation = null;
52
53 // attributs non destinés à doctrine
54 private array $children = []; // tableau de Node
55 private ?self $adopted = null; // = "new" est un enfant de "main" lorsque la page est "article"
56
57 public function __construct(string $name = '', int $position = 0, ?self $parent = null, ?Page $page = null, ?Article $article = null){
58 $this->name_node = $name;
59 $this->position = $position;
60 $this->parent = $parent;
61 $this->page = $page;
62 $this->article = $article;
63 }
64
65 // pfff...
66 public function getId(): ?int
67 {
68 return $this->id_node;
69 }
70 public function getName(): string
71 {
72 return $this->name_node;
73 }
74 /*public function setName(string $name): void
75 {
76 $this->name_node = $name;
77 }*/
78
79 public function getParent(): ?self
80 {
81 return $this->parent;
82 }
83 /*public function setParent(?self $parent): void
84 {
85 $this->parent = $parent;
86 }*/
87 public function getPosition(): int
88 {
89 return $this->position;
90 }
91 public function setPosition(int $position): void
92 {
93 $this->position = $position;
94 }
95 public function getPage(): Page
96 {
97 return $this->page;
98 }
99 /*public function setPage(Page $page): void
100 {
101 $this->page = $page;
102 }*/
103
104 // utiliser l'interface de NodeData et EmailForm
105 public function getArticle(): Article
106 {
107 return $this->article;
108 }
109 /*public function setArticle(Article $article): void
110 {
111 $this->article = $article;
112 }*/
113
114 // une interface serait cool!
115 // OU possible un "héritage doctrine", faire en sorte que Node soit la mère de NodeData, EmailForm, Article...
116 // une seule instance est matérialisée par plusieurs tables en même temps
117 public function getNodeData(): NodeData|EmailForm|Presentation|null
118 {
119 // pour du polymorphisme (une unique variable $node_data): opter pour l'héritage
120 return match($this->name_node){
121 'form' => $this->email_form,
122 'post_block', 'news_block' => $this->presentation,
123 default => $this->node_data, // inclut 'calendar'
124 };
125 }
126 public function getChildren(): array
127 {
128 return $this->children;
129 }
130 public function getNodeByName(string $name): ?Node
131 {
132 foreach($this->children as $child){
133 if($child->getName() === $name){
134 return $child;
135 }
136 }
137 return null;
138 }
139 public function addChild(self $child): void
140 {
141 $this->children[] = $child;
142
143 if(!Presentation::hasPresentation($this->getName())){ // post_block et news_block ont leurs enfants ordonnés avec ORDER BY
144 $this->sortChildren(false);
145 }
146 }
147 public function removeChild(self $child): void
148 {
149 foreach($this->children as $key => $object){
150 if($object->getId() === $child->getId()){
151 unset($this->children[$key]);
152 }
153 break;
154 }
155 $this->children = array_values($this->children); // réindexer pour supprimer la case vide
156 }
157
158 public function getAdoptedChild(): ?self // peut renvoyer null
159 {
160 return $this->adopted;
161 }
162 public function setAdoptedChild(self $child): void
163 {
164 $this->adopted = $child;
165 }
166}
diff --git a/src/model/entities/NodeData.php b/src/model/entities/NodeData.php
deleted file mode 100644
index 1646c92..0000000
--- a/src/model/entities/NodeData.php
+++ /dev/null
@@ -1,102 +0,0 @@
1<?php
2// src/model/entities/NodeData.php
3
4declare(strict_types=1);
5
6namespace App\Entity;
7
8use Doctrine\ORM\Mapping as ORM;
9use Doctrine\Common\Collections\ArrayCollection; // classe
10use Doctrine\Common\Collections\Collection; // interface
11
12#[ORM\Entity]
13#[ORM\Table(name: TABLE_PREFIX . "node_data")]
14class NodeData
15{
16 static array $social_networks = ['globe', 'facebook', 'instagram', 'whatsapp', 'snapchat', 'tiktok', 'linkedin', 'github']; // à completer
17
18 #[ORM\Id]
19 #[ORM\GeneratedValue]
20 #[ORM\Column(type: "integer")]
21 private int $id_node_data;
22
23 // onDelete: "CASCADE" supprime les données si le noeud est supprimé
24 // inverseBy fait le lien avec $node_data dans Node (qui a "mappedBy")
25 #[ORM\OneToOne(targetEntity: Node::class, inversedBy: "node_data")]
26 #[ORM\JoinColumn(name: "node_id", referencedColumnName: "id_node", onDelete: "CASCADE")]
27 private ?Node $node;
28
29 #[ORM\Column(type: "json")]
30 private array $data;
31
32 #[ORM\OneToMany(mappedBy: 'node_data', targetEntity: AssetEmployment::class, cascade: ['persist', 'remove'])]
33 private Collection $asset_employment;
34
35 private array $emails = []; // => noeud "show_emails"
36
37 public function __construct(array $data, Node $node, Collection $asset_employment = new ArrayCollection){
38 $this->data = $data;
39 $this->node = $node;
40 $this->asset_employment = $asset_employment;
41 }
42
43 public function getId(): int
44 {
45 return $this->id_node_data;
46 }
47 public function getData(): array
48 {
49 return $this->data;
50 }
51 /*public function setData(array $data): void // entrée = tableau associatif
52 {
53 $this->data = $data;
54 }*/
55 public function updateData(string $key, string|int|bool|array $value = ''): void
56 {
57 if($value !== ''){
58 $this->data[$key] = $value;
59 }
60 // si $value est vide, supprime la clé
61 elseif(isset($this->data[$key])){
62 unset($this->data[$key]);
63 }
64 }
65
66 public function setNode(?Node $node): void
67 {
68 $this->node = $node;
69 }
70
71 public function getNodeDataAssets(): Collection
72 {
73 return $this->asset_employment;
74 }
75 public function getAssetEmploymentByRole(string $role): ?AssetEmployment
76 {
77 foreach($this->asset_employment as $nda){
78 if($nda->getRole() === $role){
79 return $nda;
80 }
81 }
82 return null;
83 }
84 public function getAssetByRole(string $role): ?Asset
85 {
86 $nda = $this->getAssetEmploymentByRole($role);
87 if($nda === null){
88 return null;
89 }
90 return $nda->getAsset() ?? null;
91 }
92
93 // pour affichage page Courriels
94 public function getEmails(): array // appelée dans ShowEmailsBuilder
95 {
96 return $this->emails;
97 }
98 public function setEmails(array $emails): void // appelée dans Model
99 {
100 $this->emails = $emails;
101 }
102} \ No newline at end of file
diff --git a/src/model/entities/Page.php b/src/model/entities/Page.php
deleted file mode 100644
index 8d3dd40..0000000
--- a/src/model/entities/Page.php
+++ /dev/null
@@ -1,221 +0,0 @@
1<?php
2// src/model/entities/Page.php
3
4declare(strict_types=1);
5
6namespace App\Entity;
7
8use Doctrine\ORM\Mapping as ORM;
9use Doctrine\Common\Collections\Collection;
10use Doctrine\Common\Collections\ArrayCollection;
11
12#[ORM\Entity]
13#[ORM\Table(name: TABLE_PREFIX . "page")]
14class Page
15{
16 use \Position;
17
18 #[ORM\Id]
19 #[ORM\GeneratedValue]
20 #[ORM\Column(type: "integer")]
21 private int $id_page;
22
23 #[ORM\Column(type: "string", length: 255)]
24 private string $name_page;
25
26 #[ORM\Column(type: "string", length: 255)]
27 private string $end_of_path; // morceau d'URL plus exactement
28
29 private string $page_path = '';
30
31 #[ORM\Column(type: "text")]
32 private string $description;
33
34 #[ORM\Column(type: "json", nullable: true)]
35 private ?array $css = null;
36
37 #[ORM\Column(type: "json", nullable: true)]
38 private ?array $js = null;
39
40 static private array $default_css = ['body', 'head', 'nav', 'foot'];
41 static private array $default_js = ['main'];
42
43 #[ORM\Column(type: "boolean")]
44 private bool $reachable;
45
46 #[ORM\Column(type: "boolean")]
47 private bool $in_menu;
48
49 #[ORM\Column(type: "boolean")]
50 private bool $hidden;
51
52 #[ORM\Column(type: "integer", nullable: true)] // null si hors menu
53 private ?int $position;
54
55 #[ORM\ManyToOne(targetEntity: self::class, inversedBy: "children")]
56 #[ORM\JoinColumn(name: "parent_id", referencedColumnName: "id_page", onDelete: "SET NULL", nullable: true)]
57 private ?self $parent = null;
58
59 #[ORM\OneToMany(targetEntity: self::class, mappedBy: 'parent')]
60 protected Collection $children;
61
62 /*#[ORM\Column(type: "json", nullable: true)]
63 private ?array $metadata = null;*/
64
65 public function __construct(string $name, string $eop, string $description, bool $reachable, bool $in_menu, bool $hidden, ?int $position, ?Page $parent)
66 {
67 $this->name_page = $name;
68 $this->end_of_path = $eop;
69 $this->description = $description;
70 $this->reachable = $reachable;
71 $this->in_menu = $in_menu;
72 $this->hidden = $hidden;
73 $this->position = $position;
74 $this->parent = $parent;
75 $this->children = new ArrayCollection();
76 }
77
78 // getters/setters
79 public function getId(): int
80 {
81 return $this->id_page;
82 }
83 public function getPageName(): string
84 {
85 return $this->name_page;
86 }
87 public function setPageName(string $name): void
88 {
89 $this->name_page = $name;
90 }
91 public function getPagePath(): string
92 {
93 return $this->page_path;
94 }
95 public function setPagePath(string $path):void
96 {
97 $this->page_path = $path;
98 }
99 public function getEndOfPath(): string
100 {
101 return $this->end_of_path;
102 }
103 public function setEndOfPath(string $end_of_path):void
104 {
105 $this->end_of_path = $end_of_path;
106 }
107 public function getDescription(): string
108 {
109 return $this->description;
110 }
111 public function setDescription(string $description): void
112 {
113 $this->description = $description;
114 }
115
116 public function getCSS(): array
117 {
118 return array_merge(self::$default_css, $this->css ?? []);
119 }
120 public function addCSS(string $css): void
121 {
122 if(!in_array($css, $this->css ?? [])){
123 $this->css[] = $css;
124 }
125 }
126 public function removeCSS(string $css): void
127 {
128 // array_diff renvoie une copie du 1er tableau alégée des élements existants aussi dans le 2è, array_values réindexe
129 $this->css = array_values(array_diff($this->css, [$css]));
130 }
131 public function getJS(): array
132 {
133 return array_merge(self::$default_js, $this->js ?? []);
134 //UPDATE `nb_page` SET `js` = NULL WHERE JSON_EQUALS(`js`, '["main"]');
135 }
136 public function addJS(string $js): void
137 {
138 if(!in_array($js, $this->js ?? [])){
139 $this->js[] = $js;
140 }
141 }
142 public function removeJS(string $js): void
143 {
144 $this->js = array_values(array_diff($this->js, [$js]));
145 }
146
147 public function isReachable(): bool
148 {
149 return $this->reachable;
150 }
151 public function isInMenu(): bool
152 {
153 return $this->in_menu;
154 }
155 public function isHidden(): bool
156 {
157 return $this->hidden;
158 }
159 public function setHidden(bool $hidden): void
160 {
161 $this->hidden = $hidden;
162 }
163 public function getPosition(): ?int
164 {
165 return $this->position;
166 }
167 public function setPosition(int $position): void
168 {
169 $this->position = $position;
170 }
171 public function getParent(): ?Page
172 {
173 return $this->parent;
174 }
175 public function setParent(?Page $parent): void
176 {
177 $this->parent = $parent;
178 }
179 public function getChildren(): Collection
180 {
181 return $this->children;
182 }
183
184 // utilisée par $menu_path
185 public function fillChildrenPagePath(string $parent_path = ''): void
186 {
187 $this->page_path = $parent_path != '' ? $parent_path . '/' . $this->end_of_path : $this->end_of_path;
188 foreach($this->getChildren() as $page){
189 $page->fillChildrenPagePath($this->page_path);
190 }
191 }
192
193 public function addChild(self $child): void
194 {
195 $this->children[] = $child;
196 $this->sortChildren(false);
197 }
198 public function removeChild(self $child): void
199 {
200 $this->children->removeElement($child);
201 $this->children = new ArrayCollection(array_values($this->children->toArray())); // réindexer en passant par un tableau
202 $this->sortChildren(false);
203 }
204
205 public function findPageById(int $id): ?Page
206 {
207 $target = null;
208 foreach($this->children as $page){
209 if($page->getId() === $id){
210 return $page;
211 }
212 if(count($page->getChildren()) > 0){
213 $target = $page->findPageById($id);
214 if($target !== null){
215 return $target;
216 }
217 }
218 }
219 return $target;
220 }
221}
diff --git a/src/model/entities/Presentation.php b/src/model/entities/Presentation.php
deleted file mode 100644
index d66bf24..0000000
--- a/src/model/entities/Presentation.php
+++ /dev/null
@@ -1,135 +0,0 @@
1<?php
2// src/model/entities/Presentation.php
3
4declare(strict_types=1);
5
6namespace App\Entity;
7
8use Doctrine\ORM\Mapping as ORM;
9
10#[ORM\Entity]
11#[ORM\Table(name: TABLE_PREFIX . "presentation")]
12class Presentation{
13 #[ORM\Id]
14 #[ORM\GeneratedValue]
15 #[ORM\Column(type: "integer")]
16 private int $id_presentation;
17
18 // inverseBy fait le lien avec $presentation dans Node (qui a "mappedBy")
19 #[ORM\OneToOne(targetEntity: Node::class, inversedBy: "presentation")]
20 #[ORM\JoinColumn(name: "node_id", referencedColumnName: "id_node")]
21 private ?Node $node;
22
23 #[ORM\Column(type: "json")]
24 private array $data;
25
26 #[ORM\Column(type: "string", length: 255, nullable: true)]
27 private ?string $presentation;
28
29 #[ORM\Column(type: "boolean", length: 255, nullable: true)]
30 private ?bool $chrono_order = null;
31
32 #[ORM\Column(type: "integer", nullable: true)]
33 private ?int $grid_cols_min_width = null; // pour le mode grille
34
35 #[ORM\Column(type: "integer", nullable: true)]
36 private ?int $pagination_limit = null; // pour les post_block et news_block
37
38 private int $nb_pages = 1;
39
40 public function __construct(array $data, Node $node
41 //, ?string $presentation = null, ?bool $chrono_order = null
42 ){
43 $this->data = $data;
44 $this->node = $node;
45 /*if(!empty($presentation) && $presentation === 'grid'){
46 $this->grid_cols_min_width = 250;
47 }
48 $this->chrono_order = $chrono_order ?? null;*/
49 }
50
51 /*public function getId(): int
52 {
53 return $this->id_presentation;
54 }*/
55
56 public function setNode(?Node $node): void
57 {
58 $this->node = $node;
59 }
60
61 // un trait pour ça
62 public function getData(): array
63 {
64 return $this->data;
65 }
66 public function updateData(string $key, string|int|bool|array $value = ''): void
67 {
68 if($value !== ''){
69 $this->data[$key] = $value;
70 }
71 // si $value est vide, supprime la clé
72 elseif(isset($this->data[$key])){
73 unset($this->data[$key]);
74 }
75 }
76
77 // spécifique aux blocs contenant des articles
78 public function getPresentation(): ?string
79 {
80 return $this->presentation;
81 }
82 public function setPresentation(string $presentation): void
83 {
84 $this->presentation = $presentation;
85 }
86 public function getColsMinWidth(): int
87 {
88 $default = 320; // pixels
89 return $this->grid_cols_min_width === null ? $default : $this->grid_cols_min_width;
90 }
91 public function setColsMinWidth(int $columns): void
92 {
93 $this->grid_cols_min_width = $columns;
94 }
95 public function getChronoOrder(): bool
96 {
97 return $this->chrono_order ?? false;
98 }
99 public function setChronoOrder(bool $reverse_order): void
100 {
101 $this->chrono_order = $reverse_order;
102 }
103
104 public function getPaginationLimit(): ?int
105 {
106 $default = 12; // si 0 pas de pagination, 12 rend bien avec des grilles de 2, 3 ou 4 colonnes
107 return $this->pagination_limit === null ? $default : $this->pagination_limit;
108 }
109 public function setPaginationLimit(int $pagination_limit): void
110 {
111 $this->pagination_limit = $pagination_limit;
112 }
113 public function getNumberOfPages(): int
114 {
115 return $this->nb_pages;
116 }
117 public function setNumberOfPages(int $nb_pages): void
118 {
119 $this->nb_pages = $nb_pages;
120 }
121
122
123 /* -- blocs et présentations disponibles dans l'application -- */
124 const BLOCKS = ['post_block' => 'Articles libres', 'news_block' => 'Actualités',
125 //'galery' => 'Galerie',
126 'calendar' => 'Calendrier', 'form' => 'Formulaire'
127 ];
128 const PRESENTATIONS = ['fullwidth' => 'Pleine largeur', 'grid' => 'Grille', 'mosaic' => 'Mosaïque'
129 //, 'carousel' => 'Carrousel'
130 ];
131 static public function hasPresentation(string $block): bool
132 {
133 return in_array($block, ['post_block', 'news_block']) ? true : false;
134 }
135} \ No newline at end of file
diff --git a/src/model/entities/User.php b/src/model/entities/User.php
deleted file mode 100644
index 36bc4db..0000000
--- a/src/model/entities/User.php
+++ /dev/null
@@ -1,67 +0,0 @@
1<?php
2// src/model/entities/User.php
3
4declare(strict_types=1);
5
6namespace App\Entity;
7
8use Doctrine\ORM\Mapping as ORM;
9
10#[ORM\Entity]
11#[ORM\Table(name: TABLE_PREFIX . "user")]
12class User
13{
14 #[ORM\Id]
15 #[ORM\GeneratedValue]
16 #[ORM\Column(type: "integer")]
17 private int $id_user;
18
19 /*#[ORM\Column(type: "string", length: 255)]
20 private string $name;*/
21
22 #[ORM\Column(type: "string", length: 255, unique: true)] // risque de modifier son mot de passe sans s'apercevoir qu'il fonctionne encore sur un autre compte
23 private string $login;
24
25 #[ORM\Column(type: "string", length: 50)]
26 private string $role;
27
28 #[ORM\Column(type: "string", length: 255)]
29 private string $password;
30
31 public function __construct(string $login, string $role, string $password)
32 {
33 $this->login = $login;
34 $this->role = $role;
35 $this->password = $password;
36 }
37
38 public function getId(): int
39 {
40 return $this->id_user;
41 }
42
43 public function getLogin(): string
44 {
45 return $this->login;
46 }
47 public function setLogin(string $login): void
48 {
49 $this->login = $login;
50 }
51 public function getRole(): string
52 {
53 return $this->role;
54 }
55 public function setRole(string $role): void
56 {
57 $this->role = $role;
58 }
59 public function getPassword(): string
60 {
61 return $this->password;
62 }
63 public function setPassword(string $password): void
64 {
65 $this->password = $password;
66 }
67} \ No newline at end of file