'CAN', 'US' => 'USA', 'MX' => 'MEX', 'FR' => 'FRA', 'GB' => 'GBR', 'UK' => 'GBR', 'DE' => 'DEU', 'ES' => 'ESP', 'IT' => 'ITA', 'BE' => 'BEL', 'CH' => 'CHE', 'AU' => 'AUS', 'JP' => 'JPN', 'CN' => 'CHN', 'BR' => 'BRA', 'NL' => 'NLD', 'SE' => 'SWE', 'NO' => 'NOR', 'DK' => 'DNK', 'FI' => 'FIN', 'IE' => 'IRL', 'PT' => 'PRT', 'PL' => 'POL', 'AT' => 'AUT', 'NZ' => 'NZL', 'IN' => 'IND', 'KR' => 'KOR', 'AD' => 'AND', 'AE' => 'ARE', 'AF' => 'AFG', ); } $strIso2 = strtoupper(trim((string)$strIso2)); if ($strIso2 === '') { return ''; } if (strlen($strIso2) === 3) { return $strIso2; } return isset($tabMap[$strIso2]) ? $tabMap[$strIso2] : ''; } function fxChronotrackApiSyncMapEntryStatus($strMs1Status, $blnCancelled = false) { if ($blnCancelled) { return 'WITHDRAWN'; } $strMs1Status = strtoupper(trim((string)$strMs1Status)); switch ($strMs1Status) { case 'DQ': return 'DQ'; case 'DNS': return 'DNS'; case 'NP': return 'NP'; case 'ABANDON': return 'DNF'; case 'UNRANKED': return 'UNRANKED'; case 'DEFERRED': return 'DEFERRED'; case 'CONF': default: return 'CONF'; } } function fxChronotrackApiSyncNormalizeSex($strSexe) { $strSexe = strtolower(trim((string)$strSexe)); if ($strSexe === 'f' || $strSexe === 'F') { return 'F'; } if ($strSexe === 'h' || $strSexe === 'm' || $strSexe === 'M') { return 'M'; } if ($strSexe === '') { return 'U'; } return strtoupper(substr($strSexe, 0, 1)); } function fxChronotrackApiSyncNormalizeBirthdate($strDob) { $strDob = trim((string)$strDob); if ($strDob === '' || $strDob === '0000-00-00') { return ''; } $intTs = strtotime($strDob); if ($intTs === false) { return ''; } return date('Y-m-d', $intTs); } function fxChronotrackApiSyncLog($intEveId, $intParId, $strAction, $strStatus, $strMessage) { global $objDatabase; $sql = "INSERT INTO api_chronotrack_sync_log SET eve_id = " . intval($intEveId) . ", par_id = " . ($intParId > 0 ? intval($intParId) : 'NULL') . ", action = '" . $objDatabase->fxEscape(substr(trim($strAction), 0, 32)) . "'" . ", status = '" . $objDatabase->fxEscape($strStatus === 'ok' ? 'ok' : 'error') . "'" . ", message = '" . $objDatabase->fxEscape(substr(trim($strMessage), 0, 65000)) . "'" . ", created_at = '" . fxGetDateTime() . "'"; $objDatabase->fxQuery($sql); } function fxChronotrackApiSyncPaysHasIso3Column() { static $blnHas = null; if ($blnHas !== null) { return $blnHas; } global $objDatabase; $arrRow = $objDatabase->fxGetRow("SHOW COLUMNS FROM inscriptions_pays LIKE 'pay_iso3'"); $blnHas = ($arrRow !== null); return $blnHas; } function fxChronotrackApiSyncLoadMs1Participants($intEveId) { global $objDatabase; $intEveId = intval($intEveId); if ($intEveId <= 0) { return array(); } $strCountryCols = " (SELECT pay_iso FROM inscriptions_pays WHERE pay_id = p.pay_id) AS country_iso2"; if (fxChronotrackApiSyncPaysHasIso3Column()) { $strCountryCols = " (SELECT pay_iso3 FROM inscriptions_pays WHERE pay_id = p.pay_id) AS country_iso3," . " (SELECT pay_iso FROM inscriptions_pays WHERE pay_id = p.pay_id) AS country_iso2"; } $sql = "SELECT p.par_id, p.par_id_original, p.epr_id, p.par_prenom, p.par_nom, p.par_sexe," . " p.par_naissance, p.no_bib, p.par_statut_course, p.par_ville, p.par_adresse, p.par_codepostal," . " p.is_cancelled," . " (SELECT pro_iso FROM inscriptions_provinces WHERE pro_id = p.pro_id) AS state_iso," . $strCountryCols . " FROM resultats_participants p" . " JOIN resultats_epreuves_commandees ec ON p.pec_id = ec.pec_id_original" . " WHERE ec.pec_actif = 1 AND ec.is_cancelled = 0 AND p.is_cancelled = 0" . " AND p.eve_id = " . $intEveId . " ORDER BY p.par_id_original, p.par_id"; $tabRows = $objDatabase->fxGetResults($sql); if (!is_array($tabRows)) { return array(); } $tabOut = array(); for ($i = 1; $i <= count($tabRows); $i++) { $tabOut[] = $tabRows[$i]; } return $tabOut; } function fxChronotrackApiSyncBuildEntryPayload(array $arrRow, array $arrRaceMap) { $intEprId = intval($arrRow['epr_id'] ?? 0); $intCtRaceId = intval($arrRaceMap[$intEprId]['ct_race_id'] ?? 0); if ($intCtRaceId <= 0) { return null; } $intExternalId = intval($arrRow['par_id_original'] ?? 0); if ($intExternalId <= 0) { return null; } $strCountry = strtoupper(trim((string)($arrRow['country_iso3'] ?? ''))); if ($strCountry === '') { $strCountry = fxChronotrackApiSyncIso2ToIso3($arrRow['country_iso2'] ?? ''); } $strState = strtoupper(trim((string)($arrRow['state_iso'] ?? ''))); $arrEntry = array( 'external_id' => (string)$intExternalId, 'race_id' => $intCtRaceId, 'first_name' => trim((string)($arrRow['par_prenom'] ?? '')), 'last_name' => trim((string)($arrRow['par_nom'] ?? '')), 'sex' => fxChronotrackApiSyncNormalizeSex($arrRow['par_sexe'] ?? ''), 'entry_status' => fxChronotrackApiSyncMapEntryStatus( $arrRow['par_statut_course'] ?? '', intval($arrRow['is_cancelled'] ?? 0) === 1 ), ); $strDob = fxChronotrackApiSyncNormalizeBirthdate($arrRow['par_naissance'] ?? ''); if ($strDob !== '') { $arrEntry['birthdate'] = $strDob; } $strBib = trim((string)($arrRow['no_bib'] ?? '')); if ($strBib !== '' && $strBib !== '0') { $arrEntry['bib'] = $strBib; } $strStreet = trim((string)($arrRow['par_adresse'] ?? '')); if ($strStreet !== '') { $arrEntry['street'] = $strStreet; } $strCity = trim((string)($arrRow['par_ville'] ?? '')); if ($strCity !== '') { $arrEntry['city'] = $strCity; } $strPostal = trim((string)($arrRow['par_codepostal'] ?? '')); if ($strPostal !== '') { $arrEntry['postal_code'] = $strPostal; } if ($strState !== '') { $arrEntry['state_code'] = $strState; } if ($strCountry !== '') { $arrEntry['country_code'] = $strCountry; } return $arrEntry; } function fxChronotrackApiSyncFetchCtEntriesIndexed($intCtEventId) { $intCtEventId = intval($intCtEventId); $tabIndexed = array(); $intPage = 1; $intPageSize = 100; $intMaxPages = 50; while ($intPage <= $intMaxPages) { $arrApi = fxChronotrackApiOAuthApiGet('event/' . $intCtEventId . '/entry', array( 'page' => $intPage, 'page_size' => $intPageSize, )); if ($arrApi['state'] !== 'ok') { return array( 'state' => 'error', 'message' => $arrApi['message'] ?? 'Erreur lecture entries CT', 'entries' => $tabIndexed, ); } $arrEntities = fxChronotrackApiNormalizeEntityList($arrApi['json'], 'entry'); if (count($arrEntities) === 0) { break; } foreach ($arrEntities as $arrEntity) { if (!is_array($arrEntity)) { continue; } $strExt = fxChronotrackApiEntityExternalId($arrEntity); if ($strExt === '') { continue; } $tabIndexed[$strExt] = $arrEntity; } if (count($arrEntities) < $intPageSize) { break; } $intPage++; } return array( 'state' => 'ok', 'message' => '', 'entries' => $tabIndexed, ); } function fxChronotrackApiSyncBuildPreview($intEveId) { $arrConfig = fxChronotrackApiConfigGet($intEveId); if ($arrConfig === null) { return array('state' => 'error', 'message' => 'Événement non lié à ChronoTrack'); } if (!fxChronotrackApiSettingsConfigured()) { return array('state' => 'error', 'message' => fxChronotrackApiSettingsErrorMessage()); } $intCtEventId = intval($arrConfig['ct_event_id']); $arrRaceMap = fxChronotrackApiRaceMapGetForEvent($intEveId); $tabParticipants = fxChronotrackApiSyncLoadMs1Participants($intEveId); $intMs1Total = count($tabParticipants); $intEligible = 0; $intSkippedNoRace = 0; $intSkippedNoExternal = 0; $tabEligibleKeys = array(); foreach ($tabParticipants as $arrRow) { $arrPayload = fxChronotrackApiSyncBuildEntryPayload($arrRow, $arrRaceMap); if ($arrPayload === null) { if (intval($arrRow['par_id_original'] ?? 0) <= 0) { $intSkippedNoExternal++; } else { $intSkippedNoRace++; } continue; } $intEligible++; $tabEligibleKeys[$arrPayload['external_id']] = true; } $arrCtFetch = fxChronotrackApiSyncFetchCtEntriesIndexed($intCtEventId); if ($arrCtFetch['state'] !== 'ok') { return array( 'state' => 'error', 'message' => $arrCtFetch['message'], ); } $tabCtEntries = $arrCtFetch['entries']; $intCtTotal = count($tabCtEntries); $intAlreadyInCt = 0; $intToCreate = 0; foreach ($tabEligibleKeys as $strExt => $blnTrue) { if (isset($tabCtEntries[$strExt])) { $intAlreadyInCt++; } else { $intToCreate++; } } return array( 'state' => 'ok', 'ct_event_id' => $intCtEventId, 'ms1_total' => $intMs1Total, 'eligible' => $intEligible, 'skipped_no_race' => $intSkippedNoRace, 'skipped_no_external'=> $intSkippedNoExternal, 'ct_total' => $intCtTotal, 'already_in_ct' => $intAlreadyInCt, 'to_create' => $intToCreate, 'to_push' => $intEligible, ); } function fxChronotrackApiSyncPushEvent($intEveId) { $arrPreview = fxChronotrackApiSyncBuildPreview($intEveId); if ($arrPreview['state'] !== 'ok') { return $arrPreview; } $intCtEventId = intval($arrPreview['ct_event_id']); $arrRaceMap = fxChronotrackApiRaceMapGetForEvent($intEveId); $tabParticipants = fxChronotrackApiSyncLoadMs1Participants($intEveId); $tabBatch = array(); $tabMeta = array(); $intSkipped = 0; foreach ($tabParticipants as $arrRow) { $arrPayload = fxChronotrackApiSyncBuildEntryPayload($arrRow, $arrRaceMap); if ($arrPayload === null) { $intSkipped++; continue; } $tabBatch[] = $arrPayload; $tabMeta[] = array( 'par_id' => intval($arrRow['par_id']), 'external_id' => $arrPayload['external_id'], ); } if (count($tabBatch) === 0) { return array( 'state' => 'error', 'message' => 'Aucun participant éligible (vérifiez le mapping des épreuves).', ); } $intOk = 0; $intErr = 0; $tabErrors = array(); $intBatchSize = MSIN_API_CHRONOTRACK_SYNC_BATCH_SIZE; $intTotal = count($tabBatch); for ($intOffset = 0; $intOffset < $intTotal; $intOffset += $intBatchSize) { $tabSlice = array_slice($tabBatch, $intOffset, $intBatchSize); $tabSliceMeta = array_slice($tabMeta, $intOffset, $intBatchSize); $arrPost = fxChronotrackApiOAuthApiPost( 'event/' . $intCtEventId . '/entry', array('event_entry' => $tabSlice) ); if ($arrPost['state'] === 'ok') { $intOk += count($tabSlice); foreach ($tabSliceMeta as $arrM) { fxChronotrackApiSyncLog( $intEveId, $arrM['par_id'], 'push_entry', 'ok', 'external_id=' . $arrM['external_id'] ); } continue; } $strErrMsg = $arrPost['message'] ?? 'Erreur POST entry'; $intErr += count($tabSlice); $tabErrors[] = $strErrMsg; foreach ($tabSliceMeta as $arrM) { fxChronotrackApiSyncLog( $intEveId, $arrM['par_id'], 'push_entry', 'error', 'external_id=' . $arrM['external_id'] . ' — ' . $strErrMsg ); } } $strState = ($intErr === 0) ? 'ok' : (($intOk > 0) ? 'partial' : 'error'); return array( 'state' => $strState, 'message' => ($intErr === 0) ? ($intOk . ' participant(s) envoyé(s) vers ChronoTrack.') : ($intOk . ' OK, ' . $intErr . ' erreur(s).'), 'pushed_ok' => $intOk, 'pushed_error' => $intErr, 'skipped' => $intSkipped, 'errors' => array_slice($tabErrors, 0, 5), 'preview' => $arrPreview, ); }