This commit introduces several utility functions for database synchronization, including error handling for MySQL operations and retrieval of table metadata. The `fxStaticSyncDbLastError`, `fxStaticSyncExecQuery`, `fxStaticSyncGetTableColumnNames`, and `fxStaticSyncGetTableEngine` functions are added to improve the robustness of the synchronization process. Additionally, the `fxStaticSyncCopyTable` function is updated to handle column mismatches more gracefully and to provide clearer feedback on the synchronization results, enhancing overall user experience and error reporting in the database synchronization workflow.
609 lines
18 KiB
PHP
609 lines
18 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 fxStaticSyncDbLastError($objDb) {
|
|
if (!$objDb || empty($objDb->con)) {
|
|
return '';
|
|
}
|
|
|
|
return mysqli_error($objDb->con);
|
|
}
|
|
|
|
/**
|
|
* Exécute une requête et remonte l'erreur MySQL (sans die ni echo legacy).
|
|
*/
|
|
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) {
|
|
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 . '`');
|
|
$tabTargetCols = fxStaticSyncGetTableColumnNames($objTarget, $strTable);
|
|
|
|
if (count($tabTargetCols) === 0) {
|
|
return array('ok' => false, 'message' => 'Impossible de lire les colonnes de la table cible.');
|
|
}
|
|
|
|
$tabSourceCols = array();
|
|
if (is_array($tabRows) && count($tabRows) > 0) {
|
|
$tabSourceCols = array_keys($tabRows[1]);
|
|
}
|
|
|
|
$tabCopyCols = array();
|
|
foreach ($tabSourceCols as $strCol) {
|
|
if (in_array($strCol, $tabTargetCols, true)) {
|
|
$tabCopyCols[] = $strCol;
|
|
}
|
|
}
|
|
|
|
$tabSkippedCols = array_diff($tabSourceCols, $tabCopyCols);
|
|
if (count($tabSourceCols) > 0 && count($tabCopyCols) === 0) {
|
|
return array(
|
|
'ok' => false,
|
|
'message' => 'Aucune colonne commune entre source et cible — exécuter les scripts DDL manquants.',
|
|
);
|
|
}
|
|
|
|
// Préparer les INSERT avant toute écriture destructive (évite une table vide si échec).
|
|
$tabInsertBatches = array();
|
|
$intCopied = 0;
|
|
|
|
if (count($tabCopyCols) > 0 && is_array($tabRows) && count($tabRows) > 0) {
|
|
$strColList = '`' . implode('`,`', $tabCopyCols) . '`';
|
|
$intBatchSize = 50;
|
|
$tabBatch = array();
|
|
|
|
foreach ($tabRows 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);
|
|
$intCopied += count($tabBatch);
|
|
$tabBatch = array();
|
|
}
|
|
}
|
|
|
|
if (count($tabBatch) > 0) {
|
|
$tabInsertBatches[] = 'INSERT INTO `' . $strTable . '` (' . $strColList . ') VALUES ' . implode(',', $tabBatch);
|
|
$intCopied += count($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 (count($tabSkippedCols) > 0) {
|
|
$strMessage .= ' Colonnes ignorées (absentes en cible) : ' . implode(', ', $tabSkippedCols) . '.';
|
|
}
|
|
|
|
return array(
|
|
'ok' => true,
|
|
'message' => $strMessage,
|
|
'rows' => $intCopied,
|
|
'skipped_cols' => array_values($tabSkippedCols),
|
|
);
|
|
}
|
|
|
|
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;
|
|
}
|