CON-333 — Update role permissions synchronization to use role_code instead of role_id for improved portability across databases. Introduce new functions for handling role permissions and enhance existing synchronization logic to support non-destructive updates. Update user interface to include a new "apply_all" action for comprehensive synchronization without deletions. Increment version code.
This commit is contained in:
@ -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);
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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 !== '') {
|
||||
</div>
|
||||
|
||||
<div class="proc">
|
||||
<strong>Procédure test (dev préprod → dev prod)</strong>
|
||||
<strong>Test complet (un environnement à la fois)</strong>
|
||||
<ol>
|
||||
<li>Structures alignées (Navicat) — déjà fait.</li>
|
||||
<li>Lire le tableau : manque / poubelles / en trop.</li>
|
||||
<li>Pour chaque table concernée : <em>Appliquer manquants</em>.</li>
|
||||
<li>Textes / contenu corrigés en préprod : <em>Appliquer corrections</em>.</li>
|
||||
<li>« En trop » = revue manuelle — ne pas supprimer auto.</li>
|
||||
<li>Quand OK ici → même outil plus tard vers client préprod / prod.</li>
|
||||
<li>Rafraîchir cette page (nouveau code déployé).</li>
|
||||
<li>Cliquer <em>TOUT APPLIQUER</em> sous la colonne voulue (commencer par <strong>Dev prod</strong>).</li>
|
||||
<li>Lire le message vert/rouge en haut : succès ou échec + détail table par table.</li>
|
||||
<li>Recharger : Manque=0 et Diff=0 partout = OK. « En trop » peut rester (normal, on n’efface pas).</li>
|
||||
<li>Lignes SAUTÉES (structure / table absente) = à corriger en Navicat, puis re-cliquer.</li>
|
||||
</ol>
|
||||
<form method="post" style="margin-top:12px;" onsubmit="return confirm('CON-333 — TOUT APPLIQUER sur Dev prod ?\nToutes les tables : manquants + corrections.\nAucun DELETE.');">
|
||||
<input type="hidden" name="action" value="apply_all">
|
||||
<input type="hidden" name="csrf" value="<?php echo ssEsc($strCsrf); ?>">
|
||||
<input type="hidden" name="env" value="dev_prod">
|
||||
<button type="submit" class="btn btn-content" style="font-weight:700;padding:8px 14px;">TOUT APPLIQUER → Dev prod</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<p class="sub">
|
||||
|
||||
Reference in New Issue
Block a user