Commit 409758d9e05fdc5940758d1eef0841fda24a6585

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

Ajout d'une nouvelle table "stats"

=> maintient pour chaque utilisateur son nb de connexions et sa durée de
connexion par année

v4.106.0-3.7.9
CHANGES.txt
... ... @@ -134,7 +134,8 @@ Outre ces changements, voici d'autres changements importants :
134 134 ======= CHANGES =======
135 135  
136 136 -------
137   -21/10/2020 v4.105.35-3.7.9
  137 +21/10/2020 v4.106.0-3.7.9
  138 + - (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
138 139 - (i) Correction d'un TRÈS VIEUX problème qui faisait que lorsqu'on POST des données (bouton Submit)
139 140 puis qu'on revient en arrière, on avait une page blanche avec message erreur "Le document a expiré..."
140 141 C'est enfin réglé !
... ... @@ -148,7 +149,6 @@ Outre ces changements, voici d'autres changements importants :
148 149 - (b) GROS Bugfix tri par défaut de la liste des matériels (par date)
149 150 - (e) Précision "acheteur" et "utilisateur" dans les mails de notification
150 151 - (e) Amélioration des notifications sur actions spéciales (index avec POST, remplacement fournisseur...)
151   - - (e) Ajout légende pour le statut (C,V,TBA,A)
152 152 - (b) bugfixes champ utilisateur du matériel, et fournisseur
153 153 - (e) Version définitive du formulaire de remplacement d'un fournisseur
154 154 - (e) Ajout du champ "Utilisateur (destinataire du bien)" pour un matériel
... ...
README.md
... ... @@ -43,7 +43,7 @@ Logiciel testé et validé sur les configurations suivantes :
43 43 --------------------------------------------------------------------------------------------
44 44  
45 45 Date: 21/10/2020
46   -Version: 4.105.35-3.7.9
  46 +Version: 4.106.0-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)
... ...
database/update/script_sql/db-update-2020-10-21.sql 0 → 100755
... ... @@ -0,0 +1,45 @@
  1 +use database;
  2 +
  3 +-- On execute TOUT ou RIEN, c'est plus sur
  4 +START TRANSACTION;
  5 +
  6 +--
  7 +-- Structure de la table stats
  8 +--
  9 +
  10 +CREATE TABLE stats (
  11 +--year int(4) NOT NULL,
  12 + year year(4) NOT NULL,
  13 + user_id int(11) NOT NULL,
  14 + last_login_time datetime DEFAULT NULL,
  15 + last_logout_time datetime DEFAULT NULL,
  16 + connex_nb int(11) NOT NULL DEFAULT 0 COMMENT "nb connexions sur l'année",
  17 + connex_dur int(11) NOT NULL DEFAULT 0 COMMENT "total temps connexion sur l'année (sec)"
  18 +) ENGINE=InnoDB DEFAULT CHARSET=latin1 COMMENT='Statistiques de connexion des utilisateurs';
  19 +
  20 +--ALTER TABLE `stats` CHANGE `year` `year` YEAR NOT NULL;
  21 +--ALTER TABLE `stats`
  22 +-- ADD `last_login_time` DATETIME NULL DEFAULT NULL AFTER `user_id`,
  23 +-- ADD `last_logout_time` DATETIME NULL DEFAULT NULL AFTER `last_login_time`;
  24 +
  25 +
  26 +--
  27 +-- Contraintes pour la table stats
  28 +--
  29 +ALTER TABLE stats
  30 + ADD CONSTRAINT fk_stats_users FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
  31 +
  32 +--
  33 +-- Indexes pour la table stats
  34 +--
  35 +ALTER TABLE stats
  36 + ADD PRIMARY KEY (year,user_id),
  37 + ADD INDEX index_stats_year (year),
  38 + ADD KEY fk_stats_users (user_id);
  39 +
  40 +
  41 +-- Fin transaction, execution
  42 +COMMIT;
  43 +
  44 +
  45 +
