From 80de6834a11734c6d3e047635b63ec93f2f68345 Mon Sep 17 00:00:00 2001 From: polo Date: Mon, 11 May 2026 01:20:16 +0200 Subject: =?UTF-8?q?restauration=20compl=C3=A8te=20de=20la=20BDD=20(sauf=20?= =?UTF-8?q?table=20user)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/css/maintenance.css | 2 +- public/js/maintenance.js | 5 ++- src/controller/MaintenanceController.php | 32 +++++++++++++- src/controller/PageManagementController.php | 2 - src/controller/ViewDirector.php | 2 +- src/service/Backup.php | 66 +++++++++++++++++++++++++++-- src/service/router.php | 62 ++++++++++++++++++++++----- src/view/templates/maintenance.php | 25 ++++++----- 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 @@ { margin-top: 8px; } -.maintenance p +.maintenance p, .maintenance form { margin: 10px 0; } 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(){ } }); fetcher.send({}); -} \ No newline at end of file +} + +// notification après restauration + 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); use Doctrine\ORM\EntityManager; use App\Entity\log; use Symfony\Component\Process\Exception\ProcessFailedException; +use Symfony\Component\HttpFoundation\File\UploadedFile; class MaintenanceController { @@ -49,7 +50,7 @@ class MaintenanceController die; } - static public function getLastDump(EntityManager $entityManager): void + static public function getLastDump(): void { try{ $file_path = Backup::getLastBackupName(); @@ -66,4 +67,33 @@ class MaintenanceController } die; } + + // parce qu'il faut un contrôleur + static public function handleBackupSelection(EntityManager $entityManager, string $selected_file): void + { + if(pathinfo($selected_file)['extension'] !== 'sql'){ // pas censé se produire en fait + throw new Exception("charger un fichier au format SQL"); + } + + Backup::restoreDatabase($entityManager, $selected_file); + } + + static public function downloadSQL(EntityManager $entityManager, UploadedFile $uploaded_file): void + { + if(pathinfo($uploaded_file->getClientOriginalName())['extension'] !== 'sql'){ + throw new Exception("charger un fichier au format SQL"); + } + //echo $uploaded_file->getSize(); // à garder de côté au cas où + + $server_place = Config::$database . '_' . new DateTime()->format('Y-m-d') . '_uploaded.sql'; + + try{ + // enregistrer le fichier + var_dump($uploaded_file->move(Backup::$backup_dir, $server_place)); + + // s'en servir + Backup::restoreDatabase($entityManager, $server_place); + } + finally{} + } } \ 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); use App\Entity\Page; use App\Entity\Node; use App\Entity\NodeData; -use App\Entity\Presentation; //use App\Entity\Image; -use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\EntityManager; class 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 return new Response($this->html, 302); } elseif(CURRENT_PAGE === 'maintenance'){ - Backup::mySQLdump($entityManager); // créer un nouveau backup + Backup::mySQLdump($entityManager, 'auto'); // créer un nouveau backup } 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 class Backup { - static private string $backup_dir = '../var/backups'; + static public string $backup_dir = '../var/backups'; static private int $amount_to_keep = 30; - static public function mySQLdump(EntityManager $entityManager): string + static public function mySQLdump(EntityManager $entityManager, string $type): string { - $file_path = self::$backup_dir . '/db_' . Config::$database . '_' . new DateTime()->format('Y-m-d') . '.sql'; + $file_path = self::$backup_dir . '/' . Config::$database . '_' . new DateTime()->format('Y-m-d') . '_' . $type . '.sql'; // les versions de mysql sont comme ci: 8.0.36 // celles de mariadb sont comme ça: 10.11.6-MariaDB @@ -89,4 +89,64 @@ class Backup unlink($file); } } + + static public function restoreDatabase(EntityManager $entityManager, string $file_name): void + { + // backup de sécurité + Backup::mySQLdump($entityManager, 'before-restore'); + + $version = $entityManager->getConnection()->fetchOne('SELECT VERSION()'); + $engine = stripos($version, 'mariadb') !== false ? 'mariadb' : 'mysql'; + + // choisir les tables à restaurer + $tables = $entityManager->getConnection()->createSchemaManager()->listTableNames(); + foreach($tables as $key => $elem){ + if($elem === TABLE_PREFIX . 'user'){ + unset($tables[$key]); + } + } + // sécurité cas pas normal + if(empty($tables)){ + throw new Exception("Aucune table à supprimer"); + } + + $tmp = tempnam('../var', 'tmp_db_codes_'); // crée un fichier avec un nom aléatoire et des droits 600 (concurrence) + file_put_contents($tmp, + "[client]\n + user=" . Config::$user . "\n + password=" . Config::$password . "\n + host=" . Config::$db_host . "\n"); + + + + $command = new Process([ + $engine, // mariadb ou mysql + '--defaults-extra-file=' . $tmp, // pour ne pas enregistrer les codes dans l'historique de la console ou dans les processus de l'OS + Config::$database + ]); + $command->setInput(file_get_contents(Backup::$backup_dir . '/' . $file_name)); // l'entrée < + + + + try{ + // tout effacer + $tables_with_backquotes = array_map(fn($t) => '`' . $t . '`', $tables); + $sql = "SET FOREIGN_KEY_CHECKS=0; DROP TABLE " . implode(', ', $tables_with_backquotes) . "; SET FOREIGN_KEY_CHECKS=1;"; + $entityManager->getConnection()->executeStatement($sql); + + // la table user restante va poser problème + $entityManager->getConnection()->executeStatement('RENAME TABLE `' . TABLE_PREFIX . 'user` TO `' . TABLE_PREFIX . 'user_dont_touch`;'); + + // restaurer + $command->mustRun(); // comme run() mais lance une ProcessFailedException + + // remettre table user comme avant + $entityManager->getConnection()->executeStatement('DROP TABLE `' . TABLE_PREFIX . 'user`;'); + $entityManager->getConnection()->executeStatement('RENAME TABLE `' . TABLE_PREFIX . 'user_dont_touch` TO `' . TABLE_PREFIX . 'user`;'); + } + finally{ + // exécuté même quand situé après "return" + unlink($tmp); + } + } } \ 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 @@ // src/service/router.php // /* fonctionnement: -=> 1er test, méthode http: GET, POST ou autre chose +=> 1er test, méthode http GET? POST? => 2ème test, type de contenu (méthode POST uniquement): "application/x-www-form-urlencoded" = formulaire "application/json" = requête AJAX avec fetch() @@ -46,7 +46,7 @@ if($request->getMethod() === 'GET'){ if(IS_ADMIN === true){ if($request->query->has('action') && $request->query->get('action') === 'get_mysqldump'){ - MaintenanceController::getLastDump($entityManager); + MaintenanceController::getLastDump(); die; } } @@ -88,7 +88,6 @@ elseif($request->getMethod() === 'POST'){ } } - if(IS_ADMIN === true) { /* -- requêtes AJAX -- */ @@ -244,8 +243,8 @@ elseif($request->getMethod() === 'POST'){ } } - // upload avec FormData - elseif(strpos($_SERVER['CONTENT_TYPE'], 'multipart/form-data') !== false) + /* -- upload avec FormData OU formulaire HTML AVEC fichier -- */ + elseif(str_starts_with($request->headers->get('Content-Type'), 'multipart/form-data')) // = $_SERVER['CONTENT_TYPE'] { // dans tinymce avec le plugin (bouton "insérer une image" de l'éditeur ou glisser-déposer) if($request->query->has('action') && $request->query->get('action') === 'upload_image_tinymce'){ @@ -258,17 +257,38 @@ elseif($request->getMethod() === 'POST'){ elseif($request->query->has('head_foot_image')){ HeadFootController::uploadAsset($entityManager, $request->query->get('head_foot_image')); } + + /* -- page Maintenance -- */ + elseif($request->query->has('action') && $request->query->get('action') === 'restore_database' + && $request->request->has('hidden') && $request->get('hidden') === '' + && $request->files->has('uploaded_sql')) + { + $url = new URL; + if($request->query->has('from')){ + $url->addParams(['page' => $request->query->get('from')]); + } + + try{ + MaintenanceController::downloadSQL($entityManager, $request->files->get('uploaded_sql')); + $url->addParams(['database_restauration' => 'successful']); + } + catch(Exception $e){ + $url->addParams(['database_restauration' => $e->getMessage()]); + } + + header('Location: ' . $url); + die; + } } // requêtes XMLHttpRequest elseif(isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') { - //echo "requête XMLHttpRequest reçue par le serveur"; echo json_encode(['success' => false]); // noyer le poisson en laissant penser que le site gère les requêtes XHR die; } - /* -- envoi formulaire HTML -- */ + /* -- formulaire HTML SANS fichier -- */ elseif($_SERVER['CONTENT_TYPE'] === 'application/x-www-form-urlencoded') { if($request->query->has('action') && $request->query->get('action') === 'delete_article' && isset($_GET['id'])){ @@ -294,7 +314,6 @@ elseif($request->getMethod() === 'POST'){ /* -- mode Modification d'une page -- */ - // modification du chemins en snake_case elseif(isset($_POST['page_menu_path']) && $_POST['page_menu_path'] !== null && isset($_POST['page_id']) && $_POST['page_id'] !== null @@ -318,7 +337,6 @@ elseif($request->getMethod() === 'POST'){ /* -- page Menu et chemins -- */ - // création d'une entrée de menu avec une URL elseif(isset($_POST["label_input"]) && isset($_POST["url_input"]) && isset($_POST["location"])){ MenuAndPathsController::newUrlMenuEntry($entityManager); @@ -339,12 +357,35 @@ elseif($request->getMethod() === 'POST'){ UserController::updatePassword($entityManager); } + /* -- page Maintenance -- */ + elseif($request->query->has('action') && $request->query->get('action') === 'restore_database' + && $request->request->has('hidden') && $request->get('hidden') === '' + && $request->request->has('selected_sql')) + { + $url = new URL; + if($request->query->has('from')){ + $url->addParams(['page' => $request->query->get('from')]); + } + + try{ + MaintenanceController::handleBackupSelection($entityManager, $request->request->get('selected_sql')); + $url->addParams(['database_restauration' => 'successful']); + } + catch(Exception $e){ + $url->addParams(['database_restauration' => $e->getMessage()]); + } + + header('Location: ' . $url); + die; + } + // redirection page d'accueil else{ header("Location: " . new URL(['error' => 'paramètres inconnus'])); die; } } + // POST admin ne matchant pas else{ echo json_encode(['success' => false]); @@ -393,5 +434,4 @@ else{ http_response_code(500); echo "erreur côté serveur"; } -} -//die; // inutile \ No newline at end of file +} \ 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 @@
- Obtenir un fichier SQL à conserver sur votre ordinateur. Une sauvegarde est réalisée à chaque visite de cette page. + Obtenir un fichier SQL à conserver sur votre ordinateur. Une sauvegarde (désignée par "auto") est réalisée à chaque visite de cette page.

Restaurer la base de données à partir d'un fichier SQL.
- Attention l'actuelle BDD sera écrasée! + Attention, l'actuelle BDD sera écrasée! (à l'exception de la table user)

-

- - -

-

- - - -

+ + + +
+
+ + + +
-- cgit v1.2.3