fxGetRow($sql); } function fxChronotrackApiConfigStatusLabel($strStatus) { switch ($strStatus) { case MSIN_API_CHRONOTRACK_SYNC_ACTIF: return 'Actif'; case MSIN_API_CHRONOTRACK_SYNC_FERME: return 'Fermé'; case MSIN_API_CHRONOTRACK_SYNC_LIE: default: return 'Lié'; } } function fxChronotrackApiConfigSaveLink($intEveId, $intCtEventId, $strCtEventName, $strCtEventDate, $intUsaId) { global $objDatabase; $intEveId = intval($intEveId); $intCtEventId = intval($intCtEventId); if ($intEveId <= 0 || $intCtEventId <= 0) { return array('state' => 'error', 'message' => 'Identifiants invalides'); } $arrExisting = fxChronotrackApiConfigGet($intEveId); if ($arrExisting !== null && $arrExisting['sync_status'] === MSIN_API_CHRONOTRACK_SYNC_FERME) { return array('state' => 'error', 'message' => 'Événement fermé — réouverture non autorisée depuis cette interface.'); } $strName = $objDatabase->fxEscape(trim($strCtEventName)); $strDateSql = 'NULL'; if (trim($strCtEventDate) !== '') { $intTs = strtotime($strCtEventDate); if ($intTs !== false) { $strDateSql = "'" . $objDatabase->fxEscape(date('Y-m-d H:i:s', $intTs)) . "'"; } } if ($arrExisting === null) { $sql = "INSERT INTO api_chronotrack_config SET eve_id = " . $intEveId . ", ct_event_id = " . $intCtEventId . ", ct_event_name = '" . $strName . "'" . ", ct_event_date = " . $strDateSql . ", sync_status = '" . MSIN_API_CHRONOTRACK_SYNC_LIE . "'" . ", linked_at = '" . fxGetDateTime() . "'" . ", linked_by_usa_id = " . intval($intUsaId) . ", updated_at = '" . fxGetDateTime() . "'"; } else { $sql = "UPDATE api_chronotrack_config SET ct_event_id = " . $intCtEventId . ", ct_event_name = '" . $strName . "'" . ", ct_event_date = " . $strDateSql . ", updated_at = '" . fxGetDateTime() . "'" . " WHERE eve_id = " . $intEveId . " AND sync_status <> '" . MSIN_API_CHRONOTRACK_SYNC_FERME . "'"; } if (!$objDatabase->fxQuery($sql)) { return array('state' => 'error', 'message' => 'Erreur SQL enregistrement lien'); } return array('state' => 'ok'); } function fxChronotrackApiConfigSetStatus($intEveId, $strStatus, $intUsaId) { global $objDatabase; $intEveId = intval($intEveId); $arrExisting = fxChronotrackApiConfigGet($intEveId); if ($arrExisting === null) { return array('state' => 'error', 'message' => 'Aucun lien ChronoTrack pour cet événement'); } if ($arrExisting['sync_status'] === MSIN_API_CHRONOTRACK_SYNC_FERME) { if ($strStatus !== MSIN_API_CHRONOTRACK_SYNC_LIE) { return array('state' => 'error', 'message' => 'Événement fermé — seule la réouverture (Lié) est permise.'); } } $strStatus = trim($strStatus); if (!in_array($strStatus, array(MSIN_API_CHRONOTRACK_SYNC_LIE, MSIN_API_CHRONOTRACK_SYNC_ACTIF, MSIN_API_CHRONOTRACK_SYNC_FERME), true)) { return array('state' => 'error', 'message' => 'Statut invalide'); } $strExtra = ''; if ($strStatus === MSIN_API_CHRONOTRACK_SYNC_ACTIF) { $strExtra = ", activated_at = '" . fxGetDateTime() . "'"; } if ($strStatus === MSIN_API_CHRONOTRACK_SYNC_FERME) { $strExtra = ", closed_at = '" . fxGetDateTime() . "', closed_by_usa_id = " . intval($intUsaId); } if ($strStatus === MSIN_API_CHRONOTRACK_SYNC_LIE && $arrExisting['sync_status'] === MSIN_API_CHRONOTRACK_SYNC_FERME) { $strExtra = ", closed_at = NULL, closed_by_usa_id = NULL"; } $sql = "UPDATE api_chronotrack_config SET sync_status = '" . $objDatabase->fxEscape($strStatus) . "'" . $strExtra . ", updated_at = '" . fxGetDateTime() . "'" . " WHERE eve_id = " . $intEveId; if (!$objDatabase->fxQuery($sql)) { return array('state' => 'error', 'message' => 'Erreur SQL changement statut'); } return array('state' => 'ok'); } /** * MSIN-4328 — mémorise la fin d’un push réussi (référence pour par_maj). */ function fxChronotrackApiConfigSetLastPush($intEveId, $intOkCount) { global $objDatabase; $intEveId = intval($intEveId); if ($intEveId <= 0) { return false; } $sql = "UPDATE api_chronotrack_config SET last_push_at = '" . fxGetDateTime() . "'" . ", last_push_ok_count = " . max(0, intval($intOkCount)) . ", updated_at = '" . fxGetDateTime() . "'" . " WHERE eve_id = " . $intEveId; return (bool)$objDatabase->fxQuery($sql); } /** * MSIN-4328 — fréquences autorisées (minutes), phase A. */ function fxChronotrackApiConfigAutoSyncIntervals() { return array(1, 5, 15, 30, 60); } /** * MSIN-4328 — enregistre la config sync auto (UI). Le cron (phase B) lira ces champs. */ function fxChronotrackApiConfigSaveAutoSync($intEveId, $blnEnabled, $intIntervalMin, $strUntil) { global $objDatabase; $intEveId = intval($intEveId); $arrExisting = fxChronotrackApiConfigGet($intEveId); if ($arrExisting === null) { return array('state' => 'error', 'message' => 'Événement non lié à ChronoTrack'); } if (($arrExisting['sync_status'] ?? '') === MSIN_API_CHRONOTRACK_SYNC_FERME) { return array('state' => 'error', 'message' => 'Événement fermé — sync auto impossible.'); } $blnEnabled = $blnEnabled ? 1 : 0; $intIntervalMin = intval($intIntervalMin); if (!in_array($intIntervalMin, fxChronotrackApiConfigAutoSyncIntervals(), true)) { $intIntervalMin = 15; } $strUntilSql = 'NULL'; $strUntil = trim((string)$strUntil); if ($strUntil !== '') { // Accepte "YYYY-MM-DDTHH:MM" (input datetime-local) ou "YYYY-MM-DD HH:MM[:SS]" $strUntilNorm = str_replace('T', ' ', $strUntil); if (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/', $strUntilNorm)) { $strUntilNorm .= ':00'; } $intTs = strtotime($strUntilNorm); if ($intTs === false) { return array('state' => 'error', 'message' => 'Date / heure de fin invalide'); } if ($blnEnabled && $intTs <= time()) { return array('state' => 'error', 'message' => 'La date/heure de fin doit être dans le futur'); } $strUntilSql = "'" . $objDatabase->fxEscape(date('Y-m-d H:i:s', $intTs)) . "'"; } elseif ($blnEnabled) { return array('state' => 'error', 'message' => 'Indiquez une date et une heure de fin pour activer la sync auto'); } $sql = "UPDATE api_chronotrack_config SET auto_sync_enabled = " . $blnEnabled . ", auto_sync_interval_min = " . $intIntervalMin . ", auto_sync_until = " . $strUntilSql . ", updated_at = '" . fxGetDateTime() . "'" . " WHERE eve_id = " . $intEveId; if (!$objDatabase->fxQuery($sql)) { return array('state' => 'error', 'message' => 'Erreur SQL enregistrement sync auto'); } $arrFresh = fxChronotrackApiConfigGet($intEveId); return array( 'state' => 'ok', 'message' => $blnEnabled ? ('Sync auto enregistrée (toutes les ' . $intIntervalMin . ' min jusqu’au ' . trim((string)($arrFresh['auto_sync_until'] ?? '')) . '). Cron : auto_chronotrack_sync.php') : 'Sync auto désactivée.', 'auto_sync' => array( 'enabled' => intval($arrFresh['auto_sync_enabled'] ?? 0) === 1, 'interval_min' => intval($arrFresh['auto_sync_interval_min'] ?? 15), 'until' => ($arrFresh['auto_sync_until'] ?? null), 'last_run_at' => ($arrFresh['last_auto_run_at'] ?? null), ), ); } /** * MSIN-4328 — events avec sync auto ON (pas fermés). */ function fxChronotrackApiConfigListAutoSyncEnabled() { global $objDatabase; $sql = "SELECT c.*" . " FROM api_chronotrack_config c" . " WHERE IFNULL(c.auto_sync_enabled, 0) = 1" . " AND c.sync_status <> '" . $objDatabase->fxEscape(MSIN_API_CHRONOTRACK_SYNC_FERME) . "'" . " ORDER BY c.eve_id ASC"; $arrRows = $objDatabase->fxGetResults($sql); $tabOut = array(); if (!is_array($arrRows)) { return $tabOut; } for ($i = 1; $i <= count($arrRows); $i++) { $tabOut[] = $arrRows[$i]; } return $tabOut; } /** * MSIN-4328 — horodatage dernier passage cron auto. */ function fxChronotrackApiConfigTouchAutoRun($intEveId) { global $objDatabase; $intEveId = intval($intEveId); if ($intEveId <= 0) { return false; } $sql = "UPDATE api_chronotrack_config SET last_auto_run_at = '" . fxGetDateTime() . "'" . ", updated_at = '" . fxGetDateTime() . "'" . " WHERE eve_id = " . $intEveId; return (bool)$objDatabase->fxQuery($sql); } /** * MSIN-4328 — coupe la sync auto (échéance atteinte). */ function fxChronotrackApiConfigDisableAutoSync($intEveId) { global $objDatabase; $intEveId = intval($intEveId); if ($intEveId <= 0) { return false; } $sql = "UPDATE api_chronotrack_config SET auto_sync_enabled = 0" . ", updated_at = '" . fxGetDateTime() . "'" . " WHERE eve_id = " . $intEveId; return (bool)$objDatabase->fxQuery($sql); } function fxChronotrackApiRaceMapGetForEvent($intEveId) { global $objDatabase; $sql = "SELECT * FROM api_chronotrack_race_map WHERE eve_id = " . intval($intEveId); $arrRows = $objDatabase->fxGetResults($sql); $arrMap = array(); if ($arrRows !== null) { for ($i = 1; $i <= count($arrRows); $i++) { $arrMap[intval($arrRows[$i]['epr_id'])] = $arrRows[$i]; } } return $arrMap; } function fxChronotrackApiMs1EpreuvesForEvent($intEveId, $strLangue = 'fr') { global $objDatabase; $strLangue = ($strLangue === 'en') ? 'en' : 'fr'; $sql = "SELECT epr_id, epr_nom_" . $strLangue . " AS epr_nom, epr_type_" . $strLangue . " AS epr_type, epr_date" . " FROM inscriptions_epreuves WHERE eve_id = " . intval($intEveId) . " ORDER BY epr_date, epr_nom_" . $strLangue; return $objDatabase->fxGetResults($sql); } function fxChronotrackApiRaceMapSave($intEveId, $arrMapInput) { global $objDatabase; $intEveId = intval($intEveId); $arrExisting = fxChronotrackApiConfigGet($intEveId); if ($arrExisting === null) { return array('state' => 'error', 'message' => 'Lier d\'abord un événement ChronoTrack'); } if ($arrExisting['sync_status'] === MSIN_API_CHRONOTRACK_SYNC_FERME) { return array('state' => 'error', 'message' => 'Événement fermé — mapping en lecture seule'); } if (!is_array($arrMapInput)) { return array('state' => 'error', 'message' => 'Mapping invalide'); } foreach ($arrMapInput as $intEprId => $arrRace) { $intEprId = intval($intEprId); $intCtRaceId = intval($arrRace['ct_race_id'] ?? 0); $strCtRaceName = trim((string)($arrRace['ct_race_name'] ?? '')); if ($intEprId <= 0) { continue; } if ($intCtRaceId <= 0) { $sqlDel = "DELETE FROM api_chronotrack_race_map WHERE eve_id = " . $intEveId . " AND epr_id = " . $intEprId; $objDatabase->fxQuery($sqlDel); continue; } $sqlExists = "SELECT map_id FROM api_chronotrack_race_map WHERE eve_id = " . $intEveId . " AND epr_id = " . $intEprId . " LIMIT 1"; if ($objDatabase->fxGetRow($sqlExists) !== null) { $sql = "UPDATE api_chronotrack_race_map SET ct_race_id = " . $intCtRaceId . ", ct_race_name = '" . $objDatabase->fxEscape($strCtRaceName) . "'" . ", updated_at = '" . fxGetDateTime() . "'" . " WHERE eve_id = " . $intEveId . " AND epr_id = " . $intEprId; } else { $sql = "INSERT INTO api_chronotrack_race_map SET eve_id = " . $intEveId . ", epr_id = " . $intEprId . ", ct_race_id = " . $intCtRaceId . ", ct_race_name = '" . $objDatabase->fxEscape($strCtRaceName) . "'" . ", updated_at = '" . fxGetDateTime() . "'"; } $objDatabase->fxQuery($sql); } return array('state' => 'ok'); } function fxChronotrackApiMs1EventRow($intEveId, $strLangue = 'fr') { global $objDatabase; $strLangue = ($strLangue === 'en') ? 'en' : 'fr'; $sql = "SELECT eve_id, eve_nom_" . $strLangue . " AS eve_nom, eve_date_debut, eve_date_fin" . " FROM inscriptions_evenements WHERE eve_id = " . intval($intEveId) . " LIMIT 1"; return $objDatabase->fxGetRow($sql); } function fxChronotrackApiListActiveConfigs($arrStatuses = null) { global $objDatabase; if ($arrStatuses === null) { $arrStatuses = array(MSIN_API_CHRONOTRACK_SYNC_LIE, MSIN_API_CHRONOTRACK_SYNC_ACTIF); } $arrEsc = array(); foreach ($arrStatuses as $strStatus) { $arrEsc[] = "'" . $objDatabase->fxEscape($strStatus) . "'"; } $sql = "SELECT c.*, e.eve_nom_fr AS eve_nom" . " FROM api_chronotrack_config c" . " INNER JOIN inscriptions_evenements e ON e.eve_id = c.eve_id" . " WHERE c.sync_status IN (" . implode(',', $arrEsc) . ")" . " ORDER BY c.sync_status DESC, c.updated_at DESC"; return $objDatabase->fxGetResults($sql); }