... ...
src/Controller/StatsController.php 0 → 100644
... ... @@ -0,0 +1,111 @@
  1 +<?php
  2 +namespace App\Controller;
  3 +
  4 +use App\Controller\AppController;
  5 +
  6 +/**
  7 + * Stats Controller
  8 + *
  9 + * @property \App\Model\Table\StatsTable $Stats
  10 + *
  11 + * @method \App\Model\Entity\Stat[]|\Cake\Datasource\ResultSetInterface paginate($object = null, array $settings = [])
  12 + */
  13 +class StatsController extends AppController
  14 +{
  15 + /**
  16 + * Index method
  17 + *
  18 + * @return \Cake\Http\Response|null
  19 + */
  20 + public function index()
  21 + {
  22 + $this->paginate = [
  23 + 'contain' => ['Users']
  24 + ];
  25 + $stats = $this->paginate($this->Stats);
  26 +
  27 + $this->set(compact('stats'));
  28 + }
  29 +
  30 + /**
  31 + * View method
  32 + *
  33 + * @param string|null $id Stat id.
  34 + * @return \Cake\Http\Response|null
  35 + * @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
  36 + */
  37 + public function view($id = null)
  38 + {
  39 + $stat = $this->Stats->get($id, [
  40 + 'contain' => ['Users']
  41 + ]);
  42 +
  43 + $this->set('stat', $stat);
  44 + }
  45 +
  46 + /**
  47 + * Add method
  48 + *
  49 + * @return \Cake\Http\Response|null Redirects on successful add, renders view otherwise.
  50 + */
  51 + public function add()
  52 + {
  53 + $stat = $this->Stats->newEntity();
  54 + if ($this->request->is('post')) {
  55 + $stat = $this->Stats->patchEntity($stat, $this->request->getData());
  56 + if ($this->Stats->save($stat)) {
  57 + $this->Flash->success(__('The stat has been saved.'));
  58 +
  59 + return $this->redirect(['action' => 'index']);
  60 + }
  61 + $this->Flash->error(__('The stat could not be saved. Please, try again.'));
  62 + }
  63 + $users = $this->Stats->Users->find('list', ['limit' => 200]);
  64 + $this->set(compact('stat', 'users'));
  65 + }
  66 +
  67 + /**
  68 + * Edit method
  69 + *
  70 + * @param string|null $id Stat id.
  71 + * @return \Cake\Http\Response|null Redirects on successful edit, renders view otherwise.
  72 + * @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
  73 + */
  74 + public function edit($id = null)
  75 + {
  76 + $stat = $this->Stats->get($id, [
  77 + 'contain' => []
  78 + ]);
  79 + if ($this->request->is(['patch', 'post', 'put'])) {
  80 + $stat = $this->Stats->patchEntity($stat, $this->request->getData());
  81 + if ($this->Stats->save($stat)) {
  82 + $this->Flash->success(__('The stat has been saved.'));
  83 +
  84 + return $this->redirect(['action' => 'index']);
  85 + }
  86 + $this->Flash->error(__('The stat could not be saved. Please, try again.'));
  87 + }
  88 + $users = $this->Stats->Users->find('list', ['limit' => 200]);
  89 + $this->set(compact('stat', 'users'));
  90 + }
  91 +
  92 + /**
  93 + * Delete method
  94 + *
  95 + * @param string|null $id Stat id.
  96 + * @return \Cake\Http\Response|null Redirects to index.
  97 + * @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
  98 + */
  99 + public function delete($id = null)
  100 + {
  101 + $this->request->allowMethod(['post', 'delete']);
  102 + $stat = $this->Stats->get($id);
  103 + if ($this->Stats->delete($stat)) {
  104 + $this->Flash->success(__('The stat has been deleted.'));
  105 + } else {
  106 + $this->Flash->error(__('The stat could not be deleted. Please, try again.'));
  107 + }
  108 +
  109 + return $this->redirect(['action' => 'index']);
  110 + }
  111 +}
... ...
src/Controller/UsersController.php
... ... @@ -4,6 +4,9 @@ namespace App\Controller;
4 4 use App\Controller\AppController;
5 5 use Cake\Event\Event;
6 6 use Cake\ORM\TableRegistry;
  7 +use Cake\I18n\FrozenDate;
  8 +use Cake\I18n\FrozenTime;
  9 +use Cake\ORM\Entity;
7 10  
8 11 /**
9 12 * Users Controller
... ... @@ -182,7 +185,32 @@ class UsersController extends AppController
182 185 if ($user) {
183 186 // Sauvegarde le user dans la session
184 187 //$this->Auth->setUser($user);
185   - $this->LdapAuth->setUser($user);
  188 + $this->LdapAuth->setUser($user);
  189 +
  190 + //debug($user);
  191 + /* Voici ce que contient $user :
  192 + [
  193 + 'sn' => [
  194 + (int) 0 => 'Pallier'
  195 + ],
  196 + 'mail' => [
  197 + (int) 0 => 'Etienne.Pallier@irap.omp.eu'
  198 + ],
  199 + 'givenname' => [
  200 + (int) 0 => 'Etienne'
  201 + ],
  202 + 'uid' => [
  203 + (int) 0 => 'epallier'
  204 + ],
  205 + 'userpassword' => [
  206 + (int) 0 => 'mot-de-passe-crypté'
  207 + ]
  208 + ]
  209 + */
  210 +
  211 + $this->_updateStatsForUserLogin($user);
  212 + //exit;
  213 +
