diff --git a/auto_chronotrack_sync.php b/auto_chronotrack_sync.php
index 549753c..5a11d7e 100644
--- a/auto_chronotrack_sync.php
+++ b/auto_chronotrack_sync.php
@@ -2,10 +2,10 @@
/**
* MSIN-4328 — Cron sync auto ChronoTrack (différentiel).
*
- * Cron recommandé (toutes les minutes) — IMPORTANT : -k si certificat preprod invalide :
- * /usr/bin/curl -sk https://VOTRE-DOMAINE/auto_chronotrack_sync.php > /dev/null 2>&1
+ * Même pattern que auto_liste_attente.php :
+ * /usr/bin/curl -s https://VOTRE-DOMAINE/auto_chronotrack_sync.php > /dev/null 2>&1
*
- * Preuve d’exécution : fichier log/chronotrack_cron_last.txt (mtime = dernier appel).
+ * Minute=* Hour=* … → le script décide selon fréquence/fin de chaque event.
*/
ini_set('display_errors', 0);
@@ -14,33 +14,12 @@ ini_set('error_log', __DIR__ . '/log/php_log.txt');
@set_time_limit(300);
-$strLogDir = __DIR__ . '/log';
-if (!is_dir($strLogDir)) {
- @mkdir($strLogDir, 0755, true);
-}
-$strHeartbeat = $strLogDir . '/chronotrack_cron_last.txt';
-
-/**
- * Écrit une preuve locale que le script a été appelé (cron ou navigateur).
- */
-function fxChronotrackCronHeartbeat($strHeartbeat, $strLine) {
- $strPayload = $strLine . "\n"
- . 'host=' . (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '') . "\n"
- . 'remote=' . (isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '') . "\n"
- . 'ua=' . (isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '') . "\n";
- @file_put_contents($strHeartbeat, $strPayload, LOCK_EX);
-}
-
-// Preuve immédiate (même si lock / erreur après)
-fxChronotrackCronHeartbeat($strHeartbeat, 'HIT ' . date('Y-m-d H:i:s'));
-
$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)) {
if (is_resource($fpLock)) {
fclose($fpLock);
}
- fxChronotrackCronHeartbeat($strHeartbeat, 'BUSY ' . date('Y-m-d H:i:s'));
header('Content-Type: text/plain; charset=utf-8');
echo 'busy';
exit;
@@ -55,15 +34,14 @@ try {
$arrResult = fxChronotrackApiSyncRunAutoCron();
$strLine = 'auto_chronotrack_sync [' . date('Y-m-d H:i:s') . '] '
. ($arrResult['message'] ?? 'done');
+ // Même style que liste d’attente : une ligne dans log/php_log.txt
error_log($strLine);
- fxChronotrackCronHeartbeat($strHeartbeat, 'OK ' . $strLine);
header('Content-Type: text/plain; charset=utf-8');
echo $strLine . "\n";
} catch (Throwable $ex) {
- $strErr = 'ERROR ' . date('Y-m-d H:i:s') . ' ' . $ex->getMessage();
- error_log('auto_chronotrack_sync ' . $strErr);
- fxChronotrackCronHeartbeat($strHeartbeat, $strErr);
+ $strErr = 'auto_chronotrack_sync ERROR [' . date('Y-m-d H:i:s') . '] ' . $ex->getMessage();
+ error_log($strErr);
header('Content-Type: text/plain; charset=utf-8');
echo $strErr . "\n";
}
diff --git a/php/chronotrack_api/fx_chronotrack_admin.php b/php/chronotrack_api/fx_chronotrack_admin.php
index 47c7df2..59da8fd 100644
--- a/php/chronotrack_api/fx_chronotrack_admin.php
+++ b/php/chronotrack_api/fx_chronotrack_admin.php
@@ -298,14 +298,23 @@ function fxChronotrackApiAdminRenderPushPanel($intEveId, $blnClosed = false, $ar
echo '';
echo '
';
if ($blnAutoOn && $strUntilRaw !== '') {
- echo 'Config enregistrée — cron auto_chronotrack_sync.php (toutes les minutes). Arrêt : '
+ echo 'Config enregistrée — cron auto_chronotrack_sync.php (comme liste d’attente). Arrêt : '
. fxChronotrackApiAdminEsc($strUntilRaw) . '.';
} else {
- echo 'Cochez, choisissez fréquence + date/heure de fin, puis Enregistrer. '
- . 'Le cron serveur doit appeler auto_chronotrack_sync.php chaque minute.';
+ echo 'Cochez, choisissez fréquence + date/heure de fin, puis Enregistrer.';
+ }
+ echo '
';
+ $strLastAuto = trim((string)($arrConfig['last_auto_run_at'] ?? ''));
+ echo '';
+ echo 'Dernier passage auto : ';
+ if ($strLastAuto !== '' && $strLastAuto !== '0000-00-00 00:00:00') {
+ echo fxChronotrackApiAdminEsc($strLastAuto);
+ } else {
+ echo 'jamais (le cron n’a pas encore poussé cet event)';
}
echo '
';
echo '';
+ echo '';
echo '';
// Zone employé : statut clair + actions
@@ -320,13 +329,13 @@ function fxChronotrackApiAdminRenderPushPanel($intEveId, $blnClosed = false, $ar
echo ' Envoyer vers ChronoTrack ';
echo ' ';
- echo '';
+ echo ' ';
+ echo '';
echo '';
- echo 'Envoi manuel : nouveaux / modifiés depuis le dernier push. '
+ echo '
Envoi manuel : nouveaux / modifiés depuis le dernier push. '
. 'Page : refresh statut MS1 chaque minute.
';
- echo 'Cron : utiliser curl -sk sur preprod (certificat). '
- . 'Preuve serveur : log/chronotrack_cron_last.txt.
';
echo '';
echo '';
@@ -1285,29 +1294,74 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed
$('#ct_api_logs_wrap').addClass('d-none').empty();
});
- // MSIN-4328 — appelle auto_chronotrack_sync.php (même URL que le cron)
+ // MSIN-4328 — appelle auto_chronotrack_sync.php (même URL / même base que le CrowdJob)
$('#ct_api_btn_cron_ping').on('click', function () {
var $btn = $(this);
var $res = $('#ct_api_push_result');
$btn.prop('disabled', true);
- $res.html('Appel cron…');
+ $res.html('Appel auto_chronotrack_sync.php…');
$.ajax({
url: '/auto_chronotrack_sync.php',
method: 'GET',
dataType: 'text',
timeout: 120000
}).done(function (txt) {
- $res.html('Réponse cron :'
- + $('').text(txt || '(vide)').html() + ''
- + '
Si ran=0 : intervalle pas dû, pas de fin, ou auto OFF. Vérifier aussi log/chronotrack_cron_last.txt
');
+ $res.html('Réponse script :'
+ + $('').text(txt || '(vide)').html() + ' ');
+ loadAutoDiag();
+ loadSyncLogs();
}).fail(function (xhr) {
- $res.html('Cron injoignable (HTTP '
- + (xhr.status || '?') + '). Fichier absent sur ce domaine ? Certificat / 404 ?
');
+ $res.html('Script injoignable sur ce domaine (HTTP '
+ + (xhr.status || '?') + '). Fichier pas déployé ici ?
');
}).always(function () {
$btn.prop('disabled', false);
});
});
+ function loadAutoDiag() {
+ var $box = $('#ct_api_auto_diag');
+ if (!$box.length) {
+ return;
+ }
+ $box.text('Lecture état sync auto…');
+ $.post(strAjaxUrl, { a: 'sync_auto_diag', eve_id: intEveId }, function (res) {
+ if (rejectIfSessionLost(res)) {
+ return;
+ }
+ if (!res || res.state !== 'ok' || !res.diag) {
+ $box.html(''
+ + escHtml((res && res.message) ? res.message : 'Diag impossible') + '');
+ return;
+ }
+ var d = res.diag;
+ var html = '';
+ html += '
Verdict : ' + escHtml(d.would || '') + '
';
+ html += '
auto=' + (d.auto_enabled ? 'ON' : 'OFF')
+ + ' · freq=' + escHtml(String(d.interval_min)) + ' min'
+ + ' · fin=' + escHtml(d.until || '—')
+ + ' · dernier passage=' + escHtml(d.last_auto_run_at || 'jamais') + '
';
+ html += '
logs en base pour cet event : ' + escHtml(String(d.log_count)) + '';
+ if (d.last_log) {
+ html += ' · dernier : [' + escHtml(d.last_log.created_at || '') + '] '
+ + escHtml(d.last_log.action || '') + ' — ' + escHtml(d.last_log.message || '');
+ }
+ html += '
';
+ if (d.cols_note) {
+ html += '
' + escHtml(d.cols_note) + '
';
+ }
+ html += '
host=' + escHtml(d.host || '') + '
';
+ html += '
';
+ $box.html(html);
+ if (d.last_auto_run_at) {
+ $('#ct_api_auto_last').html('Dernier passage auto : '
+ + escHtml(d.last_auto_run_at));
+ }
+ }, 'json').fail(function () {
+ $box.html('Erreur réseau (diag).');
+ });
+ }
+ $('#ct_api_btn_auto_diag').on('click', loadAutoDiag);
+
$('#ct_api_btn_ct_count').on('click', function () {
loadCtLiveCount();
});
diff --git a/php/chronotrack_api/fx_chronotrack_sync.php b/php/chronotrack_api/fx_chronotrack_sync.php
index c3d046d..a3ba7d9 100644
--- a/php/chronotrack_api/fx_chronotrack_sync.php
+++ b/php/chronotrack_api/fx_chronotrack_sync.php
@@ -481,13 +481,24 @@ function fxChronotrackApiSyncNormalizeBirthdate($strDob) {
function fxChronotrackApiSyncLog($intEveId, $intParId, $strAction, $strStatus, $strMessage) {
global $objDatabase;
+ if (!isset($objDatabase) || !is_object($objDatabase)) {
+ error_log('MSIN-4328 sync_log: $objDatabase absent — action=' . $strAction);
+ return false;
+ }
+
$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);
+ $blnOk = (bool)$objDatabase->fxQuery($sql);
+ if (!$blnOk) {
+ // Sans ça, push/cron « OK » mais Voir les logs = vide (INSERT silencieux).
+ error_log('MSIN-4328 sync_log INSERT failed eve=' . intval($intEveId)
+ . ' action=' . $strAction . ' sql_err=' . (isset($objDatabase->con) ? mysqli_error($objDatabase->con) : '?'));
+ }
+ return $blnOk;
}
/**
@@ -1393,6 +1404,7 @@ function fxChronotrackApiSyncPushPrepare($intEveId) {
. ($arrClass['blocked_count'] > 0 ? ' ' . $arrClass['blocked_count'] . ' exclus.' : ''))
: ('Aucun changement depuis le dernier push ('
. count($arrClass['transferable']) . ' éligibles à jour).');
+ fxChronotrackApiSyncLog($intEveId, 0, 'push_prepare', 'ok', 'idle — ' . $strMsg);
return array(
'state' => 'error',
'message' => $strMsg,
@@ -1403,6 +1415,16 @@ function fxChronotrackApiSyncPushPrepare($intEveId) {
);
}
+ fxChronotrackApiSyncLog(
+ $intEveId,
+ 0,
+ 'push_prepare',
+ 'ok',
+ 'job prêt — pending=' . count($tabBatch)
+ . ' éligibles=' . count($arrClass['transferable'])
+ . ' exclus=' . intval($arrClass['blocked_count'])
+ );
+
$arrJob = array(
'ct_event_id' => $intCtEventId,
'batch' => $tabBatch,
@@ -1750,6 +1772,8 @@ function fxChronotrackApiSyncRunAutoPushForEvent($intEveId) {
/**
* MSIN-4328 — passage cron : events auto ON, fréquence / fin respectées.
+ * Chaque décision (run / skip / expire) écrit un log pour l’événement — sinon
+ * « Voir les logs » reste vide alors que le CrowdJob tourne vraiment.
*/
function fxChronotrackApiSyncRunAutoCron() {
if (!fxChronotrackApiSettingsConfigured()) {
@@ -1758,6 +1782,7 @@ function fxChronotrackApiSyncRunAutoCron() {
'message' => fxChronotrackApiSettingsErrorMessage(),
'ran' => 0,
'skipped' => 0,
+ 'enabled_configs' => 0,
);
}
@@ -1767,6 +1792,7 @@ function fxChronotrackApiSyncRunAutoCron() {
$intExpired = 0;
$tabDetails = array();
$intNow = time();
+ $intEnabled = count($tabConfigs);
foreach ($tabConfigs as $arrCfg) {
$intEveId = intval($arrCfg['eve_id'] ?? 0);
@@ -1793,6 +1819,13 @@ function fxChronotrackApiSyncRunAutoCron() {
} else {
// Pas de fin = on ne tourne pas (UI exige une fin à l’enregistrement)
$intSkipped++;
+ fxChronotrackApiSyncLog(
+ $intEveId,
+ 0,
+ 'auto_cron',
+ 'error',
+ 'Skip: date/heure de fin manquante (réenregistrer la sync auto)'
+ );
$tabDetails[] = array('eve_id' => $intEveId, 'action' => 'skip_no_until');
continue;
}
@@ -1806,6 +1839,8 @@ function fxChronotrackApiSyncRunAutoCron() {
$intLast = strtotime($strLast);
if ($intLast !== false && ($intNow - $intLast) < ($intInterval * 60)) {
$intSkipped++;
+ // Trace légère: 1 seule fois par fenêtre d’intervalle (évite 1440 lignes/jour).
+ // Déjà logué ailleurs au run — ici on ne spam pas.
$tabDetails[] = array('eve_id' => $intEveId, 'action' => 'skip_interval');
continue;
}
@@ -1823,12 +1858,94 @@ function fxChronotrackApiSyncRunAutoCron() {
);
}
+ $strMsg = 'cron auto — configs_on=' . $intEnabled
+ . ' ran=' . $intRan . ' skipped=' . $intSkipped . ' expired=' . $intExpired;
+ if ($intEnabled === 0) {
+ $strMsg .= ' (aucun event avec sync auto ON sur cette base)';
+ }
+
return array(
- 'state' => 'ok',
- 'message' => 'cron auto — ran=' . $intRan . ' skipped=' . $intSkipped . ' expired=' . $intExpired,
- 'ran' => $intRan,
- 'skipped' => $intSkipped,
- 'expired' => $intExpired,
- 'details' => $tabDetails,
+ 'state' => 'ok',
+ 'message' => $strMsg,
+ 'ran' => $intRan,
+ 'skipped' => $intSkipped,
+ 'expired' => $intExpired,
+ 'enabled_configs' => $intEnabled,
+ 'details' => $tabDetails,
+ );
+}
+
+/**
+ * MSIN-4328 — état sync auto + logs pour un événement (diag UI, pas d’essai-erreur SSL).
+ */
+function fxChronotrackApiSyncAutoDiag($intEveId) {
+ global $objDatabase;
+
+ $intEveId = intval($intEveId);
+ $arrCfg = fxChronotrackApiConfigGet($intEveId);
+ if ($arrCfg === null) {
+ return array('state' => 'error', 'message' => 'Événement non lié à ChronoTrack');
+ }
+
+ $blnColsOk = true;
+ $strColNote = '';
+ $sqlCol = "SELECT COUNT(*) AS n FROM information_schema.COLUMNS"
+ . " WHERE TABLE_SCHEMA = DATABASE()"
+ . " AND TABLE_NAME = 'api_chronotrack_config'"
+ . " AND COLUMN_NAME = 'auto_sync_enabled'";
+ $arrCol = $objDatabase->fxGetRow($sqlCol);
+ if (intval($arrCol['n'] ?? 0) < 1) {
+ $blnColsOk = false;
+ $strColNote = 'Colonnes auto_sync absentes — exécuter sql/MSIN-4328-chronotrack-auto-sync-config.sql';
+ }
+
+ $sqlLog = "SELECT COUNT(*) AS n FROM api_chronotrack_sync_log WHERE eve_id = " . $intEveId;
+ $arrLogCnt = $objDatabase->fxGetRow($sqlLog);
+ $intLogCnt = intval($arrLogCnt['n'] ?? 0);
+
+ $arrLastLog = $objDatabase->fxGetRow(
+ "SELECT log_id, action, status, message, created_at FROM api_chronotrack_sync_log"
+ . " WHERE eve_id = " . $intEveId
+ . " ORDER BY log_id DESC LIMIT 1"
+ );
+
+ $blnOn = (intval($arrCfg['auto_sync_enabled'] ?? 0) === 1);
+ $intInterval = intval($arrCfg['auto_sync_interval_min'] ?? 15);
+ $strUntil = trim((string)($arrCfg['auto_sync_until'] ?? ''));
+ $strLastRun = trim((string)($arrCfg['last_auto_run_at'] ?? ''));
+ $intNow = time();
+ $strWould = 'inconnu';
+ if (!$blnColsOk) {
+ $strWould = 'bloqué: SQL auto_sync manquant';
+ } elseif (!$blnOn) {
+ $strWould = 'ne tourne pas: sync auto OFF';
+ } elseif ($strUntil === '' || $strUntil === '0000-00-00 00:00:00') {
+ $strWould = 'ne tourne pas: date de fin manquante';
+ } elseif (strtotime($strUntil) !== false && $intNow > strtotime($strUntil)) {
+ $strWould = 'ne tourne pas: date de fin dépassée (' . $strUntil . ')';
+ } elseif ($strLastRun !== '' && $strLastRun !== '0000-00-00 00:00:00'
+ && strtotime($strLastRun) !== false
+ && ($intNow - strtotime($strLastRun)) < ($intInterval * 60)) {
+ $strWould = 'en attente d’intervalle (dernier passage ' . $strLastRun
+ . ', fréquence ' . $intInterval . ' min)';
+ } else {
+ $strWould = 'devrait pousser au prochain appel de auto_chronotrack_sync.php';
+ }
+
+ return array(
+ 'state' => 'ok',
+ 'message' => $strWould,
+ 'diag' => array(
+ 'cols_ok' => $blnColsOk,
+ 'cols_note' => $strColNote,
+ 'auto_enabled' => $blnOn,
+ 'interval_min' => $intInterval,
+ 'until' => $strUntil !== '' ? $strUntil : null,
+ 'last_auto_run_at' => $strLastRun !== '' ? $strLastRun : null,
+ 'log_count' => $intLogCnt,
+ 'last_log' => is_array($arrLastLog) ? $arrLastLog : null,
+ 'host' => isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '',
+ 'would' => $strWould,
+ ),
);
}
diff --git a/superadm/ajax_chronotrack_api.php b/superadm/ajax_chronotrack_api.php
index 5fe17f8..49e3329 100644
--- a/superadm/ajax_chronotrack_api.php
+++ b/superadm/ajax_chronotrack_api.php
@@ -152,6 +152,15 @@ switch ($strAction) {
$tabRetour = fxChronotrackApiSyncLogList($intEveId, $intLimit);
break;
+ case 'sync_auto_diag':
+ // MSIN-4328 — pourquoi le cron pousse / ne pousse pas (sans essais curl)
+ if ($intEveId <= 0) {
+ $tabRetour = array('state' => 'error', 'message' => 'eve_id manquant');
+ break;
+ }
+ $tabRetour = fxChronotrackApiSyncAutoDiag($intEveId);
+ break;
+
case 'sync_probe_push':
if ($intEveId <= 0) {
$tabRetour = array('state' => 'error', 'message' => 'eve_id manquant');