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, 10); if (!@mysqli_real_connect( $mysqli, $arrDatabase['host'], $arrDatabase['user'], $arrDatabase['pass'], $arrDatabase['db'], 3306 )) { if (!isset($GLOBALS['ms1_static_sync_connect_errors'])) { $GLOBALS['ms1_static_sync_connect_errors'] = array(); } $GLOBALS['ms1_static_sync_connect_errors'][$arrDatabase['db']] = mysqli_connect_error(); return; } // Pas de mysqli_set_charset ici — charset serveur / table legacy (latin1, utf8, etc.). $this->con = $mysqli; } } function fxStaticSyncConnectError($strDb) { if (empty($GLOBALS['ms1_static_sync_connect_errors'][$strDb])) { return ''; } return $GLOBALS['ms1_static_sync_connect_errors'][$strDb]; } function fxStaticSyncFormatConnectionError($tabEnv) { $strDb = isset($tabEnv['config']['db']) ? $tabEnv['config']['db'] : ''; $strMsg = $tabEnv['label'] . ' (' . $strDb . ')'; $strErr = fxStaticSyncConnectError($strDb); if ($strErr !== '') { $strMsg .= ' — ' . $strErr; } return $strMsg; } 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); } /** * Colonnes communes source/cible pour compare et copy (alignés). * missing_in_target / extra_in_target : écarts de structure (hors ignore_cols). */ function fxStaticSyncResolveCompareColumns($objSource, $objTarget, $strTable, array $tabIgnoreCols) { $tabSourceCols = fxStaticSyncGetTableColumnNames($objSource, $strTable); $tabTargetCols = fxStaticSyncGetTableColumnNames($objTarget, $strTable); $tabCommon = array(); foreach ($tabSourceCols as $strCol) { if (in_array($strCol, $tabTargetCols, true)) { $tabCommon[] = $strCol; } } $tabCompareCols = array(); foreach ($tabCommon as $strCol) { if (!in_array($strCol, $tabIgnoreCols, true)) { $tabCompareCols[] = $strCol; } } $tabMissingInTarget = array(); foreach ($tabSourceCols as $strCol) { if (!in_array($strCol, $tabTargetCols, true) && !in_array($strCol, $tabIgnoreCols, true)) { $tabMissingInTarget[] = $strCol; } } $tabExtraInTarget = array(); foreach ($tabTargetCols as $strCol) { if (!in_array($strCol, $tabSourceCols, true) && !in_array($strCol, $tabIgnoreCols, true)) { $tabExtraInTarget[] = $strCol; } } return array( 'copy_cols' => $tabCommon, 'compare_cols' => $tabCompareCols, 'missing_in_target' => $tabMissingInTarget, 'extra_in_target' => $tabExtraInTarget, ); } function fxStaticSyncSchemasAligned(array $tabSchema) { return count($tabSchema['missing_in_target']) === 0 && count($tabSchema['extra_in_target']) === 0; } function fxStaticSyncFormatSchemaDiff(array $tabSchema) { $tabParts = array(); if (!empty($tabSchema['missing_in_target'])) { $tabParts[] = 'absentes en cible : ' . implode(', ', $tabSchema['missing_in_target']); } if (!empty($tabSchema['extra_in_target'])) { $tabParts[] = 'en trop en cible : ' . implode(', ', $tabSchema['extra_in_target']); } return implode(' · ', $tabParts); } function fxStaticSyncNormalizeCellValue($val) { if ($val === null) { return null; } return (string)$val; } function fxStaticSyncSqlOrderByKeys(array $tabKeys) { $tabParts = array(); foreach ($tabKeys as $strCol) { $tabParts[] = '`' . $strCol . '`'; } return implode(',', $tabParts); } function fxStaticSyncGetTableRowCount($objDb, $strTable) { if (!$objDb || !fxStaticSyncTableExists($objDb, $strTable)) { return 0; } return (int)$objDb->fxGetVar('SELECT COUNT(*) FROM `' . $strTable . '`'); } function fxStaticSyncKeyColumnsValid($objDb, $strTable, array $tabKeys) { if (count($tabKeys) === 0) { return false; } $tabCols = fxStaticSyncGetTableColumnNames($objDb, $strTable); foreach ($tabKeys as $strCol) { if (!in_array($strCol, $tabCols, true)) { return false; } } return true; } /** * Charge les lignes — une entrée par clé métier (dernière gagne, ordre stable si ORDER BY possible). */ function fxStaticSyncFetchRowsByKey($objDb, array $tabDef) { $strTable = $tabDef['table']; $tabKeys = $tabDef['keys']; if (!fxStaticSyncTableExists($objDb, $strTable)) { 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'); } $sql = 'SELECT * FROM `' . $strTable . '` ORDER BY ' . fxStaticSyncSqlOrderByKeys($tabKeys); $tabResults = $objDb->fxGetResults($sql); $tabKeyed = array(); $intDupes = 0; $intPhysical = 0; if (!is_array($tabResults)) { return array('rows' => array(), 'duplicate_count' => 0, 'physical_count' => 0, 'error' => 'query_failed'); } foreach ($tabResults as $row) { if (!is_array($row)) { continue; } $intPhysical++; $strKey = fxStaticSyncRowBusinessKey($row, $tabKeys); if (isset($tabKeyed[$strKey])) { $intDupes++; } $tabKeyed[$strKey] = $row; } return array( 'rows' => $tabKeyed, 'duplicate_count' => $intDupes, 'physical_count' => $intPhysical, 'error' => null, ); } function fxStaticSyncRowContentHash(array $row, array $tabCompareCols) { $tabData = array(); foreach ($tabCompareCols as $strCol) { $tabData[$strCol] = array_key_exists($strCol, $row) ? fxStaticSyncNormalizeCellValue($row[$strCol]) : null; } ksort($tabData); return md5(json_encode($tabData, JSON_UNESCAPED_UNICODE)); } function fxStaticSyncLoadKeyedRows($objDb, array $tabDef, array $tabCompareCols = null) { $tabKeys = $tabDef['keys']; $tabIgnore = isset($tabDef['ignore_cols']) ? $tabDef['ignore_cols'] : array(); if (!fxStaticSyncTableExists($objDb, $tabDef['table'])) { return array('error' => 'missing_table', 'rows' => array(), 'count' => 0, 'physical_count' => 0); } $tabFetch = fxStaticSyncFetchRowsByKey($objDb, $tabDef); if ($tabFetch['error'] === 'bad_keys') { return array('error' => 'bad_keys', 'rows' => array(), 'count' => 0, 'physical_count' => 0); } if ($tabFetch['error'] === 'query_failed') { return array('error' => 'query_failed', 'rows' => array(), 'count' => 0, 'physical_count' => 0); } $tabKeyed = array(); if ($tabCompareCols === null) { $tabCompareCols = array(); if (count($tabFetch['rows']) > 0) { $row = reset($tabFetch['rows']); foreach (array_keys($row) as $strCol) { if (!in_array($strCol, $tabIgnore, true)) { $tabCompareCols[] = $strCol; } } } } foreach ($tabFetch['rows'] as $strKey => $row) { $tabKeyed[$strKey] = array( 'hash' => fxStaticSyncRowContentHash($row, $tabCompareCols), 'row' => $row, ); } return array( 'error' => null, 'rows' => $tabKeyed, 'count' => count($tabKeyed), 'physical_count' => $tabFetch['physical_count'], 'duplicate_count' => $tabFetch['duplicate_count'], ); } /** Tous les environnements cibles sont syncables (dev prod, client préprod, client prod). */ function fxStaticSyncWriteAllowed($strEnvId) { return true; } function fxStaticSyncSetFlash($strMessage, $strType) { $_SESSION['ms1_static_sync_flash'] = $strMessage; $_SESSION['ms1_static_sync_flash_type'] = $strType; } function fxStaticSyncPopFlash() { if (empty($_SESSION['ms1_static_sync_flash'])) { return null; } $tabFlash = array( 'message' => $_SESSION['ms1_static_sync_flash'], 'type' => !empty($_SESSION['ms1_static_sync_flash_type']) ? $_SESSION['ms1_static_sync_flash_type'] : 'info', ); unset($_SESSION['ms1_static_sync_flash'], $_SESSION['ms1_static_sync_flash_type']); return $tabFlash; } function fxStaticSyncFinalizeCompareResult(array $tabCmp) { if (in_array($tabCmp['status'], array('missing_table', 'schema_mismatch', 'no_connection'), true)) { return $tabCmp; } $intMissingKeys = count($tabCmp['missing_keys']); $intExtraKeys = count($tabCmp['extra_keys']); $intDiffKeys = count($tabCmp['diff_keys']); if ($intMissingKeys === 0 && $intExtraKeys === 0 && $intDiffKeys === 0) { $tabCmp['status'] = 'ok'; return $tabCmp; } $intDelta = (int)$tabCmp['target_count'] - (int)$tabCmp['source_count']; if ($intDelta < 0) { $tabCmp['status'] = 'missing_rows'; } elseif ($intDelta > 0) { $tabCmp['status'] = 'extra_rows'; } else { $tabCmp['status'] = 'content_diff'; } return $tabCmp; } /** * @return array status: ok|content_diff|missing_rows|extra_rows|missing_table|schema_mismatch|no_connection|error */ function fxStaticSyncCompareTable(array $tabSourceData, array $tabTargetData, array $tabSchemaInfo = array()) { 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; } } return fxStaticSyncFinalizeCompareResult(array( 'status' => 'pending', 'source_count' => $tabSourceData['count'], 'target_count' => $tabTargetData['count'], 'source_physical' => isset($tabSourceData['physical_count']) ? $tabSourceData['physical_count'] : $tabSourceData['count'], 'target_physical' => isset($tabTargetData['physical_count']) ? $tabTargetData['physical_count'] : $tabTargetData['count'], 'missing_keys' => $tabMissing, 'extra_keys' => $tabExtra, 'diff_keys' => $tabDiff, 'schema_missing_cols' => isset($tabSchemaInfo['missing_in_target']) ? $tabSchemaInfo['missing_in_target'] : array(), 'schema_extra_cols' => isset($tabSchemaInfo['extra_in_target']) ? $tabSchemaInfo['extra_in_target'] : array(), )); } function fxStaticSyncDbLastError($objDb) { if (!$objDb || empty($objDb->con)) { return ''; } return mysqli_error($objDb->con); } /** * Exécute une requête d'écriture — erreur remontée dans le message flash (fxQuery n'echo pas). */ function fxStaticSyncExecQuery($objDb, $strSql) { if (!$objDb) { return array('ok' => false, 'error' => 'Connexion BD invalide.'); } $blnOk = $objDb->fxQuery($strSql); if ($blnOk === false) { return array( 'ok' => false, 'error' => fxStaticSyncDbLastError($objDb) ?: 'Erreur SQL inconnue.', ); } return array('ok' => true, 'error' => null); } function fxStaticSyncGetTableColumnNames($objDb, $strTable) { if (!$objDb) { return array(); } $tabCols = $objDb->fxGetResults('SHOW COLUMNS FROM `' . $strTable . '`'); $tabNames = array(); if (!is_array($tabCols)) { return array(); } foreach ($tabCols as $row) { if (is_array($row) && !empty($row['Field'])) { $tabNames[] = $row['Field']; } } return $tabNames; } function fxStaticSyncGetTableEngine($objDb, $strTable) { if (!$objDb) { return ''; } $row = $objDb->fxGetRow( "SELECT ENGINE FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = '" . $objDb->fxEscape($strTable) . "'" ); return !empty($row['ENGINE']) ? strtoupper($row['ENGINE']) : ''; } function fxStaticSyncCopyTable($objSource, $objTarget, $strTable, array $tabDef = null) { 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.'); } if (!$tabDef) { return array('ok' => false, 'message' => 'Table non configurée.'); } $tabIgnore = ($tabDef && isset($tabDef['ignore_cols'])) ? $tabDef['ignore_cols'] : array(); $tabSchema = fxStaticSyncResolveCompareColumns($objSource, $objTarget, $strTable, $tabIgnore); if (!fxStaticSyncSchemasAligned($tabSchema)) { return array( 'ok' => false, 'message' => 'Structures non alignées — ' . fxStaticSyncFormatSchemaDiff($tabSchema) . '. Aligner les structures (Navicat) avant toute sync. Aucune donnée modifiée.', ); } $tabCopyCols = $tabSchema['copy_cols']; if (count($tabCopyCols) === 0) { $tabFetchCheck = fxStaticSyncFetchRowsByKey($objSource, $tabDef); if (count($tabFetchCheck['rows']) > 0) { return array( 'ok' => false, 'message' => 'Aucune colonne commune entre source et cible.', ); } } $tabFetch = fxStaticSyncFetchRowsByKey($objSource, $tabDef); $tabRowsToCopy = $tabFetch['rows']; $intDupes = $tabFetch['duplicate_count']; // Préparer les INSERT avant toute écriture destructive (évite une table vide si échec). $tabInsertBatches = array(); $intCopied = count($tabRowsToCopy); if (count($tabCopyCols) > 0 && count($tabRowsToCopy) > 0) { $strColList = '`' . implode('`,`', $tabCopyCols) . '`'; $intBatchSize = 50; $tabBatch = array(); foreach ($tabRowsToCopy as $row) { if (!is_array($row)) { continue; } $tabVals = array(); foreach ($tabCopyCols 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) { $tabInsertBatches[] = 'INSERT INTO `' . $strTable . '` (' . $strColList . ') VALUES ' . implode(',', $tabBatch); $tabBatch = array(); } } if (count($tabBatch) > 0) { $tabInsertBatches[] = 'INSERT INTO `' . $strTable . '` (' . $strColList . ') VALUES ' . implode(',', $tabBatch); } } $blnTransactional = (fxStaticSyncGetTableEngine($objTarget, $strTable) === 'INNODB'); $blnFkDisabled = false; $tabFkOff = fxStaticSyncExecQuery($objTarget, 'SET FOREIGN_KEY_CHECKS=0'); if (!$tabFkOff['ok']) { return array('ok' => false, 'message' => 'FK checks : ' . $tabFkOff['error']); } $blnFkDisabled = true; if ($blnTransactional) { $tabTx = fxStaticSyncExecQuery($objTarget, 'START TRANSACTION'); if (!$tabTx['ok']) { fxStaticSyncExecQuery($objTarget, 'SET FOREIGN_KEY_CHECKS=1'); return array('ok' => false, 'message' => 'Transaction : ' . $tabTx['error']); } } // DELETE plutôt que TRUNCATE : compatible FK (InnoDB) avec checks désactivés. $tabDelete = fxStaticSyncExecQuery($objTarget, 'DELETE FROM `' . $strTable . '`'); if (!$tabDelete['ok']) { if ($blnTransactional) { fxStaticSyncExecQuery($objTarget, 'ROLLBACK'); } if ($blnFkDisabled) { fxStaticSyncExecQuery($objTarget, 'SET FOREIGN_KEY_CHECKS=1'); } return array('ok' => false, 'message' => 'Vidage table : ' . $tabDelete['error']); } foreach ($tabInsertBatches as $strSql) { $tabInsert = fxStaticSyncExecQuery($objTarget, $strSql); if (!$tabInsert['ok']) { if ($blnTransactional) { fxStaticSyncExecQuery($objTarget, 'ROLLBACK'); } else { return array( 'ok' => false, 'message' => 'Insertion : ' . $tabInsert['error'] . ' — la table cible peut être vide (MyISAM, pas de rollback).', ); } if ($blnFkDisabled) { fxStaticSyncExecQuery($objTarget, 'SET FOREIGN_KEY_CHECKS=1'); } return array('ok' => false, 'message' => 'Insertion : ' . $tabInsert['error'] . ' (rollback effectué).'); } } if ($blnTransactional) { $tabCommit = fxStaticSyncExecQuery($objTarget, 'COMMIT'); if (!$tabCommit['ok']) { fxStaticSyncExecQuery($objTarget, 'ROLLBACK'); if ($blnFkDisabled) { fxStaticSyncExecQuery($objTarget, 'SET FOREIGN_KEY_CHECKS=1'); } return array('ok' => false, 'message' => 'Commit : ' . $tabCommit['error']); } } if ($blnFkDisabled) { fxStaticSyncExecQuery($objTarget, 'SET FOREIGN_KEY_CHECKS=1'); } $strMessage = $intCopied . ' ligne(s) copiée(s).'; if ($intDupes > 0) { $strMessage .= ' (' . $intDupes . ' doublon(s) ignoré(s) en source — même clé métier.)'; } return array( 'ok' => true, 'message' => $strMessage, 'rows' => $intCopied, ); } function fxStaticSyncStatusLabel($strStatus, array $tabCmp = null) { switch ($strStatus) { case 'ok': return 'Données alignées'; case 'missing_table': return 'Table absente'; case 'schema_mismatch': return 'Structures non alignées'; case 'no_connection': return 'Connexion impossible'; case 'error': return 'Erreur lecture'; } if (is_array($tabCmp)) { $intDelta = (int)$tabCmp['target_count'] - (int)$tabCmp['source_count']; if ($intDelta < 0) { return 'Il en manque ' . abs($intDelta); } if ($intDelta > 0) { return 'Il y en a ' . $intDelta . ' en trop'; } return 'Contenu différent'; } switch ($strStatus) { case 'content_diff': return 'Contenu différent'; case 'missing_rows': return 'Il en manque'; case 'extra_rows': return 'Il y en a en trop'; 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'; case 'schema_mismatch': return 'ss-schema'; case 'error': return 'ss-bad'; 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 fxStaticSyncCompareEnvPair($objSource, $objTarget, array $tabDef) { $tabIgnore = isset($tabDef['ignore_cols']) ? $tabDef['ignore_cols'] : array(); if (!fxStaticSyncTableExists($objTarget, $tabDef['table'])) { $tabSourceData = fxStaticSyncLoadKeyedRows($objSource, $tabDef); return array( 'status' => 'missing_table', 'source_count' => $tabSourceData['count'], 'target_count' => 0, 'missing_keys' => array(), 'extra_keys' => array(), 'diff_keys' => array(), 'schema_missing_cols' => array(), 'schema_extra_cols' => array(), ); } $tabSchema = fxStaticSyncResolveCompareColumns($objSource, $objTarget, $tabDef['table'], $tabIgnore); if (!fxStaticSyncSchemasAligned($tabSchema)) { $tabSourceData = fxStaticSyncLoadKeyedRows($objSource, $tabDef); $tabTargetData = fxStaticSyncLoadKeyedRows($objTarget, $tabDef); return array( 'status' => 'schema_mismatch', 'source_count' => $tabSourceData['count'], 'target_count' => $tabTargetData['count'], 'missing_keys' => array(), 'extra_keys' => array(), 'diff_keys' => array(), 'schema_missing_cols' => $tabSchema['missing_in_target'], 'schema_extra_cols' => $tabSchema['extra_in_target'], ); } $tabSourceData = fxStaticSyncLoadKeyedRows($objSource, $tabDef, $tabSchema['compare_cols']); $tabTargetData = fxStaticSyncLoadKeyedRows($objTarget, $tabDef, $tabSchema['compare_cols']); if (!empty($tabSourceData['error']) || !empty($tabTargetData['error'])) { return array( 'status' => 'error', 'source_count' => isset($tabSourceData['count']) ? $tabSourceData['count'] : 0, 'target_count' => isset($tabTargetData['count']) ? $tabTargetData['count'] : 0, 'missing_keys' => array(), 'extra_keys' => array(), 'diff_keys' => array(), 'schema_missing_cols' => array(), 'schema_extra_cols' => array(), ); } return fxStaticSyncCompareTable($tabSourceData, $tabTargetData, $tabSchema); } 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'], 'source_physical' => $tabSourceData['physical_count'], 'source_dupes' => $tabSourceData['duplicate_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(), 'schema_missing_cols' => array(), 'schema_extra_cols' => array(), ); continue; } $tabRow['envs'][$strEnvId] = fxStaticSyncCompareEnvPair($objSource, $objTarget, $tabDef); } $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; } $tabCompare = fxStaticSyncCompareEnvPair($objSource, $objTarget, $tabDef); $tabIgnore = isset($tabDef['ignore_cols']) ? $tabDef['ignore_cols'] : array(); $tabSchema = fxStaticSyncResolveCompareColumns($objSource, $objTarget, $tabDef['table'], $tabIgnore); $tabSourceData = fxStaticSyncLoadKeyedRows($objSource, $tabDef, $tabSchema['compare_cols']); $tabTargetData = fxStaticSyncLoadKeyedRows($objTarget, $tabDef, $tabSchema['compare_cols']); $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; }