186 214 // On va maintenant à la page qui etait demandée
187 215 //return $this->redirect($this->Auth->redirectUrl());
188 216 return $this->redirect($this->LdapAuth->redirectUrl());
... ... @@ -194,11 +222,148 @@ class UsersController extends AppController
194 222  
195 223 public function logout()
196 224 {
  225 + debug($this->u);
  226 + $this->_updateStatsForUserLogout($this->u);
  227 + //exit;
197 228 //$this->Flash->success('You are now logged out.');
198 229 return $this->redirect($this->LdapAuth->logout());
199 230 // Puis ça va sur /users/login
200 231 }
  232 +
  233 + /*
  234 + * Mise à jour des stats pour ce user (et pour l'année courante) au moment du login
  235 + * => on enregistre son heure de connexion
  236 + * => 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 + */
  257 + private function _updateStatsForUserLogin($user_infos) {
201 258  
  259 + // "Pallier Etienne"
  260 + $user_fullname = $user_infos['sn'][0].' '.$user_infos['givenname'][0];
  261 + $user = $this->Users->find()->where(['nom'=>$user_fullname])->first();
  262 + //debug($user);
  263 +
  264 + $last_login_time = new FrozenTime();
  265 + //debug($last_login_time);
  266 + $year = $last_login_time->format('yy');
  267 + //debug($year);
  268 +
  269 + $table_stats = $this->Users->Stats;
  270 + $stat_id = [$year,$user->id];
  271 + $current_user_stat = null;
  272 + try {
  273 + // Si un enregistrement existe pour ce user (et cette année) => on le récupère
  274 + $current_user_stat = $table_stats->get($stat_id);
  275 + //debug("exists");
  276 + //$current_user_stat->last_logout_time = null;
  277 + } catch (\Cake\Datasource\Exception\RecordNotFoundException $e) {
  278 + //echo 'Exception reçue : ', $e->getMessage(), "\n";
  279 + // Pas d'enregistrement pour ce user (et cette année) => on le crée
  280 + //debug("new");
  281 + /*
  282 + $stat = [
  283 + //['year','user_id'] => [$year,$user->id],
  284 + //'(year,user_id)' => [$year,$user->id],
  285 + 'year' => $year,
  286 + 'user_id' => $user->id,
  287 + 'last_login_time' => $last_login_time,
  288 + ];
  289 + debug($stat);
  290 + */
  291 + //$stat_entity = $table_stats->newEntity($stat);
  292 + $current_user_stat = $table_stats->newEntity();
  293 + $current_user_stat->year = $year;
  294 + $current_user_stat->user_id = $user->id;
  295 + }
  296 + // On met à jour le champ last_login_time
  297 + $current_user_stat->last_login_time = $last_login_time;
  298 + // et 1 connexion de plus, 1 !
  299 + $current_user_stat->connex_nb++;
  300 + //debug($current_user_stat);
  301 + // On sauvegarde en table
  302 + if (! $table_stats->save($current_user_stat)) {
  303 + echo "Impossible de sauvegarder une stat sur login !";
  304 + debug($current_user_stat->getErrors());
  305 + debug($current_user_stat);
  306 + exit;
  307 + }
  308 + /*
  309 + $query = $table_stats->findById($stat_id);
  310 + if ($query->isEmpty()) {
  311 + debug("no");
  312 + // record doesn't exist
  313 + }
  314 + else {
  315 + debug("yes");
  316 + }
  317 + */
  318 + } // _updateStatsForUserLogin()
  319 +
  320 +
  321 + /*
  322 + * Mise à jour des stats pour ce user (et pour l'année courante) au moment du logout
  323 + * => on enregistre son heure de dé-connexion
  324 + * => on calcule son temps de connexion et on l'ajoute au champ qui totalise le temps de connexion : connex_dur
  325 + */
  326 + private function _updateStatsForUserLogout(Entity $user) {
  327 + $last_logout_time = new FrozenTime();
  328 + //debug($last_login_time);
  329 + $year = $last_logout_time->format('yy');
  330 + //debug($year);
  331 +
  332 + // On récup la stat de ce user (pour cette année)
  333 + // Normalement, elle doit exister puisque il y a déjà eu login !
  334 + // TODO: Cas particulier où le login se fait le 31 décembre, et le logout l'année suivante => BUG !!!
  335 + $table_stats = $this->Users->Stats;
  336 + $stat_id = [$year,$user->id];
  337 + $current_user_stat = $table_stats->get($stat_id);
  338 + $last_login_time = $current_user_stat->last_login_time;
  339 +
  340 + // 1) On met à jour le champ last_logout_time
  341 + $current_user_stat->last_logout_time = $last_logout_time;
  342 +
  343 + // 2) On met à jour le temps de connexion total (cumulé)
  344 + // - on calcule le temps de connexion
  345 + $connex_duration = $last_logout_time->diff($last_login_time);
  346 + $connex_duration = $connex_duration->h*3600 + $connex_duration->i*60 + $connex_duration->s;
  347 + //debug($last_login_time);
  348 + //debug($last_logout_time);
  349 + //debug($connex_duration);
  350 + // - on cumule ce temps au temps total
  351 + debug($current_user_stat->connex_dur);
  352 + debug($connex_duration);
  353 + $current_user_stat->connex_dur += $connex_duration;
  354 + debug($current_user_stat->connex_dur);
  355 +
  356 + // On sauvegarde en table
  357 + if (! $table_stats->save($current_user_stat)) {
  358 + echo "Impossible de sauvegarder une stat sur logout !";
  359 + debug($current_user_stat->getErrors());
  360 + debug($current_user_stat);
  361 + exit;
  362 + }
  363 + } // _updateStatsForUserLogout()
  364 +
  365 +
  366 +
