summaryrefslogtreecommitdiff
path: root/src/controller/ajax_admin.php
diff options
context:
space:
mode:
authorpolo <ordipolo@gmx.fr>2025-07-04 00:43:35 +0200
committerpolo <ordipolo@gmx.fr>2025-07-04 00:43:35 +0200
commit2d7bacce891eab0adb0263d598bfe44418788f42 (patch)
tree4353250f80cc556a278640f22c194a0033d05829 /src/controller/ajax_admin.php
parentea3eaf84c6de3f96d6bb73e817147f8571fd6c1f (diff)
downloadcms-2d7bacce891eab0adb0263d598bfe44418788f42.zip
formulaire de contact 3
Diffstat (limited to 'src/controller/ajax_admin.php')
-rw-r--r--src/controller/ajax_admin.php626
1 files changed, 626 insertions, 0 deletions
diff --git a/src/controller/ajax_admin.php b/src/controller/ajax_admin.php
new file mode 100644
index 0000000..944e84b
--- /dev/null
+++ b/src/controller/ajax_admin.php
@@ -0,0 +1,626 @@
1<?php
2// src/controller/ajax_admin.php
3
4declare(strict_types=1);
5
6use App\Entity\Page;
7use App\Entity\Node;
8use App\Entity\Article;
9
10// mettre ça ailleurs
11function imagickCleanImage(string $image_data, string $local_path, string $format = 'jpeg'): bool // "string" parce que file_get_contents...
12{
13 try{
14 $imagick = new Imagick();
15 $imagick->readImageBlob($image_data);
16 $imagick->stripImage(); // nettoyage métadonnées
17 $imagick->setImageFormat($format);
18 if($format === 'jpeg'){
19 $imagick->setImageCompression(Imagick::COMPRESSION_JPEG);
20 $imagick->setImageCompressionQuality(85); // optionnel
21 }
22 $imagick->writeImage($local_path); // enregistrement
23 $imagick->clear();
24 $imagick->destroy();
25 return true;
26 }
27 catch(Exception $e){
28 return false;
29 }
30}
31function curlDownloadImage(string $url, $maxRetries = 3, $timeout = 10): string|false
32{
33 $attempt = 0;
34 $imageData = false;
35
36 while($attempt < $maxRetries){
37 $ch = curl_init($url); // instance de CurlHandle
38 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
39 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
40 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
41 curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
42 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
43 curl_setopt($ch, CURLOPT_USERAGENT, 'TinyMCE-Image-Downloader');
44
45 $imageData = curl_exec($ch);
46 $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
47 //$curlError = curl_error($ch);
48
49 curl_close($ch);
50
51 if($imageData !== false && $httpCode >= 200 && $httpCode < 300){
52 return $imageData;
53 }
54
55 $attempt++;
56 sleep(1);
57 }
58
59 return false; // échec après trois tentatives
60}
61
62
63// détection des requêtes d'upload d'image de tinymce
64if(strpos($_SERVER['CONTENT_TYPE'], 'multipart/form-data') !== false && isset($_GET['action']) && $_GET['action'] === 'upload_image')
65{
66 if(isset($_FILES['file'])){
67 $file = $_FILES['file'];
68 $dest = 'images/';
69 $dest_mini = 'images-mini/';
70
71 // Vérifier si les répertoires existent, sinon les créer
72 if(!is_dir($dest)) {
73 mkdir($dest, 0700, true);
74 }
75 if(!is_dir($dest_mini)) {
76 mkdir($dest_mini, 0700, true);
77 }
78
79 $allowed_extensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'tiff', 'tif'];
80 $name = Security::secureFileName(pathinfo($file['name'], PATHINFO_FILENAME));
81 $extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
82 if(!in_array($extension, $allowed_extensions) || $extension === 'jpg'){
83 $extension = 'jpeg';
84 }
85 $file_path = $dest . $name . '_' . uniqid() . '.' . $extension;
86
87 // créer une miniature de l'image
88 //
89
90 if(imagickCleanImage(file_get_contents($file['tmp_name']), $file_path, $extension)){ // recréer l’image pour la nettoyer
91 echo json_encode(['location' => $file_path]); // renvoyer l'URL de l'image téléchargée
92 }
93 else{
94 http_response_code(500);
95 echo json_encode(['message' => 'Erreur image non valide']);
96 }
97 }
98 else{
99 http_response_code(400);
100 echo json_encode(['message' => 'Erreur 400: Bad Request']);
101 }
102 die;
103}
104// cas du collage d'un contenu HTML, réception d'une URL, téléchargement par le serveur et renvoie de l'adresse sur le serveur
105elseif(isset($_GET['action']) && $_GET['action'] == 'upload_image_url')
106{
107 $json = json_decode(file_get_contents('php://input'), true);
108
109 if(isset($json['image_url'])){
110 $image_data = curlDownloadImage($json['image_url']); // téléchargement de l’image par le serveur avec cURL au lieu de file_get_contents
111 $dest = 'images/';
112
113 if(!is_dir($dest)) { // Vérifier si le répertoire existe, sinon le créer
114 mkdir($dest, 0777, true);
115 }
116
117 if($image_data === false){
118 http_response_code(400);
119 echo json_encode(['message' => "Erreur, le serveur n'a pas réussi à télécharger l'image."]);
120 die;
121 }
122
123 $allowed_extensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'tiff', 'tif'];
124 $url_path = parse_url($json['image_url'], PHP_URL_PATH);
125 $name = Security::secureFileName(pathinfo($url_path, PATHINFO_FILENAME));
126 $extension = strtolower(pathinfo($url_path, PATHINFO_EXTENSION));
127 if(!in_array($extension, $allowed_extensions) || $extension === 'jpg'){
128 $extension = 'jpeg';
129 }
130 $local_path = $dest . $name . '_' . uniqid() . '.' . $extension;
131
132 if(imagickCleanImage($image_data, $local_path, $extension)){ // recréer l’image pour la nettoyer
133 echo json_encode(['location' => $local_path]); // nouvelle adresse
134 }
135 else{
136 http_response_code(500);
137 echo json_encode(['message' => 'Erreur image non valide']);
138 }
139 }
140 else{
141 echo json_encode(['message' => 'Erreur 400: Bad Request']);
142 }
143 die;
144}
145// cas du collage d'une image (code base64) non encapsulée dans du HTML
146elseif(isset($_GET['action']) && $_GET['action'] == 'upload_image_base64')
147{
148 $json = json_decode(file_get_contents('php://input'), true);
149 $dest = 'images/';
150
151 if(!is_dir('images')){
152 mkdir('images', 0777, true);
153 }
154
155 // détection de data:image/ et de ;base64, et capture du format dans $type
156 if(!isset($json['image_base64']) || !preg_match('/^data:image\/(\w+);base64,/', $json['image_base64'], $type)){
157 http_response_code(400);
158 echo json_encode(['message' => 'Données image base64 manquantes ou invalides']);
159 die;
160 }
161
162 $allowed_extensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'tiff', 'tif'];
163 $extension = strtolower($type[1]);
164 if(!in_array($extension, $allowed_extensions) || $extension === 'jpg'){
165 $extension = 'jpeg';
166 }
167
168 $image_data = base64_decode(substr($json['image_base64'], strpos($json['image_base64'], ',') + 1)); // découpe la chaine à la virgule puis convertit en binaire
169 if($image_data === false){
170 http_response_code(400);
171 echo json_encode(['message' => 'Décodage base64 invalide']);
172 die;
173 }
174
175 $local_path = $dest . 'pasted_image_' . uniqid() . '.' . $extension;
176
177 if(imagickCleanImage($image_data, $local_path)){
178 echo json_encode(['location' => $local_path]);
179 }
180 else{
181 http_response_code(500);
182 echo json_encode(['message' => 'Erreur image non valide']);
183 }
184 die;
185}
186
187// détection des requêtes de type XHR, y en a pas à priori
188/*elseif(isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest'){
189 echo "requête XHR reçue par le serveur";
190 die;
191}*/
192
193
194// détection des requêtes envoyées avec fetch (application/json) et récupération du JSON
195if($_SERVER['CONTENT_TYPE'] === 'application/json')
196{
197 $data = file_get_contents('php://input');
198 $json = json_decode($data, true);
199
200 // requêtes de tinymce ou touchant aux articles
201 if(isset($_GET['action']))
202 {
203 if($_GET['action'] === 'editor_submit' && isset($json['id']) && isset($json['content']))
204 {
205 if(json_last_error() === JSON_ERROR_NONE)
206 {
207 $id = $json['id'];
208 $director = new Director($entityManager);
209
210 // cas d'une nouvelle "news"
211 if(is_array($json['content'])){
212 foreach($json['content'] as $one_input){
213 $one_input = Security::secureString($one_input);
214 }
215 $content = $json['content'];
216 }
217 else{
218 $content = Security::secureString($json['content']);
219 }
220
221 // nouvel article
222 if($id[0] === 'n')
223 {
224 $section_id = (int)substr($id, 1); // id du bloc <section>
225 $director->findNodeById($section_id);
226 $director->makeSectionNode();
227 $node = $director->getNode(); // = <section>
228
229 if(is_array($content)){
230 $date = new \DateTime($content['d']);
231 $article = new Article($content['i'], $date, $content['t'], $content['p']);
232 $article_node = new Node('new', 'i' . (string)$date->getTimestamp(), [], count($node->getChildren()) + 1, $node, $node->getPage(), $article);
233
234 // id_node tout juste généré
235 //$article_node->getId();
236 }
237 else{
238 $timestamp = time();
239 $date = new \DateTime;
240 $date->setTimestamp($timestamp);
241
242 $article = new Article($content, $date); // le "current" timestamp est obtenu par la BDD
243 $article_node = new Node('article', 'i' . (string)$timestamp, [], count($node->getChildren()) + 1, $node, $node->getPage(), $article);
244 }
245
246 $entityManager->persist($article_node);
247 $entityManager->flush();
248
249 echo json_encode(['success' => true, 'article_id' => $article_node->getArticleTimestamp()]);
250 die;
251 }
252 // modification article
253 else{
254 $id[0] = 'i'; // id de l'article node
255 }
256
257 if($director->makeArticleNode($id)) // une entrée est trouvée
258 {
259 $node = $director->getArticleNode(); // article
260 switch($json['id'][0]){
261 case 'i':
262 $node->getArticle()->setContent($content);
263 break;
264 case 'p':
265 $node->getArticle()->setPreview($content); // html de l'éditeur
266 break;
267 case 't':
268 $node->getArticle()->setTitle($content); // html de l'éditeur
269 break;
270 case 'd':
271 echo json_encode(['success' => false, 'message' => 'l\'action editor_submit ne supporte pas les dates, utiliser date_submit.']);
272 die;
273 default:
274 echo json_encode(['success' => false, 'message' => 'identifiant non utilisable']);
275 die;
276 }
277 $entityManager->flush();
278 echo json_encode(['success' => true]);
279 }
280 else
281 {
282 echo json_encode(['success' => false, 'message' => 'article non identifié']);
283 }
284 }
285 else{
286 echo json_encode(['success' => false, 'message' => 'Erreur de décodage JSON']);
287 }
288 die;
289 }
290 elseif($_GET['action'] === 'delete_article' && isset($json['id']))
291 {
292 $director = new Director($entityManager);
293 $director->makeArticleNode($json['id'], true);
294 $article = $director->getArticleNode();
295 $section = $director->getNode();
296
297 $entityManager->remove($article);
298 $section->removeChild($article);
299 $section->sortChildren(true); // régénère les positions
300 $entityManager->flush();
301
302 // test avec une nouvelle requête qui ne devrait rien trouver
303 if(!$director->makeArticleNode($json['id']))
304 {
305 echo json_encode(['success' => true]);
306
307 // on pourrait afficher une notification "toast"
308 }
309 else{
310 http_response_code(500);
311 echo json_encode(['success' => false, 'message' => 'Erreur lors de la suppression de l\'article.']);
312 }
313 die;
314 }
315 // inversion de la position de deux noeuds
316 elseif($_GET['action'] === 'switch_positions' && isset($json['id1']) && isset($json['id2']))
317 {
318 $director = new Director($entityManager);
319 $director->makeArticleNode($json['id1'], true);
320 $article1 = $director->getArticleNode();
321 $section = $director->getNode();
322
323 $section->sortChildren(true); // régénère les positions avant inversion
324
325 $article2;
326 foreach($section->getChildren() as $child){
327 if($child->getArticleTimestamp() === $json['id2']) // type string
328 {
329 $article2 = $child;
330 break;
331 }
332 }
333
334 // inversion
335 $tmp = $article1->getPosition();
336 $article1->setPosition($article2->getPosition());
337 $article2->setPosition($tmp);
338 $entityManager->flush();
339
340 echo json_encode(['success' => true]);
341 die;
342 }
343 elseif($_GET['action'] === 'date_submit' && isset($json['id']) && isset($json['date']))
344 {
345 $id = $json['id'];
346 $id[0] = 'i';
347 $date = new DateTime($json['date']);
348
349 $director = new Director($entityManager);
350 $director->makeArticleNode($id);
351 $node = $director->getArticleNode();
352 $node->getArticle()->setDateTime($date);
353 $entityManager->flush();
354
355 echo json_encode(['success' => true]);
356 die;
357 }
358
359 // config formulaire
360 /*elseif($_GET['action'] === 'recipient_email'){
361 $email = htmlspecialchars(trim($json['email']));
362
363 if(filter_var($email, FILTER_VALIDATE_EMAIL) && isset($json['hidden']) && empty($json['hidden'])){
364 $form_data = $entityManager->find('App\Entity\NodeData', $json['id']);
365 $form_data->updateData('email', $json['email']);
366 $entityManager->persist($form_data);
367 $entityManager->flush();
368
369 echo json_encode(['success' => true]);
370 }
371 else{
372 echo json_encode(['success' => false]);
373 }
374 die;
375 }*/
376 // e-mail de test
377 elseif($_GET['action'] === 'test_email'){
378 if(sendEmail(false, 'nom du visiteur', 'adresse@du_visiteur.fr', "TEST d'un envoi d'e-mail depuis le site web")){
379 echo json_encode(['success' => true]);
380 }
381 else{
382 echo json_encode(['success' => false]);
383 }
384 die;
385 }
386 }
387
388
389 /* -- page Menu et chemins -- */
390 elseif(isset($_GET['menu_edit']))
391 {
392 // récupération des données
393 $data = file_get_contents('php://input');
394 $json = json_decode($data, true);
395 Director::$menu_data = new Menu($entityManager);
396
397 // flèche gauche <=: position = position du parent + 1, parent = grand-parent, recalculer les positions
398 if($_GET['menu_edit'] === 'move_one_level_up' && isset($json['id'])){
399 $id = $json['id'];
400 $page = Director::$menu_data->findPageById((int)$id);
401
402 $parent = $page->getParent(); // peut être null
403 if($parent === null){
404 // 1er niveau: ne rien faire
405 echo json_encode(['success' => false]);
406 die;
407 }
408 // BDD
409 else{
410 $page->setPosition($parent->getPosition() + 1); // nouvelle position
411
412 // 2ème niveau: le parent devient $menu_data, puis null après tri
413 if($parent->getParent() === null){
414 // connexion dans les deux sens
415 $page->setParent(Director::$menu_data); // => pour la persistance
416
417 //Director::$menu_data->addChild($page); // => pour sortChildren
418 $page->getParent()->addChild($page); // => pour sortChildren
419 //Director::$menu_data->sortChildren(true); // positions décaléees des nouveaux petits frères
420 $page->getParent()->sortChildren(true); // positions décaléees des nouveaux petits frères
421 $page->setParent(null);
422
423 // affichage
424 $page->setPagePath($page->getEndOfPath());
425 $page->fillChildrenPagePath();
426 }
427 // 3ème niveau et plus
428 else{
429 $page->setParent($parent->getParent()); // nouveau parent
430 $page->getParent()->addChild($page); // => pour sortChildren
431 $page->getParent()->sortChildren(true); // positions décaléees des nouveaux petits frères
432 $page->fillChildrenPagePath($page->getParent()->getPagePath());
433 }
434 //$parent->sortChildren(true); // positions des enfants restants, inutile si la fonction est récursive?
435 $entityManager->flush();
436
437 // affichage
438 $parent->removeChild($page);
439 $nav_builder = new NavBuilder();
440 $menu_builder = new MenuBuilder(null, false);
441 echo json_encode(['success' => true, 'nav' => $nav_builder->render(), 'menu_buttons' => $menu_builder->render()]);
442 die;
443 }
444 }
445
446 // flèche droite =>: position = nombre d'éléments de la fraterie + 1, l'élément précédent devient le parent
447 if($_GET['menu_edit'] === 'move_one_level_down' && isset($json['id'])){
448 $id = $json['id'];
449 $page = Director::$menu_data->findPageById((int)$id);
450
451 $parent = $page->getParent(); // peut être null
452 if($parent == null){
453 $parent = Director::$menu_data;
454 }
455
456 // BDD
457 $parent->sortChildren(true); // trie et réindexe par sécurité: 1, 2, 3...
458 if($page->getPosition() > 1){
459 foreach($parent->getChildren() as $child){
460 if($child->getPosition() === $page->getPosition() - 1){
461 $page->setParent($child);
462 break;
463 }
464 }
465 $page->setPosition(count($page->getParent()->getChildren()) + 1);
466 }
467 $entityManager->flush();
468
469 // affichage
470 $parent->removeChild($page);
471 $page->getParent()->addChild($page);
472 $page->fillChildrenPagePath($page->getParent()->getPagePath()); // variable non mappée $page_path
473 $nav_builder = new NavBuilder();
474 $menu_builder = new MenuBuilder(null, false);
475
476 echo json_encode(['success' => true, 'nav' => $nav_builder->render(), 'menu_buttons' => $menu_builder->render()]);
477 die;
478 }
479
480 if($_GET['menu_edit'] === 'switch_positions' && isset($json['id1']) && isset($json['id2']))
481 {
482 $id1 = $json['id1'];
483 $id2 = $json['id2'];
484
485 // vérifier qu'ils ont le même parent
486 $page1 = Director::$menu_data->findPageById((int)$id1);
487 $page2 = Director::$menu_data->findPageById((int)$id2);
488
489 // double le contrôle fait en JS
490 if($page1->getParent() === $page2->getParent()) // comparaison stricte d'objet (même instance du parent?)
491 {
492 // inversion
493 $tmp = $page1->getPosition();
494 $page1->setPosition($page2->getPosition());
495 $page2->setPosition($tmp);
496 Director::$menu_data->sortChildren(true); // modifie tableau children
497 $entityManager->flush();
498
499 // nouveau menu
500 $nav_builder = new NavBuilder();
501 echo json_encode(['success' => true, 'nav' => $nav_builder->render()]);
502 }
503 else{
504 echo json_encode(['success' => false]);
505 }
506
507 die;
508 }
509
510 if($_GET['menu_edit'] === 'displayInMenu' && isset($json['id']) && isset($json['checked']))
511 {
512 $id = $json['id'];
513 $checked = $json['checked'];
514
515 $page = Director::$menu_data->findPageById((int)$id);
516 if($page->isHidden() === $checked){
517 $page->setHidden(!$checked);
518 $entityManager->flush();
519
520 // nouveau menu
521 $nav_builder = new NavBuilder();
522 echo json_encode(['success' => true, 'nav' => $nav_builder->render()]);
523 }
524 else{
525 echo json_encode(['success' => false]);
526 }
527 die;
528 }
529 }
530
531
532 /* -- mode Modification d'une page -- */
533
534 // partie "page"
535 elseif(isset($_GET['page_edit']))
536 {
537 // récupération des données
538 $data = file_get_contents('php://input');
539 $json = json_decode($data, true);
540
541 // titre de la page
542 if($_GET['page_edit'] === 'page_title'){
543 $page = $entityManager->find('App\Entity\Page', $json['page_id']);
544 $page->setPageName(htmlspecialchars($json['title']));
545 $entityManager->flush();
546 echo json_encode(['success' => true, 'title' => $page->getPageName()]);
547 }
548 // titre en snake_case pour le menu
549 /*elseif($_GET['page_edit'] === 'page_menu_path'){
550 $page = $entityManager->find('App\Entity\Page', $json['page_id']);
551 $page->setEndOfPath(htmlspecialchars($json['page_menu_path']));
552 $entityManager->flush();
553 echo json_encode(['success' => true, 'page_name_path' => $page->getEndOfPath()]);
554 }*/
555 // description dans les métadonnées
556 elseif($_GET['page_edit'] === 'page_description'){
557 $node_data = $entityManager->find('App\Entity\NodeData', $json['node_data_id']);
558 $node_data->updateData('description', htmlspecialchars($json['description']));
559 $entityManager->flush();
560 echo json_encode(['success' => true, 'description' => $node_data->getData()['description']]);
561 }
562 die;
563 }
564
565 // partie "blocs"
566 elseif(isset($_GET['bloc_edit']))
567 {
568 // renommage d'un bloc
569 if($_GET['bloc_edit'] === 'rename_page_bloc')
570 {
571 if(isset($json['bloc_title']) && $json['bloc_title'] !== null && isset($json['bloc_id']) && is_int($json['bloc_id'])){
572 $director = new Director($entityManager);
573 $director->findNodeById($json['bloc_id']);
574
575 // le titre (du JSON en BDD) est récupéré sous forme de tableau, modifié et renvoyé
576 $data = $director->getNode()->getNodeData()->getData();
577 $data['title'] = htmlspecialchars($json['bloc_title']);
578 $director->getNode()->getNodeData()->updateData('title', htmlspecialchars($json['bloc_title']));
579
580 $entityManager->flush();
581 echo json_encode(['success' => true, 'title' => $data['title']]);
582 }
583 else{
584 echo json_encode(['success' => false]);
585 }
586 die;
587 }
588 // inversion des positions de deux blocs
589 elseif($_GET['bloc_edit'] === 'switch_blocs_positions')
590 {
591 if(isset($json['id1']) && is_int($json['id1']) && isset($json['id2']) && is_int($json['id2']) && isset($_GET['page'])){
592 $director = new Director($entityManager, true);
593 $director->findUniqueNodeByName('main');
594 $director->findItsChildren();
595 $main = $director->getNode();
596 $main->sortChildren(true); // régénère les positions avant inversion
597
598 $bloc1; $bloc2;
599 foreach($main->getChildren() as $child){
600 if($child->getId() === $json['id1']){
601 $bloc1 = $child;
602 break;
603 }
604 }
605 foreach($main->getChildren() as $child){
606 if($child->getId() === $json['id2']){
607 $bloc2 = $child;
608 break;
609 }
610 }
611
612 // inversion
613 $tmp = $bloc1->getPosition();
614 $bloc1->setPosition($bloc2->getPosition());
615 $bloc2->setPosition($tmp);
616
617 $entityManager->flush();
618 echo json_encode(['success' => true]);
619 }
620 else{
621 echo json_encode(['success' => false]);
622 }
623 die;
624 }
625 }
626} \ No newline at end of file