Commit ec0d1b3787cb74a93ddfac948839da26f09d0936

Authored by Etienne Pallier
1 parent f226ca53
Exists in master and in 1 other branch dev

Grosse amélioration des stats de connexion (pages /stats)

v4.106.3-3.7.9
CHANGES.txt
... ... @@ -134,12 +134,12 @@ Outre ces changements, voici d'autres changements importants :
134 134 ======= CHANGES =======
135 135  
136 136 -------
137   -22/10/2020 v4.106.1-3.7.9
138   - - (b) Bugfix vues pour les stats
  137 +22/10/2020 v4.106.3-3.7.9
  138 + - (e) Grosse amélioration des stats de connexion (pages /stats)
139 139  
140 140 -------
141 141 21/10/2020 v4.106.0-3.7.9
142   - - (i) Ajout d'une nouvelle table "stats" qui maintient pour chaque utilisateur son nb de connexions et sa durée de connexion par année
  142 + - (i) Ajout d'une nouvelle table "stats" (/stats) qui maintient pour chaque utilisateur son nb de connexions et sa durée de connexion par année
143 143 - (i) Correction d'un TRÈS VIEUX problème qui faisait que lorsqu'on POST des données (bouton Submit)
144 144 puis qu'on revient en arrière, on avait une page blanche avec message erreur "Le document a expiré..."
145 145 C'est enfin réglé !
... ...
README.md
... ... @@ -43,7 +43,7 @@ Logiciel testé et validé sur les configurations suivantes :
43 43 --------------------------------------------------------------------------------------------
44 44  
45 45 Date: 22/10/2020
46   -Version: 4.106.1-3.7.9
  46 +Version: 4.106.3-3.7.9
47 47  
48 48  
49 49 HISTORIQUE DES CHANGEMENTS DE VERSION : voir le fichier CHANGES.txt (ou la page web /pages/changes)
... ...
config/bootstrap.php
1   - <?php
  1 +<?php
  2 +
  3 +//header('Cache-Control: private_no_expire, must-revalidate');
  4 +
2 5 /**
3 6 * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
4 7 * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
... ... @@ -277,3 +280,7 @@ Type::build(&#39;datetime&#39;)
277 280 //Cake\Core\Plugin::load('BootstrapUI');
278 281 //Plugin::load('BootstrapUI');
279 282 Plugin::load('BootstrapUI', ['autoload' => true]);
  283 +
  284 +// (EP20201022) Pour gérer le timeout auto
  285 +DispatcherFactory::add('SessionTimeout');
  286 +
... ...
database/update/script_sql/db-update-2020-10-22.sql 0 → 100755
... ... @@ -0,0 +1,8 @@
  1 +use database;
  2 +
  3 +--
  4 +-- Ajout du champ stats.last_connex_dur
  5 +--
  6 +ALTER TABLE stats
  7 + ADD last_connex_dur INT NOT NULL DEFAULT 0 AFTER last_login_time COMMENT 'Temps de connexion de la derniere session',
  8 + CHANGE connex_dur connex_dur_tot INT(11) NOT NULL DEFAULT 0 COMMENT "Total temps connexion cumulé sur l'année (sec)";
... ...
src/Controller/UsersController.php
... ... @@ -208,7 +208,7 @@ class UsersController extends AppController
208 208 ]
209 209 */
210 210  
211   - $this->_updateStatsForUserLogin($user);
  211 + $this->_updateStatsForCurrentUserOnLogin();