202 367 /**
203 368 * Index method
204 369 *
... ...
src/Model/Entity/Stat.php 0 → 100644
... ... @@ -0,0 +1,36 @@
  1 +<?php
  2 +namespace App\Model\Entity;
  3 +
  4 +use Cake\ORM\Entity;
  5 +
  6 +/**
  7 + * Stat Entity
  8 + *
  9 + * @property string $year
  10 + * @property int $user_id
  11 + * @property \Cake\I18n\FrozenTime|null $last_login_time
  12 + * @property \Cake\I18n\FrozenTime|null $last_logout_time
  13 + * @property int $connex_nb
  14 + * @property int $connex_dur
  15 + *
  16 + * @property \App\Model\Entity\User $user
  17 + */
  18 +class Stat extends Entity
  19 +{
  20 + /**
  21 + * Fields that can be mass assigned using newEntity() or patchEntity().
  22 + *
  23 + * Note that when '*' is set to true, this allows all unspecified fields to
  24 + * be mass assigned. For security purposes, it is advised to set '*' to false
  25 + * (or remove it), and explicitly make individual fields accessible as needed.
  26 + *
  27 + * @var array
  28 + */
  29 + protected $_accessible = [
  30 + 'last_login_time' => true,
  31 + 'last_logout_time' => true,
  32 + 'connex_nb' => true,
  33 + 'connex_dur' => true,
  34 + 'user' => true
  35 + ];
  36 +}
... ...
src/Model/Table/StatsTable.php 0 → 100644
... ... @@ -0,0 +1,89 @@
  1 +<?php
  2 +namespace App\Model\Table;
  3 +
  4 +use Cake\ORM\Query;
  5 +use Cake\ORM\RulesChecker;
  6 +use Cake\ORM\Table;
  7 +use Cake\Validation\Validator;
  8 +
  9 +/**
  10 + * Stats Model
  11 + *
  12 + * @property \App\Model\Table\UsersTable|\Cake\ORM\Association\BelongsTo $Users
  13 + *
  14 + * @method \App\Model\Entity\Stat get($primaryKey, $options = [])
  15 + * @method \App\Model\Entity\Stat newEntity($data = null, array $options = [])
  16 + * @method \App\Model\Entity\Stat[] newEntities(array $data, array $options = [])
  17 + * @method \App\Model\Entity\Stat|bool save(\Cake\Datasource\EntityInterface $entity, $options = [])
  18 + * @method \App\Model\Entity\Stat saveOrFail(\Cake\Datasource\EntityInterface $entity, $options = [])
  19 + * @method \App\Model\Entity\Stat patchEntity(\Cake\Datasource\EntityInterface $entity, array $data, array $options = [])
  20 + * @method \App\Model\Entity\Stat[] patchEntities($entities, array $data, array $options = [])
  21 + * @method \App\Model\Entity\Stat findOrCreate($search, callable $callback = null, $options = [])
  22 + */
  23 +class StatsTable extends Table
  24 +{
  25 + /**
  26 + * Initialize method
  27 + *
  28 + * @param array $config The configuration for the Table.
  29 + * @return void
  30 + */
  31 + public function initialize(array $config)
  32 + {
  33 + parent::initialize($config);
  34 +
  35 + $this->setTable('stats');
  36 + $this->setDisplayField('year');
  37 + $this->setPrimaryKey(['year', 'user_id']);
  38 +
  39 + $this->belongsTo('Users', [
  40 + 'foreignKey' => 'user_id',
  41 + 'joinType' => 'INNER'
  42 + ]);
  43 + }
  44 +
  45 + /**
  46 + * Default validation rules.
  47 + *
  48 + * @param \Cake\Validation\Validator $validator Validator instance.
  49 + * @return \Cake\Validation\Validator
  50 + */
  51 + public function validationDefault(Validator $validator)
  52 + {
  53 + $validator
  54 + ->scalar('year')
  55 + ->allowEmptyString('year', 'create');
  56 +
  57 + $validator
  58 + ->dateTime('last_login_time')
  59 + ->allowEmptyDateTime('last_login_time');
  60 +
  61 + $validator
  62 + ->dateTime('last_logout_time')
  63 + ->allowEmptyDateTime('last_logout_time');
  64 +
  65 + $validator
  66 + ->integer('connex_nb')
  67 + ->allowEmptyString('connex_nb', false);
  68 +
  69 + $validator
  70 + ->integer('connex_dur')
  71 + ->allowEmptyString('connex_dur', false);
  72 +
  73 + return $validator;
  74 + }
  75 +
  76 + /**
  77 + * Returns a rules checker object that will be used for validating
  78 + * application integrity.
  79 + *
  80 + * @param \Cake\ORM\RulesChecker $rules The rules object to be modified.
  81 + * @return \Cake\ORM\RulesChecker
  82 + */
  83 + public function buildRules(RulesChecker $rules)
  84 + {
  85 + $rules->add($rules->existsIn(['user_id'], 'Users'));
  86 +
  87 + return $rules;
  88 + }
  89 +}
