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 = TABLE_PREFIX ?>user)