diff --git a/main/inc/ajax/session_clock.ajax.php b/main/inc/ajax/session_clock.ajax.php new file mode 100644 index 00000000000..2ecc8afda8d --- /dev/null +++ b/main/inc/ajax/session_clock.ajax.php @@ -0,0 +1,75 @@ +getConfigurationFile())) { + require_once $kernel->getConfigurationFile(); + $alreadyInstalled = true; + } + + // Load the API library BEFORE loading the Chamilo configuration + require_once $_configuration['root_sys'].'main/inc/lib/api.lib.php'; + + if (api_get_configuration_value('session_lifetime_controller')) { + // Get the session + session_name('ch_sid'); + session_start(); + + $session = new ChamiloSession(); + + $endTime = 0; + $isExpired = false; + $timeLeft = -1; + + $currentTime = time(); + + // Existing code for time action + if ($alreadyInstalled && api_get_user_id()) { + $endTime = $session->end_time(); + $isExpired = $session->is_expired(); + } else { + // Chamilo not installed or user not logged in + $endTime = $currentTime + 315360000; // This sets a default end time far in the future + $isExpired = false; + } + + $timeLeft = $endTime - $currentTime; + } + else { + $endTime = 999999; + $isExpired = false; + $timeLeft = 999999; + } + + if ($endTime > 0) { + echo json_encode(['sessionEndDate' => $endTime, 'sessionTimeLeft' => $timeLeft, 'sessionExpired' => $isExpired]); + } else { + http_response_code(500); + echo json_encode(['error' => 'Error retrieving data from the current session']); + } + } elseif ($action == 'logout') { + require_once __DIR__.'/../../../main/inc/global-min.inc.php'; + + $userId = api_get_user_id(); + online_logout($userId, false); + echo json_encode(['message' => 'Logged out successfully']); + } else { + // Handle unexpected action value + http_response_code(400); + echo json_encode(['error' => 'Invalid action parameter']); + } +} else { + // No action provided + http_response_code(400); + echo json_encode(['error' => 'No action parameter provided']); +} \ No newline at end of file diff --git a/main/install/configuration.dist.php b/main/install/configuration.dist.php index ad8453adef4..bed94986b63 100644 --- a/main/install/configuration.dist.php +++ b/main/install/configuration.dist.php @@ -2580,3 +2580,6 @@ // is '$1EdTech-CC-FILEBASE$' (the latest), but previous versions of the standard // recommended '$IMS-CC-FILEBASE$', so you might want to use that for greater compatibility. //$_configuration['commoncartridge_path_token'] = '$IMS-CC-FILEBASE$'; + +// Set the following parameter to true to enable a session lifetime controller that notifies users that their session is about to expire +//$_configuration['session_lifetime_controller'] = false; diff --git a/main/lang/english/trad4all.inc.php b/main/lang/english/trad4all.inc.php index bb9fcff2b73..027ae4448d8 100644 --- a/main/lang/english/trad4all.inc.php +++ b/main/lang/english/trad4all.inc.php @@ -9073,4 +9073,8 @@ $CreateExport = "Create export file"; $MoodleExportAdminIDComment = "Moodle requires a user identification to be stored inside some XML files of the .mbz format. Please provide an internal (integer) ID of the user exporting this course or Moodle's internal user ID of the user that will be the owner of the imported resources. If you're in doubt, just write '1' and some fake data ton continue."; $DropboxVulnerabilityWarning = "Remember to only download files sent by people you trust. If in doubt, please use a anti-virus tool on your computer to mitigate the risk of harm to your data."; +$SessionExpiredAt = "Session expired at"; +$DueToInactivityTheSessionIsGoingToClose = "Due to your inactivity, this session is going to close in"; +$KeepGoing = "Keep going"; +$SessionIsClosing = "Your session is closing"; ?> \ No newline at end of file diff --git a/main/lang/french/trad4all.inc.php b/main/lang/french/trad4all.inc.php index 0cf91935052..fb2e08c6b73 100644 --- a/main/lang/french/trad4all.inc.php +++ b/main/lang/french/trad4all.inc.php @@ -9008,4 +9008,8 @@ $MoodleExportAdminIDComment = "Moodle requiert une identification de l'utilisateur pour la stocker au sein de fichiers XML qui font partie du format .mbz. Merci de bien vouloir fournir un numéro interne (nombre entier) de l'utilisateur qui exporte le cours, ou d'un utilisateur de notre système, qui sera désigné (s'il y a correspondance) comme le propriétaire des ressources importées sur l'autre système. Si vous avez encore des doutes, indiquez simplement '1' et donnez des données fictives."; $DropboxVulnerabilityWarning = "Ne téléchargez que des fichiers envoyés par des personnes en qui vous avez confiance. Dans le doute, merci d'utiliser un programme anti-virus sur votre ordinateur pour réduire les risques pour vos données."; +$SessionExpiredAt = "Session expirée à"; +$DueToInactivityTheSessionIsGoingToClose = "Dû à votre inactivité, la session se fermera dans"; +$KeepGoing = "Rester connecté"; +$SessionIsClosing = "Votre session est en cours de fermeture"; ?> \ No newline at end of file diff --git a/main/lang/spanish/trad4all.inc.php b/main/lang/spanish/trad4all.inc.php index f9712e55329..13d3aa1b74e 100644 --- a/main/lang/spanish/trad4all.inc.php +++ b/main/lang/spanish/trad4all.inc.php @@ -9098,4 +9098,8 @@ $CreateExport = "Crear archivo de exporte"; $MoodleExportAdminIDComment = "Moodle requiere la indentificación de algún usuario para almacenarla dentro de los archivos XML del formato .mbz. Por favor indique un número de ID interno (entero) del usuario quien está exportando este curso, o el ID interno del usuario en Moodle para el usuario quien será propietario de los recursos importados. Si tiene duda, puede simplemente marcar '1' y algunos datos falsos para seguir."; $DropboxVulnerabilityWarning = "Recuerde únicamente descargar archivos enviados por personas conocidas. Si tiene dudas, use un anti-virus en su computadora para reducir el riesgo de daños a sus datos."; +$SessionExpiredAt = "Sesión expirada el"; +$DueToInactivityTheSessionIsGoingToClose = "Debido a su inactividad, esta sesión se cerrará en"; +$KeepGoing = "Seguir conectado"; +$SessionIsClosing = "Su sesión se está cerrando"; ?> \ No newline at end of file diff --git a/main/template/default/layout/main.js.tpl b/main/template/default/layout/main.js.tpl index c1e59534d41..a16af7129d7 100644 --- a/main/template/default/layout/main.js.tpl +++ b/main/template/default/layout/main.js.tpl @@ -5,6 +5,9 @@ var offline_button = ''; var connect_lang = '{{ "ChatConnected"|get_lang | escape('js')}}'; var disconnect_lang = '{{ "ChatDisconnected"|get_lang | escape('js')}}'; var chatLang = '{{ "GlobalChat"|get_lang | escape('js')}}'; +var sessionRemainingSeconds = 0; +var sessionCounterInterval; +var sessionClosing = false; {% if 'hide_chat_video'|api_get_configuration_value %} var hide_chat_video = true; @@ -443,6 +446,10 @@ $(function() { }); }); {% endif %} + + if (window.self === window.top) { + checkSessionTime(); + } }); $(window).resize(function() { @@ -731,3 +738,193 @@ function copyTextToClipBoard(elementId) /* Copy the text inside the text field */ document.execCommand("copy"); } + +function checkSessionTime() +{ + fetch('/main/inc/ajax/session_clock.ajax.php?action=time') + .then(response => { + if (!response.ok) { + throw new Error('Server error: ' + response.statusText); + } + return response.json(); + }) + .then(data => { + if (data.sessionTimeLeft <= 0) { + if (!document.getElementById('session-checker-overlay')) { + clearInterval(sessionCounterInterval); + + var counterOverlay = document.getElementById('session-count-overlay'); + if (counterOverlay) { + counterOverlay.remove(); + } + + var now = new Date(); + var day = String(now.getDate()).padStart(2, '0'); + var month = String(now.getMonth() + 1).padStart(2, '0'); + var year = now.getFullYear(); + var hour = String(now.getHours()).padStart(2, '0'); + var minutes = String(now.getMinutes()).padStart(2, '0'); + + var dateTimeSessionExpired = day + '/' + month + '/' + year + ' ' + hour + ':' + minutes; + + document.body.insertAdjacentHTML('afterbegin', '

