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 */ 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 $tabPre * @param 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 $tabPre * @param 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 $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'] ?? '')); }