... ...
src/Model/Table/UsersTable.php
... ... @@ -42,6 +42,10 @@ class UsersTable extends AppTable
42 42 'foreignKey' => 'sur_categorie_id'
43 43 ]);
44 44  
  45 + $this->hasMany('Stats')
  46 + ->setForeignKey('user_id')
  47 + ->setDependent(true); // Si user supprimé => ses stats aussi
  48 +
45 49 // So that 'created' and 'updated' fields are filled
46 50 $this->addBehavior('Timestamp');
47 51 }
... ...
src/Template/Stats/add.ctp 0 → 100644
... ... @@ -0,0 +1,28 @@
  1 +<?php
  2 +/**
  3 + * @var \App\View\AppView $this
  4 + * @var \App\Model\Entity\Stat $stat
  5 + */
  6 +?>
  7 +<nav class="large-3 medium-4 columns" id="actions-sidebar">
  8 + <ul class="side-nav">
  9 + <li class="heading"><?= __('Actions') ?></li>
  10 + <li><?= $this->Html->link(__('List Stats'), ['action' => 'index']) ?></li>
  11 + <li><?= $this->Html->link(__('List Users'), ['controller' => 'Users', 'action' => 'index']) ?></li>
  12 + <li><?= $this->Html->link(__('New User'), ['controller' => 'Users', 'action' => 'add']) ?></li>
  13 + </ul>
  14 +</nav>
  15 +<div class="stats form large-9 medium-8 columns content">
  16 + <?= $this->Form->create($stat) ?>
  17 + <fieldset>
  18 + <legend><?= __('Add Stat') ?></legend>
  19 + <?php
  20 + echo $this->Form->control('last_login_time', ['empty' => true]);
  21 + echo $this->Form->control('last_logout_time', ['empty' => true]);
  22 + echo $this->Form->control('connex_nb');
  23 + echo $this->Form->control('connex_dur');
  24 + ?>
  25 + </fieldset>
  26 + <?= $this->Form->button(__('Submit')) ?>
  27 + <?= $this->Form->end() ?>
  28 +</div>
... ...
src/Template/Stats/edit.ctp 0 → 100644
... ... @@ -0,0 +1,34 @@
  1 +<?php
  2 +/**
  3 + * @var \App\View\AppView $this
  4 + * @var \App\Model\Entity\Stat $stat
  5 + */
  6 +?>
  7 +<nav class="large-3 medium-4 columns" id="actions-sidebar">
  8 + <ul class="side-nav">
  9 + <li class="heading"><?= __('Actions') ?></li>
  10 + <li><?= $this->Form->postLink(
  11 + __('Delete'),
  12 + ['action' => 'delete', $stat->year],
  13 + ['confirm' => __('Are you sure you want to delete # {0}?', $stat->year)]
  14 + )
  15 + ?></li>
  16 + <li><?= $this->Html->link(__('List Stats'), ['action' => 'index']) ?></li>
  17 + <li><?= $this->Html->link(__('List Users'), ['controller' => 'Users', 'action' => 'index']) ?></li>
  18 + <li><?= $this->Html->link(__('New User'), ['controller' => 'Users', 'action' => 'add']) ?></li>
  19 + </ul>
  20 +</nav>
  21 +<div class="stats form large-9 medium-8 columns content">
  22 + <?= $this->Form->create($stat) ?>
  23 + <fieldset>
  24 + <legend><?= __('Edit Stat') ?></legend>
  25 + <?php
  26 + echo $this->Form->control('last_login_time', ['empty' => true]);
  27 + echo $this->Form->control('last_logout_time', ['empty' => true]);
  28 + echo $this->Form->control('connex_nb');
  29 + echo $this->Form->control('connex_dur');
  30 + ?>
  31 + </fieldset>
  32 + <?= $this->Form->button(__('Submit')) ?>
  33 + <?= $this->Form->end() ?>
  34 +</div>