{{ 'SessionExpiredAt' | get_lang | escape('js')}} ' + dateTimeSessionExpired + '.

'); + } + } else if (data.sessionTimeLeft <= 110) { + sessionRemainingSeconds = data.sessionTimeLeft - 5; + + if (sessionRemainingSeconds < 0) { + sessionRemainingSeconds = 0; + } + + if (!document.getElementById('session-count-overlay')) { + document.body.insertAdjacentHTML('afterbegin', '

{{ 'DueToInactivityTheSessionIsGoingToClose' | get_lang | escape('js')}} ' + sessionRemainingSeconds + ' {{ 'Seconds' | get_lang | escape('js')}}

'); + + sessionCounterInterval = setInterval(updateSessionTimeCounter, 1000); + } + setTimeout(checkSessionTime, 60000); + } else { + clearInterval(sessionCounterInterval); + + var counterOverlay = document.getElementById('session-count-overlay'); + if (counterOverlay) { + counterOverlay.remove(); + } + + setTimeout(checkSessionTime, 60000); + } + }) + .catch(error => console.error('Error:', error)); +} + +function extendSession() { + fetch('/main/inc/ajax/online.ajax.php') + .then(response => { + if (!response.ok) { + throw new Error('Server error: ' + response.statusText); + } + return response; + }) + .then(data => { + console.log('Session extended'); + + clearInterval(sessionCounterInterval); + + var counterOverlay = document.getElementById('session-count-overlay'); + if (counterOverlay) { + counterOverlay.remove(); + } + }) + .catch(error => console.error('Error:', error)); +} + +function updateSessionTimeCounter() { + var sessionCounter = document.getElementById('session-counter'); + if (sessionRemainingSeconds > 3) { + sessionCounter.innerHTML = '{{ 'DueToInactivityTheSessionIsGoingToClose' | get_lang | escape('js')}} ' + sessionRemainingSeconds + ' {{ 'Seconds' | get_lang | escape('js')}}'; + sessionRemainingSeconds--; + } else if (sessionRemainingSeconds <= 3 && sessionRemainingSeconds > 1) { + var currentUrl = window.location.href; + if (currentUrl.includes('lp_controller.php') && currentUrl.includes('lp_id=') && currentUrl.includes('action=view')) { + + if (!sessionClosing) { + var btnSessionExtend = document.getElementById('btn-session-extend'); + if (btnSessionExtend) { + btnSessionExtend.remove(); + } + + document.getElementById('session-counter').innerHTML = '{{ 'SessionIsClosing' | get_lang | escape('js')}}'; + + setTimeout(function() { + fetch('/main/inc/ajax/session_clock.ajax.php?action=logout') + .then(response => response.json()) + .then(data => { + if (!document.getElementById('session-checker-overlay')) { + clearInterval(sessionCounterInterval); + + var counterOverlay = document.getElementById('session-count-overlay'); + if (counterOverlay) { + counterOverlay.remove(); + } + + var now = new Date(); + var day = String(now.getDate()).padStart(2, '0'); + var month = String(now.getMonth() + 1).padStart(2, '0'); + var year = now.getFullYear(); + var hour = String(now.getHours()).padStart(2, '0'); + var minutes = String(now.getMinutes()).padStart(2, '0'); + + var dateTimeSessionExpired = day + '/' + month + '/' + year + ' ' + hour + ':' + minutes; + + document.body.insertAdjacentHTML('afterbegin', '

