]> git.agnieray.net Git - galette.git/blob - galette/lib/Galette/Core/History.php
98ea34c3824107b6df3fa562e6b3685f61adbbeb
[galette.git] / galette / lib / Galette / Core / History.php
1 <?php
2
3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5 /**
6 * History management
7 *
8 * PHP version 5
9 *
10 * Copyright © 2009-2023 The Galette Team
11 *
12 * This file is part of Galette (http://galette.tuxfamily.org).
13 *
14 * Galette is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation, either version 3 of the License, or
17 * (at your option) any later version.
18 *
19 * Galette is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with Galette. If not, see <http://www.gnu.org/licenses/>.
26 *
27 * @category Core
28 * @package Galette
29 *
30 * @author Johan Cwiklinski <johan@x-tnd.be>
31 * @copyright 2009-2023 The Galette Team
32 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
33 * @link http://galette.tuxfamily.org
34 * @since Available since 0.7dev - 2009-02-09
35 */
36
37 namespace Galette\Core;
38
39 use Throwable;
40 use Analog\Analog;
41 use Galette\Filters\HistoryList;
42 use Laminas\Db\Sql\Expression;
43 use Laminas\Db\Adapter\Adapter;
44 use Laminas\Db\Sql\Select;
45
46 /**
47 * History management
48 *
49 * @category Core
50 * @name History
51 * @package Galette
52 * @author Johan Cwiklinski <johan@x-tnd.be>
53 * @copyright 2009-2023 The Galette Team
54 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
55 * @link http://galette.tuxfamily.org
56 * @since Available since 0.7dev - 2009-02-09
57 *
58 * @property HistoryList $filters
59 */
60
61 class History
62 {
63 public const TABLE = 'logs';
64 public const PK = 'id_log';
65
66 protected $count;
67 protected $zdb;
68 protected $login;
69 protected $preferences;
70 protected $filters;
71
72 protected $users;
73 protected $actions;
74
75 protected $with_lists = true;
76
77 /**
78 * Default constructor
79 *
80 * @param Db $zdb Database
81 * @param Login $login Login
82 * @param Preferences $preferences Preferences
83 * @param HistoryList $filters Filtering
84 */
85 public function __construct(Db $zdb, Login $login, Preferences $preferences, $filters = null)
86 {
87 $this->zdb = $zdb;
88 $this->login = $login;
89 $this->preferences = $preferences;
90
91 if ($filters === null) {
92 $this->filters = new HistoryList();
93 } else {
94 $this->filters = $filters;
95 }
96 }
97
98 /**
99 * Helper function to find the user IP address
100 *
101 * This function uses the client address or the appropriate part of
102 * X-Forwarded-For, if present and the configuration specifies it.
103 * (blindly trusting X-Forwarded-For would make the IP address logging
104 * very easy to deveive.
105 *
106 * @return string
107 */
108 public static function findUserIPAddress()
109 {
110 if (
111 defined('GALETTE_X_FORWARDED_FOR_INDEX')
112 && isset($_SERVER['HTTP_X_FORWARDED_FOR'])
113 ) {
114 $split_xff = preg_split('/,\s*/', $_SERVER['HTTP_X_FORWARDED_FOR']);
115 return $split_xff[count($split_xff) - GALETTE_X_FORWARDED_FOR_INDEX];
116 }
117 return $_SERVER['REMOTE_ADDR'];
118 }
119
120 /**
121 * Add a new entry
122 *
123 * @param string $action the action to log
124 * @param string $argument the argument
125 * @param string $query the query (if relevant)
126 *
127 * @return bool true if entry was successfully added, false otherwise
128 */
129 public function add($action, $argument = '', $query = '')
130 {
131 if ($this->preferences->pref_log == Preferences::LOG_DISABLED) {
132 //logs are disabled
133 return true;
134 }
135
136 $ip = null;
137 if (PHP_SAPI === 'cli') {
138 $ip = '127.0.0.1';
139 } else {
140 $ip = self::findUserIpAddress();
141 }
142
143 try {
144 $values = array(
145 'date_log' => date('Y-m-d H:i:s'),
146 'ip_log' => $ip,
147 'adh_log' => $this->login->login,
148 'action_log' => $action,
149 'text_log' => $argument,
150 'sql_log' => $query
151 );
152
153 $insert = $this->zdb->insert($this->getTableName());
154 $insert->values($values);
155 $this->zdb->execute($insert);
156 } catch (Throwable $e) {
157 Analog::log(
158 "An error occurred trying to add log entry. " . $e->getMessage(),
159 Analog::ERROR
160 );
161 throw $e;
162 }
163
164 return true;
165 }
166
167 /**
168 * Delete all entries
169 *
170 * @return boolean
171 */
172 public function clean()
173 {
174 try {
175 $result = $this->zdb->db->query(
176 'TRUNCATE TABLE ' . $this->getTableName(true),
177 Adapter::QUERY_MODE_EXECUTE
178 );
179
180 if (!$result) {
181 Analog::log(
182 'An error occurred cleaning history. ',
183 Analog::WARNING
184 );
185 $this->add('Error flushing logs');
186 return false;
187 }
188 $this->add('Logs flushed');
189 $this->filters = new HistoryList();
190 return true;
191 } catch (Throwable $e) {
192 Analog::log(
193 'Unable to flush logs. | ' . $e->getMessage(),
194 Analog::WARNING
195 );
196 throw $e;
197 }
198 }
199
200 /**
201 * Get the entire history list
202 *
203 * @return array
204 */
205 public function getHistory()
206 {
207 try {
208 $select = $this->zdb->select($this->getTableName());
209 $this->buildWhereClause($select);
210 $select->order($this->buildOrderClause());
211 if ($this->with_lists === true) {
212 $this->buildLists($select);
213 }
214 $this->proceedCount($select);
215 //add limits to retrieve only relavant rows
216 $this->filters->setLimits($select);
217 $results = $this->zdb->execute($select);
218
219 $entries = [];
220 foreach ($results as $result) {
221 $entries[] = $result;
222 }
223
224 return $entries;
225 } catch (Throwable $e) {
226 Analog::log(
227 'Unable to get history. | ' . $e->getMessage(),
228 Analog::WARNING
229 );
230 throw $e;
231 }
232 }
233
234 /**
235 * Builds users and actions lists
236 *
237 * @param Select $select Original select
238 *
239 * @return void
240 */
241 private function buildLists(Select $select)
242 {
243 try {
244 $usersSelect = clone $select;
245 $usersSelect->reset($usersSelect::COLUMNS);
246 $usersSelect->reset($usersSelect::ORDER);
247 $usersSelect->quantifier('DISTINCT')->columns(['adh_log']);
248 $usersSelect->order(['adh_log ASC']);
249
250 $results = $this->zdb->execute($usersSelect);
251
252 $this->users = [];
253 foreach ($results as $result) {
254 $this->users[] = $result->adh_log;
255 }
256 } catch (Throwable $e) {
257 Analog::log(
258 'Cannot list members from history! | ' . $e->getMessage(),
259 Analog::WARNING
260 );
261 }
262
263 try {
264 $actionsSelect = clone $select;
265 $actionsSelect->reset($actionsSelect::COLUMNS);
266 $actionsSelect->reset($actionsSelect::ORDER);
267 $actionsSelect->quantifier('DISTINCT')->columns(['action_log']);
268 $actionsSelect->order(['action_log ASC']);
269
270 $results = $this->zdb->execute($actionsSelect);
271
272 $this->actions = [];
273 foreach ($results as $result) {
274 $this->actions[] = $result->action_log;
275 }
276 } catch (Throwable $e) {
277 Analog::log(
278 'Cannot list actions from history! | ' . $e->getMessage(),
279 Analog::WARNING
280 );
281 throw $e;
282 }
283 }
284
285 /**
286 * Builds the order clause
287 *
288 * @return array SQL ORDER clauses
289 */
290 protected function buildOrderClause()
291 {
292 $order = array();
293
294 switch ($this->filters->orderby) {
295 case HistoryList::ORDERBY_DATE:
296 $order[] = 'date_log ' . $this->filters->ordered;
297 break;
298 case HistoryList::ORDERBY_IP:
299 $order[] = 'ip_log ' . $this->filters->ordered;
300 break;
301 case HistoryList::ORDERBY_USER:
302 $order[] = 'adh_log ' . $this->filters->ordered;
303 break;
304 case HistoryList::ORDERBY_ACTION:
305 $order[] = 'action_log ' . $this->filters->ordered;
306 break;
307 }
308
309 return $order;
310 }
311
312 /**
313 * Builds where clause, for filtering on simple list mode
314 *
315 * @param Select $select Original select
316 *
317 * @return void
318 */
319 private function buildWhereClause(Select $select)
320 {
321 try {
322 if ($this->filters->start_date_filter != null) {
323 $d = new \DateTime($this->filters->raw_start_date_filter);
324 $d->setTime(0, 0, 0);
325 $select->where->greaterThanOrEqualTo(
326 'date_log',
327 $d->format('Y-m-d H:i:s')
328 );
329 }
330
331 if ($this->filters->end_date_filter != null) {
332 $d = new \DateTime($this->filters->raw_end_date_filter);
333 $d->setTime(23, 59, 59);
334 $select->where->lessThanOrEqualTo(
335 'date_log',
336 $d->format('Y-m-d H:i:s')
337 );
338 }
339
340 if ($this->filters->user_filter != null && $this->filters->user_filter != '0') {
341 $select->where->equalTo(
342 'adh_log',
343 $this->filters->user_filter
344 );
345 }
346
347 if ($this->filters->action_filter != null && $this->filters->action_filter != '0') {
348 $select->where->equalTo(
349 'action_log',
350 $this->filters->action_filter
351 );
352 }
353 } catch (Throwable $e) {
354 Analog::log(
355 __METHOD__ . ' | ' . $e->getMessage(),
356 Analog::WARNING
357 );
358 throw $e;
359 }
360 }
361
362 /**
363 * Count history entries from the query
364 *
365 * @param Select $select Original select
366 *
367 * @return void
368 */
369 private function proceedCount(Select $select)
370 {
371 try {
372 $countSelect = clone $select;
373 $countSelect->reset($countSelect::COLUMNS);
374 $countSelect->reset($countSelect::JOINS);
375 $countSelect->reset($countSelect::ORDER);
376 $countSelect->columns(
377 array(
378 $this->getPk() => new Expression('COUNT(' . $this->getPk() . ')')
379 )
380 );
381
382 $results = $this->zdb->execute($countSelect);
383 $result = $results->current();
384
385 $k = $this->getPk();
386 $this->count = $result->$k;
387 if ($this->count > 0) {
388 $this->filters->setCounter($this->count);
389 }
390 } catch (Throwable $e) {
391 Analog::log(
392 'Cannot count history | ' . $e->getMessage(),
393 Analog::WARNING
394 );
395 throw $e;
396 }
397 }
398
399 /**
400 * Global getter method
401 *
402 * @param string $name name of the property we want to retrieve
403 *
404 * @return mixed the called property
405 */
406 public function __get($name)
407 {
408 Analog::log(
409 '[History] Getting property `' . $name . '`',
410 Analog::DEBUG
411 );
412
413 $forbidden = array();
414 if (!in_array($name, $forbidden)) {
415 return $this->$name;
416 } else {
417 Analog::log(
418 '[History] Unable to get property `' . $name . '`',
419 Analog::WARNING
420 );
421 }
422 }
423
424 /**
425 * Global isset method
426 * Required for twig to access properties via __get
427 *
428 * @param string $name name of the property we want to retrieve
429 *
430 * @return bool
431 */
432 public function __isset($name)
433 {
434 if (isset($this->$name)) {
435 return true;
436 }
437 return false;
438 }
439
440 /**
441 * Global setter method
442 *
443 * @param string $name name of the property we want to assign a value to
444 * @param object $value a relevant value for the property
445 *
446 * @return void
447 */
448 public function __set($name, $value)
449 {
450 Analog::log(
451 '[History] Setting property `' . $name . '`',
452 Analog::DEBUG
453 );
454
455 $forbidden = array();
456 if (!in_array($name, $forbidden)) {
457 switch ($name) {
458 default:
459 $this->$name = $value;
460 break;
461 }
462 } else {
463 Analog::log(
464 '[History] Unable to set property `' . $name . '`',
465 Analog::WARNING
466 );
467 }
468 }
469
470 /**
471 * Get table's name
472 *
473 * @param boolean $prefixed Whether table name should be prefixed
474 *
475 * @return string
476 */
477 protected function getTableName($prefixed = false)
478 {
479 if ($prefixed === true) {
480 return PREFIX_DB . self::TABLE;
481 } else {
482 return self::TABLE;
483 }
484 }
485
486 /**
487 * Get table's PK
488 *
489 * @return string
490 */
491 protected function getPk()
492 {
493 return self::PK;
494 }
495
496 /**
497 * Set filters
498 *
499 * @param HistoryList $filters Filters
500 *
501 * @return History
502 */
503 public function setFilters(HistoryList $filters)
504 {
505 $this->filters = $filters;
506 return $this;
507 }
508
509 /**
510 * Get count for current query
511 *
512 * @return int
513 */
514 public function getCount()
515 {
516 return $this->count;
517 }
518
519 /**
520 * Get users list
521 *
522 * @return array
523 */
524 public function getUsersList()
525 {
526 return $this->users;
527 }
528
529 /**
530 * Get actions list
531 *
532 * @return array
533 */
534 public function getActionsList()
535 {
536 return $this->actions;
537 }
538 }