212 212 //exit;
213 213  
214 214 // On va maintenant à la page qui etait demandée
... ... @@ -220,45 +220,63 @@ class UsersController extends AppController
220 220 }
221 221 }
222 222  
223   - public function logout()
224   - {
  223 + public function logout() {
  224 + //debug("LOGOUT !");
225 225 //debug($this->u);
226   - $this->_updateStatsForUserLogout($this->u);
227   - //exit;
  226 + $this->_updateStatsForCurrentUserOnLogout();
228 227 //$this->Flash->success('You are now logged out.');
229 228 return $this->redirect($this->LdapAuth->logout());
230 229 // Puis ça va sur /users/login
231 230 }
232 231  
  232 +
  233 + /*
  234 + * Renvoie les infos de l'utilisateur qui sont dans la session
  235 + *
  236 + * Voici ce que contient $_SESSION['Auth']['User'] par exemple :
  237 + [
  238 + 'sn' => [
  239 + (int) 0 => 'Pallier'
  240 + ],
  241 + 'mail' => [
  242 + (int) 0 => 'Etienne.Pallier@irap.omp.eu'
  243 + ],
  244 + 'givenname' => [
  245 + (int) 0 => 'Etienne'
  246 + ],
  247 + 'uid' => [
  248 + (int) 0 => 'epallier'
  249 + ],
  250 + 'userpassword' => [
  251 + (int) 0 => 'mot-de-passe-crypté'
  252 + ]
  253 + ]
  254 + */
  255 + private function _getCurrentUserInfosFromSession() {
  256 + // Current user infos
  257 + return isset($_SESSION['Auth']['User']) ? $_SESSION['Auth']['User'] : [];
  258 + }
  259 + private function _getCurrentUserFullNameFromSession($user_infos = null) {
  260 + if (!$user_infos) $user_infos = $this->_getCurrentUserInfosFromSession();
  261 + // "Pallier Etienne"
  262 + return $user_infos ? $user_infos['sn'][0].' '.$user_infos['givenname'][0] : 'Name Firstname';
  263 + }
  264 + private function _getCurrentUserEntityFromSession($session_user = null) {
  265 + $user_fullname = $this->_getCurrentUserFullNameFromSession($session_user);
  266 + //debug($user_fullname);
  267 + // "Pallier Etienne"
  268 + return $this->Users->find()->where(['nom'=>$user_fullname])->first();
  269 + }
  270 +
  271 +
233 272 /*
234 273 * Mise à jour des stats pour ce user (et pour l'année courante) au moment du login
235 274 * => on enregistre son heure de connexion
236 275 * => on incrémente le nombre de connexions
237   - *
238   - * Voici ce que contient $user_infos par exemple :
239   - [
240   - 'sn' => [
241   - (int) 0 => 'Pallier'
242   - ],
243   - 'mail' => [
244   - (int) 0 => 'Etienne.Pallier@irap.omp.eu'
245   - ],
246   - 'givenname' => [
247   - (int) 0 => 'Etienne'
248   - ],
249   - 'uid' => [
250   - (int) 0 => 'epallier'
251   - ],
252   - 'userpassword' => [
253   - (int) 0 => 'mot-de-passe-crypté'
254   - ]
255   - ]
256 276 */
257   - private function _updateStatsForUserLogin($user_infos) {
  277 + private function _updateStatsForCurrentUserOnLogin() {
258 278  
259   - // "Pallier Etienne"
260   - $user_fullname = $user_infos['sn'][0].' '.$user_infos['givenname'][0];
261   - $user = $this->Users->find()->where(['nom'=>$user_fullname])->first();
  279 + $user_id = $this->_getCurrentUserEntityFromSession()->id;
262 280 //debug($user);
263 281  
264 282 $now = new FrozenTime();
... ... @@ -266,8 +284,9 @@ class UsersController extends AppController
266 284 $year = $now->format('yy');
267 285 //debug($year);
268 286  
  287 + // On recup la Stat du user courant
269 288 $table_stats = $this->Users->Stats;
270   - $stat_id = [$year,$user->id];
  289 + $stat_id = [$year,$user_id];
271 290 $current_user_stat = null;
272 291 try {
273 292 // Si un enregistrement existe pour ce user (et cette année) => on le récupère
... ... @@ -291,20 +310,32 @@ class UsersController extends AppController
291 310 //$stat_entity = $table_stats->newEntity($stat);
292 311 $current_user_stat = $table_stats->newEntity();
293 312 $current_user_stat->year = $year;
294   - $current_user_stat->user_id = $user->id;
  313 + $current_user_stat->user_id = $user_id;
295 314 }
296 315 $last_login_time = $current_user_stat->last_login_time;
297 316 $last_logout_time = $current_user_stat->last_logout_time;
  317 +
298 318 /*
299 319 * Check le cas où le logout n'a pas été enregistré
300 320 * (le user n'a pas fait logout, ça s'est fait tout seul par timeout)
301   - * => Dans ce cas, on ajoute un temps de connexion FORFAITAIRE de 10 minutes à la durée totale
  321 + * => Dans ce cas, on ajoute à la durée totale le dernier temps de connexion last_connex_dur
302 322 */
303   - if ($last_logout_time < $last_login_time)
304   - $current_user_stat->connex_dur += 10*60;
305   - // On met à jour le champ last_login_time
  323 + if ($last_logout_time < $last_login_time) {
  324 + $last_connex_dur = $current_user_stat->last_connex_dur;
  325 + /*
  326 + * (Optimisation): Si la dernière session a été très courte (<10s), on ne le comptabilise pas
  327 + * => on décrémente le nb de connexions (passées)
  328 + */
  329 + if ($current_user_stat->connex_nb>0 && $last_connex_dur<10)
  330 + $current_user_stat->connex_nb--;
  331 + else
  332 + $current_user_stat->connex_dur_tot += $last_connex_dur;
  333 + }
  334 +
  335 + // On met à jour le champs last_login_time, RAZ last_connex_dur, et incrémente le nb de connexions
306 336 $current_user_stat->last_login_time = $now;
307   - // et 1 connexion de plus, 1 !
  337 + $current_user_stat->last_connex_dur = 0;
  338 + // et 1 NOUVELLE connexion de plus, 1 !
308 339 $current_user_stat->connex_nb++;
309 340 //debug($current_user_stat);
310 341 // On sauvegarde en table
... ... @@ -324,15 +355,58 @@ class UsersController extends AppController
324 355 debug("yes");
325 356 }
326 357 */
327   - } // _updateStatsForUserLogin()
  358 + } // _updateStatsForCurrentUserOnLogin()