... ...
src/Template/Stats/index.ctp 0 → 100644
... ... @@ -0,0 +1,57 @@
  1 +<?php
  2 +/**
  3 + * @var \App\View\AppView $this
  4 + * @var \App\Model\Entity\Stat[]|\Cake\Collection\CollectionInterface $stats
  5 + */
  6 +?>
  7 +<nav class="large-3 medium-4 columns" id="actions-sidebar">
  8 + <ul class="side-nav">
  9 + <li class="heading"><?= __('Actions') ?></li>
  10 + <li><?= $this->Html->link(__('New Stat'), ['action' => 'add']) ?></li>
  11 + <li><?= $this->Html->link(__('List Users'), ['controller' => 'Users', 'action' => 'index']) ?></li>
  12 + <li><?= $this->Html->link(__('New User'), ['controller' => 'Users', 'action' => 'add']) ?></li>
  13 + </ul>
  14 +</nav>
  15 +<div class="stats index large-9 medium-8 columns content">
  16 + <h3><?= __('Stats') ?></h3>
  17 + <table cellpadding="0" cellspacing="0">
  18 + <thead>
  19 + <tr>
  20 + <th scope="col"><?= $this->Paginator->sort('year') ?></th>
  21 + <th scope="col"><?= $this->Paginator->sort('user_id') ?></th>
  22 + <th scope="col"><?= $this->Paginator->sort('last_login_time') ?></th>
  23 + <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>
  26 + <th scope="col" class="actions"><?= __('Actions') ?></th>
  27 + </tr>
  28 + </thead>
  29 + <tbody>
  30 + <?php foreach ($stats as $stat): ?>
  31 + <tr>
  32 + <td><?= h($stat->year) ?></td>
  33 + <td><?= $stat->has('user') ? $this->Html->link($stat->user->nom, ['controller' => 'Users', 'action' => 'view', $stat->user->id]) : '' ?></td>
  34 + <td><?= h($stat->last_login_time) ?></td>
  35 + <td><?= h($stat->last_logout_time) ?></td>
  36 + <td><?= $this->Number->format($stat->connex_nb) ?></td>
  37 + <td><?= $this->Number->format($stat->connex_dur) ?></td>
  38 + <td class="actions">
  39 + <?= $this->Html->link(__('View'), ['action' => 'view', $stat->year]) ?>
  40 + <?= $this->Html->link(__('Edit'), ['action' => 'edit', $stat->year]) ?>
  41 + <?= $this->Form->postLink(__('Delete'), ['action' => 'delete', $stat->year], ['confirm' => __('Are you sure you want to delete # {0}?', $stat->year)]) ?>
  42 + </td>
  43 + </tr>
  44 + <?php endforeach; ?>
  45 + </tbody>
  46 + </table>
  47 + <div class="paginator">
  48 + <ul class="pagination">
  49 + <?= $this->Paginator->first('<< ' . __('first')) ?>
  50 + <?= $this->Paginator->prev('< ' . __('previous')) ?>
  51 + <?= $this->Paginator->numbers() ?>
  52 + <?= $this->Paginator->next(__('next') . ' >') ?>
  53 + <?= $this->Paginator->last(__('last') . ' >>') ?>
  54 + </ul>
  55 + <p><?= $this->Paginator->counter(['format' => __('Page {{page}} of {{pages}}, showing {{current}} record(s) out of {{count}} total')]) ?></p>
  56 + </div>
  57 +</div>
