Skip to content

Commit 14e699f

Browse files
juancp-contidosdixitaisywarnier
authored andcommitted
Authentication: User session: Add expiration notification system - refs #4776
1 parent 0f96eac commit 14e699f

File tree

6 files changed

+287
-0
lines changed

6 files changed

+287
-0
lines changed

main/inc/ajax/session_clock.ajax.php

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
require_once __DIR__.'/../../../vendor/autoload.php';
4+
require_once __DIR__.'/../../../app/AppKernel.php';
5+
6+
$kernel = new AppKernel('', '');
7+
8+
// Check for 'action' parameter in the GET request
9+
if (isset($_GET['action'])) {
10+
$action = $_GET['action'];
11+
12+
if ($action == 'time') {
13+
// Load the Chamilo configuration
14+
$alreadyInstalled = false;
15+
if (file_exists($kernel->getConfigurationFile())) {
16+
require_once $kernel->getConfigurationFile();
17+
$alreadyInstalled = true;
18+
}
19+
20+
// Load the API library BEFORE loading the Chamilo configuration
21+
require_once $_configuration['root_sys'].'main/inc/lib/api.lib.php';
22+
23+
if (api_get_configuration_value('session_lifetime_controller')) {
24+
// Get the session
25+
session_name('ch_sid');
26+
session_start();
27+
28+
$session = new ChamiloSession();
29+
30+
$endTime = 0;
31+
$isExpired = false;
32+
$timeLeft = -1;
33+
34+
$currentTime = time();
35+
36+
// Existing code for time action
37+
if ($alreadyInstalled && api_get_user_id()) {
38+
$endTime = $session->end_time();
39+
$isExpired = $session->is_expired();
40+
} else {
41+
// Chamilo not installed or user not logged in
42+
$endTime = $currentTime + 315360000; // This sets a default end time far in the future
43+
$isExpired = false;
44+
}
45+
46+
$timeLeft = $endTime - $currentTime;
47+
}
48+
else {
49+
$endTime = 999999;
50+
$isExpired = false;
51+
$timeLeft = 999999;
52+
}
53+
54+
if ($endTime > 0) {
55+
echo json_encode(['sessionEndDate' => $endTime, 'sessionTimeLeft' => $timeLeft, 'sessionExpired' => $isExpired]);
56+
} else {
57+
http_response_code(500);
58+
echo json_encode(['error' => 'Error retrieving data from the current session']);
59+
}
60+
} elseif ($action == 'logout') {
61+
require_once __DIR__.'/../../../main/inc/global-min.inc.php';
62+
63+
$userId = api_get_user_id();
64+
online_logout($userId, false);
65+
echo json_encode(['message' => 'Logged out successfully']);
66+
} else {
67+
// Handle unexpected action value
68+
http_response_code(400);
69+
echo json_encode(['error' => 'Invalid action parameter']);
70+
}
71+
} else {
72+
// No action provided
73+
http_response_code(400);
74+
echo json_encode(['error' => 'No action parameter provided']);
75+
}

main/install/configuration.dist.php

+3
Original file line numberDiff line numberDiff line change
@@ -2580,3 +2580,6 @@
25802580
// is '$1EdTech-CC-FILEBASE$' (the latest), but previous versions of the standard
25812581
// recommended '$IMS-CC-FILEBASE$', so you might want to use that for greater compatibility.
25822582
//$_configuration['commoncartridge_path_token'] = '$IMS-CC-FILEBASE$';
2583+
2584+
// Set the following parameter to true to enable a session lifetime controller that notifies users that their session is about to expire
2585+
//$_configuration['session_lifetime_controller'] = false;

main/lang/english/trad4all.inc.php

+4
Original file line numberDiff line numberDiff line change
@@ -9073,4 +9073,8 @@
90739073
$CreateExport = "Create export file";
90749074
$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.";
90759075
$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.";
9076+
$SessionExpiredAt = "Session expired at";
9077+
$DueToInactivityTheSessionIsGoingToClose = "Due to your inactivity, this session is going to close in";
9078+
$KeepGoing = "Keep going";
9079+
$SessionIsClosing = "Your session is closing";
90769080
?>

main/lang/french/trad4all.inc.php