328 359  
  360 + /*
  361 + * Mise à jour des stats pour ce user (et pour l'année courante) durant la session, à chaque action
  362 + * => on met à jour son temps de connexion
  363 + */
  364 + public function updateStatsForCurrentUserDuringSession($session_user = null) {
  365 +
  366 + //debug("ici");
  367 + //exit;
  368 + // Current user
  369 + //$user_id = $this->u->id;
  370 + //debug($session_user);
  371 + $user_id = $this->_getCurrentUserEntityFromSession($session_user)->id;
  372 + //debug($user_id);
  373 +
  374 + // On récupère la Stat de ce user
  375 + $now = new FrozenTime();
  376 + //debug($last_login_time);
  377 + $year = $now->format('yy');
  378 + //debug($year);
  379 + $table_stats = $this->Users->Stats;
  380 + $stat_id = [$year,$user_id];
  381 + $current_user_stat = $table_stats->get($stat_id);
  382 +
  383 + // On met à jour le temps de connexion
  384 + $connex_duration = $now->diff($current_user_stat->last_login_time);
  385 + //debug($connex_duration);
  386 + //exit;
  387 + $current_user_stat->last_connex_dur = $connex_duration->h*3600 + $connex_duration->i*60 + $connex_duration->s;
  388 + //debug($current_user_stat->last_connex_dur);
  389 +
  390 + // On sauvegarde en table
  391 + if (! $table_stats->save($current_user_stat)) {
  392 + echo "Impossible de sauvegarder une stat durant la session !";
  393 + debug($current_user_stat->getErrors());
  394 + debug($current_user_stat);
  395 + exit;
  396 + }
  397 +
  398 + } // updateStatsForCurrentUserDuringSession()
329 399  
330 400 /*
331 401 * Mise à jour des stats pour ce user (et pour l'année courante) au moment du logout
332 402 * => on enregistre son heure de dé-connexion
333   - * => on calcule son temps de connexion et on l'ajoute au champ qui totalise le temps de connexion : connex_dur
  403 + * => on calcule son temps de connexion et on l'ajoute au champ qui totalise le temps de connexion : connex_dur_tot
334 404 */
335   - private function _updateStatsForUserLogout(Entity $user) {
  405 + private function _updateStatsForCurrentUserOnLogout() {
  406 +
  407 + // Current user
  408 + $user_id = $this->u->id;
  409 +
336 410 $last_logout_time = new FrozenTime();
337 411 //debug($last_login_time);
338 412 $year = $last_logout_time->format('yy');
... ... @@ -342,7 +416,7 @@ class UsersController extends AppController
342 416 // Normalement, elle doit exister puisque il y a déjà eu login !
343 417 // TODO: Cas particulier où le login se fait le 31 décembre, et le logout l'année suivante => BUG !!!
344 418 $table_stats = $this->Users->Stats;
345   - $stat_id = [$year,$user->id];
  419 + $stat_id = [$year,$user_id];
346 420 $current_user_stat = $table_stats->get($stat_id);
347 421 $last_login_time = $current_user_stat->last_login_time;
348 422  
... ... @@ -355,12 +429,12 @@ class UsersController extends AppController
355 429 $connex_duration = $connex_duration->h*3600 + $connex_duration->i*60 + $connex_duration->s;
356 430 //debug($last_login_time);
357 431 //debug($last_logout_time);
358   - //debug($connex_duration);
  432 + //debug($connex_dur_totation);
359 433 // - on cumule ce temps au temps total
360   - //debug($current_user_stat->connex_dur);
361   - //debug($connex_duration);
362   - $current_user_stat->connex_dur += $connex_duration;
363   - //debug($current_user_stat->connex_dur);
  434 + //debug($current_user_stat->connex_dur_tot);
  435 + //debug($connex_dur_totation);
  436 + $current_user_stat->connex_dur_tot += $connex_duration;
  437 + //debug($current_user_stat->connex_dur_tot);
364 438  
365 439 // On sauvegarde en table
366 440 if (! $table_stats->save($current_user_stat)) {
... ... @@ -369,7 +443,7 @@ class UsersController extends AppController
369 443 debug($current_user_stat);
370 444 exit;
371 445 }
372   - } // _updateStatsForUserLogout()
  446 + } // _updateStatsForCurrentUserOnLogout()