... ...
src/Template/Stats/view.ctp 0 → 100644
... ... @@ -0,0 +1,46 @@
  1 +<?php
  2 +/**
  3 + * @var \App\View\AppView $this
  4 + * @var \App\Model\Entity\Stat $stat
  5 + */
  6 +?>
  7 +<nav class="large-3 medium-4 columns" id="actions-sidebar">
  8 + <ul class="side-nav">
  9 + <li class="heading"><?= __('Actions') ?></li>
  10 + <li><?= $this->Html->link(__('Edit Stat'), ['action' => 'edit', $stat->year]) ?> </li>
  11 + <li><?= $this->Form->postLink(__('Delete Stat'), ['action' => 'delete', $stat->year], ['confirm' => __('Are you sure you want to delete # {0}?', $stat->year)]) ?> </li>
  12 + <li><?= $this->Html->link(__('List Stats'), ['action' => 'index']) ?> </li>
  13 + <li><?= $this->Html->link(__('New Stat'), ['action' => 'add']) ?> </li>
  14 + <li><?= $this->Html->link(__('List Users'), ['controller' => 'Users', 'action' => 'index']) ?> </li>
  15 + <li><?= $this->Html->link(__('New User'), ['controller' => 'Users', 'action' => 'add']) ?> </li>
  16 + </ul>
  17 +</nav>
  18 +<div class="stats view large-9 medium-8 columns content">
  19 + <h3><?= h($stat->year) ?></h3>
  20 + <table class="vertical-table">
  21 + <tr>
  22 + <th scope="row"><?= __('Year') ?></th>
  23 + <td><?= h($stat->year) ?></td>
  24 + </tr>
  25 + <tr>
  26 + <th scope="row"><?= __('User') ?></th>
  27 + <td><?= $stat->has('user') ? $this->Html->link($stat->user->nom, ['controller' => 'Users', 'action' => 'view', $stat->user->id]) : '' ?></td>
  28 + </tr>
  29 + <tr>
  30 + <th scope="row"><?= __('Connex Nb') ?></th>
  31 + <td><?= $this->Number->format($stat->connex_nb) ?></td>
  32 + </tr>
  33 + <tr>
  34 + <th scope="row"><?= __('Connex Dur') ?></th>
  35 + <td><?= $this->Number->format($stat->connex_dur) ?></td>
  36 + </tr>
  37 + <tr>
  38 + <th scope="row"><?= __('Last Login Time') ?></th>
  39 + <td><?= h($stat->last_login_time) ?></td>
  40 + </tr>
  41 + <tr>
  42 + <th scope="row"><?= __('Last Logout Time') ?></th>
  43 + <td><?= h($stat->last_logout_time) ?></td>
  44 + </tr>
  45 + </table>
  46 +</div>
... ...
tests/Fixture/StatsFixture.php 0 → 100644
... ... @@ -0,0 +1,57 @@
  1 +<?php
  2 +namespace App\Test\Fixture;
  3 +
  4 +use Cake\TestSuite\Fixture\TestFixture;
  5 +
  6 +/**
  7 + * StatsFixture
  8 + */
  9 +class StatsFixture extends TestFixture
  10 +{
  11 + /**
  12 + * Fields
  13 + *
  14 + * @var array
  15 + */
  16 + // @codingStandardsIgnoreStart
  17 + public $fields = [
  18 + 'year' => ['type' => 'string', 'length' => null, 'null' => false, 'default' => null, 'collate' => null, 'comment' => '', 'precision' => null, 'fixed' => null],
  19 + 'user_id' => ['type' => 'integer', 'length' => 11, 'unsigned' => false, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'autoIncrement' => null],
  20 + 'last_login_time' => ['type' => 'datetime', 'length' => null, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null],
  21 + 'last_logout_time' => ['type' => 'datetime', 'length' => null, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null],
  22 + 'connex_nb' => ['type' => 'integer', 'length' => 11, 'unsigned' => false, 'null' => false, 'default' => '0', 'comment' => 'nb connexions sur l\'année', 'precision' => null, 'autoIncrement' => null],
  23 + 'connex_dur' => ['type' => 'integer', 'length' => 11, 'unsigned' => false, 'null' => false, 'default' => '0', 'comment' => 'total temps connexion sur l\'année (sec)', 'precision' => null, 'autoIncrement' => null],
  24 + '_indexes' => [
  25 + 'fk_stats_users' => ['type' => 'index', 'columns' => ['user_id'], 'length' => []],
  26 + 'index_stats_year' => ['type' => 'index', 'columns' => ['year'], 'length' => []],
  27 + ],
  28 + '_constraints' => [
  29 + 'primary' => ['type' => 'primary', 'columns' => ['year', 'user_id'], 'length' => []],
  30 + 'fk_stats_users' => ['type' => 'foreign', 'columns' => ['user_id'], 'references' => ['users', 'id'], 'update' => 'restrict', 'delete' => 'cascade', 'length' => []],
  31 + ],
  32 + '_options' => [
  33 + 'engine' => 'InnoDB',
  34 + 'collation' => 'latin1_swedish_ci'
  35 + ],
  36 + ];
  37 + // @codingStandardsIgnoreEnd
  38 + /**
  39 + * Init method
  40 + *
  41 + * @return void
  42 + */
  43 + public function init()
  44 + {
  45 + $this->records = [
  46 + [
  47 + 'year' => 'a4a821dd-3174-4fa5-9672-b2d5b3d9903c',
  48 + 'user_id' => 1,
  49 + 'last_login_time' => '2020-10-21 17:11:18',
  50 + 'last_logout_time' => '2020-10-21 17:11:18',
  51 + 'connex_nb' => 1,
  52 + 'connex_dur' => 1
  53 + ],
  54 + ];
  55 + parent::init();
  56 + }
  57 +}
