true, 'poignee'=>resource] ou ['ok'=>false, 'raison'=>string]. // ---------------------------------------------------------------------------- function Verrouiller_boitier($adresse_mac) { // Nom de fichier construit uniquement à partir des 12 caractères hexadécimaux de l'adresse // (quel que soit son format d'écriture, avec ou sans ':') - jamais de chemin arbitraire : $hex = strtoupper(preg_replace('/[^0-9A-Fa-f]/', '', (string)$adresse_mac)); if (strlen($hex) !== 12) return ['ok' => false, 'raison' => "adresse MAC invalide pour un verrou"]; if (!is_dir(REPERTOIRE_VERROUS) && !@mkdir(REPERTOIRE_VERROUS, 0770, true) && !is_dir(REPERTOIRE_VERROUS)) { return ['ok' => false, 'raison' => "répertoire de verrous " . REPERTOIRE_VERROUS . " impossible à créer"]; } // Mode 'c' : crée le fichier s'il n'existe pas, sans jamais le tronquer. $poignee = @fopen(REPERTOIRE_VERROUS . "/boitier_{$hex}.lock", 'c'); if ($poignee === false) return ['ok' => false, 'raison' => "fichier de verrou impossible à ouvrir"]; if (!flock($poignee, LOCK_EX | LOCK_NB)) { fclose($poignee); return ['ok' => false, 'raison' => "déjà en cours de traitement par un autre processus"]; } return ['ok' => true, 'poignee' => $poignee]; } function Liberer_boitier($poignee) { if (!is_resource($poignee)) return; flock($poignee, LOCK_UN); fclose($poignee); } // ---------------------------------------------------------------------------- // Résout l'affectation active d'un boîtier (par adresse MAC) - cf synthèse // §38. Renvoie ['existe' => bool, 'id_bateau' => int|null]. 'existe' => false // couvre deux cas distincts à cet endroit, traités de la même façon : boîtier // jamais affecté (première connexion), ou affecté par le passé puis retiré // (toutes ses lignes ont une Date_fin renseignée). // ---------------------------------------------------------------------------- function Resoudre_affectation_active($db, $adresse_mac) { $requete = "SELECT ID_Bateau FROM TB_Affectations_bateaux WHERE Adresse_mac=? AND Date_fin IS NULL"; $stmt = $db->prepare($requete); $stmt->bind_param('s', $adresse_mac); $stmt->execute(); $stmt->bind_result($id_bateau); $trouve = $stmt->fetch(); $stmt->close(); if (!$trouve) return ['existe' => false, 'id_bateau' => null]; return ['existe' => true, 'id_bateau' => $id_bateau]; } // ---------------------------------------------------------------------------- // Déchiffre + décompresse le contenu brut d'une archive (même chaîne openssl, // exactement, que côté CM5 - cf synthèse §33.2), puis extrait les champs de // son en-tête (# [Champ=Valeur]). Renvoie ['ok'=>true, 'entete'=>[...], // 'sql'=>string] ou ['ok'=>false, 'raison'=>string]. // Nécessite CLE_CHIFFREMENT_ARCHIVE (config/confidentiel.php) - même valeur, // exactement, que côté CM5 (cf synthèse §35.6). // ---------------------------------------------------------------------------- function Decrypter_archive($contenu_chiffre) { $fichier_enc = tempnam(sys_get_temp_dir(), 'arch_enc_'); $fichier_gz = $fichier_enc . '.gz'; file_put_contents($fichier_enc, $contenu_chiffre); $commande = "openssl enc -aes-256-cbc -pbkdf2 -iter 10000 -md sha256 -d -in " . escapeshellarg($fichier_enc) . " -out " . escapeshellarg($fichier_gz) . " -pass pass:" . escapeshellarg(CLE_CHIFFREMENT_ARCHIVE) . " 2>&1"; exec($commande, $sortie, $code_retour); if ($code_retour !== 0) { unlink($fichier_enc); return ['ok' => false, 'raison' => "échec du déchiffrement"]; } $sql = @shell_exec("gzip -dc " . escapeshellarg($fichier_gz) . " 2>&1"); unlink($fichier_enc); @unlink($fichier_gz); if ($sql === null || $sql === '') { return ['ok' => false, 'raison' => "échec de la décompression"]; } // Lecture des lignes d'en-tête (uniquement les toutes premières lignes '# [Champ=Valeur]') : $entete = []; foreach (explode("\n", $sql) as $ligne) { if (!preg_match('/^#\s*\[([A-Za-z_]+)=(.*)\]\s*$/', $ligne, $m)) break; // fin de l'en-tête dès la 1ère ligne qui ne correspond plus $entete[$m[1]] = $m[2]; } return ['ok' => true, 'entete' => $entete, 'sql' => $sql]; } // ---------------------------------------------------------------------------- // Résout la génération active d'un bateau (cf synthèse §35.4). Renvoie // ['existe'=>bool, 'cle'=>int|null, 'numero'=>int|null, 'nom_base'=>string|null, // 'version'=>string|null] - 'version' = Version_soft, version logicielle à // laquelle correspond le schéma de cette base (synthèse §46) // - 'existe'=>false signifie "aucune génération active" (tout premier bateau, // cf synthèse §35.5 point 4), PAS une erreur. // ---------------------------------------------------------------------------- function Resoudre_generation_active($db, $id_bateau) { $requete = "SELECT Cle_primaire, Numero_generation, Nom_base, Version_soft FROM TB_Generations_bateaux WHERE ID_Bateau=? AND Date_fin IS NULL"; $stmt = $db->prepare($requete); $stmt->bind_param('i', $id_bateau); $stmt->execute(); $stmt->bind_result($cle, $numero, $nom_base, $version_soft); $trouve = $stmt->fetch(); $stmt->close(); if (!$trouve) return ['existe' => false, 'cle' => null, 'numero' => null, 'nom_base' => null, 'version' => null]; return ['existe' => true, 'cle' => $cle, 'numero' => $numero, 'nom_base' => $nom_base, 'version' => $version_soft]; } // ---------------------------------------------------------------------------- // Détermine si cette archive doit démarrer une NOUVELLE génération (§35.5) : // - 'factory' ou 'restauration' dans l'en-tête -> toujours une nouvelle // génération, quelle que soit la génération active actuelle. // - 'normale' mais aucune génération active -> tout premier bateau, // création de la génération 1 (même traitement que factory/restauration // à partir de ce point, cf synthèse §35.5). // - 'normale' avec une génération active -> pas de nouvelle génération, // traitement incrémental habituel dans la génération existante. // ---------------------------------------------------------------------------- function Nouvelle_generation_necessaire($origine_bdd, $generation_active) { if ($origine_bdd === 'factory' || $origine_bdd === 'restauration') return true; return !$generation_active['existe']; } // ---------------------------------------------------------------------------- // Résout le nom du fichier de migration SQL correspondant à une version // logicielle donnée (Version_soft_no_package, transmis dans l'en-tête de // chaque archive - cf synthèse §32.2). Renvoie le nom de fichier (string), // ou false si la version est inconnue ou la ligne TB_Versions_sql absente. // ---------------------------------------------------------------------------- // ---------------------------------------------------------------------------- // Compare le MMSI reçu à celui déjà enregistré pour ce bateau (cf synthèse // §32.3). Si TB_Bateaux.MMSI est NULL, la première valeur reçue est affectée // automatiquement (aucun risque, rien à contredire). Renvoie : // ['statut' => 'ok'] // ['statut' => 'quarantaine', 'attendu' => ..., 'recu' => ...] // ---------------------------------------------------------------------------- function Verifier_mmsi($db_principale, $id_bateau, $mmsi_recu) { $requete = "SELECT MMSI FROM TB_Bateaux WHERE Cle_primaire=?"; $stmt = $db_principale->prepare($requete); $stmt->bind_param('i', $id_bateau); $stmt->execute(); $stmt->bind_result($mmsi_attendu); $stmt->fetch(); $stmt->close(); if ($mmsi_attendu === null) { $requete = "UPDATE TB_Bateaux SET MMSI=? WHERE Cle_primaire=?"; $stmt = $db_principale->prepare($requete); $stmt->bind_param('si', $mmsi_recu, $id_bateau); $stmt->execute(); $stmt->close(); // Auto-affectation : l'attendu devient le reçu (rien à contredire) - toujours renseigné en sortie, // jamais NULL, pour ne jamais laisser planer une ambiguïté entre "MMSI inconnu" et "MMSI connu mais // pas indiqué ici" (cf synthèse §39) : return ['statut' => 'ok', 'recu' => $mmsi_recu, 'attendu' => $mmsi_recu]; } if ($mmsi_attendu === $mmsi_recu) return ['statut' => 'ok', 'recu' => $mmsi_recu, 'attendu' => $mmsi_attendu]; return ['statut' => 'quarantaine', 'attendu' => $mmsi_attendu, 'recu' => $mmsi_recu]; } function Resoudre_fichier_sql($db, $version_package) { $requete = "SELECT vs.Fichier FROM TB_Versions v JOIN TB_Versions_sql vs ON vs.Cle_primaire = v.ID_Version_sql WHERE v.Nom = ?"; $stmt = $db->prepare($requete); $stmt->bind_param('s', $version_package); $stmt->execute(); $stmt->bind_result($fichier); $trouve = $stmt->fetch(); $stmt->close(); if (!$trouve) return false; return $fichier; } // ---------------------------------------------------------------------------- // Remplace le nom de base littéral "shipheart" (utilisé dans Factory_structure // pour le CM5 lui-même) par le vrai nom de la base par bateau/génération // côté serveur (cf synthèse §35.4/36) - ancré précisément sur le contexte // (DROP/CREATE DATABASE, USE), jamais un remplacement aveugle du mot : une // valeur de configuration ("logo_shipheart.png") contient aussi ce mot sans // aucun rapport avec un nom de base. Renvoie [contenu_adapte, nb_remplacements]. // ---------------------------------------------------------------------------- function Adapter_nom_base($contenu_sql, $nom_base_cible) { $nom_echappe = '`' . $nom_base_cible . '`'; $contenu_sql = preg_replace('/\bDATABASE IF EXISTS `?shipheart`?\b/', 'DATABASE IF EXISTS ' . $nom_echappe, $contenu_sql, -1, $n1); $contenu_sql = preg_replace('/\bDATABASE IF NOT EXISTS `?shipheart`?\b/', 'DATABASE IF NOT EXISTS ' . $nom_echappe, $contenu_sql, -1, $n2); $contenu_sql = preg_replace('/\bUSE `?shipheart`?\s*;/', 'USE ' . $nom_echappe . ';', $contenu_sql, -1, $n3); return [$contenu_sql, $n1 + $n2 + $n3]; } // ---------------------------------------------------------------------------- // Crée une nouvelle génération pour un bateau (cf synthèse §35.4/35.5/36) : // calcule le nom de la nouvelle base, adapte et exécute le SQL fourni (déjà // résolu par Extraire_zone_usine), clôture l'ancienne génération active si // elle existe (jamais supprimée), puis enregistre la nouvelle dans // TB_Generations_bateaux. Renvoie ['ok'=>bool, 'nom_base'=>string, ...]. // Exécution via le client mysql en ligne de commande (comme côté CM5, // updater.php) - un bloc SQL avec DROP/CREATE DATABASE ne se prête pas bien // à mysqli::multi_query. // ---------------------------------------------------------------------------- function Creer_generation($db_principale, $id_bateau, $motif, $contenu_sql, $version_soft) { $generation_actuelle = Resoudre_generation_active($db_principale, $id_bateau); $prochain_numero = $generation_actuelle['existe'] ? $generation_actuelle['numero'] + 1 : 1; $nom_base = "ship2net_bateau_{$id_bateau}_{$prochain_numero}"; [$sql_adapte, $nb_remplacements] = Adapter_nom_base($contenu_sql, $nom_base); if ($nb_remplacements < 1) { return ['ok' => false, 'raison' => "aucune substitution de nom de base effectuée - fichier SQL probablement mal formé"]; } $fichier_temp = tempnam(sys_get_temp_dir(), 'usine_'); file_put_contents($fichier_temp, $sql_adapte); $commande = "mysql --host=" . escapeshellarg(BDD_BATEAUX_HOST) . " --port=" . escapeshellarg(BDD_BATEAUX_PORT) . " --user=" . escapeshellarg(BDD_BATEAUX_USER) . " --password=" . escapeshellarg(BDD_BATEAUX_PASSWORD) . " < " . escapeshellarg($fichier_temp) . " 2>&1"; exec($commande, $sortie, $code_retour); unlink($fichier_temp); if ($code_retour !== 0) { return ['ok' => false, 'raison' => "échec de l'exécution SQL : " . implode(" | ", $sortie)]; } if ($generation_actuelle['existe']) { $requete = "UPDATE TB_Generations_bateaux SET Date_fin=NOW() WHERE Cle_primaire=?"; $stmt = $db_principale->prepare($requete); $stmt->bind_param('i', $generation_actuelle['cle']); $stmt->execute(); $stmt->close(); } // Version_soft : version de l'archive qui a provoqué cette génération - le schéma de la nouvelle base // est celui de la zone usine du fichier SQL de cette version (synthèse §46) : $requete = "INSERT INTO TB_Generations_bateaux (ID_Bateau, Numero_generation, Nom_base, Motif, Version_soft) VALUES (?, ?, ?, ?, ?)"; $stmt = $db_principale->prepare($requete); $stmt->bind_param('iisss', $id_bateau, $prochain_numero, $nom_base, $motif, $version_soft); if (!$stmt->execute()) { $erreur = $db_principale->error; $stmt->close(); return ['ok' => false, 'raison' => "base créée mais son enregistrement dans TB_Generations_bateaux a échoué : " . $erreur]; } $stmt->close(); return ['ok' => true, 'nom_base' => $nom_base, 'numero' => $prochain_numero]; } // ---------------------------------------------------------------------------- // Extrait la zone "usine" d'un fichier de migration (Factory_structure + // Factory_data ensemble, jusqu'à la fin du fichier) - pour une génération // neuve (cf synthèse §32.2, §35.5). Renvoie false si le marqueur est // introuvable (fichier mal formé - ne devrait jamais arriver en pratique). // ---------------------------------------------------------------------------- function Extraire_zone_usine($contenu_fichier) { if (!preg_match('/^#\s*Factory_structure\b/m', $contenu_fichier, $m, PREG_OFFSET_CAPTURE)) return false; return substr($contenu_fichier, $m[0][1]); } // ---------------------------------------------------------------------------- // Extrait les segments incrémentaux entre deux versions (exclusive pour le // départ, inclusive pour la cible) - pour une mise à jour incrémentale d'une // génération déjà existante (cf synthèse §32.2). $depart et $cible sont des // noms de fichier ("shipheart_sql_AAMMJJ_HHMMSS.sql"), résolus au préalable // via TB_Versions/TB_Versions_sql (bloc 4a). Si $depart === $cible, renvoie // une chaîne vide (rien à migrer - cas normal si le bateau n'a pas été mis à // jour depuis la dernière archive). Renvoie false si l'un des deux marqueurs // est introuvable. // ---------------------------------------------------------------------------- function Extraire_segments_incrementaux($contenu_fichier, $depart, $cible) { if ($depart === $cible) return ''; $motif_depart = '/^#\s*Fin de ' . preg_quote($depart, '/') . '\b.*$/m'; if (!preg_match($motif_depart, $contenu_fichier, $m_depart, PREG_OFFSET_CAPTURE)) return false; $debut = $m_depart[0][1] + strlen($m_depart[0][0]); $motif_cible = '/^#\s*Fin de ' . preg_quote($cible, '/') . '\b.*$/m'; if (!preg_match($motif_cible, $contenu_fichier, $m_cible, PREG_OFFSET_CAPTURE)) return false; $fin = $m_cible[0][1] + strlen($m_cible[0][0]); if ($fin <= $debut) return false; // Ordre incohérent (cible avant départ) - ne devrait jamais arriver, "pas d'update en arrière" (§32.2) return substr($contenu_fichier, $debut, $fin - $debut); } // ---------------------------------------------------------------------------- // Migre le schéma de la base d'une génération EXISTANTE vers la version // logicielle d'une archive 'normale' plus récente (synthèse §32.2, §46) - cas // d'une mise à jour logicielle du bateau (updater.php, Type="update"), qui // laisse Origine_bdd à 'normale' (§35.6). À appeler AVANT le chargement de // l'archive, dont les données ont été produites avec le nouveau schéma. // // Déroulé : // - Version_soft de la génération = version de l'archive -> rien à faire. // - Sinon : fichier SQL actuel (Version_soft -> TB_Versions -> TB_Versions_sql) // et fichier SQL cible (déjà résolu par l'appelant) ; extraction, dans le // fichier cible, de tout ce qui suit le marqueur "# Fin de " // jusqu'au marqueur "# Fin de " inclus ; exécution sur la base de // la génération (nom de base substitué dans les USE) ; puis mise à jour de // Version_soft - même si aucune ligne n'était à exécuter (plusieurs // versions logicielles peuvent partager le même fichier SQL). // // Version_soft n'est mise à jour qu'APRÈS une migration réussie, et AVANT le // chargement : si le chargement échoue ensuite, la base est bien migrée et // la version enregistrée le dit (pas de rejeu à tort). Si la migration // échoue, Version_soft ne bouge pas. // // Limite connue : les ALTER TABLE ne sont pas annulables. Un segment qui // échoue en cours de route laisse une base partiellement migrée -> rejet de // l'archive avec le détail de l'erreur, intervention manuelle nécessaire. // // Renvoie ['ok'=>true, 'message'=>string] ou ['ok'=>false, 'raison'=>string]. // ---------------------------------------------------------------------------- function Migrer_generation($db_principale, $generation_active, $version_cible, $fichier_cible, $contenu_fichier_cible) { $version_actuelle = $generation_active['version']; $nom_base = $generation_active['nom_base']; if ($version_actuelle === null || $version_actuelle === '') { return ['ok' => false, 'raison' => "version de schéma de la génération {$nom_base} inconnue (TB_Generations_bateaux.Version_soft vide) - à renseigner manuellement"]; } if ($version_actuelle === $version_cible) { return ['ok' => true, 'message' => "schéma déjà en version {$version_cible}"]; } $fichier_actuel = Resoudre_fichier_sql($db_principale, $version_actuelle); if ($fichier_actuel === false) { return ['ok' => false, 'raison' => "version actuelle '{$version_actuelle}' de {$nom_base} inconnue de TB_Versions"]; } $segments = Extraire_segments_incrementaux($contenu_fichier_cible, $fichier_actuel, $fichier_cible); if ($segments === false) { return ['ok' => false, 'raison' => "migration {$version_actuelle} ({$fichier_actuel}) -> {$version_cible} ({$fichier_cible}) impossible : marqueur introuvable dans {$fichier_cible}, ou version cible antérieure à la version actuelle"]; } $nb_requetes = 0; if ($segments !== '') { // Les segments commencent par "USE `shipheart`;" (base du CM5) : substitution ciblée vers la base de // la génération, comme pour la zone usine (Adapter_nom_base). Puis contrôle qu'il ne reste AUCUN // USE vers une autre base - une migration ne doit jamais pouvoir s'exécuter ailleurs que sur la base // de ce bateau : [$sql_adapte, ] = Adapter_nom_base($segments, $nom_base); if (preg_match_all('/^\s*USE\s+`?([A-Za-z0-9_]+)`?\s*;/mi', $sql_adapte, $m)) { foreach ($m[1] as $base_visee) { if ($base_visee !== $nom_base) { return ['ok' => false, 'raison' => "segment de migration visant une autre base ('{$base_visee}') - exécution refusée"]; } } } $execution = Executer_sql_base($nom_base, $sql_adapte); if (!$execution['ok']) { return ['ok' => false, 'raison' => "échec de la migration {$version_actuelle} -> {$version_cible} sur {$nom_base} (base possiblement partiellement migrée) : " . $execution['raison']]; } // Nombre de requêtes exécutées, à titre indicatif dans le journal (hors USE) : $nb_requetes = preg_match_all('/;\s*$/m', $sql_adapte) - preg_match_all('/^\s*USE\s+[^;]*;\s*$/mi', $sql_adapte); } $requete = "UPDATE TB_Generations_bateaux SET Version_soft=? WHERE Cle_primaire=?"; $stmt = $db_principale->prepare($requete); $stmt->bind_param('si', $version_cible, $generation_active['cle']); $ok = $stmt->execute(); $stmt->close(); if (!$ok) { return ['ok' => false, 'raison' => "migration {$version_actuelle} -> {$version_cible} appliquée sur {$nom_base}, mais Version_soft n'a pas pu être mise à jour : " . $db_principale->error . " - à corriger manuellement avant la prochaine archive"]; } if ($nb_requetes === 0) { return ['ok' => true, 'message' => "version {$version_actuelle} -> {$version_cible}, même fichier SQL ou aucun changement de schéma"]; } return ['ok' => true, 'message' => "schéma migré {$version_actuelle} -> {$version_cible} ({$nb_requetes} requête(s))"]; } // ---------------------------------------------------------------------------- // Exécute un bloc SQL sur une base bateau via le client mysql en ligne de // commande (même mécanique que Creer_generation/Charger_contenu_archive - un // bloc contenant des ALTER/CREATE/DROP se prête mal à mysqli::multi_query). // Renvoie ['ok'=>true] ou ['ok'=>false, 'raison'=>string]. // ---------------------------------------------------------------------------- function Executer_sql_base($nom_base, $sql) { $fichier_temp = tempnam(sys_get_temp_dir(), 'migr_'); file_put_contents($fichier_temp, $sql); $commande = "mysql --host=" . escapeshellarg(BDD_BATEAUX_HOST) . " --port=" . escapeshellarg(BDD_BATEAUX_PORT) . " --user=" . escapeshellarg(BDD_BATEAUX_USER) . " --password=" . escapeshellarg(BDD_BATEAUX_PASSWORD) . " " . escapeshellarg($nom_base) . " < " . escapeshellarg($fichier_temp) . " 2>&1"; exec($commande, $sortie, $code_retour); unlink($fichier_temp); if ($code_retour !== 0) return ['ok' => false, 'raison' => implode(" | ", $sortie)]; return ['ok' => true]; } // ---------------------------------------------------------------------------- // Met à jour TB_Bateaux.Date_derniere_maj après le chargement RÉUSSI d'une // archive (synthèse §47) : date-heure UTC de génération de l'archive par le // CM5, lue dans son nom (_AAMMJJ_HHMMSS_.sql.gz.enc) - c'est // elle qui date réellement les données, pas la date de réception (§44.6). // GREATEST : une archive plus ancienne chargée tardivement (sortie de // quarantaine, CM5 longtemps injoignable) ne fait jamais reculer la date. // Renvoie true si la requête a abouti. // ---------------------------------------------------------------------------- function Mettre_a_jour_derniere_archive($db_principale, $id_bateau, $nom_fichier) { if (!preg_match('/^[0-9A-Fa-f]{12}_(\d{2})(\d{2})(\d{2})_(\d{2})(\d{2})(\d{2})_/', $nom_fichier, $m)) return false; $date_archive = "20{$m[1]}-{$m[2]}-{$m[3]} {$m[4]}:{$m[5]}:{$m[6]}"; $requete = "UPDATE TB_Bateaux SET Date_derniere_maj = GREATEST(COALESCE(Date_derniere_maj, '1970-01-01 00:00:00'), ?) WHERE Cle_primaire=?"; $stmt = $db_principale->prepare($requete); $stmt->bind_param('si', $date_archive, $id_bateau); $ok = $stmt->execute(); $stmt->close(); return $ok; } // ---------------------------------------------------------------------------- // Détermine la liste des tables fragmentées (incrémentales) d'une base bateau, // par la MÊME méthode que côté CM5 (SHOW TABLES LIKE 'TB_Mesures%' + TB_Historique, // cf sauvegarde_bdd()/bdd_admin.c) - jamais une liste codée en dur, pour rester // robuste si une nouvelle table TB_Mesures_xxx apparaît un jour. // ---------------------------------------------------------------------------- function Resoudre_tables_fragmentees($connexion_bateau) { $tables = ['TB_Historique']; $resultat = $connexion_bateau->query("SHOW TABLES LIKE 'TB_Mesures%'"); while ($ligne = $resultat->fetch_row()) $tables[] = $ligne[0]; return $tables; } // ---------------------------------------------------------------------------- // Charge le contenu réel d'une archive (déjà déchiffré) dans la base d'une // génération : les tables permanentes sont vidées puis rechargées, les tables // incrémentales (fragmentées) sont uniquement complétées (jamais vidées) - // cf synthèse §32/37. Le contenu de l'archive (ses INSERT) complète donc // naturellement les tables fragmentées et remplace les permanentes, sans // avoir besoin de séparer les deux : seul le DELETE préalable fait la // différence entre les deux comportements. Exécution via mysql CLI (comme // Creer_generation), SET FOREIGN_KEY_CHECKS=0 par précaution (l'ordre des // INSERT du dump ne respecte pas forcément les dépendances). // ---------------------------------------------------------------------------- function Charger_contenu_archive($nom_base, $sql_archive) { $connexion_bateau = new mysqli(BDD_BATEAUX_HOST, BDD_BATEAUX_USER, BDD_BATEAUX_PASSWORD, $nom_base, (int)BDD_BATEAUX_PORT); if ($connexion_bateau->connect_error) { return ['ok' => false, 'raison' => "connexion à {$nom_base} impossible : " . $connexion_bateau->connect_error]; } $tables_fragmentees = Resoudre_tables_fragmentees($connexion_bateau); $resultat_tables = $connexion_bateau->query("SHOW TABLES"); $toutes_tables = []; while ($ligne = $resultat_tables->fetch_row()) $toutes_tables[] = $ligne[0]; $tables_permanentes = array_diff($toutes_tables, $tables_fragmentees); $connexion_bateau->close(); $script = "SET FOREIGN_KEY_CHECKS=0;\n"; foreach ($tables_permanentes as $table) { $script .= "DELETE FROM `{$table}`;\n"; } $script .= $sql_archive . "\n"; $script .= "SET FOREIGN_KEY_CHECKS=1;\n"; $fichier_temp = tempnam(sys_get_temp_dir(), 'charge_'); file_put_contents($fichier_temp, $script); $commande = "mysql --host=" . escapeshellarg(BDD_BATEAUX_HOST) . " --port=" . escapeshellarg(BDD_BATEAUX_PORT) . " --user=" . escapeshellarg(BDD_BATEAUX_USER) . " --password=" . escapeshellarg(BDD_BATEAUX_PASSWORD) . " " . escapeshellarg($nom_base) . " < " . escapeshellarg($fichier_temp) . " 2>&1"; exec($commande, $sortie, $code_retour); unlink($fichier_temp); if ($code_retour !== 0) { return ['ok' => false, 'raison' => "échec du chargement : " . implode(" | ", $sortie)]; } return ['ok' => true]; }