aboutsummaryrefslogtreecommitdiff
path: root/src/Controller
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/Controller
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/Controller')
-rw-r--r--src/Controller/ArticleController.php244
-rw-r--r--src/Controller/CalendarController.php77
-rw-r--r--src/Controller/ContactFormController.php107
-rw-r--r--src/Controller/FileUploadController.php68
-rw-r--r--src/Controller/HeadFootController.php146
-rw-r--r--src/Controller/ImageUploadController.php194
-rw-r--r--src/Controller/MaintenanceController.php152
-rw-r--r--src/Controller/MenuAndPathsController.php226
-rw-r--r--src/Controller/PageManagementController.php368
-rw-r--r--src/Controller/UserController.php237
-rw-r--r--src/Controller/ViewController.php70
11 files changed, 1889 insertions, 0 deletions
diff --git a/src/Controller/ArticleController.php b/src/Controller/ArticleController.php
new file mode 100644
index 0000000..075e3b4
--- /dev/null
+++ b/src/Controller/ArticleController.php
@@ -0,0 +1,244 @@
1<?php
2// src/Controller/ArticleController.php
3
4declare(strict_types=1);
5
6namespace App\Controller;
7
8use Doctrine\ORM\EntityManager;
9use Symfony\Component\HttpFoundation\Request;
10use Symfony\Component\HttpFoundation\Response;
11use Symfony\Component\HttpFoundation\JsonResponse;
12use Symfony\Component\HttpFoundation\RedirectResponse;
13use App\Entity\Node;
14use App\Entity\Article;
15use App\Entity\Presentation;
16use App\Model\Model;
17use App\Service\URL;
18use App\Service\Security;
19
20class ArticleController
21{
22 static public function fetch(EntityManager $entityManager, Request $request): JsonResponse
23 {
24 if($request->query->has('id') && !empty($request->query->get('id')) && $request->query->has('last_article')){
25 $id = (int)$request->get('id'); // type et nettoie
26 $model = new Model($entityManager);
27 $model->findNodeById($id);
28 $parent_block = $model->getNode();
29
30 if(Presentation::hasPresentation($parent_block->getName())){
31 $get_articles_return = $model->getNextArticles($parent_block, $request);
32 $bulk_data = $get_articles_return[0];
33
34 if($parent_block->getName() === 'post_block'){
35 $builder_name = 'App\View\PostBuilder';
36 }
37 elseif($parent_block->getName() === 'news_block'){
38 $builder_name = 'App\View\NewBuilder';
39 }
40 else{
41 return new JsonResponse(['success' => false, 'error' => 'server side error'], JsonResponse::HTTP_INTERNAL_SERVER_ERROR);
42 }
43
44 $html = '';
45 foreach($bulk_data as $article){
46 $builder = new $builder_name($article);
47 $html .= $builder->render();
48 }
49
50 return new JsonResponse(['success' => true, 'html' => $html, 'truncated' => $get_articles_return[1]]);
51 }
52 else{
53 return new JsonResponse(['success' => false, 'error' => 'server side error'], JsonResponse::HTTP_INTERNAL_SERVER_ERROR);
54 }
55 }
56 else{
57 return new JsonResponse(['success' => false, 'error' => 'bad parameters'], JsonResponse::HTTP_BAD_REQUEST);
58 }
59 }
60
61 static public function editorSubmit(EntityManager $entityManager, array $json): JsonResponse
62 {
63 if(json_last_error() === JSON_ERROR_NONE){
64 $id = $json['id'];
65 if(in_array($id[0], ['t', 'p', 'i', 'd'])){
66 $id = substr($id, 1);
67 }
68
69 $model = new Model($entityManager);
70 $content = $json['content'];
71
72 // nettoyage
73 if(is_array($content)){ // cas d'une nouvelle "news"
74 foreach($content as $one_input){
75 $one_input = Security::secureHTML($one_input);
76 }
77 }
78 else{ // autres cas
79 $content = Security::secureHTML($json['content']);
80 }
81
82 // nouvel article
83 if($json['id'][0] === 'n'){ // ici $id est un bloc
84 $section_id = (int)substr($id, 1); // id du bloc <section>
85 if(!$model->findNodeById($section_id)){ // erreur mauvais id
86 return new JsonResponse(['success' => false, 'error' => 'article_not_saved, bad id']);
87 }
88 $model->makeSectionNode();
89 $section = $model->getNode();
90
91 // ajout d'une news
92 if(is_array($content)){
93 if($section->getPage()->getEndOfPath() !== $json['from']){ // erreur mauvais from
94 return new JsonResponse(['success' => false, 'error' => 'article_not_saved, bad from']);
95 }
96
97 $date = new \DateTime($content['d'] . ':' . (new \DateTime)->format('s')); // l'input type="datetime-local" ne donne pas les secondes, on les ajoute: 'hh:mm' . ':ss'
98 $article = new Article($content['i'], $date, $content['t'], $content['p']);
99 $article_node = new Node('new', count($section->getChildren()) + 1, $section, $section->getPage(), $article);
100 }
101 // ajout d'un post
102 else{
103 $timestamp = time();
104 $date = new \DateTime;
105 $date->setTimestamp($timestamp);
106
107 $article = new Article($content, $date);
108 $placement = $json['placement'] === 'first' ? 0 : count($section->getChildren()) + 1; //
109 $article_node = new Node('post', $placement, $section, $section->getPage(), $article);
110
111 if($json['placement'] === 'first'){
112 $section->addChild($article_node);
113 $section->sortChildren(true); // mettre $article_node au début du tableau puis régénérer les positions (0 devient 1, 1 devient 2...)
114 }
115 }
116
117 $entityManager->persist($article_node);
118 $entityManager->flush();
119
120 return new JsonResponse(['success' => true, 'article_id' => $article_node->getId()]);
121 }
122 // modification article
123 //else{}
124
125 if($model->makeArticleNode($id)){ // une entrée est trouvée
126 $node = $model->getArticleNode();
127 switch($json['id'][0]){
128 case 'i':
129 $node->getArticle()->setContent($content);
130 break;
131 case 'p':
132 $node->getArticle()->setPreview($content); // html de l'éditeur
133 break;
134 case 't':
135 $node->getArticle()->setTitle($content); // html de l'éditeur
136 break;
137 case 'd':
138 return new JsonResponse(['success' => false, 'message' => 'l\'action editor_submit ne supporte pas les dates, utiliser date_submit.']);
139 default: // modif article simple (id sans lettre devant)
140 $node->getArticle()->setContent($content);
141 }
142 $entityManager->flush();
143 return new JsonResponse(['success' => true]);
144 }
145 else{
146 return new JsonResponse(['success' => false, 'message' => 'article non identifié']);
147 }
148 }
149 else{
150 return new JsonResponse(['success' => false, 'message' => 'Erreur de décodage JSON']);
151 }
152 }
153
154 static public function deleteArticle(EntityManager $entityManager, Request $request): Response
155 {
156 $model = new Model($entityManager);
157
158 if($request->headers->get('Content-Type') === 'application/json'){
159 $id = json_decode($request->getContent(), true)['id'];
160 }
161 elseif($request->headers->get('Content-Type') === 'application/x-www-form-urlencoded'){
162 $id = $request->query->get('id');
163 }
164 // ni JSON ni form, c'est quoi? un POST vide?
165 else{
166 return new Response('la méthode deleteArticle ne peut être appelée de cette manière');
167 }
168
169 if(!$model->makeArticleNode($id, true)){
170 $params = ['false', "Erreur 500 pas d\'article à supprimer"];
171 }
172 else{
173 $article = $model->getArticleNode();
174 $section = $model->getNode();
175
176 $entityManager->remove($article);
177 $section->removeChild($article);
178 $section->sortChildren(true); // régénère les positions
179
180 try{
181 $entityManager->flush();
182 $params = ['true', 'Article supprimé avec succès'];
183 }
184 catch(\Exception $e){
185 $params = ['false', 'Erreur 500 ' . $e->getMessage()];
186 }
187 }
188
189 if($request->headers->get('Content-Type') === 'application/json'){
190 return new JsonResponse(
191 ['success' => $params[0], 'message' => $params[1]],
192 $params[0] ? JsonResponse::HTTP_OK : JsonResponse::HTTP_INTERNAL_SERVER_ERROR
193 );
194 }
195 elseif($request->headers->get('Content-Type') === 'application/x-www-form-urlencoded'){
196 $url = new URL(['page' => $request->query->get('from') ?? '', 'success' => $params[0], 'message' => $params[1]]);
197 return new RedirectResponse((string)$url);
198 }
199 else{
200 // cas inaccesible
201 throw new \Exception('la méthode deleteArticle ne peut être appelée de cette manière');
202 }
203 }
204
205 static public function switchPositions(EntityManager $entityManager, array $json): JsonResponse
206 {
207 $model = new Model($entityManager);
208 $model->makeArticleNode($json['id1'], true);
209 $article1 = $model->getArticleNode();
210 $section = $model->getNode();
211
212 $section->sortChildren(true); // régénère les positions avant inversion
213 $article2 = null;
214 foreach($section->getChildren() as $child){
215 if((string)$child->getId() === $json['id2']) // type string
216 {
217 $article2 = $child;
218 break;
219 }
220 }
221
222 // inversion
223 $tmp = $article1->getPosition();
224 $article1->setPosition($article2->getPosition());
225 $article2->setPosition($tmp);
226 $entityManager->flush();
227
228 return new JsonResponse(['success' => true]);
229 }
230
231 static public function dateSubmit(EntityManager $entityManager, array $json): JsonResponse
232 {
233 $id = substr($json['id'], 1);
234 $date = new \DateTime($json['date']);
235
236 $model = new Model($entityManager);
237 $model->makeArticleNode($id);
238 $node = $model->getArticleNode();
239 $node->getArticle()->setDateTime($date);
240 $entityManager->flush();
241
242 return new JsonResponse(['success' => true]);
243 }
244} \ No newline at end of file
diff --git a/src/Controller/CalendarController.php b/src/Controller/CalendarController.php
new file mode 100644
index 0000000..679ff8a
--- /dev/null
+++ b/src/Controller/CalendarController.php
@@ -0,0 +1,77 @@
1<?php
2// /src/Controller/CalendarController.php
3
4declare(strict_types=1);
5
6namespace App\Controller;
7
8use Doctrine\ORM\EntityManager;
9use Symfony\Component\HttpFoundation\JsonResponse;
10use App\Entity\Event;
11use App\Model\EventDTO;
12
13class CalendarController
14{
15 static public function getData(EntityManager $entityManager): JsonResponse
16 {
17 // bornes début et fin du calendrier affiché à l'heure locale
18 // noter que la vue "planning" est similaire à la vue "semaine"
19 $start = new \DateTime($_GET['start']);
20 $end = new \DateTime($_GET['end']);
21 $start->setTimezone(new \DateTimeZone('UTC'));
22 $end->setTimezone(new \DateTimeZone('UTC'));
23
24 // affichage format ISO à l'heure UTC
25 //$date->format('Y-m-d\TH:i:s\Z');
26
27 // on prend les évènements se finissant après le début ou commençant avant la fin de la fourchette
28 $dql = 'SELECT e FROM App\Entity\Event e WHERE e.end >= :start AND e.start <= :end';
29 $bulk_data = $entityManager->createQuery($dql)
30 ->setParameter('start', $start)
31 ->setParameter('end', $end)
32 ->getResult();
33
34 $events = [];
35 foreach($bulk_data as $one_entry){
36 $event = new EventDTO($one_entry);
37 $events[] = $event->toArray();
38 }
39
40 return new JsonResponse($events);
41 }
42
43 static public function newEvent(array $json, EntityManager $entityManager): JsonResponse
44 {
45 try{
46 $event = new Event($json);
47 }
48 catch(\InvalidArgumentException $e){
49 return new JsonResponse(['success' => false, 'error' => $e->getMessage()], JsonResponse::HTTP_BAD_REQUEST); // code 400
50 }
51 $entityManager->persist($event);
52 $entityManager->flush();
53
54 return new JsonResponse(['success' => true, 'id' => $event->getId()]);
55 }
56 static public function updateEvent(array $json, EntityManager $entityManager): JsonResponse
57 {
58 $event = $entityManager->find('App\Entity\Event', (int)$json['id']);
59 try{
60 $event->securedUpdateFromJSON($json);
61 }
62 catch(\InvalidArgumentException $e){
63 return new JsonResponse(['success' => false, 'error' => $e->getMessage()], JsonResponse::HTTP_BAD_REQUEST); // code 400
64 }
65 $entityManager->flush();
66
67 return new JsonResponse(['success' => true]);
68 }
69 static public function removeEvent(array $json, EntityManager $entityManager): JsonResponse
70 {
71 $event = $entityManager->find('App\Entity\Event', (int)$json['id']);
72 $entityManager->remove($event);
73 $entityManager->flush();
74
75 return new JsonResponse(['success' => true]);
76 }
77} \ No newline at end of file
diff --git a/src/Controller/ContactFormController.php b/src/Controller/ContactFormController.php
new file mode 100644
index 0000000..56ef402
--- /dev/null
+++ b/src/Controller/ContactFormController.php
@@ -0,0 +1,107 @@
1<?php
2// src/Controller/ContactFormController.php
3
4declare(strict_types=1);
5
6namespace App\Controller;
7
8use Symfony\Component\HttpFoundation\JsonResponse;
9use Doctrine\ORM\EntityManager;
10use App\Entity\EmailForm;
11use App\Entity\Email;
12use App\Service\EmailService;
13use App\Service\FormValidation;
14use App\Service\ValidationStrategy;
15
16class ContactFormController
17{
18 static public function keepEmails(EntityManager $entityManager, array $json): JsonResponse
19 {
20 $form_data = $entityManager->find(EmailForm::class, $json['id']);
21 $form_data->updateData('keep_emails', $json['checked'] ? true : false);
22 $entityManager->persist($form_data);
23 $entityManager->flush();
24 return new JsonResponse(['success' => true, 'checked' => $json['checked']]);
25 }
26 static public function setEmailsRetentionPeriod(EntityManager $entityManager, array $json): JsonResponse
27 {
28 $form_data = $entityManager->find(EmailForm::class, $json['id']);
29 $form_data->updateData($json['field'], (int)$json['months']);
30 $entityManager->persist($form_data);
31 $entityManager->flush();
32 return new JsonResponse(['success' => true, 'months' => $json['months']]);
33 }
34 static public function setEmailParam(EntityManager $entityManager, array $json): JsonResponse
35 {
36 $form = new FormValidation($json, ValidationStrategy::EmailParam);
37
38 if($form->validate()){
39 $form_data = $entityManager->find(EmailForm::class, $json['id']);
40 $form_data->updateData($json['what_param'], trim($json['value']));
41 $entityManager->persist($form_data); // ??
42 $entityManager->flush();
43 return new JsonResponse(['success' => true]);
44 }
45 else{
46 return new JsonResponse(['success' => false, 'error' => $form->getError()]);
47 }
48 }
49
50 // les deux méthodes suivantes sont "factorisables", elles ne se distinguent que par la gestion ou non du formulaire rempli par le visiteur
51 static public function sendVisitorEmail(EntityManager $entityManager, array $json): JsonResponse
52 {
53 $form = new FormValidation($json, ValidationStrategy::EmailSend);
54
55 $error = '';
56 if($form->validate()){
57 // destinataire = e-mail par défaut dans config.ini OU choisi par l'utilisateur
58 $form_data = $entityManager->find(EmailForm::class, $json['id']);
59 if($form_data === null){
60 return new JsonResponse(['success' => false, 'error' => 'server_error'], JsonResponse::HTTP_INTERNAL_SERVER_ERROR); // code 500
61 }
62
63 if(!EmailService::send($entityManager, $form_data, false, $form->getField('name'), $form->getField('email'), $form->getField('message'))){
64 $error = 'email_not_sent';
65 }
66 }
67 else{
68 $error = $form->getError();
69 }
70
71 if(empty($error)){
72 return new JsonResponse(['success' => true]);
73 }
74 else{
75 return new JsonResponse(['success' => false, 'error' => $error]);
76 }
77 }
78 static public function sendTestEmail(EntityManager $entityManager, array $json): JsonResponse
79 {
80 // destinataire = e-mail par défaut dans config.ini OU choisi par l'utilisateur
81 $form_data = $entityManager->find(EmailForm::class, $json['id']);
82 if($form_data === null){
83 return new JsonResponse(['success' => false, 'error' => 'server_error'], JsonResponse::HTTP_INTERNAL_SERVER_ERROR);
84 }
85
86 if(EmailService::send($entityManager, $form_data, true, 'nom du visiteur', 'adresse@du_visiteur.fr', "TEST d'un envoi d'e-mail depuis le site web")){
87 return new JsonResponse(['success' => true]);
88 }
89 else{
90 return new JsonResponse(['success' => false, 'error' => 'email_not_sent']);
91 }
92 }
93 static public function deleteEmail(EntityManager $entityManager, array $json): JsonResponse
94 {
95 $email = $entityManager->find(Email::class, $json['id']);
96 $entityManager->remove($email);
97 $entityManager->flush();
98 return new JsonResponse(['success' => true]);
99 }
100 static public function toggleSensitiveEmail(EntityManager $entityManager, array $json): JsonResponse
101 {
102 $email = $entityManager->find(Email::class, $json['id']);
103 $email->makeSensitive($json['checked']);
104 $entityManager->flush();
105 return new JsonResponse(['success' => true, 'checked' => $json['checked'], 'deletion_date' => $email->getDeletionDate()->format('d/m/Y')]);
106 }
107} \ No newline at end of file
diff --git a/src/Controller/FileUploadController.php b/src/Controller/FileUploadController.php
new file mode 100644
index 0000000..4627017
--- /dev/null
+++ b/src/Controller/FileUploadController.php
@@ -0,0 +1,68 @@
1<?php
2// src/Controller/FileUploadController.php
3
4declare(strict_types=1);
5
6namespace App\Controller;
7
8use Symfony\Component\HttpFoundation\JsonResponse;
9use App\Service\Security;
10
11class FileUploadController
12{
13 static private function checkFileDownload(array $file): bool
14 {
15 $extensions_white_list = ['pdf', 'rtf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'odt', 'ods', 'odp']; // = extensions_white_list côté javascript
16 $mime_type_white_list = ['application/pdf', 'application/rtf', 'text/rtf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/vnd.oasis.opendocument.text', 'application/vnd.oasis.opendocument.spreadsheet', 'application/vnd.oasis.opendocument.presentation'];
17
18 // 1/ extension
19 $extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
20 if(!in_array($extension, $extensions_white_list, true)){
21 return false;
22 }
23
24 // 2/ fichier obtenu par HTTP POST (théoriquement inutile si le routeur est solide, mais ça ne mange pas de pain)
25 if(!is_uploaded_file($file['tmp_name'])){
26 return false;
27 }
28
29 // 3/ objet $finfo valide (dépend du paramètre FILEINFO_MIME_TYPE)
30 $finfo = new \finfo(FILEINFO_MIME_TYPE);
31 if($finfo === false){
32 return false;
33 }
34
35 // 4/ contrôle du "vrai" type mime (finfo_file lit les 1ers octets des fichiers pour y trouver des "signatures", très fiable sauf avec les conteneurs: doc, zip...)
36 $real_type = finfo_file($finfo, $file['tmp_name']);
37 return in_array($real_type, $mime_type_white_list, true);
38 }
39
40 static public function fileUploadTinyMce(): JsonResponse
41 {
42 if(isset($_FILES['file'])){
43 $dest = 'user_data/media/';
44 if(!is_dir($dest)){ // Vérifier si le répertoire existe, sinon le créer
45 mkdir($dest, 0755, true);
46 }
47
48 $name = Security::secureFileName(pathinfo($_FILES['file']['name'], PATHINFO_FILENAME)); // retirer caractères spéciaux et changer espaces en underscores
49 $extension = strtolower(pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION));
50 $file_path = $dest . $name . '_' . uniqid() . '.' . $extension; // nom unique
51
52 if(self::checkFileDownload($_FILES['file'])){
53 if(move_uploaded_file($_FILES['file']['tmp_name'], $file_path)){
54 return new JsonResponse(['location' => $file_path]);
55 }
56 else{
57 return new JsonResponse(['message' => 'Erreur enregistrement du fichier.'], JsonResponse::HTTP_INTERNAL_SERVER_ERROR); // code 500
58 }
59 }
60 else{
61 return new JsonResponse(['message' => 'Erreur 400: fichier non valide.'], JsonResponse::HTTP_BAD_REQUEST); // code 400
62 }
63 }
64 else{
65 return new JsonResponse(['message' => 'Erreur 400: Bad Request'], JsonResponse::HTTP_BAD_REQUEST); // code 400
66 }
67 }
68} \ No newline at end of file
diff --git a/src/Controller/HeadFootController.php b/src/Controller/HeadFootController.php
new file mode 100644
index 0000000..e39c07b
--- /dev/null
+++ b/src/Controller/HeadFootController.php
@@ -0,0 +1,146 @@
1<?php
2// src/Controller/HeadFootController.php
3
4declare(strict_types=1);
5
6namespace App\Controller;
7
8use Doctrine\ORM\EntityManager;
9use Symfony\Component\HttpFoundation\JsonResponse;
10use App\Entity\NodeData;
11use App\Entity\Asset;
12use App\Entity\AssetEmployment;
13use App\Model\Model;
14use App\Controller\ImageUploadController;
15use App\Service\Security;
16
17class HeadFootController
18{
19 static public function setTextData(EntityManager $entityManager, string $request_params, array $json): JsonResponse
20 {
21 $params_array = explode('_', $request_params); // header_title, header_description, footer_name, footer_address, footer_email
22 if(count($params_array) !== 2){
23 return new JsonResponse(['success' => false]);
24 }
25
26 $model = new Model($entityManager);
27 if($model->findWhateverNode('name_node', $params_array[0])){
28 $node_data = $model->getNode()->getNodeData();
29
30 // liens réseaux sociaux
31 if(in_array($params_array[1], NodeData::$social_networks)){
32 $social = $node_data->getData()['social'] ?? [];
33 $social[$params_array[1]] = $json['new_text'];
34 $node_data->updateData('social', $social);
35 }
36 // autres textes
37 else{
38 $node_data->updateData($params_array[1], $json['new_text']); // $params_array[1] n'est pas contrôlé
39 }
40
41 $entityManager->flush();
42 return new JsonResponse(['success' => true]);
43 }
44 else{
45 return new JsonResponse(['success' => false]);
46 }
47 }
48 static public function uploadAsset(EntityManager $entityManager, string $request_params): JsonResponse
49 {
50 if(empty($_FILES)){
51 return new JsonResponse(['success' => false], JsonResponse::HTTP_BAD_REQUEST); // code 400
52 }
53 else{
54 if(!is_dir(Asset::USER_PATH)){
55 mkdir(Asset::USER_PATH, 0777, true);
56 }
57
58 /* -- téléchargement -- */
59 $file = $_FILES['file'];
60 $allowed_extensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'tiff', 'tif', 'ico', 'bmp']; // pas de SVG
61 $extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
62 if(!in_array($extension, $allowed_extensions) || $extension === 'jpg'){
63 $extension = 'jpeg';
64 }
65 $mime_type = mime_content_type($file['tmp_name']);
66 $hash = hash_file('sha256', $file['tmp_name']);
67
68 /* -- instance d'Asset -- */
69 $model = new Model($entityManager);
70 $result = $model->getWhatever('App\Entity\Asset', 'hash', $hash);
71
72 if(count($result) > 0){ // asset existant trouvé
73 $asset = $result[0];
74
75 // correction des informations
76 $name = $asset->getFileName(); // permet à priori de réécrire par dessus le précédent fichier
77 //$asset->setFileName($name);
78 $asset->setMimeType($mime_type);
79 }
80 else{
81 $name = Security::secureFileName(pathinfo($file['name'], PATHINFO_FILENAME));
82 $name = uniqid($name . '_') . '.' . $extension;
83 $asset = new Asset($name, $mime_type, $hash);
84 }
85
86 /* -- écriture du fichier sur le disque -- */
87 if(!ImageUploadController::imagickCleanAndWriteImage(file_get_contents($file['tmp_name']), Asset::USER_PATH . $name)){ // recréer l’image pour la nettoyer
88 return new JsonResponse(['success' => false, 'message' => "Erreur de l'enregistrement de l'image: problème de permission ou format non valide.", 'format' => $extension], JsonResponse::HTTP_INTERNAL_SERVER_ERROR); // code 500
89 }
90 else{
91 $params_array = explode('_', $request_params); // head_favicon, header_logo, header_background, footer_logo
92
93 /* -- table intermédiaire node_data/asset-- */
94 if($model->findWhateverNode('name_node', $params_array[0])){ // noeud (head, header ou footer)
95 $node_data = $model->getNode()->getNodeData();
96
97 // recherche à l'aide du rôle
98 $old_nda = null;
99 foreach($node_data->getNodeDataAssets() as $nda){
100 if($nda->getRole() === $request_params){
101 $old_nda = $nda;
102 $old_nda->setAsset($asset);
103 break;
104 }
105 }
106 // entrée pas trouvée
107 if($old_nda === null){
108 $new_nda = new AssetEmployment($node_data, $asset, $request_params); // $request_params sera le rôle de l'asset
109 $entityManager->persist($new_nda);
110 }
111
112 if(count($result) === 0){
113 $entityManager->persist($asset);
114 }
115 $entityManager->flush();
116 return new JsonResponse(['success' => true, 'location' => Asset::USER_PATH . $name, 'mime_type' => $mime_type]);
117 }
118 else{
119 return new JsonResponse(['success' => false, 'message' => "Erreur noeud non trouvé, c'est pas du tout normal!"], JsonResponse::HTTP_INTERNAL_SERVER_ERROR); // code 500
120 }
121 }
122 }
123 }
124
125 static public function displaySocialNetwork(EntityManager $entityManager, string $request_params, array $json): JsonResponse
126 {
127 $params_array = explode('_', $request_params);
128 if(count($params_array) !== 2){
129 return new JsonResponse(['success' => false]);
130 }
131
132 $model = new Model($entityManager);
133 if(in_array($params_array[1], NodeData::$social_networks) && $model->findWhateverNode('name_node', $params_array[0])){
134 $node_data = $model->getNode()->getNodeData();
135 $social_show = $node_data->getData()['social_show'] ?? [];
136 $social_show[$params_array[1]] = $json['checked'];
137 $node_data->updateData('social_show', $social_show);
138
139 $entityManager->flush();
140 return new JsonResponse(['success' => true, 'checked' => $json['checked']]);
141 }
142 else{
143 return new JsonResponse(['success' => false]);
144 }
145 }
146} \ No newline at end of file
diff --git a/src/Controller/ImageUploadController.php b/src/Controller/ImageUploadController.php
new file mode 100644
index 0000000..4ca9f48
--- /dev/null
+++ b/src/Controller/ImageUploadController.php
@@ -0,0 +1,194 @@
1<?php
2// src/Controller/ImageUploadController.php
3
4declare(strict_types=1);
5
6namespace App\Controller;
7
8use Symfony\Component\HttpFoundation\JsonResponse;
9use App\Service\Security;
10
11class ImageUploadController
12{
13 const ALLOWED_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'tiff', 'tif'];
14
15 static public function imagickCleanAndWriteImage(string $image_data, string $local_path): bool // "string" parce que file_get_contents...
16 {
17 $format = strtolower(pathinfo($local_path)['extension']);
18 try{
19 $imagick = new \Imagick();
20 $imagick->readImageBlob($image_data);
21 $imagick->stripImage(); // nettoyage métadonnées
22 //$imagick->setImageFormat($format); // inutile, writeImage force la conversion
23
24 // compression
25 switch($format){
26 case 'jpeg': // particularité du switch, si 'jpeg' le test de 'jpg' est ignoré et on va jusqu'au break
27 case 'jpg':
28 $imagick->setImageCompression(\Imagick::COMPRESSION_JPEG);
29 $imagick->setImageCompressionQuality(85);
30 break;
31 case 'webp':
32 $imagick->setImageCompression(\Imagick::COMPRESSION_WEBP);
33 $imagick->setImageCompressionQuality(85);
34 break;
35 case 'png':
36 $imagick->setImageCompression(\Imagick::COMPRESSION_ZIP);
37 $imagick->setImageCompressionQuality(7); // 9 est sans perte
38 break;
39 case 'tiff':
40 $imagick->setImageCompression(\Imagick::COMPRESSION_LZW); // LZW est sans perte
41 break;
42 }
43
44 // enregistrement
45 // writeImage utilise l'extension du fichier et ignore le format détecté
46 // imagemagick est à l'origine une appli console, elle considère que l'extension montre l'intention de l'utilisateur
47 $imagick->writeImage($local_path); // enregistrement
48 $imagick->clear();
49 $imagick->destroy();
50 return true;
51 }
52 catch(\Exception $e){
53 return false;
54 }
55 }
56 static public function curlDownloadImage(string $url, int $maxRetries = 3, int $timeout = 10): string|false
57 {
58 $attempt = 0;
59 $imageData = false;
60
61 while($attempt < $maxRetries){
62 $ch = curl_init($url); // instance de CurlHandle
63 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
64 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
65 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
66 curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
67 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
68 curl_setopt($ch, CURLOPT_USERAGENT, 'TinyMCE-Image-Downloader');
69
70 $imageData = curl_exec($ch);
71 $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
72 //$curlError = curl_error($ch);
73
74 if($imageData !== false && $httpCode >= 200 && $httpCode < 300){
75 return $imageData;
76 }
77
78 $attempt++;
79 sleep(1);
80 }
81
82 return false; // échec après trois tentatives
83 }
84
85 // téléchargement par le plugin (bouton "insérer une image")
86 static public function imageUploadTinyMce(): JsonResponse
87 {
88 if(!isset($_FILES['file'])){
89 return new JsonResponse(['message' => 'Erreur 400: Bad Request'], JsonResponse::HTTP_BAD_REQUEST); // code 400
90 }
91 if(!is_uploaded_file($_FILES['file']['tmp_name'])) {
92 return new JsonResponse(['message' => "Le fichier n'a pas été téléchargé correctement."], JsonResponse::HTTP_BAD_REQUEST); // code 400
93 }
94
95 $dest = 'user_data/images/';
96 $dest_mini = 'user_data/images-mini/';
97
98 // Vérifier si les répertoires existent, sinon les créer
99 if(!is_dir($dest)){
100 mkdir($dest, 0755, true);
101 }
102 if(!is_dir($dest_mini)){
103 mkdir($dest_mini, 0755, true);
104 }
105
106 $name = Security::secureFileName(pathinfo($_FILES['file']['name'], PATHINFO_FILENAME));
107 $extension = strtolower(pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION));
108 $image_data = file_get_contents($_FILES['file']['tmp_name']);
109 if(!in_array($extension, self::ALLOWED_EXTENSIONS)){
110 $extension = 'jpeg';
111 }
112 $local_path = uniqid($dest . $name . '_') . '.' . $extension;
113
114 // créer une miniature de l'image
115 //
116
117 if(self::imagickCleanAndWriteImage($image_data, $local_path)){ // recréer l’image pour la nettoyer
118 return new JsonResponse(['location' => $local_path]); // renvoyer l'URL de l'image téléchargée
119 }
120 else{
121 return new JsonResponse(['message' => 'Erreur image non valide'], JsonResponse::HTTP_INTERNAL_SERVER_ERROR); // code 500
122 }
123 }
124
125 // collage de HTML => recherche de balises <img>, téléchargement côté serveur et renvoi de l'adresse sur le serveur
126 static public function uploadImageHtml(): JsonResponse
127 {
128 $json = json_decode(file_get_contents('php://input'), true);
129
130 if(!isset($json['image_url'])){
131 return new JsonResponse(['message' => "Erreur 400: Bad Request"], JsonResponse::HTTP_BAD_REQUEST); // code 400
132 }
133
134 $image_data = self::curlDownloadImage($json['image_url']); // téléchargement de l’image par le serveur avec cURL au lieu de file_get_contents
135 if(!$image_data){
136 return new JsonResponse(['message' => "Erreur, le serveur n'a pas réussi à télécharger l'image."], JsonResponse::HTTP_BAD_REQUEST); // code 400
137 }
138
139 $dest = 'user_data/images/';
140 if(!is_dir($dest)){ // Vérifier si le répertoire existe, sinon le créer
141 mkdir($dest, 0755, true);
142 }
143
144 $url_path = parse_url($json['image_url'], PHP_URL_PATH);
145 $name = Security::secureFileName(pathinfo($url_path, PATHINFO_FILENAME));
146 $extension = strtolower(pathinfo($url_path, PATHINFO_EXTENSION));
147 if(!in_array($extension, self::ALLOWED_EXTENSIONS) || $extension === 'jpg'){
148 $extension = 'jpeg';
149 }
150 $local_path = uniqid($dest . $name . '_') . '.' . $extension;
151
152 if(self::imagickCleanAndWriteImage($image_data, $local_path)){ // recréer l’image pour la nettoyer
153 return new JsonResponse(['location' => $local_path]); // nouvelle adresse
154 }
155 else{
156 return new JsonResponse(['message' => 'Erreur image non valide', 'format' => $extension], JsonResponse::HTTP_INTERNAL_SERVER_ERROR); // code 500
157 }
158 }
159
160 // collage simple d'une image (base64 dans le presse-papier) non encapsulée dans du HTML
161 static public function uploadImageBase64(): JsonResponse
162 {
163 $json = json_decode(file_get_contents('php://input'), true);
164 $dest = 'user_data/images/';
165
166 if(!is_dir($dest)){
167 mkdir($dest, 0755, true);
168 }
169
170 // détection de data:image/ et de ;base64, et capture du format dans $type
171 if(!isset($json['image_base64']) || !preg_match('/^data:image\/(\w+);base64,/', $json['image_base64'], $type)){
172 return new JsonResponse(['message' => 'Données image base64 manquantes ou invalides'], JsonResponse::HTTP_BAD_REQUEST); // code 400
173 }
174
175 $extension = strtolower($type[1]); // dans (\w+)
176 if(!in_array($extension, self::ALLOWED_EXTENSIONS)){
177 $extension = 'jpeg';
178 }
179
180 $name = 'pasted_image';
181 $image_data = base64_decode(substr($json['image_base64'], strpos($json['image_base64'], ',') + 1)); // découpe la chaine à la virgule puis convertit en binaire
182 if($image_data === false){
183 return new JsonResponse(['message' => 'Décodage base64 invalide'], JsonResponse::HTTP_BAD_REQUEST); // code 400
184 }
185 $local_path = uniqid($dest . $name . '_') . '.' . $extension;
186
187 if(self::imagickCleanAndWriteImage($image_data, $local_path)){
188 return new JsonResponse(['location' => $local_path]);
189 }
190 else{
191 return new JsonResponse(['message' => 'Erreur image non valide', 'format' => $extension], JsonResponse::HTTP_INTERNAL_SERVER_ERROR); // code 500
192 }
193 }
194} \ No newline at end of file
diff --git a/src/Controller/MaintenanceController.php b/src/Controller/MaintenanceController.php
new file mode 100644
index 0000000..d3be2a1
--- /dev/null
+++ b/src/Controller/MaintenanceController.php
@@ -0,0 +1,152 @@
1<?php
2// src/Controller/MaintenanceController.php
3
4declare(strict_types=1);
5
6namespace App\Controller;
7
8use Symfony\Component\HttpFoundation\Request;
9use Symfony\Component\HttpFoundation\JsonResponse;
10use Symfony\Component\HttpFoundation\BinaryFileResponse;
11use Symfony\Component\HttpFoundation\ResponseHeaderBag;
12use Symfony\Component\HttpFoundation\RedirectResponse;
13use Doctrine\ORM\EntityManager;
14use App\Entity\log;
15use App\Service\Backup;
16use App\Service\URL;
17use App\Service\Config;
18
19class MaintenanceController
20{
21 static public function getLogs(EntityManager $entityManager): JsonResponse
22 {
23 $data = $entityManager->getRepository(Log::class)->findAll();
24 if(empty($data)){
25 return new JsonResponse(['success' => false]);
26 }
27 else{
28 $view = '<h4>Table ' . TABLE_PREFIX . 'log de la base de données</h4>
29 <table>
30 <thead>
31 <tr>
32 <th>date et heure (Greenwich)</th>
33 <th>connexion réussie</th>
34 </tr>
35 </thead>
36 <tbody>';
37 foreach($data as $entry){
38 $view .= '<tr>
39 <td>' . $entry->getFormatedDate() . '</td>
40 <td>' . ($entry->getSuccess() ? 'oui' : 'non') . '</td>
41 </tr>';
42 }
43 $view .= '</tbody></table>';
44 return new JsonResponse(['success' => true, 'view' => $view]);
45 }
46 }
47 static public function eraseLogs(EntityManager $entityManager): JsonResponse
48 {
49 try{
50 $table = $entityManager->getClassMetadata(Log::class)->getTableName();
51 $entityManager->getConnection()->executeStatement("TRUNCATE TABLE {$table}"); // SQL donné à DBAL
52 return new JsonResponse(['success' => true]);
53 }
54 catch(\Exception $e){
55 return new JsonResponse(['success' => false, 'error' => $e->getMessage()]);
56 }
57 }
58
59 static public function getLastDump(EntityManager $entityManager): BinaryFileResponse|RedirectResponse
60 {
61 try{
62 $backup_list = Backup::getBackupList();
63 $nb = count($backup_list);
64
65 if($nb <= 0){ // se produit à la première connexion en mode admin pour une raison algorithimque
66 Backup::mySQLdump($entityManager, 'auto');
67 $backup_list = Backup::getBackupList();
68 $nb = count($backup_list);
69 if($nb <= 0){ // improbable, les dossiers devraient déjà avoir été créés
70 throw new \RuntimeException("Le serveur a rencontré une erreur: aucun backup n'est disponible et ce n'est pas normal.");
71 }
72 }
73
74 $file_path = Backup::$backup_dir . '/' . $backup_list[$nb - 1];
75 $response = new BinaryFileResponse($file_path);
76 $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT); // ne pas essayer de l'afficher dans le navigateur
77 }
78 catch(\RuntimeException $e){
79 $_SESSION['flash_message'] = $e->getMessage();
80 $response = new RedirectResponse((string) new URL(['page' => 'maintenance']));
81 }
82 return $response;
83 }
84 static public function getAllMedia(): BinaryFileResponse|RedirectResponse
85 {
86 try{
87 $file_path = '../var/' . Backup::createZip('all_media.zip', ['user_data/assets', 'user_data/images', 'user_data/media']);
88 $response = new BinaryFileResponse($file_path);
89 $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT); // ne pas essayer de l'afficher dans le navigateur
90 }
91 catch(\RuntimeException $e){
92 $_SESSION['flash_message'] = $e->getMessage();
93 $response = new RedirectResponse((string) new URL(['page' => 'maintenance']));
94 }
95 return $response;
96 }
97
98 // parce qu'il faut un contrôleur
99 static public function handleBackupSelection(EntityManager $entityManager, Request $request): RedirectResponse
100 {
101 $selected_file = $request->request->get('selected_sql');
102 $url = new URL;
103 if($request->query->has('from')){
104 $url->addParams(['page' => $request->query->get('from')]);
105 }
106
107 try{
108 if(pathinfo($selected_file)['extension'] !== 'sql'){ // pas censé se produire en fait
109 throw new \Exception("charger un fichier au format SQL");
110 }
111 Backup::restoreDatabase($entityManager, $selected_file, ['user']);
112
113 $_SESSION['flash_message'] = "La base de données a été restaurée avec succès !!";
114 }
115 catch(\Exception $e){
116 $_SESSION['flash_message'] = "Une erreur s'est produite: " . $e->getMessage();
117 }
118
119 return new RedirectResponse((string)$url);
120 }
121
122 static public function downloadSQL(EntityManager $entityManager, Request $request): RedirectResponse
123 {
124 $uploaded_file = $request->files->get('uploaded_sql');
125 $date = new \DateTime;
126 $server_place = Config::$database . '_' . $date->format('Y-m-d') . '_uploaded.sql';
127 $url = new URL;
128 if($request->query->has('from')){
129 $url->addParams(['page' => $request->query->get('from')]);
130 }
131
132 try{
133 if(pathinfo($uploaded_file->getClientOriginalName())['extension'] !== 'sql'){
134 throw new \Exception("Charger un fichier au format SQL");
135 }
136 //echo $uploaded_file->getSize(); // à garder de côté au cas où
137
138 // enregistrer le fichier
139 $uploaded_file->move(Backup::$backup_dir, $server_place);
140
141 // s'en servir
142 Backup::restoreDatabase($entityManager, $server_place, ['user']);
143
144 $_SESSION['flash_message'] = "La base de données a été restaurée avec succès !!";
145 }
146 catch(\Exception $e){
147 $_SESSION['flash_message'] = "Une erreur s'est produite: " . $e->getMessage();
148 }
149
150 return new RedirectResponse((string)$url);
151 }
152} \ No newline at end of file
diff --git a/src/Controller/MenuAndPathsController.php b/src/Controller/MenuAndPathsController.php
new file mode 100644
index 0000000..1916f06
--- /dev/null
+++ b/src/Controller/MenuAndPathsController.php
@@ -0,0 +1,226 @@
1<?php
2// src/Controller/MenuAndPathsController.php
3
4declare(strict_types=1);
5
6namespace App\Controller;
7
8use Doctrine\ORM\EntityManager;
9use Symfony\Component\HttpFoundation\JsonResponse;
10use Symfony\Component\HttpFoundation\RedirectResponse;
11use App\Entity\Page;
12use App\Model\Menu;
13use App\Model\Model;
14use App\Service\URL;
15use App\View\NavBuilder;
16use App\View\MenuBuilder;
17
18class MenuAndPathsController
19{
20 static public function newUrlMenuEntry(EntityManager $entityManager): RedirectResponse
21 {
22 Model::$menu = new Menu($entityManager);
23 $previous_page = Model::$menu->findPageById((int)$_POST["location"]); // (int) à cause de declare(strict_types=1);
24 $parent = $previous_page->getParent();
25
26 $url_input = trim($_POST["url_input"]); // faire htmlspecialchars à l'affichage
27 if(!filter_var($url_input, FILTER_VALIDATE_URL) || !str_starts_with($url_input, 'http')){
28 // utiliser une flash error
29 return new RedirectResponse((string)new URL(['page' => $_GET['from'], 'error' => 'invalide_url']));
30 }
31
32 $page = new Page(
33 trim(htmlspecialchars($_POST["label_input"])),
34 $url_input, '',
35 true, true, false,
36 $previous_page->getPosition(),
37 $parent); // peut et DOIT être null si on est au 1er niveau
38
39 // on a donné à la nouvelle entrée la même position qu'à la précédente,
40 // addChild l'ajoute à la fin du tableau "children" puis on trie
41 // exemple avec 2 comme position demandée: 1 2 3 4 2 devient 1 2 3 4 5 et la nouvelle entrée sera en 3è position
42 if(!$parent){
43 $parent = Model::$menu;
44 }
45 $parent->addChild($page); // true pour réindexer les positions en BDD
46 $parent->reindexPositions();
47
48 $entityManager->persist($page);
49 $entityManager->flush();
50 return new RedirectResponse((string)new URL(['page' => $_GET['from']]));
51 }
52
53 // on pourrait utiliser FormValidation ici
54 static public function editUrl(EntityManager $entityManager, array $json): JsonResponse
55 {
56 $url_data = trim($json['input_data']); // garder htmlspecialchars pour l'affichage
57 $page = $entityManager->find('App\Entity\Page', $json['id']);
58
59 if(!$page){
60 return new JsonResponse(['success' => false, 'message' => "id invalide"]);
61 }
62 elseif(!in_array($json['field'], ['url_name', 'url_content'])){
63 return new JsonResponse(['success' => false, 'message' => "champ invalide"]);
64 }
65 elseif($json['field'] === 'url_content' && (!filter_var($url_data, FILTER_VALIDATE_URL) || !str_starts_with($url_data, 'http'))){
66 return new JsonResponse(['success' => false, 'message' => "la chaîne envoyée n'est pas une URL valide"]);
67 }
68 else{
69 if($json['field'] === 'url_name'){
70 $page->setPageName($url_data);
71 }
72 elseif($json['field'] === 'url_content'){
73 $page->setEndOfPath($url_data);
74 }
75 $entityManager->flush();
76 return new JsonResponse(['success' => true, 'url_data' => $url_data]);
77 }
78 }
79
80 static public function deleteUrlMenuEntry(EntityManager $entityManager): RedirectResponse
81 {
82 Model::$menu = new Menu($entityManager);
83 $page = Model::$menu->findPageById((int)$_POST["delete"]);
84 $parent = $page->getParent();
85 if($parent == null){
86 $parent = Model::$menu;
87 }
88
89 $parent->removeChild($page); // suppression de $children avant de trier
90 $parent->reindexPositions();
91
92 $entityManager->remove($page); // suppression en BDD
93 $entityManager->flush();
94 return new RedirectResponse((string)new URL(['page' => $_GET['from']]));
95 }
96
97 static public function MoveOneLevelUp(EntityManager $entityManager, array $json): JsonResponse
98 {
99 $id = $json['id'];
100 $page = Model::$menu->findPageById((int)$id);
101
102 $parent = $page->getParent(); // peut être null
103 if($parent === null){
104 // 1er niveau: ne rien faire
105 return new JsonResponse(['success' => false, 'message' => 'nothing to do']);
106 }
107 // BDD
108 else{
109 $page->setPosition($parent->getPosition() + 1); // nouvelle position
110
111 // 2ème niveau: le parent devient $menu, puis null après tri
112 if($parent->getParent() === null){
113 // connexion dans les deux sens
114 $page->setParent(Model::$menu); // => pour la persistance
115
116 //Model::$menu->addChild($page); // => pour sortChildren
117 $page->getParent()->addChild($page); // => pour sortChildren
118 $page->getParent()->sortChildren(true); // positions décaléees des nouveaux petits frères
119 $page->setParent(null);
120
121 // affichage
122 $page->setPagePath($page->getEndOfPath());
123 $page->fillChildrenPagePath();
124 }
125 // 3ème niveau et plus
126 else{
127 $page->setParent($parent->getParent()); // nouveau parent
128 $page->getParent()->addChild($page); // => pour sortChildren
129 $page->getParent()->sortChildren(true); // positions décaléees des nouveaux petits frères
130 $page->fillChildrenPagePath($page->getParent()->getPagePath());
131 }
132 $entityManager->flush();
133
134 // affichage
135 $parent->removeChild($page);
136 $nav_builder = new NavBuilder();
137 $menu_builder = new MenuBuilder(null, false);
138 return new JsonResponse(['success' => true, 'nav' => $nav_builder->render(), 'menu_buttons' => $menu_builder->render()]);
139 }
140 }
141
142 static public function MoveOneLevelDown(EntityManager $entityManager, array $json): JsonResponse
143 {
144 $id = $json['id'];
145 $page = Model::$menu->findPageById((int)$id);
146
147 $parent = $page->getParent(); // peut être null
148 if($parent === null){
149 $parent = Model::$menu;
150 }
151
152 // BDD
153 $parent->sortChildren(true); // trie et réindexe par sécurité: 1, 2, 3...
154 if($page->getPosition() > 1){
155 foreach($parent->getChildren() as $child){
156 if($child->getPosition() === $page->getPosition() - 1){
157 // refus si $parent est une adresse, ça va casser le lien, exemple: index.php?page=chemin/http://un_site_web.fr/vers/ici
158 if(str_starts_with($child->getEndOfPath(), 'http')){
159 return new JsonResponse(['success' => false, 'error' => 'new_parent_is_a_link']);
160 }
161
162 $page->setParent($child);
163 break;
164 }
165 }
166 $page->setPosition(count($page->getParent()->getChildren()) + 1);
167 }
168 $entityManager->flush();
169
170 // affichage
171 $parent->removeChild($page);
172 $page->getParent()->addChild($page);
173 $page->fillChildrenPagePath($page->getParent()->getPagePath()); // variable non mappée $page_path
174 $nav_builder = new NavBuilder();
175 $menu_builder = new MenuBuilder(null, false);
176
177 return new JsonResponse(['success' => true, 'nav' => $nav_builder->render(), 'menu_buttons' => $menu_builder->render()]);
178 }
179
180 static public function SwitchPositions(EntityManager $entityManager, array $json): JsonResponse
181 {
182 $id1 = $json['id1'];
183 $id2 = $json['id2'];
184
185 // vérifier qu'ils ont le même parent
186 $page1 = Model::$menu->findPageById((int)$id1);
187 $page2 = Model::$menu->findPageById((int)$id2);
188
189 // double le contrôle fait en JS
190 if($page1->getParent() === $page2->getParent()) // comparaison stricte d'objet (même instance du parent?)
191 {
192 // inversion
193 $tmp = $page1->getPosition();
194 $page1->setPosition($page2->getPosition());
195 $page2->setPosition($tmp);
196 Model::$menu->sortChildren(true); // modifie tableau children
197 $entityManager->flush();
198
199 // nouveau menu
200 $nav_builder = new NavBuilder();
201 return new JsonResponse(['success' => true, 'nav' => $nav_builder->render()]);
202 }
203 else{
204 return new JsonResponse(['success' => false]);
205 }
206 }
207
208 static public function displayInMenu(EntityManager $entityManager, array $json): JsonResponse
209 {
210 $id = $json['id'];
211 $checked = $json['checked'];
212
213 $page = Model::$menu->findPageById((int)$id);
214 if($page->isHidden() === $checked){
215 $page->setHidden(!$checked);
216 $entityManager->flush();
217
218 // nouveau menu
219 $nav_builder = new NavBuilder();
220 return new JsonResponse(['success' => true, 'nav' => $nav_builder->render()]);
221 }
222 else{
223 return new JsonResponse(['success' => false]);
224 }
225 }
226} \ No newline at end of file
diff --git a/src/Controller/PageManagementController.php b/src/Controller/PageManagementController.php
new file mode 100644
index 0000000..02e6e6a
--- /dev/null
+++ b/src/Controller/PageManagementController.php
@@ -0,0 +1,368 @@
1<?php
2// src/Controller/PageManagementController.php
3
4declare(strict_types=1);
5
6namespace App\Controller;
7
8use Doctrine\ORM\EntityManager;
9use Symfony\Component\HttpFoundation\InputBag;
10use Symfony\Component\HttpFoundation\JsonResponse;
11use Symfony\Component\HttpFoundation\RedirectResponse;
12use Symfony\Component\HttpFoundation\Request;
13use App\Entity\Page;
14use App\Entity\Node;
15use App\Entity\NodeData;
16use App\Entity\EmailForm;
17use App\Entity\Presentation;
18//use App\Entity\Image;
19use App\Model\Model;
20use App\Model\Menu;
21use App\Model\Path;
22use App\Service\URL;
23
24class PageManagementController
25{
26 /* -- partie page -- */
27 static public function setPageTitle(EntityManager $entityManager, array $json): JsonResponse
28 {
29 $page = $entityManager->find('App\Entity\Page', $json['page_id']);
30 $page->setPageName(htmlspecialchars($json['title']));
31 $entityManager->flush();
32 return new JsonResponse(['success' => true, 'title' => $page->getPageName()]);
33 }
34
35 static public function updatePageMenuPath(EntityManager $entityManager, string $page_menu_path): RedirectResponse
36 {
37 Model::$menu = new Menu($entityManager);
38 Model::$page_path = new Path();
39 $page = Model::$page_path->getLast();
40 $path = htmlspecialchars($page_menu_path);
41
42 // mise en snake_case: filtre caractères non-alphanumériques, minuscule, doublons d'underscore, trim des underscores
43 $path = trim(preg_replace('/_+/', '_', strtolower(preg_replace('/[^a-zA-Z0-9]/', '_', $path))), '_');
44 $page->setEndOfPath($path);
45 foreach(Model::$menu->getChildren() as $child){
46 if($child->getEndOfPath() === Model::$page_path->getArray()[0]->getEndOfPath()){
47 $child->fillChildrenPagePath(); // MAJ de $page_path
48 }
49 }
50 $entityManager->flush();
51 return new RedirectResponse((string)new URL(['page' => $page->getPagePath(), 'mode' => 'page_modif']));
52 }
53
54 static public function setPageDescription(EntityManager $entityManager, array $json): JsonResponse
55 {
56 $page = $entityManager->find('App\Entity\Page', $json['page_id']);
57 $page->setDescription(htmlspecialchars($json['description']));
58 $entityManager->flush();
59 return new JsonResponse(['success' => true, 'description' => $page->getDescription()]);
60 }
61
62 static public function newPage(EntityManager $entityManager, InputBag $post): RedirectResponse
63 {
64 // titre et chemin
65 Model::$menu = new Menu($entityManager);
66 $previous_page = Model::$menu->findPageById((int)$post->get("page_location")); // (int) à cause de declare(strict_types=1);
67 $parent = $previous_page->getParent();
68
69 $page = new Page(
70 trim(htmlspecialchars($post->get("page_name"))),
71 trim(htmlspecialchars($post->get("page_name_path"))),
72 trim(htmlspecialchars($post->get("page_description"))),
73 true, true, false,
74 $previous_page->getPosition(),
75 $parent); // peut et DOIT être null si on est au 1er niveau
76
77 // on a donné à la nouvelle entrée la même position qu'à la précédente,
78 // addChild l'ajoute à la fin du tableau "children" puis on trie
79 // exemple avec 2 comme position demandée: 1 2 3 4 2 devient 1 2 3 4 5 et la nouvelle entrée sera en 3è position
80 if($parent == null){
81 $parent = Model::$menu;
82 }
83 $parent->addChild($page);
84 $parent->reindexPositions();
85
86 $page->setPagePath(ltrim($parent->getPagePath() . '/' . $page->getEndOfPath(), '/'));
87
88 $entityManager->persist($page);
89 $entityManager->flush();
90
91 // page créée, direction la page en mode modification pour ajouter des blocs
92 return new RedirectResponse((string)new URL(['page' => $page->getPagePath(), 'mode' => 'page_modif']));
93 }
94
95 static public function deletePage(EntityManager $entityManager, string $page_id): RedirectResponse
96 {
97 $menu = new Menu($entityManager);
98 $page = $menu->findPageById((int)$page_id);
99 $url = new URL;
100
101 // test dernière page
102 // => $menu a un enfant et pas de petits enfants
103 // => $menu->getChildren()->isEmpty() n'est théoriquement pas possible
104 if($menu->getChildren()->isEmpty() || (count($menu->getChildren()) === 1 && $menu->getChildren()[0]->getChildren()->isEmpty())){
105 $url->addParams(['page' => $page->getEndOfPath(), 'error' => 'you_tried_to_remove_the_last_page']);
106 }
107 else{
108 $nodes = $entityManager->getRepository('App\Entity\Node')->findBy(['page' => $page]);
109 $data = [];
110 foreach($nodes as $node){
111 $data[] = $entityManager->getRepository('App\Entity\NodeData')->findOneBy(['node' => $node]);
112 $entityManager->remove($node);
113 }
114 foreach($data as $one_data){
115 $entityManager->remove($one_data);
116 }
117 $entityManager->remove($page); // suppression en BDD
118 $entityManager->flush();
119
120 // destination vers la première page dispo
121 // traite les cas prévus dans le if plus haut
122 $menu->removeChild($page);
123 $next_page = $menu->getChildren()->isEmpty() ? $next_page = $page->getChildren()[0] : $menu->getChildren()[0];
124 $url->addParams(['page' => $next_page->getEndOfPath()]);
125 }
126
127 return new RedirectResponse((string)$url);
128 }
129
130 /* partie "blocs" */
131 static public function addBloc(EntityManager $entityManager, Request $request): RedirectResponse
132 {
133 $model = new Model($entityManager);
134 $model->makeMenuAndPaths(); // on a besoin de page_path qui dépend de menu
135 $page = Model::$page_path->getLast();
136 $model->findUniqueNodeByName('main');
137 $model->findItsChildren();
138 $main = $model->getNode();
139 $position = count($main->getChildren()) + 1; // position dans la fraterie
140
141 if(!in_array($request->request->get("bloc_select"), array_keys(Presentation::BLOCKS), true)){ // 3è param: contrôle du type
142 // utiliser une flash error
143 return new RedirectResponse((string)new URL(['page' => $request->query->get('page'), 'error' => 'bad_bloc_type']));
144 }
145
146 if(in_array($request->request->get("bloc_select"), ['calendar', 'form'])){
147 $page->addCSS($request->request->get("bloc_select"));
148 if($request->request->get("bloc_select") === 'form'){
149 $page->addJS($request->request->get("bloc_select"));
150 }
151 $entityManager->persist($page);
152 }
153
154 $block = new Node($request->request->get("bloc_select"), $position, $main, $page);
155
156 $DataClass = match($request->request->get("bloc_select")){
157 'form' => EmailForm::class,
158 'post_block', 'news_block' => Presentation::class,
159 'calendar' => NodeData::class,
160 default => throw new \InvalidArgumentException("Erreur: type de bloc inconnu"),
161 };
162 $data = new $DataClass(['title' => trim(htmlspecialchars($request->request->get("bloc_title")))], $block);
163
164 // valeurs par défaut
165 if($request->request->get("bloc_select") === 'post_block'){
166 $data->setPresentation('fullwidth');
167 }
168 elseif($request->request->get("bloc_select") === 'news_block'){
169 $data->setPresentation('grid');
170 }
171 elseif($request->request->get("bloc_select") === 'galery'){
172 $data->setPresentation('mosaic'); // un jour on mettra carousel
173 }
174 // else = null par défaut
175
176 $entityManager->persist($block);
177 $entityManager->persist($data);
178 $entityManager->flush();
179 return new RedirectResponse((string)new URL(['page' => $request->query->get('page'), 'mode' => 'page_modif']));
180 }
181
182 static public function deleteBloc(EntityManager $entityManager, Request $request): RedirectResponse
183 {
184 $model = new Model($entityManager);
185 $model->makeMenuAndPaths();
186 $model->findUniqueNodeByName('main');
187 $model->findItsChildren();
188 $main = $model->getNode();
189
190 $block = null;
191 $type = '';
192 $nb_same_type = 0;
193 foreach($main->getChildren() as $child){
194 if($child->getId() === (int)$request->request->get('delete_bloc_id')){
195 $block = $child;
196 $type = $block->getName();
197 }
198 if($child->getName() === $type){
199 $nb_same_type++;
200 }
201 }
202
203 // nettoyage fichiers CSS et JS si on retire le derner bloc de ce type
204 if($nb_same_type === 1 && in_array($block->getName(), ['calendar', 'form'])){
205 $page = $block->getPage();
206 $page->removeCSS($block->getName());
207 if($block->getName() === 'form'){
208 $page->removeJS($block->getName());
209 }
210 }
211
212 if(!empty($block)){ // si $block est null c'est que le HTML a été modifié volontairement
213 $main->removeChild($block); // réindex le tableau $children au passage
214 $main->reindexPositions();
215 if(isset($page)){
216 $entityManager->persist($page);
217 }
218 $block->getNodeData()->setNode(null);
219 $entityManager->remove($block);
220 try{
221 $entityManager->flush();
222 }
223 catch(\Exception $e){
224 // utiliser une flash error
225 return new RedirectResponse((string)new URL(['page' => $request->query->get('page'), 'mode' => 'page_modif', 'error' => $e->getMessage()]));
226 }
227 }
228
229 return new RedirectResponse((string)new URL(['page' => $request->query->get('page'), 'mode' => 'page_modif']));
230 }
231
232 static public function renameBloc(EntityManager $entityManager, array $json): JsonResponse
233 {
234 if(isset($json['bloc_title']) && $json['bloc_title'] !== null && isset($json['bloc_id']) && is_int($json['bloc_id'])){
235 $model = new Model($entityManager);
236 $model->findNodeById($json['bloc_id']);
237
238 // le titre (du JSON en BDD) est récupéré sous forme de tableau, modifié et renvoyé
239 $data = $model->getNode()->getNodeData()->getData();
240 $data['title'] = htmlspecialchars($json['bloc_title']);
241 $model->getNode()->getNodeData()->updateData('title', htmlspecialchars($json['bloc_title']));
242
243 $entityManager->flush();
244 return new JsonResponse(['success' => true, 'title' => $data['title']]);
245 }
246 else{
247 return new JsonResponse(['success' => false]);
248 }
249 }
250
251 static public function SwitchBlocsPositions(EntityManager $entityManager, array $json, string $page): JsonResponse
252 {
253 if(isset($json['id1']) && is_int($json['id1']) && isset($json['id2']) && is_int($json['id2']) && isset($page)){
254 $model = new Model($entityManager);
255 $model->makeMenuAndPaths(); // true pour $model->findItsChildren();
256 $model->findUniqueNodeByName('main');
257 $model->findItsChildren();
258 $main = $model->getNode();
259 $main->sortChildren(true); // régénère les positions avant inversion
260
261 $bloc1 = null;
262 $bloc2 = null;
263 foreach($main->getChildren() as $child){
264 if($child->getId() === $json['id1']){
265 $bloc1 = $child;
266 break;
267 }
268 }
269 foreach($main->getChildren() as $child){
270 if($child->getId() === $json['id2']){
271 $bloc2 = $child;
272 break;
273 }
274 }
275
276 // inversion
277 $tmp = $bloc1->getPosition();
278 $bloc1->setPosition($bloc2->getPosition());
279 $bloc2->setPosition($tmp);
280
281 $entityManager->flush();
282 return new JsonResponse(['success' => true]);
283 }
284 else{
285 return new JsonResponse(['success' => false]);
286 }
287 }
288
289 static public function changeArticlesOrder(EntityManager $entityManager, array $json): JsonResponse
290 {
291 if(isset($json['id']) && isset($json['chrono_order'])){
292 $model = new Model($entityManager);
293 $model->findNodeById($json['id']);
294
295 if($json['chrono_order'] === 'chrono'){
296 $chrono_order = true;
297 }
298 elseif($json['chrono_order'] === 'antichrono'){
299 $chrono_order = false;
300 }
301 else{
302 return new JsonResponse(['success' => false]);
303 }
304 $model->getNode()->getNodeData()->setChronoOrder($chrono_order);
305 $entityManager->flush();
306
307 return new JsonResponse(['success' => true, 'chrono_order' => $json['chrono_order']]);
308 }
309 else{
310 return new JsonResponse(['success' => false]);
311 }
312 }
313
314 static public function changePresentation(EntityManager $entityManager, array $json): JsonResponse
315 {
316 if(isset($json['id']) && isset($json['presentation'])){
317 $model = new Model($entityManager);
318 $model->findNodeById($json['id']);
319
320 if(in_array($json['presentation'], array_keys(Presentation::PRESENTATIONS))){
321 $model->getNode()->getNodeData()->setPresentation($json['presentation']);
322 $entityManager->flush();
323
324 $response_data = ['success' => true, 'presentation' => $json['presentation']];
325 if($json['presentation'] === 'grid'){
326 $response_data['cols_min_width'] = $model->getNode()->getNodeData()->getColsMinWidth();
327 }
328 return new JsonResponse($response_data);
329 }
330 else{
331 return new JsonResponse(['success' => false]);
332 }
333 }
334 else{
335 return new JsonResponse(['success' => false]);
336 }
337 }
338 static public function changeColsMinWidth(EntityManager $entityManager, array $json): JsonResponse
339 {
340 if(isset($json['id']) && isset($json['cols_min_width'])){
341 $model = new Model($entityManager);
342 $model->findNodeById($json['id']);
343 $model->getNode()->getNodeData()->setColsMinWidth((int)$json['cols_min_width']); // attention conversion?
344
345 $entityManager->flush();
346 return new JsonResponse(['success' => true, 'cols_min_width' => $json['cols_min_width']]);
347 }
348 else{
349 return new JsonResponse(['success' => false]);
350 }
351 }
352 static public function changePaginationLimit(EntityManager $entityManager, array $json): JsonResponse
353 {
354 if(isset($json['id']) && isset($json['pagination_limit'])){
355 $model = new Model($entityManager);
356 $model->findNodeById($json['id']);
357 $old_limit = $model->getNode()->getNodeData()->getPaginationLimit() ?? 12;
358 $model->getNode()->getNodeData()->setPaginationLimit((int)$json['pagination_limit']); // attention conversion?
359
360 $entityManager->flush();
361
362 return new JsonResponse(['success' => true, 'old_limit' => $old_limit, 'new_limit' => $json['pagination_limit']]);
363 }
364 else{
365 return new JsonResponse(['success' => false]);
366 }
367 }
368} \ No newline at end of file
diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php
new file mode 100644
index 0000000..812a2da
--- /dev/null
+++ b/src/Controller/UserController.php
@@ -0,0 +1,237 @@
1<?php
2// src/Controller/UserController.php
3//
4/* actuellement un fourre-tout de méthodes en rapport avec les utilisateurs
5pour l'améliorer on pourrait appliquer le principe de reponsabilité unique
6=> UserController devrait se limiter à gérer des requêtes et réponses (changement transparent pour le routeur)
7il instancierait un classe correspondant au formulaire (prend POST et SESSION) et une classe "métier" UserService
8=> UserService contiendrait des méthodes utilisant l'objet formulaire pour agir sur la BDD
9=> les formulaires peuvent tenir dans une classe "UserUpdateForm" avec des stratégies ou plusieurs, les données y sont validées
10=> il est aussi possible de découper UserController en contrôleurs par fonctionnalité:
11Auth (connexion, deconnexion), User (infos, choix photo), Account (créer, supprimer compte), Avatar (upload photo...)
12*/
13
14declare(strict_types=1);
15
16namespace App\Controller;
17
18use Symfony\Component\HttpFoundation\RedirectResponse;
19use Doctrine\ORM\EntityManager;
20use App\Entity\User;
21use App\Entity\Log;
22use App\Service\FormValidation;
23use App\Service\ValidationStrategy;
24use App\Service\URL;
25use App\Service\Backup;
26use App\Service\EmailService;
27
28class UserController
29{
30 // account
31 static public function createAdminUser(EntityManager $entityManager): RedirectResponse
32 {
33 unset($_SESSION['user']);
34
35 $form = new FormValidation($_POST, ValidationStrategy::CreateUser);
36
37 $url = new URL;
38 $error = '';
39 if($form->validate()){
40 $password = password_hash($_POST['password'], PASSWORD_DEFAULT);
41 $user = new User($_POST['login'], 'admin', $password);
42 $entityManager->persist($user);
43 $entityManager->flush();
44 }
45 else{
46 $error = $form->getError();
47 }
48
49 if(!empty($error)){
50 $url->addParams(['error' => $error]);
51 }
52
53 return new RedirectResponse((string)$url);
54 }
55
56 // account
57 //static public function createUser(EntityManager $entityManager){}
58
59 // auth
60 static public function connect(EntityManager $entityManager): RedirectResponse
61 {
62 $url = new URL;
63 if(IS_ADMIN){ // déjà connecté?
64 return new RedirectResponse((string)$url);
65 }
66 unset($_SESSION['user']);
67
68 $form = new FormValidation($_POST, ValidationStrategy::Connection);
69
70 $error = '';
71 if($form->validate()){
72 // à mettre dans une classe métier UserService, Authentication, AuthService?
73 $user = self::getUserByName($_POST['login'], $entityManager);
74 if(!empty($user) && $_POST['login'] === $user->getLogin() && password_verify($_POST['password'], $user->getPassword())){
75 $log = new Log(true);
76
77 // protection fixation de session, si l'attaquant crée un cookie de session, il est remplacé
78 session_regenerate_id(true);
79 $_SESSION['user']['id'] = $user->getId();
80 $_SESSION['user']['username'] = $user->getLogin();
81 $_SESSION['user']['role'] = $user->getRole();
82
83 EmailService::cleanEmails($entityManager);
84
85 try{
86 if(file_exists('../var/backups')){
87 Backup::mySQLdump($entityManager, 'auto'); // créer un nouveau backup, sauf si les dossiers nécessaires sont encore à créer
88 }
89 }
90 catch(\RuntimeException $e){
91 echo '<script>window.error_message = "' . $e->getMessage() . '";</script>';
92 }
93
94 $url->addParams(isset($_GET['from']) ? ['page' => $_GET['from']] : []);
95 isset($_GET['id']) ? $url->addParams(['id' => $_GET['id']]) : '';
96 }
97 else
98 {
99 $log = new Log(false);
100 $error = 'bad_login_or_password';
101 }
102 $entityManager->persist($log);
103 $entityManager->flush();
104 }
105 else{
106 $error = $form->getError();
107 }
108
109 if(!empty($error)){
110 sleep(1); // défense basique à la force brute
111 $url->addParams(['page' => 'connection']);
112 isset($_GET['from']) ? $url->addParams(['from' => $_GET['from']]) : null;
113 isset($_GET['id']) ? $url->addParams(['id' => $_GET['id']]) : null;
114 $url->addParams(['error' => $error]);
115 }
116
117 return new RedirectResponse((string)$url);
118 }
119
120 // auth
121 static public function disconnect(): RedirectResponse
122 {
123 // nettoyage complet
124 unset($_SESSION['user']); // mémoire vive
125 session_destroy(); // fichier côté serveur
126 setcookie('PHPSESSID', '', time() - 86400, '/'); // cookie de session
127
128 // retour même page
129 $url = new URL;
130 isset($_GET['from']) ? $url->addParams(['page' => $_GET['from']]) : '';
131 isset($_GET['id']) ? $url->addParams(['id' => $_GET['id']]) : '';
132 return new RedirectResponse((string)$url);
133 }
134
135 // user
136 static public function updateUsername(EntityManager $entityManager): RedirectResponse
137 {
138 if(!IS_ADMIN){ // superflux, fait dans le routeur
139 return self::disconnect();
140 }
141
142 $url = new URL(['page' => 'user_edit']);
143 isset($_GET['from']) ? $url->addParams(['from' => $_GET['from']]) : null;
144
145 $form = new FormValidation($_POST, ValidationStrategy::UsernameUpdate);
146
147 $error = '';
148 if($form->validate()){
149 // à mettre dans une classe métier UserService?
150 $user = self::getUserByName($_POST['login'], $entityManager);
151 if(password_verify($_POST['password'], $user->getPassword())){
152 $user->setLogin($_POST['new_login']);
153 $entityManager->flush();
154 $_SESSION['user']['username'] = $_POST['new_login'];
155
156 $url->addParams(['success_username' => 'new_login']);
157 $error = '';
158 }
159 else{
160 $error = 'bad_login_or_password';
161 }
162 }
163 else{
164 $error = $form->getError();
165 }
166
167 if(!empty($error)){
168 sleep(1);
169 $url->addParams(['error_username' => $error]);
170 }
171 return new RedirectResponse((string)$url);
172 }
173
174 // user
175 static public function updatePassword(EntityManager $entityManager): RedirectResponse
176 {
177 if(!IS_ADMIN){ // superflux, fait dans le routeur
178 return self::disconnect();
179 }
180
181 $url = new URL(['page' => 'user_edit']);
182 isset($_GET['from']) ? $url->addParams(['from' => $_GET['from']]) : null;
183
184 $form = new FormValidation($_POST, ValidationStrategy::PasswordUpdate);
185
186 $error = '';
187 if($form->validate()){
188 // à mettre dans une classe métier UserService?
189 $user = self::getUserByName($_POST['login'], $entityManager);
190 if(password_verify($_POST['password'], $user->getPassword())){
191 $new_password = password_hash($_POST['new_password'], PASSWORD_DEFAULT);
192 $user->setPassword($new_password);
193 $entityManager->flush();
194
195 $url->addParams(['success_password' => 'new_password']);
196 $error = '';
197 }
198 else{
199 $error = 'bad_login_or_password';
200 }
201 }
202 else{
203 $error = $form->getError();
204 }
205
206 if(!empty($error)){
207 sleep(1);
208 $url->addParams(['error_password' => $error]);
209 }
210 return new RedirectResponse((string)$url);
211 }
212
213 // dans une classe mère ou un trait après découpage de UserController?
214 static private function getUserByName(string $login, EntityManager $entityManager): ?User
215 {
216 $users = $entityManager->getRepository(User::class)->findBy(['login' => $login]);
217 foreach($users as $user){
218 if($user->getLogin() === $login){
219 return $user;
220 }
221 }
222 return null;
223 }
224
225 static public function getUserById(int $id, EntityManager $entityManager): ?User
226 {
227 return $entityManager->find(User::class, $id);
228 }
229
230 // dans une classe Form?
231 // erreurs à la création des mots de passe
232 static private function removeSpacesTabsCRLF(string $chaine): string
233 {
234 $cibles = [' ', "\t", "\n", "\r"]; // doubles quotes !!
235 return(str_replace($cibles, '', $chaine));
236 }
237} \ No newline at end of file
diff --git a/src/Controller/ViewController.php b/src/Controller/ViewController.php
new file mode 100644
index 0000000..1f13861
--- /dev/null
+++ b/src/Controller/ViewController.php
@@ -0,0 +1,70 @@
1<?php
2// src/Controller/ViewController.php
3
4declare(strict_types=1);
5
6namespace App\Controller;
7
8use Doctrine\ORM\EntityManager;
9use Symfony\Component\HttpFoundation\Request;
10use Symfony\Component\HttpFoundation\Response;
11use Symfony\Component\HttpFoundation\RedirectResponse;
12use App\Model\Model;
13use App\View\ViewDirector;
14use App\View\MainBuilder;
15use App\View\NewBuilder;
16use App\Service\URL;
17
18class ViewController{
19 const SPECIAL_PAGES = ['article', 'new_page', 'menu_paths', 'user_edit', 'connection', 'emails', 'maintenance'];
20
21 static function getWebPage(EntityManager $entityManager, Request $request): Response
22 {
23 /* 1/ 1er contrôle des paramètres */
24
25 // mode modification d'une page
26 if(IS_ADMIN
27 && $request->query->has('mode') && $request->query->get('mode') === 'page_modif'
28 && !in_array(CURRENT_PAGE, self::SPECIAL_PAGES))
29 {
30 MainBuilder::$modif_mode = true;
31 }
32 // page article: mode création et erreurs d'id
33 if(CURRENT_PAGE === 'article'){
34 if(IS_ADMIN){
35 if(!$request->query->has('id')){
36 return new RedirectResponse((string)new URL(['page' => $request->query->get('from') ?? '']));
37 }
38 else{
39 // mode création d'article
40 // l'id du bloc et 'from=' sont vérifiés dans ArticleController::editorSubmit
41 if($request->query->get('id')[0] === 'n' && $request->query->has('from') && !empty($request->query->get('from'))){
42 NewBuilder::$new_article_mode = true;
43 }
44 }
45 }
46 elseif($request->query->get('id')[0] === 'n'){ // accès page nouvelle article interdit sans être admin
47 return new RedirectResponse((string)new URL(['page' => $request->query->get('from') ?? '']));
48 }
49 }
50 // pas de else, l'id dans l'URL n'a pas d'effet ailleurs
51
52
53 /* 2/ accès au modèle */
54 $model = new Model($entityManager);
55 $model->makeMenuAndPaths();
56 $model->getWholePageData($request);
57
58
59 /* 3/ 2ème contrôle des paramètres avec les données récupérées */
60
61 // article non trouvé en BDD
62 if(CURRENT_PAGE === 'article' && !IS_ADMIN && $model->getNode()->getNodeByName('main')->getAdoptedChild() === null){
63 return new RedirectResponse((string)new URL(['page' => $request->query->get('from') ?? '']));
64 }
65
66
67 /* 4/ construction de la page avec builders et vues */
68 return new Response((new ViewDirector)->buildHTML($model->getNode()));
69 }
70} \ No newline at end of file