aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--public/css/maintenance.css2
-rw-r--r--public/js/maintenance.js5
-rw-r--r--src/controller/MaintenanceController.php32
-rw-r--r--src/controller/PageManagementController.php2
-rw-r--r--src/controller/ViewDirector.php2
-rw-r--r--src/service/Backup.php66
-rw-r--r--src/service/router.php62
-rw-r--r--src/view/templates/maintenance.php25
8 files changed, 165 insertions, 31 deletions
diff --git a/public/css/maintenance.css b/public/css/maintenance.css
index 8fd0601..9b6f525 100644
--- a/public/css/maintenance.css
+++ b/public/css/maintenance.css
@@ -2,7 +2,7 @@
2{ 2{
3 margin-top: 8px; 3 margin-top: 8px;
4} 4}
5.maintenance p 5.maintenance p, .maintenance form
6{ 6{
7 margin: 10px 0; 7 margin: 10px 0;
8} 8}
diff --git a/public/js/maintenance.js b/public/js/maintenance.js
index c12bd6b..87f4aec 100644
--- a/public/js/maintenance.js
+++ b/public/js/maintenance.js
@@ -38,4 +38,7 @@ function cleanLogs(){
38 } 38 }
39 }); 39 });
40 fetcher.send({}); 40 fetcher.send({});
41} \ No newline at end of file 41}
42
43// notification après restauration
44
diff --git a/src/controller/MaintenanceController.php b/src/controller/MaintenanceController.php
index c60ca1c..c62b21b 100644
--- a/src/controller/MaintenanceController.php
+++ b/src/controller/MaintenanceController.php
@@ -6,6 +6,7 @@ declare(strict_types=1);
6use Doctrine\ORM\EntityManager; 6use Doctrine\ORM\EntityManager;
7use App\Entity\log; 7use App\Entity\log;
8use Symfony\Component\Process\Exception\ProcessFailedException; 8use Symfony\Component\Process\Exception\ProcessFailedException;
9use Symfony\Component\HttpFoundation\File\UploadedFile;
9 10
10class MaintenanceController 11class MaintenanceController
11{ 12{
@@ -49,7 +50,7 @@ class MaintenanceController
49 die; 50 die;
50 } 51 }
51 52
52 static public function getLastDump(EntityManager $entityManager): void 53 static public function getLastDump(): void
53 { 54 {
54 try{ 55 try{
55 $file_path = Backup::getLastBackupName(); 56 $file_path = Backup::getLastBackupName();
@@ -66,4 +67,33 @@ class MaintenanceController
66 } 67 }
67 die; 68 die;
68 } 69 }
70
71 // parce qu'il faut un contrôleur
72 static public function handleBackupSelection(EntityManager $entityManager, string $selected_file): void
73 {
74 if(pathinfo($selected_file)['extension'] !== 'sql'){ // pas censé se produire en fait
75 throw new Exception("charger un fichier au format SQL");
76 }
77
78 Backup::restoreDatabase($entityManager, $selected_file);
79 }
80
81 static public function downloadSQL(EntityManager $entityManager, UploadedFile $uploaded_file): void
82 {
83 if(pathinfo($uploaded_file->getClientOriginalName())['extension'] !== 'sql'){
84 throw new Exception("charger un fichier au format SQL");
85 }
86 //echo $uploaded_file->getSize(); // à garder de côté au cas où
87
88 $server_place = Config::$database . '_' . new DateTime()->format('Y-m-d') . '_uploaded.sql';
89
90 try{
91 // enregistrer le fichier
92 var_dump($uploaded_file->move(Backup::$backup_dir, $server_place));
93
94 // s'en servir
95 Backup::restoreDatabase($entityManager, $server_place);
96 }
97 finally{}
98 }
69} \ No newline at end of file 99} \ No newline at end of file
diff --git a/src/controller/PageManagementController.php b/src/controller/PageManagementController.php
index 7078866..8c4092f 100644
--- a/src/controller/PageManagementController.php
+++ b/src/controller/PageManagementController.php
@@ -6,9 +6,7 @@ declare(strict_types=1);
6use App\Entity\Page; 6use App\Entity\Page;
7use App\Entity\Node; 7use App\Entity\Node;
8use App\Entity\NodeData; 8use App\Entity\NodeData;
9use App\Entity\Presentation;
10//use App\Entity\Image; 9//use App\Entity\Image;
11use Doctrine\Common\Collections\ArrayCollection;
12use Doctrine\ORM\EntityManager; 10use Doctrine\ORM\EntityManager;
13 11
14class PageManagementController 12class PageManagementController
diff --git a/src/controller/ViewDirector.php b/src/controller/ViewDirector.php
index 6883b73..a572f14 100644
--- a/src/controller/ViewDirector.php
+++ b/src/controller/ViewDirector.php
@@ -62,7 +62,7 @@ class ViewDirector extends AbstractBuilder // ViewDirector est aussi le premier
62 return new Response($this->html, 302); 62 return new Response($this->html, 302);
63 } 63 }
64 elseif(CURRENT_PAGE === 'maintenance'){ 64 elseif(CURRENT_PAGE === 'maintenance'){
65 Backup::mySQLdump($entityManager); // créer un nouveau backup 65 Backup::mySQLdump($entityManager, 'auto'); // créer un nouveau backup
66 } 66 }
67 67
68 68
diff --git a/src/service/Backup.php b/src/service/Backup.php
index 313d1f6..63368b5 100644
--- a/src/service/Backup.php
+++ b/src/service/Backup.php
@@ -8,12 +8,12 @@ use Symfony\Component\Process\Process; // protection injection dans le shell
8 8
9class Backup 9class Backup
10{ 10{
11 static private string $backup_dir = '../var/backups'; 11 static public string $backup_dir = '../var/backups';
12 static private int $amount_to_keep = 30; 12 static private int $amount_to_keep = 30;
13 13
14 static public function mySQLdump(EntityManager $entityManager): string 14 static public function mySQLdump(EntityManager $entityManager, string $type): string
15 { 15 {
16 $file_path = self::$backup_dir . '/db_' . Config::$database . '_' . new DateTime()->format('Y-m-d') . '.sql'; 16 $file_path = self::$backup_dir . '/' . Config::$database . '_' . new DateTime()->format('Y-m-d') . '_' . $type . '.sql';
17 17
18 // les versions de mysql sont comme ci: 8.0.36 18 // les versions de mysql sont comme ci: 8.0.36
19 // celles de mariadb sont comme ça: 10.11.6-MariaDB 19 // celles de mariadb sont comme ça: 10.11.6-MariaDB
@@ -89,4 +89,64 @@ class Backup
89 unlink($file); 89 unlink($file);
90 } 90 }
91 } 91 }
92
93 static public function restoreDatabase(EntityManager $entityManager, string $file_name): void
94 {
95 // backup de sécurité
96 Backup::mySQLdump($entityManager, 'before-restore');
97
98 $version = $entityManager->getConnection()->fetchOne('SELECT VERSION()');
99 $engine = stripos($version, 'mariadb') !== false ? 'mariadb' : 'mysql';
100
101 // choisir les tables à restaurer
102 $tables = $entityManager->getConnection()->createSchemaManager()->listTableNames();
103 foreach($tables as $key => $elem){
104 if($elem === TABLE_PREFIX . 'user'){
105 unset($tables[$key]);
106 }
107 }
108 // sécurité cas pas normal
109 if(empty($tables)){
110 throw new Exception("Aucune table à supprimer");
111 }
112
113 $tmp = tempnam('../var', 'tmp_db_codes_'); // crée un fichier avec un nom aléatoire et des droits 600 (concurrence)
114 file_put_contents($tmp,
115 "[client]\n
116 user=" . Config::$user . "\n
117 password=" . Config::$password . "\n
118 host=" . Config::$db_host . "\n");
119
120
121
122 $command = new Process([
123 $engine, // mariadb ou mysql
124 '--defaults-extra-file=' . $tmp, // pour ne pas enregistrer les codes dans l'historique de la console ou dans les processus de l'OS
125 Config::$database
126 ]);
127 $command->setInput(file_get_contents(Backup::$backup_dir . '/' . $file_name)); // l'entrée <
128
129
130
131 try{
132 // tout effacer
133 $tables_with_backquotes = array_map(fn($t) => '`' . $t . '`', $tables);
134 $sql = "SET FOREIGN_KEY_CHECKS=0; DROP TABLE " . implode(', ', $tables_with_backquotes) . "; SET FOREIGN_KEY_CHECKS=1;";
135 $entityManager->getConnection()->executeStatement($sql);
136
137 // la table user restante va poser problème
138 $entityManager->getConnection()->executeStatement('RENAME TABLE `' . TABLE_PREFIX . 'user` TO `' . TABLE_PREFIX . 'user_dont_touch`;');
139
140 // restaurer
141 $command->mustRun(); // comme run() mais lance une ProcessFailedException
142
143 // remettre table user comme avant
144 $entityManager->getConnection()->executeStatement('DROP TABLE `' . TABLE_PREFIX . 'user`;');
145 $entityManager->getConnection()->executeStatement('RENAME TABLE `' . TABLE_PREFIX . 'user_dont_touch` TO `' . TABLE_PREFIX . 'user`;');
146 }
147 finally{
148 // exécuté même quand situé après "return"
149 unlink($tmp);
150 }
151 }
92} \ No newline at end of file 152} \ No newline at end of file
diff --git a/src/service/router.php b/src/service/router.php
index 98f8fd6..8ddaf7f 100644
--- a/src/service/router.php
+++ b/src/service/router.php
@@ -2,7 +2,7 @@
2// src/service/router.php 2// src/service/router.php
3// 3//
4/* fonctionnement: 4/* fonctionnement:
5=> 1er test, méthode http: GET, POST ou autre chose 5=> 1er test, méthode http GET? POST?
6=> 2ème test, type de contenu (méthode POST uniquement): 6=> 2ème test, type de contenu (méthode POST uniquement):
7"application/x-www-form-urlencoded" = formulaire 7"application/x-www-form-urlencoded" = formulaire
8"application/json" = requête AJAX avec fetch() 8"application/json" = requête AJAX avec fetch()
@@ -46,7 +46,7 @@ if($request->getMethod() === 'GET'){
46 46
47 if(IS_ADMIN === true){ 47 if(IS_ADMIN === true){
48 if($request->query->has('action') && $request->query->get('action') === 'get_mysqldump'){ 48 if($request->query->has('action') && $request->query->get('action') === 'get_mysqldump'){
49 MaintenanceController::getLastDump($entityManager); 49 MaintenanceController::getLastDump();
50 die; 50 die;
51 } 51 }
52 } 52 }
@@ -88,7 +88,6 @@ elseif($request->getMethod() === 'POST'){
88 } 88 }
89 } 89 }
90 90
91
92 if(IS_ADMIN === true) 91 if(IS_ADMIN === true)
93 { 92 {
94 /* -- requêtes AJAX -- */ 93 /* -- requêtes AJAX -- */
@@ -244,8 +243,8 @@ elseif($request->getMethod() === 'POST'){
244 } 243 }
245 } 244 }
246 245
247 // upload avec FormData 246 /* -- upload avec FormData OU formulaire HTML AVEC fichier -- */
248 elseif(strpos($_SERVER['CONTENT_TYPE'], 'multipart/form-data') !== false) 247 elseif(str_starts_with($request->headers->get('Content-Type'), 'multipart/form-data')) // = $_SERVER['CONTENT_TYPE']
249 { 248 {
250 // dans tinymce avec le plugin (bouton "insérer une image" de l'éditeur ou glisser-déposer) 249 // dans tinymce avec le plugin (bouton "insérer une image" de l'éditeur ou glisser-déposer)
251 if($request->query->has('action') && $request->query->get('action') === 'upload_image_tinymce'){ 250 if($request->query->has('action') && $request->query->get('action') === 'upload_image_tinymce'){
@@ -258,17 +257,38 @@ elseif($request->getMethod() === 'POST'){
258 elseif($request->query->has('head_foot_image')){ 257 elseif($request->query->has('head_foot_image')){
259 HeadFootController::uploadAsset($entityManager, $request->query->get('head_foot_image')); 258 HeadFootController::uploadAsset($entityManager, $request->query->get('head_foot_image'));
260 } 259 }
260
261 /* -- page Maintenance -- */
262 elseif($request->query->has('action') && $request->query->get('action') === 'restore_database'
263 && $request->request->has('hidden') && $request->get('hidden') === ''
264 && $request->files->has('uploaded_sql'))
265 {
266 $url = new URL;
267 if($request->query->has('from')){
268 $url->addParams(['page' => $request->query->get('from')]);
269 }
270
271 try{
272 MaintenanceController::downloadSQL($entityManager, $request->files->get('uploaded_sql'));
273 $url->addParams(['database_restauration' => 'successful']);
274 }
275 catch(Exception $e){
276 $url->addParams(['database_restauration' => $e->getMessage()]);
277 }
278
279 header('Location: ' . $url);
280 die;
281 }
261 } 282 }
262 283
263 // requêtes XMLHttpRequest 284 // requêtes XMLHttpRequest
264 elseif(isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') 285 elseif(isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest')
265 { 286 {
266 //echo "requête XMLHttpRequest reçue par le serveur";
267 echo json_encode(['success' => false]); // noyer le poisson en laissant penser que le site gère les requêtes XHR 287 echo json_encode(['success' => false]); // noyer le poisson en laissant penser que le site gère les requêtes XHR
268 die; 288 die;
269 } 289 }
270 290
271 /* -- envoi formulaire HTML -- */ 291 /* -- formulaire HTML SANS fichier -- */
272 elseif($_SERVER['CONTENT_TYPE'] === 'application/x-www-form-urlencoded') 292 elseif($_SERVER['CONTENT_TYPE'] === 'application/x-www-form-urlencoded')
273 { 293 {
274 if($request->query->has('action') && $request->query->get('action') === 'delete_article' && isset($_GET['id'])){ 294 if($request->query->has('action') && $request->query->get('action') === 'delete_article' && isset($_GET['id'])){
@@ -294,7 +314,6 @@ elseif($request->getMethod() === 'POST'){
294 314
295 315
296 /* -- mode Modification d'une page -- */ 316 /* -- mode Modification d'une page -- */
297
298 // modification du chemins en snake_case 317 // modification du chemins en snake_case
299 elseif(isset($_POST['page_menu_path']) && $_POST['page_menu_path'] !== null 318 elseif(isset($_POST['page_menu_path']) && $_POST['page_menu_path'] !== null
300 && isset($_POST['page_id']) && $_POST['page_id'] !== null 319 && isset($_POST['page_id']) && $_POST['page_id'] !== null
@@ -318,7 +337,6 @@ elseif($request->getMethod() === 'POST'){
318 337
319 338
320 /* -- page Menu et chemins -- */ 339 /* -- page Menu et chemins -- */
321
322 // création d'une entrée de menu avec une URL 340 // création d'une entrée de menu avec une URL
323 elseif(isset($_POST["label_input"]) && isset($_POST["url_input"]) && isset($_POST["location"])){ 341 elseif(isset($_POST["label_input"]) && isset($_POST["url_input"]) && isset($_POST["location"])){
324 MenuAndPathsController::newUrlMenuEntry($entityManager); 342 MenuAndPathsController::newUrlMenuEntry($entityManager);
@@ -339,12 +357,35 @@ elseif($request->getMethod() === 'POST'){
339 UserController::updatePassword($entityManager); 357 UserController::updatePassword($entityManager);
340 } 358 }
341 359
360 /* -- page Maintenance -- */
361 elseif($request->query->has('action') && $request->query->get('action') === 'restore_database'
362 && $request->request->has('hidden') && $request->get('hidden') === ''
363 && $request->request->has('selected_sql'))
364 {
365 $url = new URL;
366 if($request->query->has('from')){
367 $url->addParams(['page' => $request->query->get('from')]);
368 }
369
370 try{
371 MaintenanceController::handleBackupSelection($entityManager, $request->request->get('selected_sql'));
372 $url->addParams(['database_restauration' => 'successful']);
373 }
374 catch(Exception $e){
375 $url->addParams(['database_restauration' => $e->getMessage()]);
376 }
377
378 header('Location: ' . $url);
379 die;
380 }
381
342 // redirection page d'accueil 382 // redirection page d'accueil
343 else{ 383 else{
344 header("Location: " . new URL(['error' => 'paramètres inconnus'])); 384 header("Location: " . new URL(['error' => 'paramètres inconnus']));
345 die; 385 die;
346 } 386 }
347 } 387 }
388
348 // POST admin ne matchant pas 389 // POST admin ne matchant pas
349 else{ 390 else{
350 echo json_encode(['success' => false]); 391 echo json_encode(['success' => false]);
@@ -393,5 +434,4 @@ else{
393 http_response_code(500); 434 http_response_code(500);
394 echo "erreur côté serveur"; 435 echo "erreur côté serveur";
395 } 436 }
396} 437} \ No newline at end of file
397//die; // inutile \ No newline at end of file
diff --git a/src/view/templates/maintenance.php b/src/view/templates/maintenance.php
index 58e04c0..bc9caf0 100644
--- a/src/view/templates/maintenance.php
+++ b/src/view/templates/maintenance.php
@@ -21,24 +21,27 @@
21 <a href="<?= new URL(['action' => 'get_mysqldump']) ?>"> 21 <a href="<?= new URL(['action' => 'get_mysqldump']) ?>">
22 <button id="get_mysqldump">Télécharger une sauvegarde de la base de données</button> 22 <button id="get_mysqldump">Télécharger une sauvegarde de la base de données</button>
23 </a><br> 23 </a><br>
24 <i>Obtenir un fichier SQL à conserver sur votre ordinateur. Une sauvegarde est réalisée à chaque visite de cette page.</i> 24 <i>Obtenir un fichier SQL à conserver sur votre ordinateur. Une sauvegarde (désignée par "auto") est réalisée à chaque visite de cette page.</i>
25 </p> 25 </p>
26 </div> 26 </div>
27 <div class="basic_div"> 27 <div class="basic_div">
28 <p>Restaurer la base de données à partir d'un fichier SQL.<br> 28 <p>Restaurer la base de données à partir d'un fichier SQL.<br>
29 <i>Attention l'actuelle BDD sera écrasée!</i> 29 <i>Attention, l'actuelle BDD sera écrasée! (à l'exception de la table <?= TABLE_PREFIX ?>user)</i>
30 </p> 30 </p>
31 <p> 31 <form action="<?= new URL(['from' => 'maintenance', 'action' => 'restore_database']) ?>" method="post">
32 <label for="">Utiliser une sauvegarde conservée sur le serveur</label> 32 <label for="selected_sql">Utiliser une sauvegarde conservée sur le serveur:</label><br>
33 <select> 33 <select id="selected_sql" name="selected_sql">
34 <?= $backup_options ?> 34 <?= $backup_options ?>
35 </select> 35 </select>
36 </p> 36 <input type="hidden" name="hidden" value="">
37 <p> 37 <input type="submit" value="Valider" onclick="return confirm('Voulez-vous vraiment restaurer la base de données? Toutes les données seront supprimées et remplacées par les nouvelles.')">
38 <label for="restore_sql_dump">Utiliser un fichier sur votre ordinateur:</label> 38 </form>
39 <input id="restore_sql_dump" type="file" accept=".sql" name="restore_sql_dump"> 39 <form enctype="multipart/form-data" action="<?= new URL(['from' => 'maintenance', 'action' => 'restore_database']) ?>" method="post">
40 40 <label for="uploaded_sql">Utiliser un fichier sur votre ordinateur:</label><br>
41 </p> 41 <input id="uploaded_sql" type="file" accept=".sql" name="uploaded_sql">
42 <input type="hidden" name="hidden" value="">
43 <input type="submit" value="Valider" onclick="return confirm('Voulez-vous vraiment restaurer la base de données? Toutes les données seront supprimées et remplacées par les nouvelles.')">
44 </form>
42 </div> 45 </div>
43 46
44 <div class="basic_div"> 47 <div class="basic_div">