This commit updates the static database synchronization logic by removing hardcoded connection details from the `fxStaticSyncClientDatabaseConfigs` function. It now relies on configurations defined in `inc_settings.php` for client connections. The `fxStaticSyncEnvironmentDefinitions` and `fxStaticSyncOpenConnections` functions are modified to utilize these new configurations, enhancing flexibility and maintainability in managing database connections for different environments.
459 lines
14 KiB
PHP
459 lines
14 KiB
PHP
<?php
|
|
|
|
require_once __DIR__ . '/inc_fx_static_sync_config.php';
|
|
|
|
/**
|
|
* Environnements comparables — configs depuis inc_settings.php uniquement.
|
|
* Source : $arrConDatabasePreProd (dev préprod sur serveur dev).
|
|
*/
|
|
function fxStaticSyncEnvironmentDefinitions() {
|
|
global $arrConDatabasePreProd, $arrConDatabaseProd;
|
|
global $arrConDatabaseClientPreProd, $arrConDatabaseClientProd;
|
|
|
|
$tabEnvs = array(
|
|
'dev_preprod' => array(
|
|
'label' => 'Dev préprod',
|
|
'config' => $arrConDatabasePreProd,
|
|
'is_source' => true,
|
|
),
|
|
'dev_prod' => array(
|
|
'label' => 'Dev prod',
|
|
'config' => $arrConDatabaseProd,
|
|
'is_source' => false,
|
|
),
|
|
);
|
|
|
|
if (!empty($arrConDatabaseClientPreProd)) {
|
|
$tabEnvs['client_preprod'] = array(
|
|
'label' => 'Client préprod',
|
|
'config' => $arrConDatabaseClientPreProd,
|
|
'is_source' => false,
|
|
);
|
|
}
|
|
|
|
if (!empty($arrConDatabaseClientProd)) {
|
|
$tabEnvs['client_prod'] = array(
|
|
'label' => 'Client prod',
|
|
'config' => $arrConDatabaseClientProd,
|
|
'is_source' => false,
|
|
);
|
|
}
|
|
|
|
return $tabEnvs;
|
|
}
|
|
|
|
/** Connexion MySQL avec timeout court, sans die() — pour BD client additionnelles. */
|
|
class clsMysqlStaticSync extends clsMysql {
|
|
function __construct($arrDatabase = array()) {
|
|
$this->last_id = 0;
|
|
$this->affected_rows = 0;
|
|
$this->con = null;
|
|
|
|
if (empty($arrDatabase['host']) || empty($arrDatabase['db'])) {
|
|
return;
|
|
}
|
|
|
|
$mysqli = mysqli_init();
|
|
if (!$mysqli) {
|
|
return;
|
|
}
|
|
|
|
mysqli_options($mysqli, MYSQLI_OPT_CONNECT_TIMEOUT, 3);
|
|
|
|
if (!@mysqli_real_connect(
|
|
$mysqli,
|
|
$arrDatabase['host'],
|
|
$arrDatabase['user'],
|
|
$arrDatabase['pass'],
|
|
$arrDatabase['db'],
|
|
3306
|
|
)) {
|
|
return;
|
|
}
|
|
|
|
mysqli_set_charset($mysqli, 'utf8');
|
|
$this->con = $mysqli;
|
|
}
|
|
}
|
|
|
|
function fxStaticSyncConnect($arrConfig) {
|
|
if (empty($arrConfig['host']) || empty($arrConfig['db'])) {
|
|
return null;
|
|
}
|
|
|
|
$obj = new clsMysqlStaticSync($arrConfig);
|
|
|
|
return !empty($obj->con) ? $obj : null;
|
|
}
|
|
|
|
/**
|
|
* Dev : réutilise objDatabase* de inc_fonctions.php.
|
|
* Client : $arrConDatabaseClient* de inc_settings.php (si définis pour l'environnement).
|
|
*/
|
|
function fxStaticSyncOpenConnections() {
|
|
global $objDatabasePreProd, $objDatabaseProd;
|
|
global $arrConDatabaseClientPreProd, $arrConDatabaseClientProd;
|
|
|
|
$tabConnections = array(
|
|
'dev_preprod' => !empty($objDatabasePreProd) ? $objDatabasePreProd : null,
|
|
'dev_prod' => !empty($objDatabaseProd) ? $objDatabaseProd : null,
|
|
);
|
|
|
|
if (!empty($arrConDatabaseClientPreProd)) {
|
|
$tabConnections['client_preprod'] = fxStaticSyncConnect($arrConDatabaseClientPreProd);
|
|
}
|
|
|
|
if (!empty($arrConDatabaseClientProd)) {
|
|
$tabConnections['client_prod'] = fxStaticSyncConnect($arrConDatabaseClientProd);
|
|
}
|
|
|
|
return $tabConnections;
|
|
}
|
|
|
|
function fxStaticSyncTableExists($objDb, $strTable) {
|
|
if (!$objDb) {
|
|
return false;
|
|
}
|
|
|
|
$sql = "SHOW TABLES LIKE '" . $objDb->fxEscape($strTable) . "'";
|
|
$row = $objDb->fxGetRow($sql);
|
|
|
|
return !empty($row);
|
|
}
|
|
|
|
function fxStaticSyncRowBusinessKey(array $row, array $tabKeyCols) {
|
|
$tabParts = array();
|
|
foreach ($tabKeyCols as $strCol) {
|
|
$tabParts[] = isset($row[$strCol]) ? (string)$row[$strCol] : '';
|
|
}
|
|
|
|
return implode("\0", $tabParts);
|
|
}
|
|
|
|
function fxStaticSyncRowContentHash(array $row, array $tabIgnoreCols) {
|
|
$tabData = $row;
|
|
foreach ($tabIgnoreCols as $strCol) {
|
|
unset($tabData[$strCol]);
|
|
}
|
|
ksort($tabData);
|
|
|
|
return md5(json_encode($tabData, JSON_UNESCAPED_UNICODE));
|
|
}
|
|
|
|
function fxStaticSyncLoadKeyedRows($objDb, array $tabDef) {
|
|
$strTable = $tabDef['table'];
|
|
$tabKeys = $tabDef['keys'];
|
|
$tabIgnore = isset($tabDef['ignore_cols']) ? $tabDef['ignore_cols'] : array();
|
|
|
|
if (!fxStaticSyncTableExists($objDb, $strTable)) {
|
|
return array('error' => 'missing_table', 'rows' => array(), 'count' => 0);
|
|
}
|
|
|
|
$sql = 'SELECT * FROM `' . $strTable . '`';
|
|
$tabResults = $objDb->fxGetResults($sql);
|
|
$tabKeyed = array();
|
|
|
|
if (!is_array($tabResults)) {
|
|
return array('error' => null, 'rows' => array(), 'count' => 0);
|
|
}
|
|
|
|
foreach ($tabResults as $row) {
|
|
if (!is_array($row)) {
|
|
continue;
|
|
}
|
|
$strKey = fxStaticSyncRowBusinessKey($row, $tabKeys);
|
|
$tabKeyed[$strKey] = array(
|
|
'hash' => fxStaticSyncRowContentHash($row, $tabIgnore),
|
|
'row' => $row,
|
|
);
|
|
}
|
|
|
|
return array('error' => null, 'rows' => $tabKeyed, 'count' => count($tabKeyed));
|
|
}
|
|
|
|
/**
|
|
* @return array status: ok|content_diff|missing_rows|extra_rows|missing_table|no_connection|error
|
|
*/
|
|
function fxStaticSyncCompareTable(array $tabSourceData, array $tabTargetData) {
|
|
if (!empty($tabTargetData['error']) && $tabTargetData['error'] === 'missing_table') {
|
|
return array(
|
|
'status' => 'missing_table',
|
|
'source_count' => $tabSourceData['count'],
|
|
'target_count' => 0,
|
|
'missing_keys' => array(),
|
|
'extra_keys' => array(),
|
|
'diff_keys' => array(),
|
|
);
|
|
}
|
|
|
|
$tabSourceRows = $tabSourceData['rows'];
|
|
$tabTargetRows = $tabTargetData['rows'];
|
|
|
|
$tabMissing = array();
|
|
$tabDiff = array();
|
|
|
|
foreach ($tabSourceRows as $strKey => $tabRow) {
|
|
if (!isset($tabTargetRows[$strKey])) {
|
|
$tabMissing[] = $strKey;
|
|
continue;
|
|
}
|
|
if ($tabTargetRows[$strKey]['hash'] !== $tabRow['hash']) {
|
|
$tabDiff[] = $strKey;
|
|
}
|
|
}
|
|
|
|
$tabExtra = array();
|
|
foreach ($tabTargetRows as $strKey => $tabRow) {
|
|
if (!isset($tabSourceRows[$strKey])) {
|
|
$tabExtra[] = $strKey;
|
|
}
|
|
}
|
|
|
|
$strStatus = 'ok';
|
|
if (count($tabExtra) > 0) {
|
|
$strStatus = 'extra_rows';
|
|
} elseif (count($tabMissing) > 0) {
|
|
$strStatus = 'missing_rows';
|
|
} elseif (count($tabDiff) > 0) {
|
|
$strStatus = 'content_diff';
|
|
}
|
|
|
|
return array(
|
|
'status' => $strStatus,
|
|
'source_count' => $tabSourceData['count'],
|
|
'target_count' => $tabTargetData['count'],
|
|
'missing_keys' => $tabMissing,
|
|
'extra_keys' => $tabExtra,
|
|
'diff_keys' => $tabDiff,
|
|
);
|
|
}
|
|
|
|
function fxStaticSyncCopyTable($objSource, $objTarget, $strTable) {
|
|
if (!$objSource || !$objTarget) {
|
|
return array('ok' => false, 'message' => 'Connexion BD invalide.');
|
|
}
|
|
|
|
if (!fxStaticSyncTableExists($objSource, $strTable)) {
|
|
return array('ok' => false, 'message' => 'Table absente dans la source.');
|
|
}
|
|
|
|
if (!fxStaticSyncTableExists($objTarget, $strTable)) {
|
|
return array('ok' => false, 'message' => 'Table absente dans la cible — exécuter le DDL d\'abord.');
|
|
}
|
|
|
|
$tabRows = $objSource->fxGetResults('SELECT * FROM `' . $strTable . '`');
|
|
$intCopied = 0;
|
|
|
|
$objTarget->fxQuery('SET FOREIGN_KEY_CHECKS=0');
|
|
$objTarget->fxQuery('TRUNCATE TABLE `' . $strTable . '`');
|
|
|
|
if (is_array($tabRows) && count($tabRows) > 0) {
|
|
$tabColumns = array_keys($tabRows[1]);
|
|
$strColList = '`' . implode('`,`', $tabColumns) . '`';
|
|
$intBatchSize = 50;
|
|
$tabBatch = array();
|
|
|
|
foreach ($tabRows as $row) {
|
|
if (!is_array($row)) {
|
|
continue;
|
|
}
|
|
|
|
$tabVals = array();
|
|
foreach ($tabColumns as $strCol) {
|
|
if (!array_key_exists($strCol, $row) || $row[$strCol] === null) {
|
|
$tabVals[] = 'NULL';
|
|
} else {
|
|
$tabVals[] = "'" . $objTarget->fxEscape($row[$strCol]) . "'";
|
|
}
|
|
}
|
|
$tabBatch[] = '(' . implode(',', $tabVals) . ')';
|
|
|
|
if (count($tabBatch) >= $intBatchSize) {
|
|
$sql = 'INSERT INTO `' . $strTable . '` (' . $strColList . ') VALUES ' . implode(',', $tabBatch);
|
|
$objTarget->fxQuery($sql);
|
|
$intCopied += count($tabBatch);
|
|
$tabBatch = array();
|
|
}
|
|
}
|
|
|
|
if (count($tabBatch) > 0) {
|
|
$sql = 'INSERT INTO `' . $strTable . '` (' . $strColList . ') VALUES ' . implode(',', $tabBatch);
|
|
$objTarget->fxQuery($sql);
|
|
$intCopied += count($tabBatch);
|
|
}
|
|
}
|
|
|
|
$objTarget->fxQuery('SET FOREIGN_KEY_CHECKS=1');
|
|
|
|
return array(
|
|
'ok' => true,
|
|
'message' => $intCopied . ' ligne(s) copiée(s).',
|
|
'rows' => $intCopied,
|
|
);
|
|
}
|
|
|
|
function fxStaticSyncStatusLabel($strStatus) {
|
|
switch ($strStatus) {
|
|
case 'ok':
|
|
return 'Aligné';
|
|
case 'content_diff':
|
|
return 'Contenu différent';
|
|
case 'missing_rows':
|
|
return 'Lignes manquantes';
|
|
case 'extra_rows':
|
|
return 'Lignes en trop';
|
|
case 'missing_table':
|
|
return 'Table absente';
|
|
case 'no_connection':
|
|
return 'Connexion impossible';
|
|
default:
|
|
return 'Erreur';
|
|
}
|
|
}
|
|
|
|
function fxStaticSyncStatusClass($strStatus) {
|
|
switch ($strStatus) {
|
|
case 'ok':
|
|
return 'ss-ok';
|
|
case 'content_diff':
|
|
case 'missing_rows':
|
|
return 'ss-warn';
|
|
case 'extra_rows':
|
|
return 'ss-bad';
|
|
case 'missing_table':
|
|
case 'no_connection':
|
|
return 'ss-muted';
|
|
default:
|
|
return 'ss-bad';
|
|
}
|
|
}
|
|
|
|
function fxStaticSyncFormatKeyPreview($strKey) {
|
|
$strKey = str_replace("\0", ' · ', $strKey);
|
|
if (strlen($strKey) > 120) {
|
|
return substr($strKey, 0, 117) . '…';
|
|
}
|
|
|
|
return $strKey;
|
|
}
|
|
|
|
function fxStaticSyncEnsureCsrfToken() {
|
|
if (empty($_SESSION['ms1_static_sync_csrf'])) {
|
|
$_SESSION['ms1_static_sync_csrf'] = bin2hex(random_bytes(16));
|
|
}
|
|
|
|
return $_SESSION['ms1_static_sync_csrf'];
|
|
}
|
|
|
|
function fxStaticSyncValidateCsrf($strToken) {
|
|
return !empty($_SESSION['ms1_static_sync_csrf'])
|
|
&& hash_equals($_SESSION['ms1_static_sync_csrf'], (string)$strToken);
|
|
}
|
|
|
|
function fxStaticSyncFindTableDef($strTable) {
|
|
foreach (fxStaticSyncTableDefinitions() as $tabDef) {
|
|
if ($tabDef['table'] === $strTable) {
|
|
return $tabDef;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function fxStaticSyncFindEnvDef($strEnvId) {
|
|
$tabEnvs = fxStaticSyncEnvironmentDefinitions();
|
|
return isset($tabEnvs[$strEnvId]) ? $tabEnvs[$strEnvId] : null;
|
|
}
|
|
|
|
function fxStaticSyncBuildReport($tabConnections) {
|
|
$tabDefs = fxStaticSyncTableDefinitions();
|
|
$tabEnvs = fxStaticSyncEnvironmentDefinitions();
|
|
$objSource = $tabConnections['dev_preprod'];
|
|
$tabReport = array();
|
|
|
|
foreach ($tabDefs as $tabDef) {
|
|
$tabSourceData = fxStaticSyncLoadKeyedRows($objSource, $tabDef);
|
|
$tabRow = array(
|
|
'def' => $tabDef,
|
|
'source_count' => $tabSourceData['count'],
|
|
'envs' => array(),
|
|
);
|
|
|
|
foreach ($tabEnvs as $strEnvId => $tabEnv) {
|
|
if (!empty($tabEnv['is_source'])) {
|
|
continue;
|
|
}
|
|
|
|
$objTarget = isset($tabConnections[$strEnvId]) ? $tabConnections[$strEnvId] : null;
|
|
if (!$objTarget) {
|
|
$tabRow['envs'][$strEnvId] = array(
|
|
'status' => 'no_connection',
|
|
'source_count' => $tabSourceData['count'],
|
|
'target_count' => 0,
|
|
'missing_keys' => array(),
|
|
'extra_keys' => array(),
|
|
'diff_keys' => array(),
|
|
);
|
|
continue;
|
|
}
|
|
|
|
$tabTargetData = fxStaticSyncLoadKeyedRows($objTarget, $tabDef);
|
|
$tabRow['envs'][$strEnvId] = fxStaticSyncCompareTable($tabSourceData, $tabTargetData);
|
|
}
|
|
|
|
$tabReport[] = $tabRow;
|
|
}
|
|
|
|
return $tabReport;
|
|
}
|
|
|
|
function fxStaticSyncGetDiffDetail($strTable, $strEnvId, $tabConnections) {
|
|
$tabDef = fxStaticSyncFindTableDef($strTable);
|
|
$tabEnv = fxStaticSyncFindEnvDef($strEnvId);
|
|
|
|
if (!$tabDef || !$tabEnv || !empty($tabEnv['is_source'])) {
|
|
return null;
|
|
}
|
|
|
|
$objSource = $tabConnections['dev_preprod'];
|
|
$objTarget = $tabConnections[$strEnvId];
|
|
|
|
if (!$objSource || !$objTarget) {
|
|
return null;
|
|
}
|
|
|
|
$tabSourceData = fxStaticSyncLoadKeyedRows($objSource, $tabDef);
|
|
$tabTargetData = fxStaticSyncLoadKeyedRows($objTarget, $tabDef);
|
|
$tabCompare = fxStaticSyncCompareTable($tabSourceData, $tabTargetData);
|
|
|
|
$tabDetail = array(
|
|
'compare' => $tabCompare,
|
|
'samples' => array(),
|
|
);
|
|
|
|
$tabKeysToShow = array_slice(
|
|
array_merge($tabCompare['missing_keys'], $tabCompare['diff_keys'], $tabCompare['extra_keys']),
|
|
0,
|
|
30
|
|
);
|
|
|
|
foreach ($tabKeysToShow as $strKey) {
|
|
$tabSample = array(
|
|
'key' => $strKey,
|
|
'type' => in_array($strKey, $tabCompare['extra_keys'], true) ? 'extra'
|
|
: (in_array($strKey, $tabCompare['missing_keys'], true) ? 'missing' : 'diff'),
|
|
'source' => isset($tabSourceData['rows'][$strKey]['row']) ? $tabSourceData['rows'][$strKey]['row'] : null,
|
|
'target' => isset($tabTargetData['rows'][$strKey]['row']) ? $tabTargetData['rows'][$strKey]['row'] : null,
|
|
);
|
|
|
|
if ($tabDef['table'] === 'info' && $tabSample['type'] === 'diff') {
|
|
$tabSample['source_texte'] = isset($tabSample['source']['info_texte']) ? $tabSample['source']['info_texte'] : '';
|
|
$tabSample['target_texte'] = isset($tabSample['target']['info_texte']) ? $tabSample['target']['info_texte'] : '';
|
|
}
|
|
|
|
$tabDetail['samples'][] = $tabSample;
|
|
}
|
|
|
|
return $tabDetail;
|
|
}
|