Commit 409758d9e05fdc5940758d1eef0841fda24a6585
1 parent
f4f72aca
Exists in
master
and in
1 other branch
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
Showing
15 changed files
with
833 additions
and
4 deletions
Show diff stats
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) | ... | ... |
... | ... | @@ -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 | + | ... | ... |
... | ... | @@ -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 | * | ... | ... |
... | ... | @@ -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 | +} | ... | ... |
... | ... | @@ -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 | } | ... | ... |
... | ... | @@ -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> | ... | ... |
... | ... | @@ -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> | ... | ... |
... | ... | @@ -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> | ... | ... |
... | ... | @@ -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> | ... | ... |
... | ... | @@ -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 | +} | ... | ... |
... | ... | @@ -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 | +} | ... | ... |
... | ... | @@ -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 | +} | ... | ... |