... ...
tests/TestCase/Controller/StatsControllerTest.php 0 → 100644
... ... @@ -0,0 +1,74 @@
  1 +<?php
  2 +namespace App\Test\TestCase\Controller;
  3 +
  4 +use App\Controller\StatsController;
  5 +use Cake\TestSuite\IntegrationTestTrait;
  6 +use Cake\TestSuite\TestCase;
  7 +
  8 +/**
  9 + * App\Controller\StatsController Test Case
  10 + */
  11 +class StatsControllerTest extends TestCase
  12 +{
  13 + use IntegrationTestTrait;
  14 +
  15 + /**
  16 + * Fixtures
  17 + *
  18 + * @var array
  19 + */
  20 + public $fixtures = [
  21 + 'app.Stats',
  22 + 'app.Users'
  23 + ];
  24 +
  25 + /**
  26 + * Test index method
  27 + *
  28 + * @return void
  29 + */
  30 + public function testIndex()
  31 + {
  32 + $this->markTestIncomplete('Not implemented yet.');
  33 + }
  34 +
  35 + /**
  36 + * Test view method
  37 + *
  38 + * @return void
  39 + */
  40 + public function testView()
  41 + {
  42 + $this->markTestIncomplete('Not implemented yet.');
  43 + }
  44 +
  45 + /**
  46 + * Test add method
  47 + *
  48 + * @return void
  49 + */
  50 + public function testAdd()
  51 + {
  52 + $this->markTestIncomplete('Not implemented yet.');
  53 + }
  54 +
  55 + /**
  56 + * Test edit method
  57 + *
  58 + * @return void
  59 + */
  60 + public function testEdit()
  61 + {
  62 + $this->markTestIncomplete('Not implemented yet.');
  63 + }
  64 +
  65 + /**
  66 + * Test delete method
  67 + *
  68 + * @return void
  69 + */
  70 + public function testDelete()
  71 + {
  72 + $this->markTestIncomplete('Not implemented yet.');
  73 + }
  74 +}
... ...
tests/TestCase/Model/Table/StatsTableTest.php 0 → 100644
... ... @@ -0,0 +1,83 @@
  1 +<?php
  2 +namespace App\Test\TestCase\Model\Table;
  3 +
  4 +use App\Model\Table\StatsTable;
  5 +use Cake\ORM\TableRegistry;
  6 +use Cake\TestSuite\TestCase;
  7 +
  8 +/**
  9 + * App\Model\Table\StatsTable Test Case
  10 + */
  11 +class StatsTableTest extends TestCase
  12 +{
  13 + /**
  14 + * Test subject
  15 + *
  16 + * @var \App\Model\Table\StatsTable
  17 + */
  18 + public $Stats;
  19 +
  20 + /**
  21 + * Fixtures
  22 + *
  23 + * @var array
  24 + */
  25 + public $fixtures = [
  26 + 'app.Stats',
  27 + 'app.Users'
  28 + ];
  29 +
  30 + /**
  31 + * setUp method
  32 + *
  33 + * @return void
  34 + */
  35 + public function setUp()
  36 + {
  37 + parent::setUp();
  38 + $config = TableRegistry::getTableLocator()->exists('Stats') ? [] : ['className' => StatsTable::class];
  39 + $this->Stats = TableRegistry::getTableLocator()->get('Stats', $config);
  40 + }
  41 +
  42 + /**
  43 + * tearDown method
  44 + *
  45 + * @return void
  46 + */
  47 + public function tearDown()
  48 + {
  49 + unset($this->Stats);
  50 +
  51 + parent::tearDown();
  52 + }
  53 +
  54 + /**
  55 + * Test initialize method
  56 + *
  57 + * @return void
  58 + */
  59 + public function testInitialize()
  60 + {
  61 + $this->markTestIncomplete('Not implemented yet.');
  62 + }
  63 +
  64 + /**
  65 + * Test validationDefault method
  66 + *
  67 + * @return void
  68 + */
  69 + public function testValidationDefault()
  70 + {
  71 + $this->markTestIncomplete('Not implemented yet.');
  72 + }
  73 +
  74 + /**
  75 + * Test buildRules method
  76 + *
  77 + * @return void
  78 + */
  79 + public function testBuildRules()
  80 + {
  81 + $this->markTestIncomplete('Not implemented yet.');
  82 + }
  83 +}
... ...