{{ 'SessionExpiredAt' | get_lang | escape('js')}} ' + dateTimeSessionExpired + '.

'); + } + }) + .catch((error) => { + console.error('Error:', error); + }); + }, 1000); + + lastCall(); + } + sessionClosing = true; + } + else { + if (!sessionClosing) { + var btnSessionExtend = document.getElementById('btn-session-extend'); + if (btnSessionExtend) { + btnSessionExtend.remove(); + } + + document.getElementById('session-counter').innerHTML = '{{ 'SessionIsClosing' | get_lang | escape('js')}}'; + + setTimeout(function() { + fetch('/main/inc/ajax/session_clock.ajax.php?action=logout') + .then(response => response.json()) + .then(data => { + if (!document.getElementById('session-checker-overlay')) { + clearInterval(sessionCounterInterval); + + var counterOverlay = document.getElementById('session-count-overlay'); + if (counterOverlay) { + counterOverlay.remove(); + } + + var now = new Date(); + var day = String(now.getDate()).padStart(2, '0'); + var month = String(now.getMonth() + 1).padStart(2, '0'); + var year = now.getFullYear(); + var hour = String(now.getHours()).padStart(2, '0'); + var minutes = String(now.getMinutes()).padStart(2, '0'); + + var dateTimeSessionExpired = day + '/' + month + '/' + year + ' ' + hour + ':' + minutes; + + document.body.insertAdjacentHTML('afterbegin', '

{{ 'SessionExpiredAt' | get_lang | escape('js')}} ' + dateTimeSessionExpired + '.

'); + } + }) + .catch((error) => { + console.error('Error:', error); + }); + }, 1000); + } + sessionClosing = true; + } + } + else { + clearInterval(sessionCounterInterval); + + var counterOverlay = document.getElementById('session-count-overlay'); + if (counterOverlay) { + counterOverlay.remove(); + } + + var now = new Date(); + var day = String(now.getDate()).padStart(2, '0'); + var month = String(now.getMonth() + 1).padStart(2, '0'); + var year = now.getFullYear(); + var hour = String(now.getHours()).padStart(2, '0'); + var minutes = String(now.getMinutes()).padStart(2, '0'); + + var dateTimeSessionExpired = day + '/' + month + '/' + year + ' ' + hour + ':' + minutes; + + document.body.insertAdjacentHTML('afterbegin', '

{{ 'SessionExpiredAt' | get_lang | escape('js')}} ' + dateTimeSessionExpired + '.

'); + } +}