373 447  
374 448  
375 449  
... ...
src/Routing/Filter/SessionTimeoutFilter.php 0 → 100644
... ... @@ -0,0 +1,77 @@
  1 +<?php
  2 +
  3 +/*
  4 + * (EP20201022)
  5 + *
  6 + * Pour pouvoir mettre à jour les stats de connexion
  7 + * au moment d'un timeout automatique (qui fait un logout auto sans appeler Users.logout())
  8 + *
  9 + * https://stackoverflow.com/questions/32298817/how-to-prevent-cakephp-3-0-from-extending-session-timeout-with-ajax-requests
  10 + *
  11 + */
  12 +
  13 +namespace App\Routing\Filter;
  14 +
  15 +use Cake\Core\Configure;
  16 +use Cake\Event\Event;
  17 +use Cake\Routing\DispatcherFilter;
  18 +use App\Controller\UsersController;
  19 +
  20 +class SessionTimeoutFilter extends DispatcherFilter
  21 +{
  22 +
  23 + public function beforeDispatch(Event $event) {
  24 +
  25 + //debug("iciii1");
  26 + // Si déjà TIMEOUT => on ne peut plus rien faire, donc on quitte
  27 + //debug($_SESSION);
  28 + $request = $event->data['request'];
  29 + $session = $request->session();
  30 + $session_user = $session->read('Auth.User');
  31 + if (! $session_user) return;
  32 + // MARCHE PAS, WHY ???????
  33 + //if (! isset($_SESSION['Auth']['User']) ) return;
  34 + //debug("iciii2");
  35 +
  36 + // Sinon, on met à jour les stats de connexion
  37 + //(new UsersController())->updateStatsForCurrentUserDuringSession();
  38 + (new UsersController())->updateStatsForCurrentUserDuringSession($session_user);
  39 + //exit;
  40 +
  41 +
  42 + /*
  43 + //debug($event->data['request']);
  44 + //debug($event);
  45 +
  46 + /S @var $request \Cake\Network\Request S/
  47 + $request = $event->data['request'];
  48 + $session = $request->session();
  49 + $lastAccess = $session->read('SessionTimeoutFilter.lastAccess'); // en sec
  50 + //debug($lastAccess);
  51 +
  52 + if ($lastAccess !== null) {
  53 + // On calcule le temps d'inactivité de l'utilisateur
  54 + $inactivity_duration = time() - $lastAccess;
  55 + //debug($inactivity_duration);
  56 + //debug(Configure::read('Session.timeout')*60);
  57 + //debug("avant");
  58 + /S
  59 + $sess = $_SESSION['Auth']['User'];
  60 + $user_fullname = $sess['sn'][0].' '.$sess['givenname'][0];
  61 + //debug($user_fullname);
  62 + S/
  63 + //debug("after");
  64 + // Si ce temps d'inactivité est supérieur au timeout, on détruit la session,
  65 + if ($inactivity_duration > Configure::read('Session.timeout') * 60) {
  66 + // Sans doute inutile vu que logout() l'a déjà fait normalement...
  67 + $request->session()->destroy();
  68 + }
  69 + }
  70 +
  71 + // On écrit l'heure actuelle dans la variable de session lastAccess
  72 + if (! $request->is('ajax') )
  73 + $session->write('SessionTimeoutFilter.lastAccess', time()); // en sec
  74 + */
  75 + }
  76 +
  77 +}
