377 lines
12 KiB
PHP
377 lines
12 KiB
PHP
<?php
|
||
/**
|
||
* MSIN-4482 — Audit lecture seule : questions préprod ↔ prod.
|
||
* Aucune écriture BD. Détecte collisions que_id, absents, libellés divergents.
|
||
*/
|
||
|
||
/**
|
||
* @return array{ok:bool,error:string,preprod_db:string,prod_db:string,preprod:object|null,prod:object|null}
|
||
*/
|
||
function fxAuditQuestionsEnvOpen() {
|
||
global $objDatabasePreProd, $objDatabaseProd, $arrConDatabasePreProd, $arrConDatabaseProd;
|
||
|
||
$strPre = isset($arrConDatabasePreProd['db']) ? (string)$arrConDatabasePreProd['db'] : '(préprod)';
|
||
$strProd = isset($arrConDatabaseProd['db']) ? (string)$arrConDatabaseProd['db'] : '(prod)';
|
||
|
||
if (empty($objDatabasePreProd) || empty($objDatabaseProd)) {
|
||
return array(
|
||
'ok' => false,
|
||
'error' => 'Connexions préprod / prod indisponibles.',
|
||
'preprod_db' => $strPre,
|
||
'prod_db' => $strProd,
|
||
'preprod' => null,
|
||
'prod' => null,
|
||
);
|
||
}
|
||
|
||
if ($strPre !== '' && $strPre === $strProd) {
|
||
return array(
|
||
'ok' => false,
|
||
'error' => 'Préprod et prod pointent sur la même base (' . $strPre . ') — comparaison impossible.',
|
||
'preprod_db' => $strPre,
|
||
'prod_db' => $strProd,
|
||
'preprod' => null,
|
||
'prod' => null,
|
||
);
|
||
}
|
||
|
||
return array(
|
||
'ok' => true,
|
||
'error' => '',
|
||
'preprod_db' => $strPre,
|
||
'prod_db' => $strProd,
|
||
'preprod' => $objDatabasePreProd,
|
||
'prod' => $objDatabaseProd,
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Charge le catalogue questions d'un événement (ou tous si eve_id = 0).
|
||
* Clé = que_id.
|
||
*
|
||
* @return array<int,array>
|
||
*/
|
||
function fxAuditQuestionsEnvLoad($objDb, $intEveId) {
|
||
$intEveId = intval($intEveId);
|
||
$sql = "SELECT que_id, eve_id, que_question_fr, que_question_en, que_actif,"
|
||
. " que_cart_show, que_tri, que_hidden"
|
||
. " FROM inscriptions_questions";
|
||
if ($intEveId > 0) {
|
||
$sql .= " WHERE eve_id = " . $intEveId;
|
||
}
|
||
$sql .= " ORDER BY eve_id, que_id";
|
||
|
||
$tab = $objDb->fxGetResults($sql);
|
||
$tabOut = array();
|
||
if (!is_array($tab) || count($tab) < 1) {
|
||
return $tabOut;
|
||
}
|
||
for ($i = 1; $i <= count($tab); $i++) {
|
||
$intQue = intval($tab[$i]['que_id'] ?? 0);
|
||
if ($intQue <= 0) {
|
||
continue;
|
||
}
|
||
$tabOut[$intQue] = array(
|
||
'que_id' => $intQue,
|
||
'eve_id' => intval($tab[$i]['eve_id'] ?? 0),
|
||
'que_question_fr' => trim((string)($tab[$i]['que_question_fr'] ?? '')),
|
||
'que_question_en' => trim((string)($tab[$i]['que_question_en'] ?? '')),
|
||
'que_actif' => intval($tab[$i]['que_actif'] ?? 0),
|
||
'que_cart_show' => intval($tab[$i]['que_cart_show'] ?? 0),
|
||
'que_tri' => intval($tab[$i]['que_tri'] ?? 0),
|
||
'que_hidden' => intval($tab[$i]['que_hidden'] ?? 0),
|
||
);
|
||
}
|
||
return $tabOut;
|
||
}
|
||
|
||
/**
|
||
* @return array{min:int,max:int,count:int}
|
||
*/
|
||
function fxAuditQuestionsEnvStats($objDb) {
|
||
$sql = "SELECT COUNT(*) AS n, MIN(que_id) AS mn, MAX(que_id) AS mx FROM inscriptions_questions";
|
||
$row = $objDb->fxGetRow($sql);
|
||
return array(
|
||
'count' => intval($row['n'] ?? 0),
|
||
'min' => intval($row['mn'] ?? 0),
|
||
'max' => intval($row['mx'] ?? 0),
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Lookup ponctuel d'un que_id (tous événements).
|
||
* @return array|null
|
||
*/
|
||
function fxAuditQuestionsEnvFindQueId($objDb, $intQueId) {
|
||
$intQueId = intval($intQueId);
|
||
if ($intQueId <= 0 || empty($objDb)) {
|
||
return null;
|
||
}
|
||
$row = $objDb->fxGetRow(
|
||
"SELECT que_id, eve_id, que_question_fr, que_question_en, que_actif,"
|
||
. " que_cart_show, que_tri, que_hidden"
|
||
. " FROM inscriptions_questions WHERE que_id = " . $intQueId . " LIMIT 1"
|
||
);
|
||
if (!is_array($row) || empty($row['que_id'])) {
|
||
return null;
|
||
}
|
||
return array(
|
||
'que_id' => intval($row['que_id']),
|
||
'eve_id' => intval($row['eve_id'] ?? 0),
|
||
'que_question_fr' => trim((string)($row['que_question_fr'] ?? '')),
|
||
'que_question_en' => trim((string)($row['que_question_en'] ?? '')),
|
||
'que_actif' => intval($row['que_actif'] ?? 0),
|
||
'que_cart_show' => intval($row['que_cart_show'] ?? 0),
|
||
'que_tri' => intval($row['que_tri'] ?? 0),
|
||
'que_hidden' => intval($row['que_hidden'] ?? 0),
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Compare deux catalogues indexés par que_id.
|
||
* $objPre / $objProd optionnels : pour détecter qu’un que_id « manquant » sur l’eve
|
||
* existe déjà sur un autre événement (collision PK réelle).
|
||
*
|
||
* @param array<int,array> $tabPre
|
||
* @param array<int,array> $tabProd
|
||
* @return array
|
||
*/
|
||
function fxAuditQuestionsEnvCompare(array $tabPre, array $tabProd, $intEveId, $objPre = null, $objProd = null) {
|
||
$intEveId = intval($intEveId);
|
||
$tabReport = array(
|
||
'eve_id' => $intEveId,
|
||
'pre_count' => count($tabPre),
|
||
'prod_count' => count($tabProd),
|
||
'ok_same' => array(),
|
||
'collision_pk' => array(),
|
||
'contenu_diff' => array(),
|
||
'seulement_preprod' => array(),
|
||
'seulement_prod' => array(),
|
||
'meme_libelle_ids_diff' => array(),
|
||
'doublons_libelle_pre' => array(),
|
||
'doublons_libelle_prod' => array(),
|
||
);
|
||
|
||
$tabAllIds = array_unique(array_merge(array_keys($tabPre), array_keys($tabProd)));
|
||
sort($tabAllIds, SORT_NUMERIC);
|
||
|
||
foreach ($tabAllIds as $intQue) {
|
||
$blnPre = isset($tabPre[$intQue]);
|
||
$blnProd = isset($tabProd[$intQue]);
|
||
|
||
if ($blnPre && !$blnProd) {
|
||
// Le que_id peut exister en prod sur un AUTRE eve → collision
|
||
$rowOther = fxAuditQuestionsEnvFindQueId($objProd, $intQue);
|
||
if (is_array($rowOther) && intval($rowOther['eve_id']) !== $intEveId) {
|
||
$tabReport['collision_pk'][] = array(
|
||
'que_id' => $intQue,
|
||
'pre' => $tabPre[$intQue],
|
||
'prod' => $rowOther,
|
||
);
|
||
} else {
|
||
$tabReport['seulement_preprod'][] = $tabPre[$intQue];
|
||
}
|
||
continue;
|
||
}
|
||
if (!$blnPre && $blnProd) {
|
||
$rowOther = fxAuditQuestionsEnvFindQueId($objPre, $intQue);
|
||
if (is_array($rowOther) && intval($rowOther['eve_id']) !== $intEveId) {
|
||
$tabReport['collision_pk'][] = array(
|
||
'que_id' => $intQue,
|
||
'pre' => $rowOther,
|
||
'prod' => $tabProd[$intQue],
|
||
);
|
||
} else {
|
||
$tabReport['seulement_prod'][] = $tabProd[$intQue];
|
||
}
|
||
continue;
|
||
}
|
||
|
||
$a = $tabPre[$intQue];
|
||
$b = $tabProd[$intQue];
|
||
|
||
// Même que_id, événements différents = collision fatale à la mise en prod
|
||
if (intval($a['eve_id']) !== intval($b['eve_id'])) {
|
||
$tabReport['collision_pk'][] = array(
|
||
'que_id' => $intQue,
|
||
'pre' => $a,
|
||
'prod' => $b,
|
||
);
|
||
continue;
|
||
}
|
||
|
||
$arrDiff = array();
|
||
foreach (array('que_question_fr', 'que_question_en', 'que_actif', 'que_cart_show', 'que_tri', 'que_hidden') as $strCol) {
|
||
if ((string)$a[$strCol] !== (string)$b[$strCol]) {
|
||
$arrDiff[] = $strCol;
|
||
}
|
||
}
|
||
if (count($arrDiff) > 0) {
|
||
$tabReport['contenu_diff'][] = array(
|
||
'que_id' => $intQue,
|
||
'eve_id' => intval($a['eve_id']),
|
||
'fields' => $arrDiff,
|
||
'pre' => $a,
|
||
'prod' => $b,
|
||
);
|
||
} else {
|
||
$tabReport['ok_same'][] = $intQue;
|
||
}
|
||
}
|
||
|
||
// Même eve + même libellé FR, que_id différents (drift naturel)
|
||
$tabReport['meme_libelle_ids_diff'] = fxAuditQuestionsEnvLabelDrift($tabPre, $tabProd, $intEveId);
|
||
$tabReport['doublons_libelle_pre'] = fxAuditQuestionsEnvDupLabels($tabPre);
|
||
$tabReport['doublons_libelle_prod'] = fxAuditQuestionsEnvDupLabels($tabProd);
|
||
|
||
$tabReport['n_problems'] =
|
||
count($tabReport['collision_pk'])
|
||
+ count($tabReport['contenu_diff'])
|
||
+ count($tabReport['seulement_preprod'])
|
||
+ count($tabReport['seulement_prod'])
|
||
+ count($tabReport['meme_libelle_ids_diff'])
|
||
+ count($tabReport['doublons_libelle_pre'])
|
||
+ count($tabReport['doublons_libelle_prod']);
|
||
|
||
return $tabReport;
|
||
}
|
||
|
||
/**
|
||
* @param array<int,array> $tabPre
|
||
* @param array<int,array> $tabProd
|
||
* @return array
|
||
*/
|
||
function fxAuditQuestionsEnvLabelDrift(array $tabPre, array $tabProd, $intEveId) {
|
||
$intEveId = intval($intEveId);
|
||
$fnIndex = function (array $tab) use ($intEveId) {
|
||
$idx = array();
|
||
foreach ($tab as $row) {
|
||
if ($intEveId > 0 && intval($row['eve_id']) !== $intEveId) {
|
||
continue;
|
||
}
|
||
$strLabel = $row['que_question_fr'];
|
||
if ($strLabel === '') {
|
||
continue;
|
||
}
|
||
$strKey = intval($row['eve_id']) . "\0" . mb_strtolower($strLabel, 'UTF-8');
|
||
if (!isset($idx[$strKey])) {
|
||
$idx[$strKey] = array();
|
||
}
|
||
$idx[$strKey][] = $row;
|
||
}
|
||
return $idx;
|
||
};
|
||
|
||
$idxPre = $fnIndex($tabPre);
|
||
$idxProd = $fnIndex($tabProd);
|
||
$tabOut = array();
|
||
|
||
$tabKeys = array_unique(array_merge(array_keys($idxPre), array_keys($idxProd)));
|
||
foreach ($tabKeys as $strKey) {
|
||
if (!isset($idxPre[$strKey]) || !isset($idxProd[$strKey])) {
|
||
continue;
|
||
}
|
||
$arrPreIds = array();
|
||
foreach ($idxPre[$strKey] as $r) {
|
||
$arrPreIds[] = intval($r['que_id']);
|
||
}
|
||
$arrProdIds = array();
|
||
foreach ($idxProd[$strKey] as $r) {
|
||
$arrProdIds[] = intval($r['que_id']);
|
||
}
|
||
sort($arrPreIds);
|
||
sort($arrProdIds);
|
||
if ($arrPreIds === $arrProdIds) {
|
||
continue;
|
||
}
|
||
$parts = explode("\0", $strKey, 2);
|
||
$tabOut[] = array(
|
||
'eve_id' => intval($parts[0]),
|
||
'que_question_fr' => $idxPre[$strKey][0]['que_question_fr'],
|
||
'que_ids_preprod' => $arrPreIds,
|
||
'que_ids_prod' => $arrProdIds,
|
||
);
|
||
}
|
||
return $tabOut;
|
||
}
|
||
|
||
/**
|
||
* @param array<int,array> $tab
|
||
* @return array
|
||
*/
|
||
function fxAuditQuestionsEnvDupLabels(array $tab) {
|
||
$idx = array();
|
||
foreach ($tab as $row) {
|
||
$strLabel = $row['que_question_fr'];
|
||
if ($strLabel === '') {
|
||
continue;
|
||
}
|
||
$strKey = intval($row['eve_id']) . "\0" . mb_strtolower($strLabel, 'UTF-8');
|
||
if (!isset($idx[$strKey])) {
|
||
$idx[$strKey] = array(
|
||
'eve_id' => intval($row['eve_id']),
|
||
'que_question_fr' => $strLabel,
|
||
'que_ids' => array(),
|
||
);
|
||
}
|
||
$idx[$strKey]['que_ids'][] = intval($row['que_id']);
|
||
}
|
||
$tabOut = array();
|
||
foreach ($idx as $row) {
|
||
if (count($row['que_ids']) > 1) {
|
||
$tabOut[] = $row;
|
||
}
|
||
}
|
||
return $tabOut;
|
||
}
|
||
|
||
/**
|
||
* Scan global : que_id présents des deux côtés avec eve_id différent (risque mise en prod).
|
||
*
|
||
* @return array
|
||
*/
|
||
function fxAuditQuestionsEnvScanCollisions($objPre, $objProd, $intLimit = 500) {
|
||
$intLimit = max(50, min(2000, intval($intLimit)));
|
||
$tabPre = fxAuditQuestionsEnvLoad($objPre, 0);
|
||
$tabProd = fxAuditQuestionsEnvLoad($objProd, 0);
|
||
$tabCollisions = array();
|
||
foreach ($tabPre as $intQue => $a) {
|
||
if (!isset($tabProd[$intQue])) {
|
||
continue;
|
||
}
|
||
$b = $tabProd[$intQue];
|
||
if (intval($a['eve_id']) !== intval($b['eve_id'])) {
|
||
$tabCollisions[] = array(
|
||
'que_id' => $intQue,
|
||
'eve_preprod' => intval($a['eve_id']),
|
||
'label_preprod' => $a['que_question_fr'],
|
||
'eve_prod' => intval($b['eve_id']),
|
||
'label_prod' => $b['que_question_fr'],
|
||
);
|
||
if (count($tabCollisions) >= $intLimit) {
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
return array(
|
||
'pre_stats' => fxAuditQuestionsEnvStats($objPre),
|
||
'prod_stats' => fxAuditQuestionsEnvStats($objProd),
|
||
'collisions' => $tabCollisions,
|
||
'truncated' => count($tabCollisions) >= $intLimit,
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Nom événement (préprod en priorité).
|
||
*/
|
||
function fxAuditQuestionsEnvEveLabel($objDb, $intEveId) {
|
||
$intEveId = intval($intEveId);
|
||
if ($intEveId <= 0) {
|
||
return '';
|
||
}
|
||
$row = $objDb->fxGetRow(
|
||
"SELECT eve_nom_fr FROM inscriptions_evenements WHERE eve_id = " . $intEveId . " LIMIT 1"
|
||
);
|
||
return trim((string)($row['eve_nom_fr'] ?? ''));
|
||
}
|