+4
Original file line numberDiff line numberDiff line change
@@ -9008,4 +9008,8 @@
90089008
$MoodleExportAdminIDComment = "Moodle requiert une identification de l'utilisateur pour la stocker au sein de fichiers XML qui font partie du format .mbz.
90099009
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.";
90109010
$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.";
9011+
$SessionExpiredAt = "Session expirée à";
9012+
$DueToInactivityTheSessionIsGoingToClose = "Dû à votre inactivité, la session se fermera dans";
9013+
$KeepGoing = "Rester connecté";
9014+
$SessionIsClosing = "Votre session est en cours de fermeture";
90119015
?>

main/lang/spanish/trad4all.inc.php

+4
Original file line numberDiff line numberDiff line change
@@ -9098,4 +9098,8 @@
90989098
$CreateExport = "Crear archivo de exporte";
90999099
$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.";
91009100
$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.";
9101+
$SessionExpiredAt = "Sesión expirada el";
9102+
$DueToInactivityTheSessionIsGoingToClose = "Debido a su inactividad, esta sesión se cerrará en";
9103+
$KeepGoing = "Seguir conectado";
9104+
$SessionIsClosing = "Su sesión se está cerrando";
91019105
?>

main/template/default/layout/main.js.tpl

+197
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ var offline_button = '<img src="' + _p.web_img + 'statusoffline.png">';
55
var connect_lang = '{{ "ChatConnected"|get_lang | escape('js')}}';
66
var disconnect_lang = '{{ "ChatDisconnected"|get_lang | escape('js')}}';
77
var chatLang = '{{ "GlobalChat"|get_lang | escape('js')}}';
8+
var sessionRemainingSeconds = 0;
9+
var sessionCounterInterval;
10+
var sessionClosing = false;
811

912
{% if 'hide_chat_video'|api_get_configuration_value %}
1013
var hide_chat_video = true;
@@ -443,6 +446,10 @@ $(function() {
443446
});
444447
});
445448
{% endif %}
449+
450+
if (window.self === window.top) {
451+
checkSessionTime();
452+
}
446453
});
447454