0 78 \ No newline at end of file
... ...
src/Template/Stats/index.ctp
... ... @@ -3,6 +3,22 @@
3 3 * @var \App\View\AppView $this
4 4 * @var \App\Model\Entity\Stat[]|\Cake\Collection\CollectionInterface $stats
5 5 */
  6 +
  7 +// Variables passées par le controleur
  8 +$stats = $stats;
  9 +
  10 +
  11 +function getHourMnSecForDuration($duration_sec) {
  12 + //debug($duration_sec);
  13 + $h = intdiv($duration_sec,3600);
  14 + $m = intdiv($duration_sec-$h*3600 , 60);
  15 + $s = $duration_sec-$h*3600-$m*60;
  16 + return [$h,$m,$s];
  17 +}
  18 +
  19 +
  20 +
  21 +
6 22 ?>
7 23 <nav class="large-3 medium-4 columns" id="actions-sidebar">
8 24 <ul class="side-nav">
... ... @@ -20,9 +36,11 @@
20 36 <th scope="col"><?= $this->Paginator->sort('year') ?></th>
21 37 <th scope="col"><?= $this->Paginator->sort('user_id') ?></th>
22 38 <th scope="col"><?= $this->Paginator->sort('last_login_time') ?></th>
  39 + <th scope="col"><?= $this->Paginator->sort('last_connex_dur', 'Durée connexion (mn)') ?></th>
23 40 <th scope="col"><?= $this->Paginator->sort('last_logout_time') ?></th>
24   - <th scope="col"><?= $this->Paginator->sort('connex_nb') ?></th>
25   - <th scope="col"><?= $this->Paginator->sort('connex_dur') ?></th>
  41 + <th scope="col"><?= $this->Paginator->sort('connex_dur_tot', "Durée connexion cumulée (sur année) (mn)") ?></th>
  42 + <th scope="col"><?= $this->Paginator->sort('connex_nb', "Nb connexions (sur année)") ?></th>
  43 + <th scope="col"><?= "Durée connexion moyenne (sur année)" ?></th>
26 44 <th scope="col" class="actions"><?= __('Actions') ?></th>
27 45 </tr>
28 46 </thead>
... ... @@ -32,9 +50,33 @@
32 50 <td><?= h($stat->year) ?></td>
33 51 <td><?= $stat->has('user') ? $this->Html->link($stat->user->nom, ['controller' => 'Users', 'action' => 'view', $stat->user->id]) : '' ?></td>
34 52 <td><?= h($stat->last_login_time) ?></td>
  53 +
  54 + <?php
  55 + list($h,$m,$s) = getHourMnSecForDuration($stat->last_connex_dur);
  56 + ?>
  57 + <td><?="$h h $m mn $s sec"?></td>
  58 + <!--
  59 + <td><= h($stat->last_connex_dur) ?></td>
  60 + -->
  61 +
35 62 <td><?= h($stat->last_logout_time) ?></td>
  63 +
  64 + <?php
  65 + list($h,$m,$s) = getHourMnSecForDuration($stat->connex_dur_tot);
  66 + ?>
  67 + <td><?="$h h $m mn $s sec"?></td>
  68 + <!--
  69 + <td><= $this->Number->format($stat->connex_dur_tot) ?></td>
  70 + -->
  71 +
36 72 <td><?= $this->Number->format($stat->connex_nb) ?></td>
37   - <td><?= $this->Number->format($stat->connex_dur) ?></td>
  73 +
  74 + <?php
  75 + $connex_dur_avg = intdiv($stat->connex_dur_tot , $stat->connex_nb);
  76 + list($h,$m,$s) = getHourMnSecForDuration($connex_dur_avg);
  77 + ?>
  78 + <td><?="$h h $m mn $s sec"?></td>
  79 +
38 80 <td class="actions">
39 81 <?= $this->Html->link(__('View'), ['action' => 'view', $stat->year,$stat->user_id]) ?>
40 82 <?= $this->Html->link(__('Edit'), ['action' => 'edit', $stat->year,$stat->user_id]) ?>
... ...
tests/Fixture/UsersFixture.php
... ... @@ -58,7 +58,8 @@ class UsersFixture extends TestFixture
58 58 [
59 59 //'id' => 1,
60 60 // Nom Prénom
61   - 'nom' => 'user1 SUPER',
  61 + /////'nom' => 'user1 SUPER',
  62 + 'nom' => 'SUPER user1',
62 63 //'nom' => 'test1 test2',
63 64 //'username' => 'testa',
64 65 'username' => 'user1_SUPER',
... ...