diff options
-rw-r--r-- | public/index.php | 4 | ||||
-rw-r--r-- | src/controller/ArticleController.php | 169 | ||||
-rw-r--r-- | src/controller/CalendarController.php | 79 | ||||
-rw-r--r-- | src/controller/ContactFormController.php | 44 | ||||
-rw-r--r-- | src/controller/EmailController.php | 91 | ||||
-rw-r--r-- | src/controller/ImageUploadController.php | 184 | ||||
-rw-r--r-- | src/controller/MenuAndPathsController.php | 189 | ||||
-rw-r--r-- | src/controller/PageManagementController.php | 264 | ||||
-rw-r--r-- | src/controller/ajax_admin.php | 636 | ||||
-rw-r--r-- | src/controller/ajax_calendar_admin.php | 54 | ||||
-rw-r--r-- | src/controller/ajax_calendar_visitor.php | 39 | ||||
-rw-r--r-- | src/controller/ajax_email.php | 105 | ||||
-rw-r--r-- | src/controller/password.php | 3 | ||||
-rw-r--r-- | src/controller/post_functions_admin.php | 231 | ||||
-rw-r--r-- | src/controller/post_router.php | 120 | ||||
-rw-r--r-- | src/controller/request_router.php | 256 | ||||
-rw-r--r-- | src/model/EventDTO.php | 2 |
17 files changed, 1281 insertions, 1189 deletions
diff --git a/public/index.php b/public/index.php index 09af532..ba0e354 100644 --- a/public/index.php +++ b/public/index.php | |||
@@ -53,8 +53,8 @@ if(!empty($_GET['id'])) | |||
53 | $id = htmlspecialchars($_GET['id']); // nettoyage qui n'abime pas les id du genre "n16" | 53 | $id = htmlspecialchars($_GET['id']); // nettoyage qui n'abime pas les id du genre "n16" |
54 | } | 54 | } |
55 | 55 | ||
56 | /* -- contrôleurs qui traitent les POST (formulaires ou AJAX) -- */ | 56 | /* -- routeur des données de formulaires et requêtes AJAX -- */ |
57 | require '../src/controller/post_router.php'; | 57 | require '../src/controller/request_router.php'; |
58 | 58 | ||
59 | 59 | ||
60 | /* -- affichage d'une page -- */ | 60 | /* -- affichage d'une page -- */ |
diff --git a/src/controller/ArticleController.php b/src/controller/ArticleController.php new file mode 100644 index 0000000..e3e4edb --- /dev/null +++ b/src/controller/ArticleController.php | |||
@@ -0,0 +1,169 @@ | |||
1 | <?php | ||
2 | // src/controller/ArticleController.php | ||
3 | |||
4 | declare(strict_types=1); | ||
5 | |||
6 | use App\Entity\Node; | ||
7 | use App\Entity\Article; | ||
8 | use Doctrine\ORM\EntityManager; | ||
9 | |||
10 | class ArticleController | ||
11 | { | ||
12 | static public function editorSubmit(EntityManager $entityManager, array $json): void | ||
13 | { | ||
14 | if(json_last_error() === JSON_ERROR_NONE) | ||
15 | { | ||
16 | $id = $json['id']; | ||
17 | $director = new Director($entityManager); | ||
18 | |||
19 | // cas d'une nouvelle "news" | ||
20 | if(is_array($json['content'])){ | ||
21 | foreach($json['content'] as $one_input){ | ||
22 | $one_input = Security::secureString($one_input); | ||
23 | } | ||
24 | $content = $json['content']; | ||
25 | } | ||
26 | else{ | ||
27 | $content = Security::secureString($json['content']); | ||
28 | } | ||
29 | |||
30 | // nouvel article | ||
31 | if($id[0] === 'n') | ||
32 | { | ||
33 | $section_id = (int)substr($id, 1); // id du bloc <section> | ||
34 | $director->findNodeById($section_id); | ||
35 | $director->makeSectionNode(); | ||
36 | $node = $director->getNode(); // = <section> | ||
37 | |||
38 | if(is_array($content)){ | ||
39 | $date = new \DateTime($content['d']); | ||
40 | $article = new Article($content['i'], $date, $content['t'], $content['p']); | ||
41 | $article_node = new Node('new', 'i' . (string)$date->getTimestamp(), [], count($node->getChildren()) + 1, $node, $node->getPage(), $article); | ||
42 | |||
43 | // id_node tout juste généré | ||
44 | //$article_node->getId(); | ||
45 | } | ||
46 | else{ | ||
47 | $timestamp = time(); | ||
48 | $date = new \DateTime; | ||
49 | $date->setTimestamp($timestamp); | ||
50 | |||
51 | $article = new Article($content, $date); // le "current" timestamp est obtenu par la BDD | ||
52 | $article_node = new Node('article', 'i' . (string)$timestamp, [], count($node->getChildren()) + 1, $node, $node->getPage(), $article); | ||
53 | } | ||
54 | |||
55 | $entityManager->persist($article_node); | ||
56 | $entityManager->flush(); | ||
57 | |||
58 | echo json_encode(['success' => true, 'article_id' => $article_node->getArticleTimestamp()]); | ||
59 | die; | ||
60 | } | ||
61 | // modification article | ||
62 | else{ | ||
63 | $id[0] = 'i'; // id de l'article node | ||
64 | } | ||
65 | |||
66 | if($director->makeArticleNode($id)) // une entrée est trouvée | ||
67 | { | ||
68 | $node = $director->getArticleNode(); // article | ||
69 | switch($json['id'][0]){ | ||
70 | case 'i': | ||
71 | $node->getArticle()->setContent($content); | ||
72 | break; | ||
73 | case 'p': | ||
74 | $node->getArticle()->setPreview($content); // html de l'éditeur | ||
75 | break; | ||
76 | case 't': | ||
77 | $node->getArticle()->setTitle($content); // html de l'éditeur | ||
78 | break; | ||
79 | case 'd': | ||
80 | echo json_encode(['success' => false, 'message' => 'l\'action editor_submit ne supporte pas les dates, utiliser date_submit.']); | ||
81 | die; | ||
82 | default: | ||
83 | echo json_encode(['success' => false, 'message' => 'identifiant non utilisable']); | ||
84 | die; | ||
85 | } | ||
86 | $entityManager->flush(); | ||
87 | echo json_encode(['success' => true]); | ||
88 | } | ||
89 | else | ||
90 | { | ||
91 | echo json_encode(['success' => false, 'message' => 'article non identifié']); | ||
92 | } | ||
93 | } | ||
94 | else{ | ||
95 | echo json_encode(['success' => false, 'message' => 'Erreur de décodage JSON']); | ||
96 | } | ||
97 | die; | ||
98 | } | ||
99 | |||
100 | static public function deleteArticle(EntityManager $entityManager, array $json): void | ||
101 | { | ||
102 | $director = new Director($entityManager); | ||
103 | $director->makeArticleNode($json['id'], true); | ||
104 | $article = $director->getArticleNode(); | ||
105 | $section = $director->getNode(); | ||
106 | |||
107 | $entityManager->remove($article); | ||
108 | $section->removeChild($article); | ||
109 | $section->sortChildren(true); // régénère les positions | ||
110 | $entityManager->flush(); | ||
111 | |||
112 | // test avec une nouvelle requête qui ne devrait rien trouver | ||
113 | if(!$director->makeArticleNode($json['id'])) | ||
114 | { | ||
115 | echo json_encode(['success' => true]); | ||
116 | |||
117 | // on pourrait afficher une notification "toast" | ||
118 | } | ||
119 | else{ | ||
120 | http_response_code(500); | ||
121 | echo json_encode(['success' => false, 'message' => 'Erreur lors de la suppression de l\'article.']); | ||
122 | } | ||
123 | die; | ||
124 | } | ||
125 | |||
126 | static public function switchPositions(EntityManager $entityManager, array $json): void | ||
127 | { | ||
128 | $director = new Director($entityManager); | ||
129 | $director->makeArticleNode($json['id1'], true); | ||
130 | $article1 = $director->getArticleNode(); | ||
131 | $section = $director->getNode(); | ||
132 | |||
133 | $section->sortChildren(true); // régénère les positions avant inversion | ||
134 | |||
135 | $article2 = null; | ||
136 | foreach($section->getChildren() as $child){ | ||
137 | if($child->getArticleTimestamp() === $json['id2']) // type string | ||
138 | { | ||
139 | $article2 = $child; | ||
140 | break; | ||
141 | } | ||
142 | } | ||
143 | |||
144 | // inversion | ||
145 | $tmp = $article1->getPosition(); | ||
146 | $article1->setPosition($article2->getPosition()); | ||
147 | $article2->setPosition($tmp); | ||
148 | $entityManager->flush(); | ||
149 | |||
150 | echo json_encode(['success' => true]); | ||
151 | die; | ||
152 | } | ||
153 | |||
154 | static public function dateSubmit(EntityManager $entityManager, array $json): void | ||
155 | { | ||
156 | $id = $json['id']; | ||
157 | $id[0] = 'i'; | ||
158 | $date = new DateTime($json['date']); | ||
159 | |||
160 | $director = new Director($entityManager); | ||
161 | $director->makeArticleNode($id); | ||
162 | $node = $director->getArticleNode(); | ||
163 | $node->getArticle()->setDateTime($date); | ||
164 | $entityManager->flush(); | ||
165 | |||
166 | echo json_encode(['success' => true]); | ||
167 | die; | ||
168 | } | ||
169 | } \ No newline at end of file | ||
diff --git a/src/controller/CalendarController.php b/src/controller/CalendarController.php new file mode 100644 index 0000000..cc37d0f --- /dev/null +++ b/src/controller/CalendarController.php | |||
@@ -0,0 +1,79 @@ | |||
1 | <?php | ||
2 | // /src/controller/CalendarController.php | ||
3 | |||
4 | declare(strict_types=1); | ||
5 | |||
6 | use Doctrine\ORM\EntityManager; | ||
7 | use App\Entity\Event; | ||
8 | |||
9 | class CalendarController | ||
10 | { | ||
11 | static public function getData(EntityManager $entityManager): void | ||
12 | { | ||
13 | // bornes début et fin du calendrier affiché à l'heure locale | ||
14 | // noter que la vue "planning" est similaire à la vue "semaine" | ||
15 | $start = new DateTime($_GET['start']); | ||
16 | $end = new DateTime($_GET['end']); | ||
17 | $start->setTimezone(new DateTimeZone('UTC')); | ||
18 | $end->setTimezone(new DateTimeZone('UTC')); | ||
19 | |||
20 | // affichage format ISO à l'heure UTC | ||
21 | //$date->format('Y-m-d\TH:i:s\Z'); | ||
22 | |||
23 | // on prend les évènements se finissant après le début ou commençant avant la fin de la fourchette | ||
24 | $dql = 'SELECT e FROM App\Entity\Event e WHERE e.end >= :start AND e.start <= :end'; | ||
25 | $bulk_data = $entityManager->createQuery($dql) | ||
26 | ->setParameter('start', $start) | ||
27 | ->setParameter('end', $end) | ||
28 | ->getResult(); | ||
29 | |||
30 | $events = []; | ||
31 | foreach($bulk_data as $one_entry){ | ||
32 | $event = new EventDTO($one_entry); | ||
33 | $events[] = $event->toArray(); | ||
34 | } | ||
35 | |||
36 | header('Content-Type: application/json'); | ||
37 | echo json_encode($events); | ||
38 | die; | ||
39 | } | ||
40 | |||
41 | static public function newEvent(array $json, EntityManager $entityManager):void | ||
42 | { | ||
43 | try{ | ||
44 | $event = new Event($json); | ||
45 | } | ||
46 | catch(InvalidArgumentException $e){ | ||
47 | echo json_encode(['success' => false, 'error' => $e->getMessage()]); | ||
48 | http_response_code(400); | ||
49 | die; | ||
50 | } | ||
51 | $entityManager->persist($event); | ||
52 | $entityManager->flush(); | ||
53 | |||
54 | echo json_encode(['success' => true, 'id' => $event->getId()]); | ||
55 | } | ||
56 | static public function updateEvent(array $json, EntityManager $entityManager):void | ||
57 | { | ||
58 | $event = $entityManager->find('App\Entity\Event', (int)$json['id']); | ||
59 | try{ | ||
60 | $event->securedUpdateFromJSON($json); | ||
61 | } | ||
62 | catch(InvalidArgumentException $e){ | ||
63 | echo json_encode(['success' => false, 'error' => $e->getMessage()]); | ||
64 | http_response_code(400); | ||
65 | die; | ||
66 | } | ||
67 | $entityManager->flush(); | ||
68 | |||
69 | echo json_encode(['success' => true]); | ||
70 | } | ||
71 | static public function removeEvent(array $json, EntityManager $entityManager):void | ||
72 | { | ||
73 | $event = $entityManager->find('App\Entity\Event', (int)$json['id']); | ||
74 | $entityManager->remove($event); | ||
75 | $entityManager->flush(); | ||
76 | |||
77 | echo json_encode(['success' => true]); | ||
78 | } | ||
79 | } \ No newline at end of file | ||
diff --git a/src/controller/ContactFormController.php b/src/controller/ContactFormController.php new file mode 100644 index 0000000..9d62a77 --- /dev/null +++ b/src/controller/ContactFormController.php | |||
@@ -0,0 +1,44 @@ | |||
1 | <?php | ||
2 | // src/controller/ContactFormController.php | ||
3 | |||
4 | declare(strict_types=1); | ||
5 | |||
6 | use Doctrine\ORM\EntityManager; | ||
7 | |||
8 | class ContactFormController | ||
9 | { | ||
10 | static public function updateRecipient(EntityManager $entityManager, array $json): void | ||
11 | { | ||
12 | $email = htmlspecialchars(trim($json['email'])); | ||
13 | |||
14 | if((filter_var($email, FILTER_VALIDATE_EMAIL) // nouvel e-mail | ||
15 | || ($json['email'] === '' && !empty(Config::$email_dest))) // e-mail par défaut | ||
16 | && isset($json['hidden']) && empty($json['hidden'])) | ||
17 | { | ||
18 | $form_data = $entityManager->find('App\Entity\NodeData', $json['id']); | ||
19 | $form_data->updateData('email', $email); | ||
20 | $entityManager->persist($form_data); | ||
21 | $entityManager->flush(); | ||
22 | |||
23 | echo json_encode(['success' => true]); | ||
24 | } | ||
25 | else{ | ||
26 | echo json_encode(['success' => false]); | ||
27 | } | ||
28 | die; | ||
29 | } | ||
30 | static public function sendTestEmail(EntityManager $entityManager, array $json): void | ||
31 | { | ||
32 | // destinataire = e-mail par défaut dans config.ini OU choisi par l'utilisateur | ||
33 | $form_data = $entityManager->find('App\Entity\NodeData', $json['id']); | ||
34 | $recipient = $form_data->getData()['email'] ?? Config::$email_dest; | ||
35 | |||
36 | if(EmailController::send($recipient, false, 'nom du visiteur', 'adresse@du_visiteur.fr', "TEST d'un envoi d'e-mail depuis le site web")){ | ||
37 | echo json_encode(['success' => true]); | ||
38 | } | ||
39 | else{ | ||
40 | echo json_encode(['success' => false]); | ||
41 | } | ||
42 | die; | ||
43 | } | ||
44 | } \ No newline at end of file | ||
diff --git a/src/controller/EmailController.php b/src/controller/EmailController.php new file mode 100644 index 0000000..1eea257 --- /dev/null +++ b/src/controller/EmailController.php | |||
@@ -0,0 +1,91 @@ | |||
1 | <?php | ||
2 | // src/controller/EmailController.php | ||
3 | |||
4 | declare(strict_types=1); | ||
5 | |||
6 | use PHPMailer\PHPMailer\PHPMailer; | ||
7 | use PHPMailer\PHPMailer\Exception; | ||
8 | use App\Entity\Email; | ||
9 | use Doctrine\ORM\EntityManager; | ||
10 | |||
11 | class EmailController | ||
12 | { | ||
13 | static public function send(string $recipient, bool $true_email, string $name = '', string $email = '', string $message = ''): bool | ||
14 | { | ||
15 | $mail = new PHPMailer(true); // true => exceptions | ||
16 | $mail->CharSet = 'UTF-8'; | ||
17 | |||
18 | try{ | ||
19 | // Paramètres du serveur | ||
20 | $mail->isSMTP(); | ||
21 | $mail->Host = Config::$smtp_host; | ||
22 | $mail->SMTPAuth = true; | ||
23 | $mail->Port = 25; | ||
24 | |||
25 | if($mail->SMTPAuth){ | ||
26 | $mail->Username = Config::$smtp_username; // e-mail | ||
27 | $mail->Password = Config::$smtp_password; | ||
28 | $mail->SMTPSecure = Config::$smtp_secure; // tls (starttls) ou ssl (smtps) | ||
29 | if($mail->SMTPSecure === 'tls'){ | ||
30 | $mail->Port = 587; | ||
31 | } | ||
32 | elseif($mail->SMTPSecure === 'ssl'){ | ||
33 | $mail->Port = 465; | ||
34 | } | ||
35 | } | ||
36 | //var_dump($mail->smtpConnect());die; // test de connexion | ||
37 | |||
38 | // Expéditeur et destinataire | ||
39 | $mail->setFrom(strtolower(Config::$email_from), Config::$email_from_name); // expéditeur | ||
40 | $mail->addAddress(strtolower($recipient), Config::$email_dest_name); // destinataire | ||
41 | |||
42 | // Contenu | ||
43 | $mail->isHTML(true); | ||
44 | if($true_email){ | ||
45 | $mail->Subject = 'Message envoyé par: ' . $name . ' (' . $email . ') depuis le site web'; | ||
46 | |||
47 | } | ||
48 | else{ | ||
49 | $mail->Subject = "TEST d'un envoi d'e-mail depuis le site web"; | ||
50 | } | ||
51 | $mail->Body = $message; | ||
52 | $mail->AltBody = $message; | ||
53 | |||
54 | $mail->send(); | ||
55 | return true; | ||
56 | } | ||
57 | catch(Exception $e){ | ||
58 | return false; | ||
59 | //echo "Le message n'a pas pu être envoyé. Erreur : {$mail->ErrorInfo}"; | ||
60 | } | ||
61 | } | ||
62 | |||
63 | static public function submit(array $json, EntityManager $entityManager): void | ||
64 | { | ||
65 | $captcha_solution = (isset($_SESSION['captcha']) && is_int($_SESSION['captcha'])) ? $_SESSION['captcha'] : 0; | ||
66 | $captcha_try = isset($json['captcha']) ? Captcha::controlInput($json['captcha']) : 0; | ||
67 | |||
68 | // contrôles des entrées | ||
69 | $name = htmlspecialchars(trim($json['name'])); | ||
70 | $email = strtolower(htmlspecialchars(trim($json['email']))); | ||
71 | $message = htmlspecialchars(trim($json['message'])); | ||
72 | |||
73 | // destinataire = e-mail par défaut dans config.ini OU choisi par l'utilisateur | ||
74 | $form_data = $entityManager->find('App\Entity\NodeData', $json['id']); | ||
75 | $recipient = $form_data->getData()['email'] ?? Config::$email_dest; | ||
76 | |||
77 | if($captcha_try != 0 && $captcha_solution != 0 && ($captcha_try === $captcha_solution) | ||
78 | && filter_var($email, FILTER_VALIDATE_EMAIL) && isset($json['hidden']) && empty($json['hidden']) | ||
79 | && self::send($recipient, true, $name, $email, $message)) | ||
80 | { | ||
81 | $db_email = new Email($email, Config::$email_dest, $message); | ||
82 | $entityManager->persist($db_email); | ||
83 | $entityManager->flush(); | ||
84 | echo json_encode(['success' => true]); | ||
85 | } | ||
86 | else{ | ||
87 | echo json_encode(['success' => false]); | ||
88 | } | ||
89 | die; | ||
90 | } | ||
91 | } \ No newline at end of file | ||
diff --git a/src/controller/ImageUploadController.php b/src/controller/ImageUploadController.php new file mode 100644 index 0000000..29b8059 --- /dev/null +++ b/src/controller/ImageUploadController.php | |||
@@ -0,0 +1,184 @@ | |||
1 | <?php | ||
2 | // src/controller/ImageUploadController.php | ||
3 | |||
4 | declare(strict_types=1); | ||
5 | |||
6 | class ImageUploadController | ||
7 | { | ||
8 | static public function imagickCleanImage(string $image_data, string $local_path, string $format = 'jpeg'): bool // "string" parce que file_get_contents... | ||
9 | { | ||
10 | try{ | ||
11 | $imagick = new Imagick(); | ||
12 | $imagick->readImageBlob($image_data); | ||
13 | $imagick->stripImage(); // nettoyage métadonnées | ||
14 | $imagick->setImageFormat($format); | ||
15 | if($format === 'jpeg'){ | ||
16 | $imagick->setImageCompression(Imagick::COMPRESSION_JPEG); | ||
17 | $imagick->setImageCompressionQuality(85); // optionnel | ||
18 | } | ||
19 | $imagick->writeImage($local_path); // enregistrement | ||
20 | $imagick->clear(); | ||
21 | $imagick->destroy(); | ||
22 | return true; | ||
23 | } | ||
24 | catch(Exception $e){ | ||
25 | return false; | ||
26 | } | ||
27 | } | ||
28 | static public function curlDownloadImage(string $url, $maxRetries = 3, $timeout = 10): string|false | ||
29 | { | ||
30 | $attempt = 0; | ||
31 | $imageData = false; | ||
32 | |||
33 | while($attempt < $maxRetries){ | ||
34 | $ch = curl_init($url); // instance de CurlHandle | ||
35 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); | ||
36 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); | ||
37 | curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout); | ||
38 | curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); | ||
39 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); | ||
40 | curl_setopt($ch, CURLOPT_USERAGENT, 'TinyMCE-Image-Downloader'); | ||
41 | |||
42 | $imageData = curl_exec($ch); | ||
43 | $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); | ||
44 | //$curlError = curl_error($ch); | ||
45 | |||
46 | curl_close($ch); | ||
47 | |||
48 | if($imageData !== false && $httpCode >= 200 && $httpCode < 300){ | ||
49 | return $imageData; | ||
50 | } | ||
51 | |||
52 | $attempt++; | ||
53 | sleep(1); | ||
54 | } | ||
55 | |||
56 | return false; // échec après trois tentatives | ||
57 | } | ||
58 | |||
59 | // téléchargement par le plugin (bouton "insérer une image") | ||
60 | static public function imageUploadTinyMce(): void | ||
61 | { | ||
62 | if(isset($_FILES['file'])){ | ||
63 | $file = $_FILES['file']; | ||
64 | $dest = 'images/'; | ||
65 | $dest_mini = 'images-mini/'; | ||
66 | |||
67 | // Vérifier si les répertoires existent, sinon les créer | ||
68 | if(!is_dir($dest)) { | ||
69 | mkdir($dest, 0700, true); | ||
70 | } | ||
71 | if(!is_dir($dest_mini)) { | ||
72 | mkdir($dest_mini, 0700, true); | ||
73 | } | ||
74 | |||
75 | $allowed_extensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'tiff', 'tif']; | ||
76 | $name = Security::secureFileName(pathinfo($file['name'], PATHINFO_FILENAME)); | ||
77 | $extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); | ||
78 | if(!in_array($extension, $allowed_extensions) || $extension === 'jpg'){ | ||
79 | $extension = 'jpeg'; | ||
80 | } | ||
81 | $file_path = $dest . $name . '_' . uniqid() . '.' . $extension; | ||
82 | |||
83 | // créer une miniature de l'image | ||
84 | // | ||
85 | |||
86 | if(self::imagickCleanImage(file_get_contents($file['tmp_name']), $file_path, $extension)){ // recréer l’image pour la nettoyer | ||
87 | echo json_encode(['location' => $file_path]); // renvoyer l'URL de l'image téléchargée | ||
88 | } | ||
89 | else{ | ||
90 | http_response_code(500); | ||
91 | echo json_encode(['message' => 'Erreur image non valide']); | ||
92 | } | ||
93 | } | ||
94 | else{ | ||
95 | http_response_code(400); | ||
96 | echo json_encode(['message' => 'Erreur 400: Bad Request']); | ||
97 | } | ||
98 | die; | ||
99 | } | ||
100 | |||
101 | // collage de HTML => recherche de balises <img>, téléchargement côté serveur et renvoi de l'adresse sur le serveur | ||
102 | static public function uploadImageHtml(): void | ||
103 | { | ||
104 | $json = json_decode(file_get_contents('php://input'), true); | ||
105 | |||
106 | if(isset($json['image_url'])){ | ||
107 | $image_data = self::curlDownloadImage($json['image_url']); // téléchargement de l’image par le serveur avec cURL au lieu de file_get_contents | ||
108 | $dest = 'images/'; | ||
109 | |||
110 | if(!is_dir($dest)) { // Vérifier si le répertoire existe, sinon le créer | ||
111 | mkdir($dest, 0777, true); | ||
112 | } | ||
113 | |||
114 | if($image_data === false){ | ||
115 | http_response_code(400); | ||
116 | echo json_encode(['message' => "Erreur, le serveur n'a pas réussi à télécharger l'image."]); | ||
117 | die; | ||
118 | } | ||
119 | |||
120 | $allowed_extensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'tiff', 'tif']; | ||
121 | $url_path = parse_url($json['image_url'], PHP_URL_PATH); | ||
122 | $name = Security::secureFileName(pathinfo($url_path, PATHINFO_FILENAME)); | ||
123 | $extension = strtolower(pathinfo($url_path, PATHINFO_EXTENSION)); | ||
124 | if(!in_array($extension, $allowed_extensions) || $extension === 'jpg'){ | ||
125 | $extension = 'jpeg'; | ||
126 | } | ||
127 | $local_path = $dest . $name . '_' . uniqid() . '.' . $extension; | ||
128 | |||
129 | if(self::imagickCleanImage($image_data, $local_path, $extension)){ // recréer l’image pour la nettoyer | ||
130 | echo json_encode(['location' => $local_path]); // nouvelle adresse | ||
131 | } | ||
132 | else{ | ||
133 | http_response_code(500); | ||
134 | echo json_encode(['message' => 'Erreur image non valide']); | ||
135 | } | ||
136 | } | ||
137 | else{ | ||
138 | echo json_encode(['message' => 'Erreur 400: Bad Request']); | ||
139 | } | ||
140 | die; | ||
141 | } | ||
142 | |||
143 | // collage simple d'une image (base64 dans le presse-papier) non encapsulée dans du HTML | ||
144 | static public function uploadImageBase64(): void | ||
145 | { | ||
146 | $json = json_decode(file_get_contents('php://input'), true); | ||
147 | $dest = 'images/'; | ||
148 | |||
149 | if(!is_dir('images')){ | ||
150 | mkdir('images', 0777, true); | ||
151 | } | ||
152 | |||
153 | // détection de data:image/ et de ;base64, et capture du format dans $type | ||
154 | if(!isset($json['image_base64']) || !preg_match('/^data:image\/(\w+);base64,/', $json['image_base64'], $type)){ | ||
155 | http_response_code(400); | ||
156 | echo json_encode(['message' => 'Données image base64 manquantes ou invalides']); | ||
157 | die; | ||
158 | } | ||
159 | |||
160 | $allowed_extensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'tiff', 'tif']; | ||
161 | $extension = strtolower($type[1]); | ||
162 | if(!in_array($extension, $allowed_extensions) || $extension === 'jpg'){ | ||
163 | $extension = 'jpeg'; | ||
164 | } | ||
165 | |||
166 | $image_data = base64_decode(substr($json['image_base64'], strpos($json['image_base64'], ',') + 1)); // découpe la chaine à la virgule puis convertit en binaire | ||
167 | if($image_data === false){ | ||
168 | http_response_code(400); | ||
169 | echo json_encode(['message' => 'Décodage base64 invalide']); | ||
170 | die; | ||
171 | } | ||
172 | |||
173 | $local_path = $dest . 'pasted_image_' . uniqid() . '.' . $extension; | ||
174 | |||
175 | if(self::imagickCleanImage($image_data, $local_path)){ | ||
176 | echo json_encode(['location' => $local_path]); | ||
177 | } | ||
178 | else{ | ||
179 | http_response_code(500); | ||
180 | echo json_encode(['message' => 'Erreur image non valide']); | ||
181 | } | ||
182 | die; | ||
183 | } | ||
184 | } \ No newline at end of file | ||
diff --git a/src/controller/MenuAndPathsController.php b/src/controller/MenuAndPathsController.php new file mode 100644 index 0000000..d429287 --- /dev/null +++ b/src/controller/MenuAndPathsController.php | |||
@@ -0,0 +1,189 @@ | |||
1 | <?php | ||
2 | // src/controller/MenuAndPathsController.php | ||
3 | |||
4 | declare(strict_types=1); | ||
5 | |||
6 | use App\Entity\Page; | ||
7 | use Doctrine\ORM\EntityManager; | ||
8 | |||
9 | class MenuAndPathsController | ||
10 | { | ||
11 | static public function newUrlMenuEntry(EntityManager $entityManager): void | ||
12 | { | ||
13 | Director::$menu_data = new Menu($entityManager); | ||
14 | $previous_page = Director::$menu_data->findPageById((int)$_POST["location"]); // (int) à cause de declare(strict_types=1); | ||
15 | $parent = $previous_page->getParent(); | ||
16 | |||
17 | $page = new Page( | ||
18 | trim(htmlspecialchars($_POST["label_input"])), | ||
19 | filter_var($_POST["url_input"], FILTER_VALIDATE_URL), | ||
20 | true, true, false, | ||
21 | $previous_page->getPosition(), | ||
22 | $parent); // peut et DOIT être null si on est au 1er niveau | ||
23 | |||
24 | // on a donné à la nouvelle entrée la même position qu'à la précédente, | ||
25 | // addChild l'ajoute à la fin du tableau "children" puis on trie | ||
26 | // 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 | ||
27 | if($parent == null){ | ||
28 | $parent = Director::$menu_data; | ||
29 | } | ||
30 | $parent->addChild($page); // true pour réindexer les positions en BDD | ||
31 | $parent->reindexPositions(); | ||
32 | |||
33 | $entityManager->persist($page); | ||
34 | $entityManager->flush(); | ||
35 | header("Location: " . new URL(['page' => $_GET['from']])); | ||
36 | die; | ||
37 | } | ||
38 | |||
39 | static public function deleteUrlMenuEntry(EntityManager $entityManager): void | ||
40 | { | ||
41 | Director::$menu_data = new Menu($entityManager); | ||
42 | $page = Director::$menu_data->findPageById((int)$_POST["delete"]); | ||
43 | $parent = $page->getParent(); | ||
44 | if($parent == null){ | ||
45 | $parent = Director::$menu_data; | ||
46 | } | ||
47 | |||
48 | $parent->removeChild($page); // suppression de $children avant de trier | ||
49 | $parent->reindexPositions(); | ||
50 | |||
51 | $entityManager->remove($page); // suppression en BDD | ||
52 | $entityManager->flush(); | ||
53 | header("Location: " . new URL(['page' => $_GET['from']])); | ||
54 | die; | ||
55 | } | ||
56 | |||
57 | static public function MoveOneLevelUp(EntityManager $entityManager, array $json): void | ||
58 | { | ||
59 | $id = $json['id']; | ||
60 | $page = Director::$menu_data->findPageById((int)$id); | ||
61 | |||
62 | $parent = $page->getParent(); // peut être null | ||
63 | if($parent === null){ | ||
64 | // 1er niveau: ne rien faire | ||
65 | echo json_encode(['success' => false]); | ||
66 | die; | ||
67 | } | ||
68 | // BDD | ||
69 | else{ | ||
70 | $page->setPosition($parent->getPosition() + 1); // nouvelle position | ||
71 | |||
72 | // 2ème niveau: le parent devient $menu_data, puis null après tri | ||
73 | if($parent->getParent() === null){ | ||
74 | // connexion dans les deux sens | ||
75 | $page->setParent(Director::$menu_data); // => pour la persistance | ||
76 | |||
77 | //Director::$menu_data->addChild($page); // => pour sortChildren | ||
78 | $page->getParent()->addChild($page); // => pour sortChildren | ||
79 | //Director::$menu_data->sortChildren(true); // positions décaléees des nouveaux petits frères | ||
80 | $page->getParent()->sortChildren(true); // positions décaléees des nouveaux petits frères | ||
81 | $page->setParent(null); | ||
82 | |||
83 | // affichage | ||
84 | $page->setPagePath($page->getEndOfPath()); | ||
85 | $page->fillChildrenPagePath(); | ||
86 | } | ||
87 | // 3ème niveau et plus | ||
88 | else{ | ||
89 | $page->setParent($parent->getParent()); // nouveau parent | ||
90 | $page->getParent()->addChild($page); // => pour sortChildren | ||
91 | $page->getParent()->sortChildren(true); // positions décaléees des nouveaux petits frères | ||
92 | $page->fillChildrenPagePath($page->getParent()->getPagePath()); | ||
93 | } | ||
94 | //$parent->sortChildren(true); // positions des enfants restants, inutile si la fonction est récursive? | ||
95 | $entityManager->flush(); | ||
96 | |||
97 | // affichage | ||
98 | $parent->removeChild($page); | ||
99 | $nav_builder = new NavBuilder(); | ||
100 | $menu_builder = new MenuBuilder(null, false); | ||
101 | echo json_encode(['success' => true, 'nav' => $nav_builder->render(), 'menu_buttons' => $menu_builder->render()]); | ||
102 | die; | ||
103 | } | ||
104 | } | ||
105 | |||
106 | static public function MoveOneLevelDown(EntityManager $entityManager, array $json): void | ||
107 | { | ||
108 | $id = $json['id']; | ||
109 | $page = Director::$menu_data->findPageById((int)$id); | ||
110 | |||
111 | $parent = $page->getParent(); // peut être null | ||
112 | if($parent == null){ | ||
113 | $parent = Director::$menu_data; | ||
114 | } | ||
115 | |||
116 | // BDD | ||
117 | $parent->sortChildren(true); // trie et réindexe par sécurité: 1, 2, 3... | ||
118 | if($page->getPosition() > 1){ | ||
119 | foreach($parent->getChildren() as $child){ | ||
120 | if($child->getPosition() === $page->getPosition() - 1){ | ||
121 | $page->setParent($child); | ||
122 | break; | ||
123 | } | ||
124 | } | ||
125 | $page->setPosition(count($page->getParent()->getChildren()) + 1); | ||
126 | } | ||
127 | $entityManager->flush(); | ||
128 | |||
129 | // affichage | ||
130 | $parent->removeChild($page); | ||
131 | $page->getParent()->addChild($page); | ||
132 | $page->fillChildrenPagePath($page->getParent()->getPagePath()); // variable non mappée $page_path | ||
133 | $nav_builder = new NavBuilder(); | ||
134 | $menu_builder = new MenuBuilder(null, false); | ||
135 | |||
136 | echo json_encode(['success' => true, 'nav' => $nav_builder->render(), 'menu_buttons' => $menu_builder->render()]); | ||
137 | die; | ||
138 | } | ||
139 | |||
140 | static public function SwitchPositions(EntityManager $entityManager, array $json): void | ||
141 | { | ||
142 | $id1 = $json['id1']; | ||
143 | $id2 = $json['id2']; | ||
144 | |||
145 | // vérifier qu'ils ont le même parent | ||
146 | $page1 = Director::$menu_data->findPageById((int)$id1); | ||
147 | $page2 = Director::$menu_data->findPageById((int)$id2); | ||
148 | |||
149 | // double le contrôle fait en JS | ||
150 | if($page1->getParent() === $page2->getParent()) // comparaison stricte d'objet (même instance du parent?) | ||
151 | { | ||
152 | // inversion | ||
153 | $tmp = $page1->getPosition(); | ||
154 | $page1->setPosition($page2->getPosition()); | ||
155 | $page2->setPosition($tmp); | ||
156 | Director::$menu_data->sortChildren(true); // modifie tableau children | ||
157 | $entityManager->flush(); | ||
158 | |||
159 | // nouveau menu | ||
160 | $nav_builder = new NavBuilder(); | ||
161 | echo json_encode(['success' => true, 'nav' => $nav_builder->render()]); | ||
162 | } | ||
163 | else{ | ||
164 | echo json_encode(['success' => false]); | ||
165 | } | ||
166 | |||
167 | die; | ||
168 | } | ||
169 | |||
170 | static public function displayInMenu(EntityManager $entityManager, array $json): void | ||
171 | { | ||
172 | $id = $json['id']; | ||
173 | $checked = $json['checked']; | ||
174 | |||
175 | $page = Director::$menu_data->findPageById((int)$id); | ||
176 | if($page->isHidden() === $checked){ | ||
177 | $page->setHidden(!$checked); | ||
178 | $entityManager->flush(); | ||
179 | |||
180 | // nouveau menu | ||
181 | $nav_builder = new NavBuilder(); | ||
182 | echo json_encode(['success' => true, 'nav' => $nav_builder->render()]); | ||
183 | } | ||
184 | else{ | ||
185 | echo json_encode(['success' => false]); | ||
186 | } | ||
187 | die; | ||
188 | } | ||
189 | } \ No newline at end of file | ||
diff --git a/src/controller/PageManagementController.php b/src/controller/PageManagementController.php new file mode 100644 index 0000000..f84c528 --- /dev/null +++ b/src/controller/PageManagementController.php | |||
@@ -0,0 +1,264 @@ | |||
1 | <?php | ||
2 | // src/controller/PageManagementController.php | ||
3 | |||
4 | declare(strict_types=1); | ||
5 | |||
6 | use App\Entity\Page; | ||
7 | use App\Entity\Node; | ||
8 | use App\Entity\NodeData; | ||
9 | //use App\Entity\Image; | ||
10 | use Doctrine\Common\Collections\ArrayCollection; | ||
11 | use Doctrine\ORM\EntityManager; | ||
12 | |||
13 | class PageManagementController | ||
14 | { | ||
15 | /* -- partie page -- */ | ||
16 | static public function setPageTitle(EntityManager $entityManager, array $json): void | ||
17 | { | ||
18 | $page = $entityManager->find('App\Entity\Page', $json['page_id']); | ||
19 | $page->setPageName(htmlspecialchars($json['title'])); | ||
20 | $entityManager->flush(); | ||
21 | echo json_encode(['success' => true, 'title' => $page->getPageName()]); | ||
22 | die; | ||
23 | } | ||
24 | |||
25 | static public function updatePageMenuPath(EntityManager $entityManager): void | ||
26 | { | ||
27 | Director::$menu_data = new Menu($entityManager); | ||
28 | Director::$page_path = new Path(); | ||
29 | $page = Director::$page_path->getLast(); | ||
30 | $path = htmlspecialchars($_POST['page_menu_path']); | ||
31 | |||
32 | // mise en snake_case: filtre caractères non-alphanumériques, minuscule, doublons d'underscore, trim des underscores | ||
33 | $path = trim(preg_replace('/_+/', '_', strtolower(preg_replace('/[^a-zA-Z0-9]/', '_', $path))), '_'); | ||
34 | $page->setEndOfPath($path); | ||
35 | foreach(Director::$menu_data->getChildren() as $child){ | ||
36 | if($child->getEndOfPath() === Director::$page_path->getArray()[0]->getEndOfPath()){ | ||
37 | $child->fillChildrenPagePath(); // MAJ de $page_path | ||
38 | } | ||
39 | } | ||
40 | $entityManager->flush(); | ||
41 | header("Location: " . new URL(['page' => $page->getPagePath(), 'action' => 'modif_page'])); | ||
42 | die; | ||
43 | } | ||
44 | |||
45 | static public function setPageDescription(EntityManager $entityManager, array $json): void | ||
46 | { | ||
47 | $node_data = $entityManager->find('App\Entity\NodeData', $json['node_data_id']); | ||
48 | $node_data->updateData('description', htmlspecialchars($json['description'])); | ||
49 | $entityManager->flush(); | ||
50 | echo json_encode(['success' => true, 'description' => $node_data->getData()['description']]); | ||
51 | die; | ||
52 | } | ||
53 | |||
54 | static public function newPage(EntityManager $entityManager): void | ||
55 | { | ||
56 | // titre et chemin | ||
57 | $director = new Director($entityManager, true); | ||
58 | //Director::$menu_data = new Menu($entityManager); | ||
59 | $previous_page = Director::$menu_data->findPageById((int)$_POST["page_location"]); // (int) à cause de declare(strict_types=1); | ||
60 | $parent = $previous_page->getParent(); | ||
61 | |||
62 | $page = new Page( | ||
63 | trim(htmlspecialchars($_POST["page_name"])), | ||
64 | trim(htmlspecialchars($_POST["page_name_path"])), | ||
65 | true, true, false, | ||
66 | $previous_page->getPosition(), | ||
67 | $parent); // peut et DOIT être null si on est au 1er niveau | ||
68 | |||
69 | // on a donné à la nouvelle entrée la même position qu'à la précédente, | ||
70 | // addChild l'ajoute à la fin du tableau "children" puis on trie | ||
71 | // 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 | ||
72 | if($parent == null){ | ||
73 | $parent = Director::$menu_data; | ||
74 | } | ||
75 | $parent->addChild($page); | ||
76 | $parent->reindexPositions(); | ||
77 | |||
78 | $page->setPagePath(ltrim($parent->getPagePath() . '/' . $page->getEndOfPath(), '/')); | ||
79 | |||
80 | // noeud "head" | ||
81 | $node = new Node( | ||
82 | 'head', | ||
83 | null, [], | ||
84 | 1, // position d'un head = 1 | ||
85 | null, // pas de parent | ||
86 | $page); | ||
87 | $node->useDefaultAttributes(); // fichiers CSS et JS | ||
88 | |||
89 | $data = new NodeData([ | ||
90 | // pas de titre, il est dans $page | ||
91 | 'description' => trim(htmlspecialchars($_POST["page_description"]))], | ||
92 | $node); | ||
93 | |||
94 | $bulk_data = $entityManager | ||
95 | ->createQuery('SELECT n FROM App\Entity\Image n WHERE n.file_name LIKE :name') | ||
96 | ->setParameter('name', '%favicon%') | ||
97 | ->getResult(); | ||
98 | $data->setImages(new ArrayCollection($bulk_data)); | ||
99 | |||
100 | $entityManager->persist($page); | ||
101 | $entityManager->persist($node); | ||
102 | $entityManager->persist($data); | ||
103 | $entityManager->flush(); | ||
104 | |||
105 | // page créée, direction la page en mode modification pour ajouter des blocs | ||
106 | header("Location: " . new URL(['page' => $page->getPagePath(), 'action' => 'modif_page'])); | ||
107 | die; | ||
108 | } | ||
109 | |||
110 | static public function deletePage(EntityManager $entityManager): void | ||
111 | { | ||
112 | $page = $entityManager->find('App\Entity\Page', (int)$_POST['page_id']); | ||
113 | $nodes = $entityManager->getRepository('App\Entity\Node')->findBy(['page' => $page]); | ||
114 | $data = []; | ||
115 | foreach($nodes as $node){ | ||
116 | $data[] = $entityManager->getRepository('App\Entity\NodeData')->findOneBy(['node' => $node]); | ||
117 | $entityManager->remove($node); | ||
118 | } | ||
119 | foreach($data as $one_data){ | ||
120 | $entityManager->remove($one_data); | ||
121 | } | ||
122 | $entityManager->remove($page); // suppression en BDD | ||
123 | |||
124 | $entityManager->flush(); | ||
125 | header("Location: " . new URL); | ||
126 | die; | ||
127 | } | ||
128 | |||
129 | /* partie "blocs" */ | ||
130 | static public function addBloc(EntityManager $entityManager): void | ||
131 | { | ||
132 | $director = new Director($entityManager, true); // on a besoin de page_path qui dépend de menu_data | ||
133 | $page = Director::$page_path->getLast(); | ||
134 | $director->findUniqueNodeByName('main'); | ||
135 | $director->findItsChildren(); | ||
136 | $main = $director->getNode(); | ||
137 | $position = count($main->getChildren()) + 1; // position dans la fraterie | ||
138 | |||
139 | $blocks = ['blog', 'grid', 'calendar', 'galery', 'form']; // même liste dans FormBuilder.php | ||
140 | if(!in_array($_POST["bloc_select"], $blocks, true)) // 3è param: contrôle du type | ||
141 | { | ||
142 | header("Location: " . new URL(['page' => $_GET['page'], 'error' => 'bad_bloc_type'])); | ||
143 | die; | ||
144 | } | ||
145 | |||
146 | if($_POST["bloc_select"] === 'calendar' || $_POST["bloc_select"] === 'form'){ | ||
147 | $dql = 'SELECT n FROM App\Entity\Node n WHERE n.page = :page AND n.name_node = :name'; // noeud 'head' de la page | ||
148 | $bulk_data = $entityManager | ||
149 | ->createQuery($dql) | ||
150 | ->setParameter('page', $page) | ||
151 | ->setParameter('name', 'head') | ||
152 | ->getResult(); | ||
153 | |||
154 | if(count($bulk_data) != 1){ // 1 head par page | ||
155 | header("Location: " . new URL(['page' => $_GET['page'], 'error' => 'head_node_not_found'])); | ||
156 | die; | ||
157 | } | ||
158 | |||
159 | $bulk_data[0]->addAttribute('css_array', $_POST["bloc_select"]); | ||
160 | if($_POST["bloc_select"] === 'form'){ | ||
161 | $bulk_data[0]->addAttribute('js_array', $_POST["bloc_select"]); | ||
162 | } | ||
163 | $entityManager->persist($bulk_data[0]); | ||
164 | } | ||
165 | |||
166 | $bloc = new Node( | ||
167 | $_POST["bloc_select"], | ||
168 | null, [], | ||
169 | $position, | ||
170 | $main, | ||
171 | $page); | ||
172 | $data = new NodeData( | ||
173 | ['title' => trim(htmlspecialchars($_POST["bloc_title"]))], | ||
174 | $bloc); | ||
175 | |||
176 | $entityManager->persist($bloc); | ||
177 | $entityManager->persist($data); | ||
178 | $entityManager->flush(); | ||
179 | header("Location: " . new URL(['page' => $_GET['page'], 'action' => 'modif_page'])); | ||
180 | die; | ||
181 | } | ||
182 | |||
183 | static public function deleteBloc(EntityManager $entityManager): void | ||
184 | { | ||
185 | $director = new Director($entityManager, true); | ||
186 | $director->findUniqueNodeByName('main'); | ||
187 | $director->findItsChildren(); | ||
188 | //$director->findNodeById((int)$_POST['delete_bloc_id']); | ||
189 | $main = $director->getNode(); | ||
190 | $bloc = null; | ||
191 | foreach($main->getChildren() as $child){ | ||
192 | if($child->getId() === (int)$_POST['delete_bloc_id']){ | ||
193 | $bloc = $child; | ||
194 | break; | ||
195 | } | ||
196 | } | ||
197 | if(!empty($bloc)){ // si $bloc est null c'est que le HTML a été modifié volontairement | ||
198 | $main->removeChild($bloc); // réindex le tableau $children au passage | ||
199 | $main->reindexPositions(); | ||
200 | $entityManager->remove($bloc); // suppression en BDD | ||
201 | $entityManager->flush(); | ||
202 | } | ||
203 | header("Location: " . new URL(['page' => $_GET['page'], 'action' => 'modif_page'])); | ||
204 | die; | ||
205 | } | ||
206 | |||
207 | static public function renameBloc(EntityManager $entityManager, array $json): void | ||
208 | { | ||
209 | if(isset($json['bloc_title']) && $json['bloc_title'] !== null && isset($json['bloc_id']) && is_int($json['bloc_id'])){ | ||
210 | $director = new Director($entityManager); | ||
211 | $director->findNodeById($json['bloc_id']); | ||
212 | |||
213 | // le titre (du JSON en BDD) est récupéré sous forme de tableau, modifié et renvoyé | ||
214 | $data = $director->getNode()->getNodeData()->getData(); | ||
215 | $data['title'] = htmlspecialchars($json['bloc_title']); | ||
216 | $director->getNode()->getNodeData()->updateData('title', htmlspecialchars($json['bloc_title'])); | ||
217 | |||
218 | $entityManager->flush(); | ||
219 | echo json_encode(['success' => true, 'title' => $data['title']]); | ||
220 | } | ||
221 | else{ | ||
222 | echo json_encode(['success' => false]); | ||
223 | } | ||
224 | die; | ||
225 | } | ||
226 | |||
227 | static public function SwitchBlocsPositions(EntityManager $entityManager, array $json): void | ||
228 | { | ||
229 | if(isset($json['id1']) && is_int($json['id1']) && isset($json['id2']) && is_int($json['id2']) && isset($_GET['page'])){ | ||
230 | $director = new Director($entityManager, true); | ||
231 | $director->findUniqueNodeByName('main'); | ||
232 | $director->findItsChildren(); | ||
233 | $main = $director->getNode(); | ||
234 | $main->sortChildren(true); // régénère les positions avant inversion | ||
235 | |||
236 | $bloc1 = null; | ||
237 | $bloc2 = null; | ||
238 | foreach($main->getChildren() as $child){ | ||
239 | if($child->getId() === $json['id1']){ | ||
240 | $bloc1 = $child; | ||
241 | break; | ||
242 | } | ||
243 | } | ||
244 | foreach($main->getChildren() as $child){ | ||
245 | if($child->getId() === $json['id2']){ | ||
246 | $bloc2 = $child; | ||
247 | break; | ||
248 | } | ||
249 | } | ||
250 | |||
251 | // inversion | ||
252 | $tmp = $bloc1->getPosition(); | ||
253 | $bloc1->setPosition($bloc2->getPosition()); | ||
254 | $bloc2->setPosition($tmp); | ||
255 | |||
256 | $entityManager->flush(); | ||
257 | echo json_encode(['success' => true]); | ||
258 | } | ||
259 | else{ | ||
260 | echo json_encode(['success' => false]); | ||
261 | } | ||
262 | die; | ||
263 | } | ||
264 | } \ No newline at end of file | ||
diff --git a/src/controller/ajax_admin.php b/src/controller/ajax_admin.php deleted file mode 100644 index b69be77..0000000 --- a/src/controller/ajax_admin.php +++ /dev/null | |||
@@ -1,636 +0,0 @@ | |||
1 | <?php | ||
2 | // src/controller/ajax_admin.php | ||
3 | |||
4 | declare(strict_types=1); | ||
5 | |||
6 | use App\Entity\Page; | ||
7 | use App\Entity\Node; | ||
8 | use App\Entity\Article; | ||
9 | |||
10 | // mettre ça ailleurs | ||
11 | function 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 | } | ||
31 | function 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 | function imageUploadTinyMce(): void | ||
63 | { | ||
64 | if(isset($_FILES['file'])){ | ||
65 | $file = $_FILES['file']; | ||
66 | $dest = 'images/'; | ||
67 | $dest_mini = 'images-mini/'; | ||
68 | |||
69 | // Vérifier si les répertoires existent, sinon les créer | ||
70 | if(!is_dir($dest)) { | ||
71 | mkdir($dest, 0700, true); | ||
72 | } | ||
73 | if(!is_dir($dest_mini)) { | ||
74 | mkdir($dest_mini, 0700, true); | ||
75 | } | ||
76 | |||
77 | $allowed_extensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'tiff', 'tif']; | ||
78 | $name = Security::secureFileName(pathinfo($file['name'], PATHINFO_FILENAME)); | ||
79 | $extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); | ||
80 | if(!in_array($extension, $allowed_extensions) || $extension === 'jpg'){ | ||
81 | $extension = 'jpeg'; | ||
82 | } | ||
83 | $file_path = $dest . $name . '_' . uniqid() . '.' . $extension; | ||
84 | |||
85 | // créer une miniature de l'image | ||
86 | // | ||
87 | |||
88 | if(imagickCleanImage(file_get_contents($file['tmp_name']), $file_path, $extension)){ // recréer l’image pour la nettoyer | ||
89 | echo json_encode(['location' => $file_path]); // renvoyer l'URL de l'image téléchargée | ||
90 | } | ||
91 | else{ | ||
92 | http_response_code(500); | ||
93 | echo json_encode(['message' => 'Erreur image non valide']); | ||
94 | } | ||
95 | } | ||
96 | else{ | ||
97 | http_response_code(400); | ||
98 | echo json_encode(['message' => 'Erreur 400: Bad Request']); | ||
99 | } | ||
100 | die; | ||
101 | } | ||
102 | |||
103 | // détection des requêtes d'upload d'image de tinymce | ||
104 | if(strpos($_SERVER['CONTENT_TYPE'], 'multipart/form-data') !== false && isset($_GET['action']) && $_GET['action'] === 'upload_image') | ||
105 | { | ||
106 | imageUploadTinyMce(); | ||
107 | } | ||
108 | // 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 | ||
109 | elseif(isset($_GET['action']) && $_GET['action'] == 'upload_image_url') | ||
110 | { | ||
111 | $json = json_decode(file_get_contents('php://input'), true); | ||
112 | |||
113 | if(isset($json['image_url'])){ | ||
114 | $image_data = curlDownloadImage($json['image_url']); // téléchargement de l’image par le serveur avec cURL au lieu de file_get_contents | ||
115 | $dest = 'images/'; | ||
116 | |||
117 | if(!is_dir($dest)) { // Vérifier si le répertoire existe, sinon le créer | ||
118 | mkdir($dest, 0777, true); | ||
119 | } | ||
120 | |||
121 | if($image_data === false){ | ||
122 | http_response_code(400); | ||
123 | echo json_encode(['message' => "Erreur, le serveur n'a pas réussi à télécharger l'image."]); | ||
124 | die; | ||
125 | } | ||
126 | |||
127 | $allowed_extensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'tiff', 'tif']; | ||
128 | $url_path = parse_url($json['image_url'], PHP_URL_PATH); | ||
129 | $name = Security::secureFileName(pathinfo($url_path, PATHINFO_FILENAME)); | ||
130 | $extension = strtolower(pathinfo($url_path, PATHINFO_EXTENSION)); | ||
131 | if(!in_array($extension, $allowed_extensions) || $extension === 'jpg'){ | ||
132 | $extension = 'jpeg'; | ||
133 | } | ||
134 | $local_path = $dest . $name . '_' . uniqid() . '.' . $extension; | ||
135 | |||
136 | if(imagickCleanImage($image_data, $local_path, $extension)){ // recréer l’image pour la nettoyer | ||
137 | echo json_encode(['location' => $local_path]); // nouvelle adresse | ||
138 | } | ||
139 | else{ | ||
140 | http_response_code(500); | ||
141 | echo json_encode(['message' => 'Erreur image non valide']); | ||
142 | } | ||
143 | } | ||
144 | else{ | ||
145 | echo json_encode(['message' => 'Erreur 400: Bad Request']); | ||
146 | } | ||
147 | die; | ||
148 | } | ||
149 | // cas du collage d'une image (code base64) non encapsulée dans du HTML | ||
150 | elseif(isset($_GET['action']) && $_GET['action'] == 'upload_image_base64') | ||
151 | { | ||
152 | $json = json_decode(file_get_contents('php://input'), true); | ||
153 | $dest = 'images/'; | ||
154 | |||
155 | if(!is_dir('images')){ | ||
156 | mkdir('images', 0777, true); | ||
157 | } | ||
158 | |||
159 | // détection de data:image/ et de ;base64, et capture du format dans $type | ||
160 | if(!isset($json['image_base64']) || !preg_match('/^data:image\/(\w+);base64,/', $json['image_base64'], $type)){ | ||
161 | http_response_code(400); | ||
162 | echo json_encode(['message' => 'Données image base64 manquantes ou invalides']); | ||
163 | die; | ||
164 | } | ||
165 | |||
166 | $allowed_extensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'tiff', 'tif']; | ||
167 | $extension = strtolower($type[1]); | ||
168 | if(!in_array($extension, $allowed_extensions) || $extension === 'jpg'){ | ||
169 | $extension = 'jpeg'; | ||
170 | } | ||
171 | |||
172 | $image_data = base64_decode(substr($json['image_base64'], strpos($json['image_base64'], ',') + 1)); // découpe la chaine à la virgule puis convertit en binaire | ||
173 | if($image_data === false){ | ||
174 | http_response_code(400); | ||
175 | echo json_encode(['message' => 'Décodage base64 invalide']); | ||
176 | die; | ||
177 | } | ||
178 | |||
179 | $local_path = $dest . 'pasted_image_' . uniqid() . '.' . $extension; | ||
180 | |||
181 | if(imagickCleanImage($image_data, $local_path)){ | ||
182 | echo json_encode(['location' => $local_path]); | ||
183 | } | ||
184 | else{ | ||
185 | http_response_code(500); | ||
186 | echo json_encode(['message' => 'Erreur image non valide']); | ||
187 | } | ||
188 | die; | ||
189 | } | ||
190 | |||
191 | // détection des requêtes de type XHR, y en a pas à priori | ||
192 | /*elseif(isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest'){ | ||
193 | echo "requête XHR reçue par le serveur"; | ||
194 | die; | ||
195 | }*/ | ||
196 | |||
197 | |||
198 | // détection des requêtes envoyées avec fetch (application/json) et récupération du JSON | ||
199 | if($_SERVER['CONTENT_TYPE'] === 'application/json') | ||
200 | { | ||
201 | $data = file_get_contents('php://input'); | ||
202 | $json = json_decode($data, true); | ||
203 | |||
204 | if(isset($_GET['action'])) | ||
205 | { | ||
206 | if($_GET['action'] === 'editor_submit' && isset($json['id']) && isset($json['content'])) | ||
207 | { | ||
208 | if(json_last_error() === JSON_ERROR_NONE) | ||
209 | { | ||
210 | $id = $json['id']; | ||
211 | $director = new Director($entityManager); | ||
212 | |||
213 | // cas d'une nouvelle "news" | ||
214 | if(is_array($json['content'])){ | ||
215 | foreach($json['content'] as $one_input){ | ||
216 | $one_input = Security::secureString($one_input); | ||
217 | } | ||
218 | $content = $json['content']; | ||
219 | } | ||
220 | else{ | ||
221 | $content = Security::secureString($json['content']); | ||
222 | } | ||
223 | |||
224 | // nouvel article | ||
225 | if($id[0] === 'n') | ||
226 | { | ||
227 | $section_id = (int)substr($id, 1); // id du bloc <section> | ||
228 | $director->findNodeById($section_id); | ||
229 | $director->makeSectionNode(); | ||
230 | $node = $director->getNode(); // = <section> | ||
231 | |||
232 | if(is_array($content)){ | ||
233 | $date = new \DateTime($content['d']); | ||
234 | $article = new Article($content['i'], $date, $content['t'], $content['p']); | ||
235 | $article_node = new Node('new', 'i' . (string)$date->getTimestamp(), [], count($node->getChildren()) + 1, $node, $node->getPage(), $article); | ||
236 | |||
237 | // id_node tout juste généré | ||
238 | //$article_node->getId(); | ||
239 | } | ||
240 | else{ | ||
241 | $timestamp = time(); | ||
242 | $date = new \DateTime; | ||
243 | $date->setTimestamp($timestamp); | ||
244 | |||
245 | $article = new Article($content, $date); // le "current" timestamp est obtenu par la BDD | ||
246 | $article_node = new Node('article', 'i' . (string)$timestamp, [], count($node->getChildren()) + 1, $node, $node->getPage(), $article); | ||
247 | } | ||
248 | |||
249 | $entityManager->persist($article_node); | ||
250 | $entityManager->flush(); | ||
251 | |||
252 | echo json_encode(['success' => true, 'article_id' => $article_node->getArticleTimestamp()]); | ||
253 | die; | ||
254 | } | ||
255 | // modification article | ||
256 | else{ | ||
257 | $id[0] = 'i'; // id de l'article node | ||
258 | } | ||
259 | |||
260 | if($director->makeArticleNode($id)) // une entrée est trouvée | ||
261 | { | ||
262 | $node = $director->getArticleNode(); // article | ||
263 | switch($json['id'][0]){ | ||
264 | case 'i': | ||
265 | $node->getArticle()->setContent($content); | ||
266 | break; | ||
267 | case 'p': | ||
268 | $node->getArticle()->setPreview($content); // html de l'éditeur | ||
269 | break; | ||
270 | case 't': | ||
271 | $node->getArticle()->setTitle($content); // html de l'éditeur | ||
272 | break; | ||
273 | case 'd': | ||
274 | echo json_encode(['success' => false, 'message' => 'l\'action editor_submit ne supporte pas les dates, utiliser date_submit.']); | ||
275 | die; | ||
276 | default: | ||
277 | echo json_encode(['success' => false, 'message' => 'identifiant non utilisable']); | ||
278 | die; | ||
279 | } | ||
280 | $entityManager->flush(); | ||
281 | echo json_encode(['success' => true]); | ||
282 | } | ||
283 | else | ||
284 | { | ||
285 | echo json_encode(['success' => false, 'message' => 'article non identifié']); | ||
286 | } | ||
287 | } | ||
288 | else{ | ||
289 | echo json_encode(['success' => false, 'message' => 'Erreur de décodage JSON']); | ||
290 | } | ||
291 | die; | ||
292 | } | ||
293 | elseif($_GET['action'] === 'delete_article' && isset($json['id'])) | ||
294 | { | ||
295 | $director = new Director($entityManager); | ||
296 | $director->makeArticleNode($json['id'], true); | ||
297 | $article = $director->getArticleNode(); | ||
298 | $section = $director->getNode(); | ||
299 | |||
300 | $entityManager->remove($article); | ||
301 | $section->removeChild($article); | ||
302 | $section->sortChildren(true); // régénère les positions | ||
303 | $entityManager->flush(); | ||
304 | |||
305 | // test avec une nouvelle requête qui ne devrait rien trouver | ||
306 | if(!$director->makeArticleNode($json['id'])) | ||
307 | { | ||
308 | echo json_encode(['success' => true]); | ||
309 | |||
310 | // on pourrait afficher une notification "toast" | ||
311 | } | ||
312 | else{ | ||
313 | http_response_code(500); | ||
314 | echo json_encode(['success' => false, 'message' => 'Erreur lors de la suppression de l\'article.']); | ||
315 | } | ||
316 | die; | ||
317 | } | ||
318 | // inversion de la position de deux noeuds | ||
319 | elseif($_GET['action'] === 'switch_positions' && isset($json['id1']) && isset($json['id2'])) | ||
320 | { | ||
321 | $director = new Director($entityManager); | ||
322 | $director->makeArticleNode($json['id1'], true); | ||
323 | $article1 = $director->getArticleNode(); | ||
324 | $section = $director->getNode(); | ||
325 | |||
326 | $section->sortChildren(true); // régénère les positions avant inversion | ||
327 | |||
328 | $article2; | ||
329 | foreach($section->getChildren() as $child){ | ||
330 | if($child->getArticleTimestamp() === $json['id2']) // type string | ||
331 | { | ||
332 | $article2 = $child; | ||
333 | break; | ||
334 | } | ||
335 | } | ||
336 | |||
337 | // inversion | ||
338 | $tmp = $article1->getPosition(); | ||
339 | $article1->setPosition($article2->getPosition()); | ||
340 | $article2->setPosition($tmp); | ||
341 | $entityManager->flush(); | ||
342 | |||
343 | echo json_encode(['success' => true]); | ||
344 | die; | ||
345 | } | ||
346 | elseif($_GET['action'] === 'date_submit' && isset($json['id']) && isset($json['date'])) | ||
347 | { | ||
348 | $id = $json['id']; | ||
349 | $id[0] = 'i'; | ||
350 | $date = new DateTime($json['date']); | ||
351 | |||
352 | $director = new Director($entityManager); | ||
353 | $director->makeArticleNode($id); | ||
354 | $node = $director->getArticleNode(); | ||
355 | $node->getArticle()->setDateTime($date); | ||
356 | $entityManager->flush(); | ||
357 | |||
358 | echo json_encode(['success' => true]); | ||
359 | die; | ||
360 | } | ||
361 | |||
362 | |||
363 | /* -- bloc Formulaire -- */ | ||
364 | elseif($_GET['action'] === 'recipient_email'){ | ||
365 | $email = htmlspecialchars(trim($json['email'])); | ||
366 | |||
367 | if((filter_var($email, FILTER_VALIDATE_EMAIL) // nouvel e-mail | ||
368 | || ($json['email'] === '' && !empty(Config::$email_dest))) // e-mail par défaut | ||
369 | && isset($json['hidden']) && empty($json['hidden'])) | ||
370 | { | ||
371 | $form_data = $entityManager->find('App\Entity\NodeData', $json['id']); | ||
372 | $form_data->updateData('email', $email); | ||
373 | $entityManager->persist($form_data); | ||
374 | $entityManager->flush(); | ||
375 | |||
376 | echo json_encode(['success' => true]); | ||
377 | } | ||
378 | else{ | ||
379 | echo json_encode(['success' => false]); | ||
380 | } | ||
381 | die; | ||
382 | } | ||
383 | elseif($_GET['action'] === 'test_email'){ | ||
384 | // destinataire = e-mail par défaut dans config.ini OU choisi par l'utilisateur | ||
385 | $form_data = $entityManager->find('App\Entity\NodeData', $json['id']); | ||
386 | $recipient = $form_data->getData()['email'] ?? Config::$email_dest; | ||
387 | |||
388 | if(sendEmail($recipient, false, 'nom du visiteur', 'adresse@du_visiteur.fr', "TEST d'un envoi d'e-mail depuis le site web")){ | ||
389 | echo json_encode(['success' => true]); | ||
390 | } | ||
391 | else{ | ||
392 | echo json_encode(['success' => false]); | ||
393 | } | ||
394 | die; | ||
395 | } | ||
396 | } | ||
397 | |||
398 | |||
399 | /* -- page Menu et chemins -- */ | ||
400 | elseif(isset($_GET['menu_edit'])) | ||
401 | { | ||
402 | // récupération des données | ||
403 | $data = file_get_contents('php://input'); | ||
404 | $json = json_decode($data, true); | ||
405 | Director::$menu_data = new Menu($entityManager); | ||
406 | |||
407 | // flèche gauche <=: position = position du parent + 1, parent = grand-parent, recalculer les positions | ||
408 | if($_GET['menu_edit'] === 'move_one_level_up' && isset($json['id'])){ | ||
409 | $id = $json['id']; | ||
410 | $page = Director::$menu_data->findPageById((int)$id); | ||
411 | |||
412 | $parent = $page->getParent(); // peut être null | ||
413 | if($parent === null){ | ||
414 | // 1er niveau: ne rien faire | ||
415 | echo json_encode(['success' => false]); | ||
416 | die; | ||
417 | } | ||
418 | // BDD | ||
419 | else{ | ||
420 | $page->setPosition($parent->getPosition() + 1); // nouvelle position | ||
421 | |||
422 | // 2ème niveau: le parent devient $menu_data, puis null après tri | ||
423 | if($parent->getParent() === null){ | ||
424 | // connexion dans les deux sens | ||
425 | $page->setParent(Director::$menu_data); // => pour la persistance | ||
426 | |||
427 | //Director::$menu_data->addChild($page); // => pour sortChildren | ||
428 | $page->getParent()->addChild($page); // => pour sortChildren | ||
429 | //Director::$menu_data->sortChildren(true); // positions décaléees des nouveaux petits frères | ||
430 | $page->getParent()->sortChildren(true); // positions décaléees des nouveaux petits frères | ||
431 | $page->setParent(null); | ||
432 | |||
433 | // affichage | ||
434 | $page->setPagePath($page->getEndOfPath()); | ||
435 | $page->fillChildrenPagePath(); | ||
436 | } | ||
437 | // 3ème niveau et plus | ||
438 | else{ | ||
439 | $page->setParent($parent->getParent()); // nouveau parent | ||
440 | $page->getParent()->addChild($page); // => pour sortChildren | ||
441 | $page->getParent()->sortChildren(true); // positions décaléees des nouveaux petits frères | ||
442 | $page->fillChildrenPagePath($page->getParent()->getPagePath()); | ||
443 | } | ||
444 | //$parent->sortChildren(true); // positions des enfants restants, inutile si la fonction est récursive? | ||
445 | $entityManager->flush(); | ||
446 | |||
447 | // affichage | ||
448 | $parent->removeChild($page); | ||
449 | $nav_builder = new NavBuilder(); | ||
450 | $menu_builder = new MenuBuilder(null, false); | ||
451 | echo json_encode(['success' => true, 'nav' => $nav_builder->render(), 'menu_buttons' => $menu_builder->render()]); | ||
452 | die; | ||
453 | } | ||
454 | } | ||
455 | |||
456 | // flèche droite =>: position = nombre d'éléments de la fraterie + 1, l'élément précédent devient le parent | ||
457 | if($_GET['menu_edit'] === 'move_one_level_down' && isset($json['id'])){ | ||
458 | $id = $json['id']; | ||
459 | $page = Director::$menu_data->findPageById((int)$id); | ||
460 | |||
461 | $parent = $page->getParent(); // peut être null | ||
462 | if($parent == null){ | ||
463 | $parent = Director::$menu_data; | ||
464 | } | ||
465 | |||
466 | // BDD | ||
467 | $parent->sortChildren(true); // trie et réindexe par sécurité: 1, 2, 3... | ||
468 | if($page->getPosition() > 1){ | ||
469 | foreach($parent->getChildren() as $child){ | ||
470 | if($child->getPosition() === $page->getPosition() - 1){ | ||
471 | $page->setParent($child); | ||
472 | break; | ||
473 | } | ||
474 | } | ||
475 | $page->setPosition(count($page->getParent()->getChildren()) + 1); | ||
476 | } | ||
477 | $entityManager->flush(); | ||
478 | |||
479 | // affichage | ||
480 | $parent->removeChild($page); | ||
481 | $page->getParent()->addChild($page); | ||
482 | $page->fillChildrenPagePath($page->getParent()->getPagePath()); // variable non mappée $page_path | ||
483 | $nav_builder = new NavBuilder(); | ||
484 | $menu_builder = new MenuBuilder(null, false); | ||
485 | |||
486 | echo json_encode(['success' => true, 'nav' => $nav_builder->render(), 'menu_buttons' => $menu_builder->render()]); | ||
487 | die; | ||
488 | } | ||
489 | |||
490 | if($_GET['menu_edit'] === 'switch_positions' && isset($json['id1']) && isset($json['id2'])) | ||
491 | { | ||
492 | $id1 = $json['id1']; | ||
493 | $id2 = $json['id2']; | ||
494 | |||
495 | // vérifier qu'ils ont le même parent | ||
496 | $page1 = Director::$menu_data->findPageById((int)$id1); | ||
497 | $page2 = Director::$menu_data->findPageById((int)$id2); | ||
498 | |||
499 | // double le contrôle fait en JS | ||
500 | if($page1->getParent() === $page2->getParent()) // comparaison stricte d'objet (même instance du parent?) | ||
501 | { | ||
502 | // inversion | ||
503 | $tmp = $page1->getPosition(); | ||
504 | $page1->setPosition($page2->getPosition()); | ||
505 | $page2->setPosition($tmp); | ||
506 | Director::$menu_data->sortChildren(true); // modifie tableau children | ||
507 | $entityManager->flush(); | ||
508 | |||
509 | // nouveau menu | ||
510 | $nav_builder = new NavBuilder(); | ||
511 | echo json_encode(['success' => true, 'nav' => $nav_builder->render()]); | ||
512 | } | ||
513 | else{ | ||
514 | echo json_encode(['success' => false]); | ||
515 | } | ||
516 | |||
517 | die; | ||
518 | } | ||
519 | |||
520 | if($_GET['menu_edit'] === 'displayInMenu' && isset($json['id']) && isset($json['checked'])) | ||
521 | { | ||
522 | $id = $json['id']; | ||
523 | $checked = $json['checked']; | ||
524 | |||
525 | $page = Director::$menu_data->findPageById((int)$id); | ||
526 | if($page->isHidden() === $checked){ | ||
527 | $page->setHidden(!$checked); | ||
528 | $entityManager->flush(); | ||
529 | |||
530 | // nouveau menu | ||
531 | $nav_builder = new NavBuilder(); | ||
532 | echo json_encode(['success' => true, 'nav' => $nav_builder->render()]); | ||
533 | } | ||
534 | else{ | ||
535 | echo json_encode(['success' => false]); | ||
536 | } | ||
537 | die; | ||
538 | } | ||
539 | } | ||
540 | |||
541 | |||
542 | /* -- mode Modification d'une page -- */ | ||
543 | |||
544 | // partie "page" | ||
545 | elseif(isset($_GET['page_edit'])) | ||
546 | { | ||
547 | // récupération des données | ||
548 | $data = file_get_contents('php://input'); | ||
549 | $json = json_decode($data, true); | ||
550 | |||
551 | // titre de la page | ||
552 | if($_GET['page_edit'] === 'page_title'){ | ||
553 | $page = $entityManager->find('App\Entity\Page', $json['page_id']); | ||
554 | $page->setPageName(htmlspecialchars($json['title'])); | ||
555 | $entityManager->flush(); | ||
556 | echo json_encode(['success' => true, 'title' => $page->getPageName()]); | ||
557 | } | ||
558 | // titre en snake_case pour le menu | ||
559 | /*elseif($_GET['page_edit'] === 'page_menu_path'){ | ||
560 | $page = $entityManager->find('App\Entity\Page', $json['page_id']); | ||
561 | $page->setEndOfPath(htmlspecialchars($json['page_menu_path'])); | ||
562 | $entityManager->flush(); | ||
563 | echo json_encode(['success' => true, 'page_name_path' => $page->getEndOfPath()]); | ||
564 | }*/ | ||
565 | // description dans les métadonnées | ||
566 | elseif($_GET['page_edit'] === 'page_description'){ | ||
567 | $node_data = $entityManager->find('App\Entity\NodeData', $json['node_data_id']); | ||
568 | $node_data->updateData('description', htmlspecialchars($json['description'])); | ||
569 | $entityManager->flush(); | ||
570 | echo json_encode(['success' => true, 'description' => $node_data->getData()['description']]); | ||
571 | } | ||
572 | die; | ||
573 | } | ||
574 | |||
575 | // partie "blocs" | ||
576 | elseif(isset($_GET['bloc_edit'])) | ||
577 | { | ||
578 | // renommage d'un bloc | ||
579 | if($_GET['bloc_edit'] === 'rename_page_bloc') | ||
580 | { | ||
581 | if(isset($json['bloc_title']) && $json['bloc_title'] !== null && isset($json['bloc_id']) && is_int($json['bloc_id'])){ | ||
582 | $director = new Director($entityManager); | ||
583 | $director->findNodeById($json['bloc_id']); | ||
584 | |||
585 | // le titre (du JSON en BDD) est récupéré sous forme de tableau, modifié et renvoyé | ||
586 | $data = $director->getNode()->getNodeData()->getData(); | ||
587 | $data['title'] = htmlspecialchars($json['bloc_title']); | ||
588 | $director->getNode()->getNodeData()->updateData('title', htmlspecialchars($json['bloc_title'])); | ||
589 | |||
590 | $entityManager->flush(); | ||
591 | echo json_encode(['success' => true, 'title' => $data['title']]); | ||
592 | } | ||
593 | else{ | ||
594 | echo json_encode(['success' => false]); | ||
595 | } | ||
596 | die; | ||
597 | } | ||
598 | // inversion des positions de deux blocs | ||
599 | elseif($_GET['bloc_edit'] === 'switch_blocs_positions') | ||
600 | { | ||
601 | if(isset($json['id1']) && is_int($json['id1']) && isset($json['id2']) && is_int($json['id2']) && isset($_GET['page'])){ | ||
602 | $director = new Director($entityManager, true); | ||
603 | $director->findUniqueNodeByName('main'); | ||
604 | $director->findItsChildren(); | ||
605 | $main = $director->getNode(); | ||
606 | $main->sortChildren(true); // régénère les positions avant inversion | ||
607 | |||
608 | $bloc1; $bloc2; | ||
609 | foreach($main->getChildren() as $child){ | ||
610 | if($child->getId() === $json['id1']){ | ||
611 | $bloc1 = $child; | ||
612 | break; | ||
613 | } | ||
614 | } | ||
615 | foreach($main->getChildren() as $child){ | ||
616 | if($child->getId() === $json['id2']){ | ||
617 | $bloc2 = $child; | ||
618 | break; | ||
619 | } | ||
620 | } | ||
621 | |||
622 | // inversion | ||
623 | $tmp = $bloc1->getPosition(); | ||
624 | $bloc1->setPosition($bloc2->getPosition()); | ||
625 | $bloc2->setPosition($tmp); | ||
626 | |||
627 | $entityManager->flush(); | ||
628 | echo json_encode(['success' => true]); | ||
629 | } | ||
630 | else{ | ||
631 | echo json_encode(['success' => false]); | ||
632 | } | ||
633 | die; | ||
634 | } | ||
635 | } | ||
636 | } \ No newline at end of file | ||
diff --git a/src/controller/ajax_calendar_admin.php b/src/controller/ajax_calendar_admin.php deleted file mode 100644 index 0baf73e..0000000 --- a/src/controller/ajax_calendar_admin.php +++ /dev/null | |||
@@ -1,54 +0,0 @@ | |||
1 | <?php | ||
2 | // src/controller/ajax_calendar_admin.php | ||
3 | |||
4 | declare(strict_types=1); | ||
5 | |||
6 | use App\Entity\Event; | ||
7 | |||
8 | // actions sur le calendrier | ||
9 | if(isset($_SESSION['admin']) && $_SESSION['admin'] === true | ||
10 | && $_SERVER['REQUEST_METHOD'] === 'POST' && $_SERVER['CONTENT_TYPE'] === 'application/json') | ||
11 | { | ||
12 | $data = file_get_contents('php://input'); | ||
13 | $json = json_decode($data, true); | ||
14 | |||
15 | if($_GET['action'] === 'new_event'){ | ||
16 | try{ | ||
17 | $event = new Event($json); | ||
18 | } | ||
19 | catch(InvalidArgumentException $e){ | ||
20 | echo json_encode(['success' => false, 'error' => $e->getMessage()]); | ||
21 | http_response_code(400); | ||
22 | die; | ||
23 | } | ||
24 | $entityManager->persist($event); | ||
25 | $entityManager->flush(); | ||
26 | |||
27 | echo json_encode(['success' => true, 'id' => $event->getId()]); | ||
28 | } | ||
29 | elseif($_GET['action'] === 'update_event'){ | ||
30 | $event = $entityManager->find('App\Entity\Event', (int)$json['id']); | ||
31 | try{ | ||
32 | $event->securedUpdateFromJSON($json); | ||
33 | } | ||
34 | catch(InvalidArgumentException $e){ | ||
35 | echo json_encode(['success' => false, 'error' => $e->getMessage()]); | ||
36 | http_response_code(400); | ||
37 | die; | ||
38 | } | ||
39 | $entityManager->flush(); | ||
40 | |||
41 | echo json_encode(['success' => true]); | ||
42 | } | ||
43 | elseif($_GET['action'] === 'remove_event'){ | ||
44 | $event = $entityManager->find('App\Entity\Event', (int)$json['id']); | ||
45 | $entityManager->remove($event); | ||
46 | $entityManager->flush(); | ||
47 | |||
48 | echo json_encode(['success' => true]); | ||
49 | } | ||
50 | else{ | ||
51 | echo json_encode(['success' => false]); | ||
52 | } | ||
53 | die; | ||
54 | } \ No newline at end of file | ||
diff --git a/src/controller/ajax_calendar_visitor.php b/src/controller/ajax_calendar_visitor.php deleted file mode 100644 index dcdbebd..0000000 --- a/src/controller/ajax_calendar_visitor.php +++ /dev/null | |||
@@ -1,39 +0,0 @@ | |||
1 | <?php | ||
2 | // src/controller/ajax_calendar_visitor.php | ||
3 | |||
4 | declare(strict_types=1); | ||
5 | |||
6 | use App\Entity\Event; | ||
7 | |||
8 | // chargement des évènements à la création du calendrier | ||
9 | // et au changement de dates affichées (boutons flèches mais pas changement de vue) | ||
10 | if($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['action']) && $_GET['action'] === 'get_events' | ||
11 | && isset($_GET['start']) && isset($_GET['end']) && empty($_POST)) | ||
12 | { | ||
13 | // bornes début et fin du calendrier affiché à l'heure locale | ||
14 | // noter que la vue "planning" est similaire à la vue "semaine" | ||
15 | $start = new DateTime($_GET['start']); | ||
16 | $end = new DateTime($_GET['end']); | ||
17 | $start->setTimezone(new DateTimeZone('UTC')); | ||
18 | $end->setTimezone(new DateTimeZone('UTC')); | ||
19 | |||
20 | // affichage format ISO à l'heure UTC | ||
21 | //$date->format('Y-m-d\TH:i:s\Z'); | ||
22 | |||
23 | // on prend les évènements se finissant après le début ou commençant avant la fin de la fourchette | ||
24 | $dql = 'SELECT e FROM App\Entity\Event e WHERE e.end >= :start AND e.start <= :end'; | ||
25 | $bulk_data = $entityManager->createQuery($dql) | ||
26 | ->setParameter('start', $start) | ||
27 | ->setParameter('end', $end) | ||
28 | ->getResult(); | ||
29 | |||
30 | $events = []; | ||
31 | foreach($bulk_data as $one_entry){ | ||
32 | $event = new EventDTO($one_entry); | ||
33 | $events[] = $event->toArray(); | ||
34 | } | ||
35 | |||
36 | header('Content-Type: application/json'); | ||
37 | echo json_encode($events); | ||
38 | die; | ||
39 | } \ No newline at end of file | ||
diff --git a/src/controller/ajax_email.php b/src/controller/ajax_email.php deleted file mode 100644 index 1138e04..0000000 --- a/src/controller/ajax_email.php +++ /dev/null | |||
@@ -1,105 +0,0 @@ | |||
1 | <?php | ||
2 | // src/controller/ajax.php | ||
3 | |||
4 | declare(strict_types=1); | ||
5 | |||
6 | use PHPMailer\PHPMailer\PHPMailer; | ||
7 | use PHPMailer\PHPMailer\Exception; | ||
8 | use App\Entity\Email; | ||
9 | use Doctrine\ORM\EntityManager; | ||
10 | |||
11 | // mettre ça ailleurs? | ||
12 | function sendEmail(string $recipient, bool $true_email, string $name = '', string $email = '', string $message = ''): bool | ||
13 | { | ||
14 | $mail = new PHPMailer(true); // true => exceptions | ||
15 | $mail->CharSet = 'UTF-8'; | ||
16 | |||
17 | try{ | ||
18 | // Paramètres du serveur | ||
19 | $mail->isSMTP(); | ||
20 | $mail->Host = Config::$smtp_host; | ||
21 | $mail->SMTPAuth = true; | ||
22 | $mail->Port = 25; | ||
23 | |||
24 | if($mail->SMTPAuth){ | ||
25 | $mail->Username = Config::$smtp_username; // e-mail | ||
26 | $mail->Password = Config::$smtp_password; | ||
27 | $mail->SMTPSecure = Config::$smtp_secure; // tls (starttls) ou ssl (smtps) | ||
28 | if($mail->SMTPSecure === 'tls'){ | ||
29 | $mail->Port = 587; | ||
30 | } | ||
31 | elseif($mail->SMTPSecure === 'ssl'){ | ||
32 | $mail->Port = 465; | ||
33 | } | ||
34 | } | ||
35 | //var_dump($mail->smtpConnect());die; // test de connexion | ||
36 | |||
37 | // Expéditeur et destinataire | ||
38 | $mail->setFrom(strtolower(Config::$email_from), Config::$email_from_name); // expéditeur | ||
39 | $mail->addAddress(strtolower($recipient), Config::$email_dest_name); // destinataire | ||
40 | |||
41 | // Contenu | ||
42 | $mail->isHTML(true); | ||
43 | if($true_email){ | ||
44 | $mail->Subject = 'Message envoyé par: ' . $name . ' (' . $email . ') depuis le site web'; | ||
45 | |||
46 | } | ||
47 | else{ | ||
48 | $mail->Subject = "TEST d'un envoi d'e-mail depuis le site web"; | ||
49 | } | ||
50 | $mail->Body = $message; | ||
51 | $mail->AltBody = $message; | ||
52 | |||
53 | $mail->send(); | ||
54 | return true; | ||
55 | } | ||
56 | catch(Exception $e){ | ||
57 | return false; | ||
58 | //echo "Le message n'a pas pu être envoyé. Erreur : {$mail->ErrorInfo}"; | ||
59 | } | ||
60 | } | ||
61 | |||
62 | function submitEmail(array $json, EntityManager $entityManager): void | ||
63 | { | ||
64 | $captcha_solution = (isset($_SESSION['captcha']) && is_int($_SESSION['captcha'])) ? $_SESSION['captcha'] : 0; | ||
65 | $captcha_try = isset($json['captcha']) ? Captcha::controlInput($json['captcha']) : 0; | ||
66 | |||
67 | // contrôles des entrées | ||
68 | $name = htmlspecialchars(trim($json['name'])); | ||
69 | $email = strtolower(htmlspecialchars(trim($json['email']))); | ||
70 | $message = htmlspecialchars(trim($json['message'])); | ||
71 | |||
72 | // destinataire = e-mail par défaut dans config.ini OU choisi par l'utilisateur | ||
73 | $form_data = $entityManager->find('App\Entity\NodeData', $json['id']); | ||
74 | $recipient = $form_data->getData()['email'] ?? Config::$email_dest; | ||
75 | |||
76 | if($captcha_try != 0 && $captcha_solution != 0 && ($captcha_try === $captcha_solution) | ||
77 | && filter_var($email, FILTER_VALIDATE_EMAIL) && isset($json['hidden']) && empty($json['hidden']) | ||
78 | && sendEmail($recipient, true, $name, $email, $message)) | ||
79 | { | ||
80 | $db_email = new Email($email, Config::$email_dest, $message); | ||
81 | $entityManager->persist($db_email); | ||
82 | $entityManager->flush(); | ||
83 | echo json_encode(['success' => true]); | ||
84 | } | ||
85 | else{ | ||
86 | echo json_encode(['success' => false]); | ||
87 | } | ||
88 | die; | ||
89 | } | ||
90 | |||
91 | |||
92 | // détection des requêtes envoyées avec fetch (application/json) et récupération du JSON | ||
93 | if($_SERVER['CONTENT_TYPE'] === 'application/json') | ||
94 | { | ||
95 | $data = file_get_contents('php://input'); | ||
96 | $json = json_decode($data, true); | ||
97 | |||
98 | if(isset($_GET['action'])) | ||
99 | { | ||
100 | // formulaire de contact | ||
101 | if($_GET['action'] === 'send_email'){ | ||
102 | submitEmail($json, $entityManager); | ||
103 | } | ||
104 | } | ||
105 | } \ No newline at end of file | ||
diff --git a/src/controller/password.php b/src/controller/password.php index e91cc16..3d003da 100644 --- a/src/controller/password.php +++ b/src/controller/password.php | |||
@@ -226,7 +226,8 @@ function changePassword(EntityManager $entityManager): void | |||
226 | } | 226 | } |
227 | elseif(!isset($_POST['login']) || empty($_POST['login']) | 227 | elseif(!isset($_POST['login']) || empty($_POST['login']) |
228 | || !isset($_POST['old_password']) || empty($_POST['old_password']) | 228 | || !isset($_POST['old_password']) || empty($_POST['old_password']) |
229 | || !isset($_POST['new_password']) || empty($_POST['new_password'])) | 229 | || !isset($_POST['new_password']) || empty($_POST['new_password']) |
230 | || !isset($_POST['modify_password_hidden']) || !empty($_POST['modify_password_hidden'])) | ||
230 | { | 231 | { |
231 | $error = 'bad_login_or_password'; | 232 | $error = 'bad_login_or_password'; |
232 | } | 233 | } |
diff --git a/src/controller/post_functions_admin.php b/src/controller/post_functions_admin.php deleted file mode 100644 index b47850a..0000000 --- a/src/controller/post_functions_admin.php +++ /dev/null | |||
@@ -1,231 +0,0 @@ | |||
1 | <?php | ||
2 | // src/controller/post_functions_admin.php | ||
3 | // | ||
4 | // sera remplacé pas une ou plusieurs classes pour le routeur de symfony | ||
5 | |||
6 | declare(strict_types=1); | ||
7 | |||
8 | use App\Entity\Node; | ||
9 | use App\Entity\NodeData; | ||
10 | use App\Entity\Page; | ||
11 | //use App\Entity\Image; | ||
12 | use Doctrine\Common\Collections\ArrayCollection; | ||
13 | use Doctrine\ORM\EntityManager; | ||
14 | |||
15 | |||
16 | function newPage(EntityManager $entityManager): void | ||
17 | { | ||
18 | // titre et chemin | ||
19 | $director = new Director($entityManager, true); | ||
20 | //Director::$menu_data = new Menu($entityManager); | ||
21 | $previous_page = Director::$menu_data->findPageById((int)$_POST["page_location"]); // (int) à cause de declare(strict_types=1); | ||
22 | $parent = $previous_page->getParent(); | ||
23 | |||
24 | $page = new Page( | ||
25 | trim(htmlspecialchars($_POST["page_name"])), | ||
26 | trim(htmlspecialchars($_POST["page_name_path"])), | ||
27 | true, true, false, | ||
28 | $previous_page->getPosition(), | ||
29 | $parent); // peut et DOIT être null si on est au 1er niveau | ||
30 | |||
31 | // on a donné à la nouvelle entrée la même position qu'à la précédente, | ||
32 | // addChild l'ajoute à la fin du tableau "children" puis on trie | ||
33 | // 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 | ||
34 | if($parent == null){ | ||
35 | $parent = Director::$menu_data; | ||
36 | } | ||
37 | $parent->addChild($page); | ||
38 | $parent->reindexPositions(); | ||
39 | |||
40 | $page->setPagePath(ltrim($parent->getPagePath() . '/' . $page->getEndOfPath(), '/')); | ||
41 | |||
42 | // noeud "head" | ||
43 | $node = new Node( | ||
44 | 'head', | ||
45 | null, [], | ||
46 | 1, // position d'un head = 1 | ||
47 | null, // pas de parent | ||
48 | $page); | ||
49 | $node->useDefaultAttributes(); // fichiers CSS et JS | ||
50 | |||
51 | $data = new NodeData([ | ||
52 | // pas de titre, il est dans $page | ||
53 | 'description' => trim(htmlspecialchars($_POST["page_description"]))], | ||
54 | $node); | ||
55 | |||
56 | $bulk_data = $entityManager | ||
57 | ->createQuery('SELECT n FROM App\Entity\Image n WHERE n.file_name LIKE :name') | ||
58 | ->setParameter('name', '%favicon%') | ||
59 | ->getResult(); | ||
60 | $data->setImages(new ArrayCollection($bulk_data)); | ||
61 | |||
62 | $entityManager->persist($page); | ||
63 | $entityManager->persist($node); | ||
64 | $entityManager->persist($data); | ||
65 | $entityManager->flush(); | ||
66 | |||
67 | // page créée, direction la page en mode modification pour ajouter des blocs | ||
68 | header("Location: " . new URL(['page' => $page->getPagePath(), 'action' => 'modif_page'])); | ||
69 | die; | ||
70 | } | ||
71 | |||
72 | function deletePage(EntityManager $entityManager): void | ||
73 | { | ||
74 | $page = $entityManager->find('App\Entity\Page', (int)$_POST['page_id']); | ||
75 | $nodes = $entityManager->getRepository('App\Entity\Node')->findBy(['page' => $page]); | ||
76 | $data = []; | ||
77 | foreach($nodes as $node){ | ||
78 | $data[] = $entityManager->getRepository('App\Entity\NodeData')->findOneBy(['node' => $node]); | ||
79 | $entityManager->remove($node); | ||
80 | } | ||
81 | foreach($data as $one_data){ | ||
82 | $entityManager->remove($one_data); | ||
83 | } | ||
84 | $entityManager->remove($page); // suppression en BDD | ||
85 | |||
86 | $entityManager->flush(); | ||
87 | header("Location: " . new URL); | ||
88 | die; | ||
89 | } | ||
90 | |||
91 | function pageMenuPathUpdate(EntityManager $entityManager): void | ||
92 | { | ||
93 | $director = new Director($entityManager, true); | ||
94 | $page = Director::$page_path->getLast(); | ||
95 | $path = htmlspecialchars($_POST['page_menu_path']); | ||
96 | |||
97 | // mise en snake_case: filtre caractères non-alphanumériques, minuscule, doublons d'underscore, trim des underscores | ||
98 | $path = trim(preg_replace('/_+/', '_', strtolower(preg_replace('/[^a-zA-Z0-9]/', '_', $path))), '_'); | ||
99 | $page->setEndOfPath($path); | ||
100 | foreach(Director::$menu_data->getChildren() as $child){ | ||
101 | if($child->getEndOfPath() === Director::$page_path->getArray()[0]->getEndOfPath()){ | ||
102 | $child->fillChildrenPagePath(); // MAJ de $page_path | ||
103 | } | ||
104 | } | ||
105 | $entityManager->flush(); | ||
106 | header("Location: " . new URL(['page' => $page->getPagePath(), 'action' => 'modif_page'])); | ||
107 | die; | ||
108 | } | ||
109 | |||
110 | function addBloc(EntityManager $entityManager): void | ||
111 | { | ||
112 | $director = new Director($entityManager, true); // on a besoin de page_path qui dépend de menu_data | ||
113 | $page = Director::$page_path->getLast(); | ||
114 | $director->findUniqueNodeByName('main'); | ||
115 | $director->findItsChildren(); | ||
116 | $main = $director->getNode(); | ||
117 | $position = count($main->getChildren()) + 1; // position dans la fraterie | ||
118 | |||
119 | $blocks = ['blog', 'grid', 'calendar', 'galery', 'form']; // même liste dans FormBuilder.php | ||
120 | if(!in_array($_POST["bloc_select"], $blocks, true)) // 3è param: contrôle du type | ||
121 | { | ||
122 | header("Location: " . new URL(['page' => $_GET['page'], 'error' => 'bad_bloc_type'])); | ||
123 | die; | ||
124 | } | ||
125 | |||
126 | if($_POST["bloc_select"] === 'calendar' || $_POST["bloc_select"] === 'form'){ | ||
127 | $dql = 'SELECT n FROM App\Entity\Node n WHERE n.page = :page AND n.name_node = :name'; // noeud 'head' de la page | ||
128 | $bulk_data = $entityManager | ||
129 | ->createQuery($dql) | ||
130 | ->setParameter('page', $page) | ||
131 | ->setParameter('name', 'head') | ||
132 | ->getResult(); | ||
133 | |||
134 | if(count($bulk_data) != 1){ // 1 head par page | ||
135 | header("Location: " . new URL(['page' => $_GET['page'], 'error' => 'head_node_not_found'])); | ||
136 | die; | ||
137 | } | ||
138 | |||
139 | $bulk_data[0]->addAttribute('css_array', $_POST["bloc_select"]); | ||
140 | if($_POST["bloc_select"] === 'form'){ | ||
141 | $bulk_data[0]->addAttribute('js_array', $_POST["bloc_select"]); | ||
142 | } | ||
143 | $entityManager->persist($bulk_data[0]); | ||
144 | } | ||
145 | |||
146 | $bloc = new Node( | ||
147 | $_POST["bloc_select"], | ||
148 | null, [], | ||
149 | $position, | ||
150 | $main, | ||
151 | $page); | ||
152 | $data = new NodeData( | ||
153 | ['title' => trim(htmlspecialchars($_POST["bloc_title"]))], | ||
154 | $bloc); | ||
155 | |||
156 | $entityManager->persist($bloc); | ||
157 | $entityManager->persist($data); | ||
158 | $entityManager->flush(); | ||
159 | header("Location: " . new URL(['page' => $_GET['page'], 'action' => 'modif_page'])); | ||
160 | die; | ||
161 | } | ||
162 | |||
163 | function deleteBloc(EntityManager $entityManager): void | ||
164 | { | ||
165 | $director = new Director($entityManager, true); | ||
166 | $director->findUniqueNodeByName('main'); | ||
167 | $director->findItsChildren(); | ||
168 | //$director->findNodeById((int)$_POST['delete_bloc_id']); | ||
169 | $main = $director->getNode(); | ||
170 | $bloc = null; | ||
171 | foreach($main->getChildren() as $child){ | ||
172 | if($child->getId() === (int)$_POST['delete_bloc_id']){ | ||
173 | $bloc = $child; | ||
174 | break; | ||
175 | } | ||
176 | } | ||
177 | if(!empty($bloc)){ // si $bloc est null c'est que le HTML a été modifié volontairement | ||
178 | $main->removeChild($bloc); // réindex le tableau $children au passage | ||
179 | $main->reindexPositions(); | ||
180 | $entityManager->remove($bloc); // suppression en BDD | ||
181 | $entityManager->flush(); | ||
182 | } | ||
183 | header("Location: " . new URL(['page' => $_GET['page'], 'action' => 'modif_page'])); | ||
184 | die; | ||
185 | } | ||
186 | |||
187 | function newUrlMenuEntry(EntityManager $entityManager): void | ||
188 | { | ||
189 | Director::$menu_data = new Menu($entityManager); | ||
190 | $previous_page = Director::$menu_data->findPageById((int)$_POST["location"]); // (int) à cause de declare(strict_types=1); | ||
191 | $parent = $previous_page->getParent(); | ||
192 | |||
193 | $page = new Page( | ||
194 | trim(htmlspecialchars($_POST["label_input"])), | ||
195 | filter_var($_POST["url_input"], FILTER_VALIDATE_URL), | ||
196 | true, true, false, | ||
197 | $previous_page->getPosition(), | ||
198 | $parent); // peut et DOIT être null si on est au 1er niveau | ||
199 | |||
200 | // on a donné à la nouvelle entrée la même position qu'à la précédente, | ||
201 | // addChild l'ajoute à la fin du tableau "children" puis on trie | ||
202 | // 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 | ||
203 | if($parent == null){ | ||
204 | $parent = Director::$menu_data; | ||
205 | } | ||
206 | $parent->addChild($page); // true pour réindexer les positions en BDD | ||
207 | $parent->reindexPositions(); | ||
208 | |||
209 | $entityManager->persist($page); | ||
210 | $entityManager->flush(); | ||
211 | header("Location: " . new URL(['page' => $_GET['from']])); | ||
212 | die; | ||
213 | } | ||
214 | |||
215 | function deleteUrlMenuEntry(EntityManager $entityManager): void | ||
216 | { | ||
217 | Director::$menu_data = new Menu($entityManager); | ||
218 | $page = Director::$menu_data->findPageById((int)$_POST["delete"]); | ||
219 | $parent = $page->getParent(); | ||
220 | if($parent == null){ | ||
221 | $parent = Director::$menu_data; | ||
222 | } | ||
223 | |||
224 | $parent->removeChild($page); // suppression de $children avant de trier | ||
225 | $parent->reindexPositions(); | ||
226 | |||
227 | $entityManager->remove($page); // suppression en BDD | ||
228 | $entityManager->flush(); | ||
229 | header("Location: " . new URL(['page' => $_GET['from']])); | ||
230 | die; | ||
231 | } \ No newline at end of file | ||
diff --git a/src/controller/post_router.php b/src/controller/post_router.php deleted file mode 100644 index e71852a..0000000 --- a/src/controller/post_router.php +++ /dev/null | |||
@@ -1,120 +0,0 @@ | |||
1 | <?php | ||
2 | // src/controller/post_router.php | ||
3 | // | ||
4 | // routage des requêtes des formulaires et AJAX | ||
5 | // n'utilisent que des POST à l'exception d'un GET par fullcalendar | ||
6 | // les contrôleurs des formulaires sont appelés ici, | ||
7 | // ceux des requêtes AJAX sont derrière d'autres routeurs | ||
8 | |||
9 | declare(strict_types=1); | ||
10 | |||
11 | |||
12 | /* appel des contrôleurs dans password.php */ | ||
13 | if(isset($_GET['action']) && $_GET['action'] === 'deconnexion') | ||
14 | { | ||
15 | disconnect($entityManager); | ||
16 | } | ||
17 | elseif(isset($_GET['action']) && $_GET['action'] === 'modif_mdp') | ||
18 | { | ||
19 | changePassword($entityManager); | ||
20 | } | ||
21 | |||
22 | |||
23 | if($_SERVER['REQUEST_METHOD'] === 'POST'){ | ||
24 | /* -- contrôleurs appellables par tout le monde -- */ | ||
25 | // POST "ajax" | ||
26 | require '../src/controller/ajax_email.php'; | ||
27 | |||
28 | // POST "form" | ||
29 | // ... | ||
30 | |||
31 | |||
32 | if($_SESSION['admin'] === true) | ||
33 | { | ||
34 | /* -- requêtes "form" -- */ | ||
35 | if($_SERVER['CONTENT_TYPE'] === 'application/x-www-form-urlencoded') // moyen approximatif de distinguer les requêtes de formulaires et AJAX | ||
36 | { | ||
37 | require '../src/controller/post_functions_admin.php'; | ||
38 | |||
39 | /* -- nouvelle page -- */ | ||
40 | if(isset($_POST['page_name']) && $_POST['page_name'] !== null | ||
41 | && isset($_POST['page_name_path']) && $_POST['page_name_path'] !== null | ||
42 | && isset($_POST['page_location']) && $_POST['page_location'] !== null | ||
43 | && isset($_POST['page_description']) && $_POST['page_description'] !== null | ||
44 | && isset($_POST['new_page_hidden']) && $_POST['new_page_hidden'] === '') | ||
45 | { | ||
46 | newPage($entityManager); | ||
47 | } | ||
48 | |||
49 | /* -- suppression d'une page -- */ | ||
50 | elseif(isset($_POST['page_id']) && $_POST['page_id'] !== null | ||
51 | && isset($_POST['submit_hidden']) && $_POST['submit_hidden'] === '') | ||
52 | { | ||
53 | deletePage($entityManager); | ||
54 | } | ||
55 | |||
56 | |||
57 | /* -- mode Modification d'une page -- */ | ||
58 | |||
59 | // modification du chemins en snake_case | ||
60 | elseif(isset($_POST['page_menu_path']) && $_POST['page_menu_path'] !== null | ||
61 | && isset($_POST['page_id']) && $_POST['page_id'] !== null | ||
62 | && isset($_POST['page_name_path_hidden']) && $_POST['page_name_path_hidden'] === '') | ||
63 | { | ||
64 | pageMenuPathUpdate($entityManager); | ||
65 | } | ||
66 | // ajout d'un bloc dans une page | ||
67 | elseif(isset($_POST['bloc_title']) && $_POST['bloc_title'] !== null | ||
68 | && isset($_POST['bloc_select']) && $_POST['bloc_select'] !== null | ||
69 | && isset($_POST['bloc_title_hidden']) && $_POST['bloc_title_hidden'] === '') // contrôle anti-robot avec input hidden | ||
70 | { | ||
71 | addBloc($entityManager); | ||
72 | } | ||
73 | // suppression d'un bloc de page | ||
74 | elseif(isset($_POST['delete_bloc_id']) && $_POST['delete_bloc_id'] !== null | ||
75 | && isset($_POST['delete_bloc_hidden']) && $_POST['delete_bloc_hidden'] === '') // contrôle anti-robot avec input hidden | ||
76 | { | ||
77 | deleteBloc($entityManager); | ||
78 | } | ||
79 | |||
80 | |||
81 | /* -- page Menu et chemins -- */ | ||
82 | |||
83 | // création d'une entrée de menu avec une URL | ||
84 | elseif(isset($_POST["label_input"]) && isset($_POST["url_input"]) && isset($_POST["location"])){ | ||
85 | newUrlMenuEntry($entityManager); | ||
86 | } | ||
87 | // suppression d'une entrée de menu avec une URL | ||
88 | elseif(isset($_POST['delete']) && isset($_POST['x']) && isset($_POST['y'])){ // 2 params x et y sont là parce qu'on a cliqué sur une image | ||
89 | deleteUrlMenuEntry($entityManager); | ||
90 | } | ||
91 | |||
92 | // modification du mot de passe | ||
93 | elseif(isset($_GET['action']) && $_GET['action'] === 'modif_mdp' | ||
94 | && isset($_POST['login']) && isset($_POST['old_password']) && isset($_POST['new_password']) | ||
95 | && isset($_POST['modify_password_hidden']) && empty($_POST['modify_password_hidden'])) | ||
96 | { | ||
97 | changePassword($entityManager); | ||
98 | } | ||
99 | else{ | ||
100 | header("Location: " . new URL(['error' => 'paramètres inconnus'])); | ||
101 | die; | ||
102 | } | ||
103 | } | ||
104 | |||
105 | /* -- requêtes AJAX -- */ | ||
106 | else{ | ||
107 | require '../src/controller/ajax_admin.php'; | ||
108 | require '../src/controller/ajax_calendar_admin.php'; | ||
109 | } | ||
110 | } | ||
111 | } | ||
112 | // cas particulier d'un GET ajax non-admin par fullcalendar | ||
113 | elseif($_SERVER['REQUEST_METHOD'] === 'GET'){ | ||
114 | // non-admin | ||
115 | require '../src/controller/ajax_calendar_visitor.php'; | ||
116 | |||
117 | if($_SESSION['admin'] === true){ | ||
118 | // ... | ||
119 | } | ||
120 | } \ No newline at end of file | ||
diff --git a/src/controller/request_router.php b/src/controller/request_router.php new file mode 100644 index 0000000..157bc80 --- /dev/null +++ b/src/controller/request_router.php | |||
@@ -0,0 +1,256 @@ | |||
1 | <?php | ||
2 | // src/controller/request_router.php | ||
3 | // | ||
4 | // routage des requêtes des formulaires et AJAX | ||
5 | // n'utilisent que des POST à l'exception d'un GET par fullcalendar | ||
6 | // les contrôleurs des formulaires sont appelés ici, | ||
7 | // ceux des requêtes AJAX sont derrière d'autres routeurs | ||
8 | |||
9 | declare(strict_types=1); | ||
10 | |||
11 | |||
12 | /* appel des contrôleurs dans password.php */ | ||
13 | if(isset($_GET['action']) && $_GET['action'] === 'deconnexion') | ||
14 | { | ||
15 | disconnect($entityManager); | ||
16 | } | ||
17 | elseif(isset($_GET['action']) && $_GET['action'] === 'modif_mdp') | ||
18 | { | ||
19 | changePassword($entityManager); | ||
20 | } | ||
21 | |||
22 | |||
23 | // presque tout est ici | ||
24 | if($_SERVER['REQUEST_METHOD'] === 'POST'){ | ||
25 | /* -- contrôleurs appellables par tout le monde -- */ | ||
26 | |||
27 | // POST "ajax" avec fetch (application/json) | ||
28 | if($_SERVER['CONTENT_TYPE'] === 'application/json') | ||
29 | { | ||
30 | $data = file_get_contents('php://input'); | ||
31 | $json = json_decode($data, true); | ||
32 | |||
33 | if(isset($_GET['action'])) | ||
34 | { | ||
35 | // formulaire de contact | ||
36 | if($_GET['action'] === 'send_email'){ | ||
37 | EmailController::submit($json, $entityManager); | ||
38 | } | ||
39 | } | ||
40 | } | ||
41 | |||
42 | // POST "form" | ||
43 | // ... | ||
44 | |||
45 | |||
46 | if($_SESSION['admin'] === true) | ||
47 | { | ||
48 | /* -- requêtes AJAX -- */ | ||
49 | // requêtes JSON avec fetch() | ||
50 | if($_SERVER['CONTENT_TYPE'] === 'application/json') | ||
51 | { | ||
52 | $data = file_get_contents('php://input'); | ||
53 | $json = json_decode($data, true); | ||
54 | |||
55 | if(isset($_GET['action'])) | ||
56 | { | ||
57 | /* -- manipulation des articles -- */ | ||
58 | if($_GET['action'] === 'editor_submit' && isset($json['id']) && isset($json['content'])) | ||
59 | { | ||
60 | ArticleController::editorSubmit($entityManager, $json); | ||
61 | } | ||
62 | elseif($_GET['action'] === 'delete_article' && isset($json['id'])) | ||
63 | { | ||
64 | ArticleController::deleteArticle($entityManager, $json); | ||
65 | } | ||
66 | // inversion de la position de deux noeuds | ||
67 | elseif($_GET['action'] === 'switch_positions' && isset($json['id1']) && isset($json['id2'])) | ||
68 | { | ||
69 | ArticleController::switchPositions($entityManager, $json); | ||
70 | } | ||
71 | elseif($_GET['action'] === 'date_submit' && isset($json['id']) && isset($json['date'])) | ||
72 | { | ||
73 | ArticleController::dateSubmit($entityManager, $json); | ||
74 | } | ||
75 | |||
76 | /* -- bloc Formulaire -- */ | ||
77 | elseif($_GET['action'] === 'recipient_email'){ | ||
78 | ContactFormController::updateRecipient($entityManager, $json); | ||
79 | } | ||
80 | elseif($_GET['action'] === 'test_email'){ | ||
81 | ContactFormController::sendTestEmail($entityManager, $json); | ||
82 | } | ||
83 | } | ||
84 | |||
85 | /* -- page Menu et chemins -- */ | ||
86 | elseif(isset($_GET['menu_edit'])) | ||
87 | { | ||
88 | // récupération des données (serait peut-être mieux dans la classe) | ||
89 | Director::$menu_data = new Menu($entityManager); | ||
90 | |||
91 | // flèche gauche <=: position = position du parent + 1, parent = grand-parent, recalculer les positions | ||
92 | if($_GET['menu_edit'] === 'move_one_level_up' && isset($json['id'])){ | ||
93 | MenuAndPathsController::MoveOneLevelUp($entityManager, $json); | ||
94 | } | ||
95 | |||
96 | // flèche droite =>: position = nombre d'éléments de la fraterie + 1, l'élément précédent devient le parent | ||
97 | if($_GET['menu_edit'] === 'move_one_level_down' && isset($json['id'])){ | ||
98 | MenuAndPathsController::MoveOneLevelDown($entityManager, $json); | ||
99 | } | ||
100 | |||
101 | if($_GET['menu_edit'] === 'switch_positions' && isset($json['id1']) && isset($json['id2'])){ | ||
102 | MenuAndPathsController::switchPositions($entityManager, $json); | ||
103 | } | ||
104 | |||
105 | if($_GET['menu_edit'] === 'displayInMenu' && isset($json['id']) && isset($json['checked'])){ | ||
106 | MenuAndPathsController::displayInMenu($entityManager, $json); | ||
107 | } | ||
108 | } | ||
109 | |||
110 | /* -- mode Modification d'une page -- */ | ||
111 | // partie "page" | ||
112 | elseif(isset($_GET['page_edit'])) | ||
113 | { | ||
114 | // titre de la page | ||
115 | if($_GET['page_edit'] === 'page_title'){ | ||
116 | PageManagementController::setPageTitle($entityManager, $json); | ||
117 | } | ||
118 | // description dans les métadonnées | ||
119 | elseif($_GET['page_edit'] === 'page_description'){ | ||
120 | PageManagementController::setPageDescription($entityManager, $json); | ||
121 | } | ||
122 | } | ||
123 | |||
124 | // partie "blocs" | ||
125 | elseif(isset($_GET['bloc_edit'])) | ||
126 | { | ||
127 | // renommage d'un bloc | ||
128 | if($_GET['bloc_edit'] === 'rename_page_bloc') | ||
129 | { | ||
130 | PageManagementController::renameBloc($entityManager, $json); | ||
131 | } | ||
132 | // inversion des positions de deux blocs | ||
133 | elseif($_GET['bloc_edit'] === 'switch_blocs_positions') | ||
134 | { | ||
135 | PageManagementController::SwitchBlocsPositions($entityManager, $json); | ||
136 | } | ||
137 | } | ||
138 | |||
139 | /* -- upload d'image dans tinymce par copier-coller -- */ | ||
140 | // collage de HTML contenant une ou plusieurs balises <img> | ||
141 | if(isset($_GET['action']) && $_GET['action'] == 'upload_image_html'){ | ||
142 | ImageUploadController::uploadImageHtml(); | ||
143 | } | ||
144 | // collage d'une image (code base64 dans le presse-papier) non encapsulée dans du HTML | ||
145 | elseif(isset($_GET['action']) && $_GET['action'] == 'upload_image_base64'){ | ||
146 | ImageUploadController::uploadImageBase64(); | ||
147 | } | ||
148 | |||
149 | /* -- requêtes spécifiques au calendrier -- */ | ||
150 | if($_GET['action'] === 'new_event'){ | ||
151 | CalendarController::newEvent($json, $entityManager); | ||
152 | } | ||
153 | elseif($_GET['action'] === 'update_event'){ | ||
154 | CalendarController::updateEvent($json, $entityManager); | ||
155 | } | ||
156 | elseif($_GET['action'] === 'remove_event'){ | ||
157 | CalendarController::removeEvent($json, $entityManager); | ||
158 | } | ||
159 | else{ | ||
160 | echo json_encode(['success' => false]); | ||
161 | } | ||
162 | die; | ||
163 | } | ||
164 | |||
165 | // upload d'image dans tinymce avec le plugin (bouton "insérer une image" de l'éditeur) | ||
166 | elseif(strpos($_SERVER['CONTENT_TYPE'], 'multipart/form-data') !== false && isset($_GET['action']) && $_GET['action'] === 'upload_image') | ||
167 | { | ||
168 | ImageUploadController::imageUploadTinyMce(); | ||
169 | } | ||
170 | // requêtes XMLHttpRequest | ||
171 | elseif(isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') | ||
172 | { | ||
173 | //echo "requête XMLHttpRequest reçue par le serveur"; | ||
174 | echo json_encode(['success' => false]); // ça marche mais ça marche pas... | ||
175 | die; | ||
176 | } | ||
177 | |||
178 | /* -- envoi d'un formulaire HTML -- */ | ||
179 | elseif($_SERVER['CONTENT_TYPE'] === 'application/x-www-form-urlencoded') | ||
180 | { | ||
181 | /* -- nouvelle page -- */ | ||
182 | if(isset($_POST['page_name']) && $_POST['page_name'] !== null | ||
183 | && isset($_POST['page_name_path']) && $_POST['page_name_path'] !== null | ||
184 | && isset($_POST['page_location']) && $_POST['page_location'] !== null | ||
185 | && isset($_POST['page_description']) && $_POST['page_description'] !== null | ||
186 | && isset($_POST['new_page_hidden']) && $_POST['new_page_hidden'] === '') | ||
187 | { | ||
188 | PageManagementController::newPage($entityManager); | ||
189 | } | ||
190 | |||
191 | /* -- suppression d'une page -- */ | ||
192 | elseif(isset($_POST['page_id']) && $_POST['page_id'] !== null | ||
193 | && isset($_POST['submit_hidden']) && $_POST['submit_hidden'] === '') | ||
194 | { | ||
195 | PageManagementController::deletePage($entityManager); | ||
196 | } | ||
197 | |||
198 | /* -- mode Modification d'une page -- */ | ||
199 | |||
200 | // modification du chemins en snake_case | ||
201 | elseif(isset($_POST['page_menu_path']) && $_POST['page_menu_path'] !== null | ||
202 | && isset($_POST['page_id']) && $_POST['page_id'] !== null | ||
203 | && isset($_POST['page_name_path_hidden']) && $_POST['page_name_path_hidden'] === '') | ||
204 | { | ||
205 | PageManagementController::updatePageMenuPath($entityManager); | ||
206 | } | ||
207 | // ajout d'un bloc dans une page | ||
208 | elseif(isset($_POST['bloc_title']) && $_POST['bloc_title'] !== null | ||
209 | && isset($_POST['bloc_select']) && $_POST['bloc_select'] !== null | ||
210 | && isset($_POST['bloc_title_hidden']) && $_POST['bloc_title_hidden'] === '') // contrôle anti-robot avec input hidden | ||
211 | { | ||
212 | PageManagementController::addBloc($entityManager); | ||
213 | } | ||
214 | // suppression d'un bloc de page | ||
215 | elseif(isset($_POST['delete_bloc_id']) && $_POST['delete_bloc_id'] !== null | ||
216 | && isset($_POST['delete_bloc_hidden']) && $_POST['delete_bloc_hidden'] === '') // contrôle anti-robot avec input hidden | ||
217 | { | ||
218 | PageManagementController::deleteBloc($entityManager); | ||
219 | } | ||
220 | |||
221 | |||
222 | /* -- page Menu et chemins -- */ | ||
223 | |||
224 | // création d'une entrée de menu avec une URL | ||
225 | elseif(isset($_POST["label_input"]) && isset($_POST["url_input"]) && isset($_POST["location"])){ | ||
226 | MenuAndPathsController::newUrlMenuEntry($entityManager); | ||
227 | } | ||
228 | // suppression d'une entrée de menu avec une URL | ||
229 | elseif(isset($_POST['delete']) && isset($_POST['x']) && isset($_POST['y'])){ // 2 params x et y sont là parce qu'on a cliqué sur une image | ||
230 | MenuAndPathsController::deleteUrlMenuEntry($entityManager); | ||
231 | } | ||
232 | |||
233 | // redirection page d'accueil | ||
234 | else{ | ||
235 | header("Location: " . new URL(['error' => 'paramètres inconnus'])); | ||
236 | die; | ||
237 | } | ||
238 | } | ||
239 | } | ||
240 | } | ||
241 | |||
242 | // cas particulier d'un GET ajax non-admin par fullcalendar | ||
243 | elseif($_SERVER['REQUEST_METHOD'] === 'GET'){ | ||
244 | /* -- non-admin -- */ | ||
245 | // chargement des évènements à la création du calendrier | ||
246 | // et au changement de dates affichées (boutons flèches mais pas changement de vue) | ||
247 | if($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['action']) && $_GET['action'] === 'get_events' | ||
248 | && isset($_GET['start']) && isset($_GET['end']) && empty($_POST)) | ||
249 | { | ||
250 | CalendarController::getData($entityManager); | ||
251 | } | ||
252 | |||
253 | if($_SESSION['admin'] === true){ | ||
254 | // ... | ||
255 | } | ||
256 | } \ No newline at end of file | ||
diff --git a/src/model/EventDTO.php b/src/model/EventDTO.php index 70013dd..6dd3722 100644 --- a/src/model/EventDTO.php +++ b/src/model/EventDTO.php | |||
@@ -2,7 +2,7 @@ | |||
2 | // src/model/EventDTO.php | 2 | // src/model/EventDTO.php |
3 | // | 3 | // |
4 | // classe de données JSONifiable compatible avec fullcalendar | 4 | // classe de données JSONifiable compatible avec fullcalendar |
5 | // servira aussi pour l'import/export de fichiers .ics | 5 | // servira éventuellement pour l'import/export de fichiers .ics |
6 | 6 | ||
7 | declare(strict_types=1); | 7 | declare(strict_types=1); |
8 | 8 | ||