Files
ms1inscription-v5/php/inc_fx_audit_questions_env.php

377 lines
12 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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 quun que_id « manquant » sur leve
* 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'] ?? ''));
}