From 9697b37263091bec7da68c6baf4eef706c519837 Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 23 Jul 2026 09:00:04 -0400 Subject: [PATCH] =?UTF-8?q?CON-333=20=E2=80=94=20Update=20role=20permissio?= =?UTF-8?q?ns=20synchronization=20to=20use=20role=5Fcode=20instead=20of=20?= =?UTF-8?q?role=5Fid=20for=20improved=20portability=20across=20databases.?= =?UTF-8?q?=20Introduce=20new=20functions=20for=20handling=20role=20permis?= =?UTF-8?q?sions=20and=20enhance=20existing=20synchronization=20logic=20to?= =?UTF-8?q?=20support=20non-destructive=20updates.=20Update=20user=20inter?= =?UTF-8?q?face=20to=20include=20a=20new=20"apply=5Fall"=20action=20for=20?= =?UTF-8?q?comprehensive=20synchronization=20without=20deletions.=20Increm?= =?UTF-8?q?ent=20version=20code.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- php/inc_fx_static_sync.php | 183 +++++++++++++++++++++++++++++- php/inc_fx_static_sync_config.php | 6 +- php/sync_static_db.php | 90 +++++++++------ 3 files changed, 235 insertions(+), 44 deletions(-) diff --git a/php/inc_fx_static_sync.php b/php/inc_fx_static_sync.php index 5fe1824..dcf97fe 100644 --- a/php/inc_fx_static_sync.php +++ b/php/inc_fx_static_sync.php @@ -257,6 +257,7 @@ function fxStaticSyncKeyColumnsValid($objDb, $strTable, array $tabKeys) { /** * Charge les lignes — une entrée par clé métier (dernière gagne, ordre stable si ORDER BY possible). + * CON-333 — matrice rôles : jointure role_code (role_id non portable entre BD). */ function fxStaticSyncFetchRowsByKey($objDb, array $tabDef) { $strTable = $tabDef['table']; @@ -266,12 +267,23 @@ function fxStaticSyncFetchRowsByKey($objDb, array $tabDef) { return array('rows' => array(), 'duplicate_count' => 0, 'physical_count' => 0, 'error' => null); } - if (!fxStaticSyncKeyColumnsValid($objDb, $strTable, $tabKeys)) { - return array('rows' => array(), 'duplicate_count' => 0, 'physical_count' => 0, 'error' => 'bad_keys'); + if (!empty($tabDef['join_role_code'])) { + if (!fxStaticSyncTableExists($objDb, 'inscriptions_eve_roles')) { + return array('rows' => array(), 'duplicate_count' => 0, 'physical_count' => 0, 'error' => 'bad_keys'); + } + $sql = 'SELECT erp.role_id, erp.perm_key, r.role_code' + . ' FROM `inscriptions_eve_role_permissions` erp' + . ' INNER JOIN `inscriptions_eve_roles` r ON r.role_id = erp.role_id' + . ' ORDER BY r.role_code, erp.perm_key'; + $tabResults = $objDb->fxGetResults($sql); + } else { + if (!fxStaticSyncKeyColumnsValid($objDb, $strTable, $tabKeys)) { + return array('rows' => array(), 'duplicate_count' => 0, 'physical_count' => 0, 'error' => 'bad_keys'); + } + $sql = 'SELECT * FROM `' . $strTable . '` ORDER BY ' . fxStaticSyncSqlOrderByKeys($tabKeys); + $tabResults = $objDb->fxGetResults($sql); } - $sql = 'SELECT * FROM `' . $strTable . '` ORDER BY ' . fxStaticSyncSqlOrderByKeys($tabKeys); - $tabResults = $objDb->fxGetResults($sql); $tabKeyed = array(); $intDupes = 0; $intPhysical = 0; @@ -581,6 +593,11 @@ function fxStaticSyncPrepareSoftWrite($objSource, $objTarget, $strTable, array $ $tabIgnore = isset($tabDef['ignore_cols']) ? $tabDef['ignore_cols'] : array(); $tabSchema = fxStaticSyncResolveCompareColumns($objSource, $objTarget, $strTable, $tabIgnore); + // CON-333 — matrice : comparer sur role_code+perm_key seulement. + if (!empty($tabDef['join_role_code'])) { + $tabSchema['compare_cols'] = $tabDef['keys']; + } + if (!fxStaticSyncSchemasAligned($tabSchema)) { return array( 'ok' => false, @@ -615,6 +632,10 @@ function fxStaticSyncPrepareSoftWrite($objSource, $objTarget, $strTable, array $ * Ne touche pas aux lignes déjà présentes (même poubelles — voir repair). */ function fxStaticSyncApplyMissingRows($objSource, $objTarget, $strTable, array $tabDef = null) { + if ($tabDef && !empty($tabDef['join_role_code'])) { + return fxStaticSyncApplyMissingRolePermissions($objSource, $objTarget, $tabDef); + } + $tabPrep = fxStaticSyncPrepareSoftWrite($objSource, $objTarget, $strTable, $tabDef); if (empty($tabPrep['ok'])) { return $tabPrep; @@ -732,11 +753,98 @@ function fxStaticSyncSqlWhereBusinessKeys($objDb, array $row, array $tabKeys) { return implode(' AND ', $tabParts); } +/** + * CON-333 — Matrice rôles : INSERT (role_id cible, perm_key) via role_code. + * Les role_id source ne sont jamais copiés tels quels. + */ +function fxStaticSyncApplyMissingRolePermissions($objSource, $objTarget, array $tabDef) { + $tabPrep = fxStaticSyncPrepareSoftWrite($objSource, $objTarget, 'inscriptions_eve_role_permissions', $tabDef); + if (empty($tabPrep['ok'])) { + return $tabPrep; + } + + $tabMissing = array(); + foreach ($tabPrep['source_rows'] as $strKey => $tabEntry) { + if (!isset($tabPrep['target_rows'][$strKey])) { + $tabMissing[] = $tabEntry['row']; + } + } + + if (count($tabMissing) === 0) { + return array('ok' => true, 'message' => 'Aucun manquant à ajouter (matrice rôles).', 'inserted' => 0); + } + + $blnTransactional = (fxStaticSyncGetTableEngine($objTarget, 'inscriptions_eve_role_permissions') === 'INNODB'); + if ($blnTransactional) { + $tabTx = fxStaticSyncExecQuery($objTarget, 'START TRANSACTION'); + if (!$tabTx['ok']) { + return array('ok' => false, 'message' => 'Transaction : ' . $tabTx['error']); + } + } + + $intInserted = 0; + foreach ($tabMissing as $row) { + $strCode = isset($row['role_code']) ? (string)$row['role_code'] : ''; + $strPerm = isset($row['perm_key']) ? (string)$row['perm_key'] : ''; + if ($strCode === '' || $strPerm === '') { + continue; + } + + $sqlRole = "SELECT role_id FROM inscriptions_eve_roles WHERE role_code='" + . $objTarget->fxEscape($strCode) . "' LIMIT 1"; + $strRoleId = $objTarget->fxGetVar($sqlRole); + if ($strRoleId === null || $strRoleId === '' || (int)$strRoleId <= 0) { + if ($blnTransactional) { + fxStaticSyncExecQuery($objTarget, 'ROLLBACK'); + } + return array( + 'ok' => false, + 'message' => 'Rôle absent en cible (role_code=' . $strCode + . '). Appliquer d\'abord inscriptions_eve_roles.', + ); + } + + $strSql = 'INSERT INTO `inscriptions_eve_role_permissions` (`role_id`,`perm_key`) VALUES (' + . (int)$strRoleId . ",'" . $objTarget->fxEscape($strPerm) . "')"; + $tabIns = fxStaticSyncExecQuery($objTarget, $strSql); + if (!$tabIns['ok']) { + if ($blnTransactional) { + fxStaticSyncExecQuery($objTarget, 'ROLLBACK'); + } + return array('ok' => false, 'message' => 'INSERT matrice : ' . $tabIns['error']); + } + $intInserted++; + } + + if ($blnTransactional) { + $tabCommit = fxStaticSyncExecQuery($objTarget, 'COMMIT'); + if (!$tabCommit['ok']) { + fxStaticSyncExecQuery($objTarget, 'ROLLBACK'); + return array('ok' => false, 'message' => 'Commit : ' . $tabCommit['error']); + } + } + + return array( + 'ok' => true, + 'message' => $intInserted . ' lien(s) matrice ajouté(s) via role_code (aucun DELETE).', + 'inserted' => $intInserted, + ); +} + /** * CON-333 — UPDATE des clés déjà présentes mais contenu différent (préprod = vérité textes). * Inclut les corrections de libellés et les poubelles. Ne DELETE rien. N’insère pas les manquants. */ function fxStaticSyncApplyContentUpdates($objSource, $objTarget, $strTable, array $tabDef = null) { + // Matrice rôles = présence de liens seulement ; pas de colonnes de contenu à écraser. + if ($tabDef && !empty($tabDef['join_role_code'])) { + return array( + 'ok' => true, + 'message' => 'Matrice rôles : pas de corrections de contenu (seulement Appliquer manquants).', + 'updated' => 0, + ); + } + $tabPrep = fxStaticSyncPrepareSoftWrite($objSource, $objTarget, $strTable, $tabDef); if (empty($tabPrep['ok'])) { return $tabPrep; @@ -910,7 +1018,67 @@ function fxStaticSyncCopyTable($objSource, $objTarget, $strTable, array $tabDef return array( 'ok' => false, 'message' => 'CON-333 — Sync destructif désactivé (DELETE interdit). ' - . 'Utiliser « Appliquer manquants » et/ou « Réparer poubelles ».', + . 'Utiliser « Appliquer manquants » et/ou « Appliquer corrections ».', + ); +} + +/** + * CON-333 — Dry-run complet : toutes les tables, manquants puis corrections, un environnement. + * Aucun DELETE. Arrête à la première erreur d’écriture. + * + * @return array{ok:bool,message:string,lines:array} + */ +function fxStaticSyncApplyAllSoft($objSource, $objTarget, $strEnvId) { + $tabLines = array(); + $tabDefs = fxStaticSyncTableDefinitions(); + + foreach ($tabDefs as $tabDef) { + $strTable = $tabDef['table']; + $tabCmp = fxStaticSyncCompareEnvPair($objSource, $objTarget, $tabDef); + + if (in_array($tabCmp['status'], array('no_connection', 'missing_table', 'schema_mismatch', 'error'), true)) { + $tabLines[] = $tabDef['label'] . ' : SAUTÉ — ' . fxStaticSyncStatusLabel($tabCmp['status'], $tabCmp); + continue; + } + + $intMiss = count($tabCmp['missing_keys']); + + if ($intMiss > 0) { + $tabMiss = fxStaticSyncApplyMissingRows($objSource, $objTarget, $strTable, $tabDef); + if (empty($tabMiss['ok'])) { + return array( + 'ok' => false, + 'message' => 'ÉCHEC sur ' . $tabDef['label'] . ' (manquants) : ' . $tabMiss['message'], + 'lines' => $tabLines, + ); + } + $tabLines[] = $tabDef['label'] . ' manquants : ' . $tabMiss['message']; + } else { + $tabLines[] = $tabDef['label'] . ' manquants : 0'; + } + + $tabCmp2 = fxStaticSyncCompareEnvPair($objSource, $objTarget, $tabDef); + $intDiff2 = count($tabCmp2['diff_keys']); + + if ($intDiff2 > 0 && empty($tabDef['join_role_code'])) { + $tabUpd = fxStaticSyncApplyContentUpdates($objSource, $objTarget, $strTable, $tabDef); + if (empty($tabUpd['ok'])) { + return array( + 'ok' => false, + 'message' => 'ÉCHEC sur ' . $tabDef['label'] . ' (corrections) : ' . $tabUpd['message'], + 'lines' => $tabLines, + ); + } + $tabLines[] = $tabDef['label'] . ' corrections : ' . $tabUpd['message']; + } else { + $tabLines[] = $tabDef['label'] . ' corrections : 0'; + } + } + + return array( + 'ok' => true, + 'message' => 'Tout appliquer terminé sur ' . $strEnvId . ' (aucun DELETE). Voir détail ci-dessous.', + 'lines' => $tabLines, ); } @@ -1029,6 +1197,11 @@ function fxStaticSyncCompareEnvPair($objSource, $objTarget, array $tabDef) { $tabSchema = fxStaticSyncResolveCompareColumns($objSource, $objTarget, $tabDef['table'], $tabIgnore); + // CON-333 — matrice : comparer sur role_code+perm_key seulement. + if (!empty($tabDef['join_role_code'])) { + $tabSchema['compare_cols'] = $tabDef['keys']; + } + if (!fxStaticSyncSchemasAligned($tabSchema)) { $tabSourceData = fxStaticSyncLoadKeyedRows($objSource, $tabDef); $tabTargetData = fxStaticSyncLoadKeyedRows($objTarget, $tabDef); diff --git a/php/inc_fx_static_sync_config.php b/php/inc_fx_static_sync_config.php index d4054f5..e9bfe44 100644 --- a/php/inc_fx_static_sync_config.php +++ b/php/inc_fx_static_sync_config.php @@ -53,8 +53,10 @@ function fxStaticSyncTableDefinitions() { array( 'table' => 'inscriptions_eve_role_permissions', 'label' => 'Accès v2 — matrice rôles', - 'keys' => array('role_id', 'perm_key'), - 'ignore_cols' => array(), + // CON-333 — role_id change entre BD ; comparer / sync via role_code. + 'keys' => array('role_code', 'perm_key'), + 'ignore_cols' => array('role_id'), + 'join_role_code' => true, ), array( 'table' => 'inscriptions_pages', diff --git a/php/sync_static_db.php b/php/sync_static_db.php index 3a8c394..7377b3f 100644 --- a/php/sync_static_db.php +++ b/php/sync_static_db.php @@ -60,19 +60,18 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) { if (!fxStaticSyncValidateCsrf($_POST['csrf'] ?? '')) { fxStaticSyncSetFlash('Jeton CSRF invalide.', 'bad'); - } elseif (!in_array($strAction, array('apply_missing', 'apply_content'), true)) { + } elseif (!in_array($strAction, array('apply_missing', 'apply_content', 'apply_all'), true)) { fxStaticSyncSetFlash( 'Action refusée. Sync destructif désactivé (CON-333). ' - . 'Utiliser Appliquer manquants / Appliquer corrections.', + . 'Utiliser Appliquer manquants / Appliquer corrections / Tout appliquer.', 'bad' ); } else { $strTable = trim($_POST['table'] ?? ''); $strEnvId = trim($_POST['env'] ?? ''); - $tabDef = fxStaticSyncFindTableDef($strTable); $tabEnv = fxStaticSyncFindEnvDef($strEnvId); - if (!$tabDef || !$tabEnv || !empty($tabEnv['is_source'])) { + if (!$tabEnv || !empty($tabEnv['is_source'])) { fxStaticSyncSetFlash('Paramètres invalides.', 'bad'); } else { $tabConnections = fxStaticSyncOpenConnections(); @@ -84,34 +83,46 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) { 'Connexion BD impossible — ' . fxStaticSyncFormatConnectionError($tabEnv), 'bad' ); - } else { - if ($strAction === 'apply_missing') { - $tabResult = fxStaticSyncApplyMissingRows($objSource, $objTarget, $strTable, $tabDef); - } else { - $tabResult = fxStaticSyncApplyContentUpdates($objSource, $objTarget, $strTable, $tabDef); + } elseif ($strAction === 'apply_all') { + $tabResult = fxStaticSyncApplyAllSoft($objSource, $objTarget, $strEnvId); + $strMsg = $tabEnv['label'] . ' — ' . $tabResult['message']; + if (!empty($tabResult['lines'])) { + $strMsg .= "\n" . implode("\n", $tabResult['lines']); } - - if ($tabResult['ok']) { - $tabVerify = fxStaticSyncCompareEnvPair($objSource, $objTarget, $tabDef); - $strMsg = $tabDef['label'] . ' → ' . $tabEnv['label'] . ' : ' . $tabResult['message']; - $intExtra = count($tabVerify['extra_keys']); - $intMiss = count($tabVerify['missing_keys']); - $intDiff = count($tabVerify['diff_keys']); - $strMsg .= ' Après : manque ' . $intMiss - . ', contenu diff. ' . $intDiff - . ', en trop (conservés) ' . $intExtra . '.'; - if ($intMiss === 0 && $intDiff === 0 && $tabVerify['status'] === 'ok') { - fxStaticSyncSetFlash($strMsg, 'ok'); - } elseif ($intMiss === 0 && $intDiff === 0) { - fxStaticSyncSetFlash($strMsg . ' Écarts restants = revue manuelle (pas de DELETE).', 'ok'); - } else { - fxStaticSyncSetFlash($strMsg, 'ok'); - } + fxStaticSyncSetFlash($strMsg, !empty($tabResult['ok']) ? 'ok' : 'bad'); + } else { + $tabDef = fxStaticSyncFindTableDef($strTable); + if (!$tabDef) { + fxStaticSyncSetFlash('Paramètres invalides.', 'bad'); } else { - fxStaticSyncSetFlash( - $tabDef['label'] . ' → ' . $tabEnv['label'] . ' — échec : ' . $tabResult['message'], - 'bad' - ); + if ($strAction === 'apply_missing') { + $tabResult = fxStaticSyncApplyMissingRows($objSource, $objTarget, $strTable, $tabDef); + } else { + $tabResult = fxStaticSyncApplyContentUpdates($objSource, $objTarget, $strTable, $tabDef); + } + + if ($tabResult['ok']) { + $tabVerify = fxStaticSyncCompareEnvPair($objSource, $objTarget, $tabDef); + $strMsg = $tabDef['label'] . ' → ' . $tabEnv['label'] . ' : ' . $tabResult['message']; + $intExtra = count($tabVerify['extra_keys']); + $intMiss = count($tabVerify['missing_keys']); + $intDiff = count($tabVerify['diff_keys']); + $strMsg .= ' Après : manque ' . $intMiss + . ', contenu diff. ' . $intDiff + . ', en trop (conservés) ' . $intExtra . '.'; + if ($intMiss === 0 && $intDiff === 0 && $tabVerify['status'] === 'ok') { + fxStaticSyncSetFlash($strMsg, 'ok'); + } elseif ($intMiss === 0 && $intDiff === 0) { + fxStaticSyncSetFlash($strMsg . ' Écarts restants = revue manuelle (pas de DELETE).', 'ok'); + } else { + fxStaticSyncSetFlash($strMsg, 'ok'); + } + } else { + fxStaticSyncSetFlash( + $tabDef['label'] . ' → ' . $tabEnv['label'] . ' — échec : ' . $tabResult['message'], + 'bad' + ); + } } } } @@ -186,15 +197,20 @@ if ($strDetailTable !== '' && $strDetailEnv !== '') {
- Procédure test (dev préprod → dev prod) + Test complet (un environnement à la fois)
    -
  1. Structures alignées (Navicat) — déjà fait.
  2. -
  3. Lire le tableau : manque / poubelles / en trop.
  4. -
  5. Pour chaque table concernée : Appliquer manquants.
  6. -
  7. Textes / contenu corrigés en préprod : Appliquer corrections.
  8. -
  9. « En trop » = revue manuelle — ne pas supprimer auto.
  10. -
  11. Quand OK ici → même outil plus tard vers client préprod / prod.
  12. +
  13. Rafraîchir cette page (nouveau code déployé).
  14. +
  15. Cliquer TOUT APPLIQUER sous la colonne voulue (commencer par Dev prod).
  16. +
  17. Lire le message vert/rouge en haut : succès ou échec + détail table par table.
  18. +
  19. Recharger : Manque=0 et Diff=0 partout = OK. « En trop » peut rester (normal, on n’efface pas).
  20. +
  21. Lignes SAUTÉES (structure / table absente) = à corriger en Navicat, puis re-cliquer.
+
+ + + + +