448455
$(window).resize(function() {
@@ -731,3 +738,193 @@ function copyTextToClipBoard(elementId)
731738
/* Copy the text inside the text field */
732739
document.execCommand("copy");
733740
}
741+
742+
function checkSessionTime()
743+
{
744+
fetch('/main/inc/ajax/session_clock.ajax.php?action=time')
745+
.then(response => {
746+
if (!response.ok) {
747+
throw new Error('Server error: ' + response.statusText);
748+
}
749+
return response.json();
750+
})
751+
.then(data => {
752+
if (data.sessionTimeLeft <= 0) {
753+
if (!document.getElementById('session-checker-overlay')) {
754+
clearInterval(sessionCounterInterval);
755+
756+
var counterOverlay = document.getElementById('session-count-overlay');
757+
if (counterOverlay) {
758+
counterOverlay.remove();
759+
}
760+
761+
var now = new Date();
762+
var day = String(now.getDate()).padStart(2, '0');
763+
var month = String(now.getMonth() + 1).padStart(2, '0');
764+
var year = now.getFullYear();
765+
var hour = String(now.getHours()).padStart(2, '0');
766+
var minutes = String(now.getMinutes()).padStart(2, '0');
767+
768+
var dateTimeSessionExpired = day + '/' + month + '/' + year + ' ' + hour + ':' + minutes;
769+
770+
document.body.insertAdjacentHTML('afterbegin', '<div id="session-checker-overlay" style="position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,1);display:flex;justify-content:center;align-items:center;z-index:1000;"><div id="session-checker-modal" style="background:white;padding:20px;border-radius:5px;box-shadow:0010pxrgba(0,0,0,0.5);width:35%;text-align:center;"><p style="margin-bottom:20px;">{{ 'SessionExpiredAt' | get_lang | escape('js')}} ' + dateTimeSessionExpired + '.</p><button class="btn btn-primary" onclick="window.location.pathname = \'/\';">OK</button></div></div>');
771+
}
772+
} else if (data.sessionTimeLeft <= 110) {
773+
sessionRemainingSeconds = data.sessionTimeLeft - 5;
774+
775+
if (sessionRemainingSeconds < 0) {
776+
sessionRemainingSeconds = 0;
777+
}
778+
779+
if (!document.getElementById('session-count-overlay')) {
780+
document.body.insertAdjacentHTML('afterbegin', '<div id="session-count-overlay" style="position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);display:flex;justify-content:center;align-items:center;z-index:1000;"><div id="session-checker-modal" style="background:white;padding:20px;border-radius:5px;box-shadow:0010pxrgba(0,0,0,0.5);width:35%;text-align:center;"><p id="session-counter" style="margin-bottom:20px;">{{ 'DueToInactivityTheSessionIsGoingToClose' | get_lang | escape('js')}} ' + sessionRemainingSeconds + ' {{ 'Seconds' | get_lang | escape('js')}}</p><button class="btn btn-primary" id="btn-session-extend" onclick="extendSession();">{{ 'KeepGoing' | get_lang | escape('js')}}</button></div></div>');
781+
782+
sessionCounterInterval = setInterval(updateSessionTimeCounter, 1000);
783+
}
784+
setTimeout(checkSessionTime, 60000);
785+
} else {
786+
clearInterval(sessionCounterInterval);
787+
788+
var counterOverlay = document.getElementById('session-count-overlay');
789+
if (counterOverlay) {
790+
counterOverlay.remove();
791+
}
792+
793+
setTimeout(checkSessionTime, 60000);
794+
}
795+
})
796+
.catch(error => console.error('Error:', error));
797+
}
798+
799+
function extendSession() {
800+
fetch('/main/inc/ajax/online.ajax.php')
801+
.then(response => {
802+
if (!response.ok) {
803+
throw new Error('Server error: ' + response.statusText);
804+
}
805+
return response;
806+
})
807+
.then(data => {
808+
console.log('Session extended');
809+
810+
clearInterval(sessionCounterInterval);
811+
812+
var counterOverlay = document.getElementById('session-count-overlay');
813+
if (counterOverlay) {
814+
counterOverlay.remove();
815+
}
816+
})
817+
.catch(error => console.error('Error:', error));
818+
}
819+
820+
function updateSessionTimeCounter() {
821+
var sessionCounter = document.getElementById('session-counter');
822+
if (sessionRemainingSeconds > 3) {
823+
sessionCounter.innerHTML = '{{ 'DueToInactivityTheSessionIsGoingToClose' | get_lang | escape('js')}} ' + sessionRemainingSeconds + ' {{ 'Seconds' | get_lang | escape('js')}}';
824+
sessionRemainingSeconds--;
825+
} else if (sessionRemainingSeconds <= 3 && sessionRemainingSeconds > 1) {
826+
var currentUrl = window.location.href;
827+
if (currentUrl.includes('lp_controller.php') && currentUrl.includes('lp_id=') && currentUrl.includes('action=view')) {
828+
829+
if (!sessionClosing) {
830+
var btnSessionExtend = document.getElementById('btn-session-extend');
831+
if (btnSessionExtend) {
832+
btnSessionExtend.remove();
833+
}
834+
835+
document.getElementById('session-counter').innerHTML = '{{ 'SessionIsClosing' | get_lang | escape('js')}}';
836+
837+
setTimeout(function() {
838+
fetch('/main/inc/ajax/session_clock.ajax.php?action=logout')
839+
.then(response => response.json())
840+
.then(data => {
841+
if (!document.getElementById('session-checker-overlay')) {
842+
clearInterval(sessionCounterInterval);
843+
844+
var counterOverlay = document.getElementById('session-count-overlay');
845+
if (counterOverlay) {
846+
counterOverlay.remove();
847+
}
848+
849+
var now = new Date();
850+
var day = String(now.getDate()).padStart(2, '0');
851+
var month = String(now.getMonth() + 1).padStart(2, '0');
852+
var year = now.getFullYear();
853+
var hour = String(now.getHours()).padStart(2, '0');
854+
var minutes = String(now.getMinutes()).padStart(2, '0');
855+
856+
var dateTimeSessionExpired = day + '/' + month + '/' + year + ' ' + hour + ':' + minutes;
857+
858+
document.body.insertAdjacentHTML('afterbegin', '<div id="session-checker-overlay" style="position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,1);display:flex;justify-content:center;align-items:center;z-index:1000;"><div id="session-checker-modal" style="background:white;padding:20px;border-radius:5px;box-shadow:0010pxrgba(0,0,0,0.5);width:35%;text-align:center;"><p style="margin-bottom:20px;">{{ 'SessionExpiredAt' | get_lang | escape('js')}} ' + dateTimeSessionExpired + '.</p><button class="btn btn-primary" onclick="window.location.pathname = \'/\';">OK</button></div></div>');
859+
}
860+
})
861+
.catch((error) => {
862+
console.error('Error:', error);
863+
});
864+
}, 1000);
865+
866+
lastCall();
867+
}
868+
sessionClosing = true;
869+
}
870+
else {
871+
if (!sessionClosing) {
872+
var btnSessionExtend = document.getElementById('btn-session-extend');
873+
if (btnSessionExtend) {
874+
btnSessionExtend.remove();
875+
}
876+
877+
document.getElementById('session-counter').innerHTML = '{{ 'SessionIsClosing' | get_lang | escape('js')}}';
878+
879+
setTimeout(function() {
880+
fetch('/main/inc/ajax/session_clock.ajax.php?action=logout')
881+
.then(response => response.json())
882+
.then(data => {
883+
if (!document.getElementById('session-checker-overlay')) {
884+
clearInterval(sessionCounterInterval);
885+
886+
var counterOverlay = document.getElementById('session-count-overlay');
887+
if (counterOverlay) {
888+
counterOverlay.remove();
889+
}
890+
891+
var now = new Date();
892+
var day = String(now.getDate()).padStart(2, '0');
893+
var month = String(now.getMonth() + 1).padStart(2, '0');
894+
var year = now.getFullYear();
895+
var hour = String(now.getHours()).padStart(2, '0');
896+
var minutes = String(now.getMinutes()).padStart(2, '0');
897+
898+
var dateTimeSessionExpired = day + '/' + month + '/' + year + ' ' + hour + ':' + minutes;
899+
900+
document.body.insertAdjacentHTML('afterbegin', '<div id="session-checker-overlay" style="position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,1);display:flex;justify-content:center;align-items:center;z-index:1000;"><div id="session-checker-modal" style="background:white;padding:20px;border-radius:5px;box-shadow:0010pxrgba(0,0,0,0.5);width:35%;text-align:center;"><p style="margin-bottom:20px;">{{ 'SessionExpiredAt' | get_lang | escape('js')}} ' + dateTimeSessionExpired + '.</p><button class="btn btn-primary" onclick="window.location.pathname = \'/\';">OK</button></div></div>');
901+
}
902+
})
903+
.catch((error) => {
904+
console.error('Error:', error);
905+
});
906+
}, 1000);
907+
}
908+
sessionClosing = true;
909+
}
910+
}
911+
else {
912+
clearInterval(sessionCounterInterval);
913+
914+
var counterOverlay = document.getElementById('session-count-overlay');
915+
if (counterOverlay) {
916+
counterOverlay.remove();
917+
}
918+
919+
var now = new Date();
920+
var day = String(now.getDate()).padStart(2, '0');
921+
var month = String(now.getMonth() + 1).padStart(2, '0');
922+
var year = now.getFullYear();
923+
var hour = String(now.getHours()).padStart(2, '0');
924+
var minutes = String(now.getMinutes()).padStart(2, '0');
925+
926+
var dateTimeSessionExpired = day + '/' + month + '/' + year + ' ' + hour + ':' + minutes;
927+
928+
document.body.insertAdjacentHTML('afterbegin', '<div id="session-checker-overlay" style="position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,1);display:flex;justify-content:center;align-items:center;z-index:1000;"><div id="session-checker-modal" style="background:white;padding:20px;border-radius:5px;box-shadow:0010pxrgba(0,0,0,0.5);width:35%;text-align:center;"><p style="margin-bottom:20px;">{{ 'SessionExpiredAt' | get_lang | escape('js')}} ' + dateTimeSessionExpired + '.</p><button class="btn btn-primary" onclick="window.location.pathname = \'/\';">OK</button></div></div>');
929+
}
930+
}

0 commit comments

Comments
 (0)