diff --git a/auto_chronotrack_sync.php b/auto_chronotrack_sync.php new file mode 100644 index 0000000..5ad278a --- /dev/null +++ b/auto_chronotrack_sync.php @@ -0,0 +1,44 @@ + /dev/null 2>&1 + * + * La fréquence réelle d’envoi par événement est celle choisie dans l’admin + * (1 / 5 / 15 / 30 / 60 min) + date/heure de fin. + */ + +ini_set('display_errors', 0); +ini_set('log_errors', 1); +ini_set('error_log', __DIR__ . '/log/php_log.txt'); + +@set_time_limit(300); + +$strLockPath = rtrim(sys_get_temp_dir(), '/\\') . DIRECTORY_SEPARATOR . 'ms1_chronotrack_auto_sync.lock'; +$fpLock = @fopen($strLockPath, 'c'); +if ($fpLock === false || !flock($fpLock, LOCK_EX | LOCK_NB)) { + // Un passage précédent tourne encore — on saute ce tick. + if (is_resource($fpLock)) { + fclose($fpLock); + } + header('Content-Type: text/plain; charset=utf-8'); + echo 'busy'; + exit; +} + +session_start(); + +require_once __DIR__ . '/php/inc_fonctions.php'; +require_once __DIR__ . '/php/chronotrack_api/inc_bootstrap.php'; + +$arrResult = fxChronotrackApiSyncRunAutoCron(); +$strLine = 'auto_chronotrack_sync [' . date('Y-m-d H:i:s') . '] ' + . ($arrResult['message'] ?? 'done'); +error_log($strLine); + +header('Content-Type: text/plain; charset=utf-8'); +echo $strLine . "\n"; + +flock($fpLock, LOCK_UN); +fclose($fpLock); diff --git a/php/chronotrack_api/fx_chronotrack_admin.php b/php/chronotrack_api/fx_chronotrack_admin.php index f9380e1..7ca13df 100644 --- a/php/chronotrack_api/fx_chronotrack_admin.php +++ b/php/chronotrack_api/fx_chronotrack_admin.php @@ -298,11 +298,11 @@ function fxChronotrackApiAdminRenderPushPanel($intEveId, $blnClosed = false, $ar echo ''; echo '

'; if ($blnAutoOn && $strUntilRaw !== '') { - echo 'Config enregistrée — exécution serveur (cron) à brancher. Arrêt prévu : ' + echo 'Config enregistrée — cron auto_chronotrack_sync.php (toutes les minutes). Arrêt : ' . fxChronotrackApiAdminEsc($strUntilRaw) . '.'; } else { - echo 'Quand c’est coché, le serveur (cron) poussera le différentiel à cet intervalle jusqu’à la date de fin. ' - . 'Pas encore actif côté serveur.'; + echo 'Cochez, choisissez fréquence + date/heure de fin, puis Enregistrer. ' + . 'Le cron serveur doit appeler auto_chronotrack_sync.php chaque minute.'; } echo '

'; echo '
'; @@ -1174,10 +1174,10 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed $msg.addClass('text-success').text(res.message || 'Enregistré'); if (res.auto_sync) { if (res.auto_sync.enabled && res.auto_sync.until) { - $hint.text('Config enregistrée — exécution serveur (cron) à brancher. Arrêt prévu : ' + $hint.text('Config OK — cron toutes les minutes lit cette config (auto_chronotrack_sync.php). Arrêt : ' + res.auto_sync.until + '.'); } else { - $hint.text('Sync auto désactivée. Le cron (phase B) ne traitera pas cet événement.'); + $hint.text('Sync auto désactivée.'); } } }, 'json').fail(function () { diff --git a/php/chronotrack_api/fx_chronotrack_config.php b/php/chronotrack_api/fx_chronotrack_config.php index 19ed628..6a71cc3 100644 --- a/php/chronotrack_api/fx_chronotrack_config.php +++ b/php/chronotrack_api/fx_chronotrack_config.php @@ -197,7 +197,7 @@ function fxChronotrackApiConfigSaveAutoSync($intEveId, $blnEnabled, $intInterval 'state' => 'ok', 'message' => $blnEnabled ? ('Sync auto enregistrée (toutes les ' . $intIntervalMin . ' min jusqu’au ' - . trim((string)($arrFresh['auto_sync_until'] ?? '')) . '). Le cron démarrera en phase B.') + . 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, @@ -208,6 +208,60 @@ function fxChronotrackApiConfigSaveAutoSync($intEveId, $blnEnabled, $intInterval ); } +/** + * 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; diff --git a/php/chronotrack_api/fx_chronotrack_sync.php b/php/chronotrack_api/fx_chronotrack_sync.php index 6cda232..5a25787 100644 --- a/php/chronotrack_api/fx_chronotrack_sync.php +++ b/php/chronotrack_api/fx_chronotrack_sync.php @@ -1661,3 +1661,143 @@ function fxChronotrackApiSyncListCtOrphans($intEveId) { 'orphans' => $tabOrphans, ); } + +/** + * MSIN-4328 — push différentiel complet pour un event (cron auto). + */ +function fxChronotrackApiSyncRunAutoPushForEvent($intEveId) { + $intEveId = intval($intEveId); + $arrPrep = fxChronotrackApiSyncPushPrepare($intEveId); + if (($arrPrep['state'] ?? '') !== 'ok') { + // Rien à pousser = succès « idle » + $strMsg = (string)($arrPrep['message'] ?? 'prepare failed'); + if (stripos($strMsg, 'Aucun changement') !== false || intval($arrPrep['pending_push'] ?? -1) === 0) { + fxChronotrackApiSyncLog($intEveId, 0, 'auto_push', 'ok', 'idle — ' . $strMsg); + return array('state' => 'ok', 'message' => $strMsg, 'pushed_ok' => 0, 'idle' => true); + } + fxChronotrackApiSyncLog($intEveId, 0, 'auto_push', 'error', $strMsg); + return $arrPrep; + } + + $intOffset = 0; + $intLimit = MSIN_API_CHRONOTRACK_SYNC_CHUNK_SIZE; + $intOkSum = 0; + $intErrSum = 0; + $intLoops = 0; + $intMaxLoops = 500; + + while ($intLoops < $intMaxLoops) { + $intLoops++; + $arrChunk = fxChronotrackApiSyncPushEvent($intEveId, array( + 'offset' => $intOffset, + 'limit' => $intLimit, + )); + $intOkSum += intval($arrChunk['pushed_ok'] ?? 0); + $intErrSum += intval($arrChunk['pushed_error'] ?? 0); + if (!empty($arrChunk['done'])) { + break; + } + if (($arrChunk['state'] ?? '') === 'error' && intval($arrChunk['pushed_ok'] ?? 0) === 0) { + break; + } + $intOffset = intval($arrChunk['next_offset'] ?? ($intOffset + $intLimit)); + } + + $strState = ($intErrSum === 0) ? 'ok' : (($intOkSum > 0) ? 'partial' : 'error'); + $strMsg = 'auto push ok=' . $intOkSum . ' err=' . $intErrSum + . ' total_job=' . intval($arrPrep['total'] ?? 0); + fxChronotrackApiSyncLog($intEveId, 0, 'auto_push', ($strState === 'ok') ? 'ok' : 'error', $strMsg); + + return array( + 'state' => $strState, + 'message' => $strMsg, + 'pushed_ok' => $intOkSum, + 'pushed_error' => $intErrSum, + 'idle' => false, + ); +} + +/** + * MSIN-4328 — passage cron : events auto ON, fréquence / fin respectées. + */ +function fxChronotrackApiSyncRunAutoCron() { + if (!fxChronotrackApiSettingsConfigured()) { + return array( + 'state' => 'error', + 'message' => fxChronotrackApiSettingsErrorMessage(), + 'ran' => 0, + 'skipped' => 0, + ); + } + + $tabConfigs = fxChronotrackApiConfigListAutoSyncEnabled(); + $intRan = 0; + $intSkipped = 0; + $intExpired = 0; + $tabDetails = array(); + $intNow = time(); + + foreach ($tabConfigs as $arrCfg) { + $intEveId = intval($arrCfg['eve_id'] ?? 0); + if ($intEveId <= 0) { + continue; + } + + $strUntil = trim((string)($arrCfg['auto_sync_until'] ?? '')); + if ($strUntil !== '' && $strUntil !== '0000-00-00 00:00:00') { + $intUntil = strtotime($strUntil); + if ($intUntil !== false && $intNow > $intUntil) { + fxChronotrackApiConfigDisableAutoSync($intEveId); + fxChronotrackApiSyncLog( + $intEveId, + 0, + 'auto_sync', + 'ok', + 'Échéance atteinte (' . $strUntil . ') — sync auto désactivée' + ); + $intExpired++; + $tabDetails[] = array('eve_id' => $intEveId, 'action' => 'expired'); + continue; + } + } else { + // Pas de fin = on ne tourne pas (UI exige une fin à l’enregistrement) + $intSkipped++; + $tabDetails[] = array('eve_id' => $intEveId, 'action' => 'skip_no_until'); + continue; + } + + $intInterval = intval($arrCfg['auto_sync_interval_min'] ?? 15); + if (!in_array($intInterval, fxChronotrackApiConfigAutoSyncIntervals(), true)) { + $intInterval = 15; + } + $strLast = trim((string)($arrCfg['last_auto_run_at'] ?? '')); + if ($strLast !== '' && $strLast !== '0000-00-00 00:00:00') { + $intLast = strtotime($strLast); + if ($intLast !== false && ($intNow - $intLast) < ($intInterval * 60)) { + $intSkipped++; + $tabDetails[] = array('eve_id' => $intEveId, 'action' => 'skip_interval'); + continue; + } + } + + $arrRes = fxChronotrackApiSyncRunAutoPushForEvent($intEveId); + fxChronotrackApiConfigTouchAutoRun($intEveId); + $intRan++; + $tabDetails[] = array( + 'eve_id' => $intEveId, + 'action' => 'run', + 'result' => $arrRes['state'] ?? 'error', + 'message' => $arrRes['message'] ?? '', + 'ok' => intval($arrRes['pushed_ok'] ?? 0), + ); + } + + return array( + 'state' => 'ok', + 'message' => 'cron auto — ran=' . $intRan . ' skipped=' . $intSkipped . ' expired=' . $intExpired, + 'ran' => $intRan, + 'skipped' => $intSkipped, + 'expired' => $intExpired, + 'details' => $tabDetails, + ); +}