4 * Copyright © 2003-2024 The Galette Team
6 * This file is part of Galette (https://galette.eu).
8 * Galette is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
13 * Galette is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with Galette. If not, see <http://www.gnu.org/licenses/>.
22 namespace Galette\Core
;
26 use Galette\Filters\HistoryList
;
27 use Laminas\Db\Sql\Expression
;
28 use Laminas\Db\Adapter\Adapter
;
29 use Laminas\Db\Sql\Select
;
34 * @author Johan Cwiklinski <johan@x-tnd.be>
36 * @property HistoryList $filters
41 public const TABLE
= 'logs';
42 public const PK
= 'id_log';
46 protected Login
$login;
47 protected Preferences
$preferences;
48 protected HistoryList
$filters;
50 /** @var array<int, string> */
51 protected array $users;
52 /** @var array<int, string> */
53 protected array $actions;
55 protected bool $with_lists = true;
60 * @param Db $zdb Database
61 * @param Login $login Login
62 * @param Preferences $preferences Preferences
63 * @param ?HistoryList $filters Filtering
65 public function __construct(Db
$zdb, Login
$login, Preferences
$preferences, HistoryList
$filters = null)
68 $this->login
= $login;
69 $this->preferences
= $preferences;
71 if ($filters === null) {
72 $this->filters
= new HistoryList();
74 $this->filters
= $filters;
79 * Helper function to find the user IP address
81 * This function uses the client address or the appropriate part of
82 * X-Forwarded-For, if present and the configuration specifies it.
83 * (blindly trusting X-Forwarded-For would make the IP address logging
84 * very easy to deveive.
88 public static function findUserIPAddress(): string
91 defined('GALETTE_X_FORWARDED_FOR_INDEX')
92 && isset($_SERVER['HTTP_X_FORWARDED_FOR'])
94 $split_xff = preg_split('/,\s*/', $_SERVER['HTTP_X_FORWARDED_FOR']);
95 return $split_xff[count($split_xff) - GALETTE_X_FORWARDED_FOR_INDEX
];
97 return $_SERVER['REMOTE_ADDR'];
103 * @param string $action the action to log
104 * @param string $argument the argument
105 * @param string $query the query (if relevant)
107 * @return bool true if entry was successfully added, false otherwise
109 public function add(string $action, string $argument = '', string $query = ''): bool
111 if ($this->preferences
->pref_log
== Preferences
::LOG_DISABLED
) {
117 if (PHP_SAPI
=== 'cli') {
120 $ip = self
::findUserIpAddress();
125 'date_log' => date('Y-m-d H:i:s'),
127 'adh_log' => $this->login
->login ??
'',
128 'action_log' => $action,
129 'text_log' => $argument,
133 $insert = $this->zdb
->insert($this->getTableName());
134 $insert->values($values);
135 $this->zdb
->execute($insert);
136 } catch (Throwable
$e) {
138 "An error occurred trying to add log entry. " . $e->getMessage(),
152 public function clean(): bool
155 $this->zdb
->db
->query(
156 'TRUNCATE TABLE ' . $this->getTableName(true),
157 Adapter
::QUERY_MODE_EXECUTE
159 $this->add('Logs flushed');
160 $this->filters
= new HistoryList();
162 } catch (Throwable
$e) {
163 $this->add('Error flushing logs');
165 'Unable to flush logs. | ' . $e->getMessage(),
173 * Get the entire history list
175 * @return array<int, object>
177 public function getHistory(): array
180 $select = $this->zdb
->select($this->getTableName());
181 $this->buildWhereClause($select);
182 $select->order($this->buildOrderClause());
183 if ($this->with_lists
=== true) {
184 $this->buildLists($select);
186 $this->proceedCount($select);
187 //add limits to retrieve only relavant rows
188 $this->filters
->setLimits($select);
189 $results = $this->zdb
->execute($select);
192 foreach ($results as $result) {
193 $entries[] = $result;
197 } catch (Throwable
$e) {
199 'Unable to get history. | ' . $e->getMessage(),
207 * Builds users and actions lists
209 * @param Select $select Original select
213 private function buildLists(Select
$select): void
216 $usersSelect = clone $select;
217 $usersSelect->reset($usersSelect::COLUMNS
);
218 $usersSelect->reset($usersSelect::ORDER
);
219 $usersSelect->quantifier('DISTINCT')->columns(['adh_log']);
220 $usersSelect->order(['adh_log ASC']);
222 $results = $this->zdb
->execute($usersSelect);
225 foreach ($results as $result) {
226 $this->users
[] = $result->adh_log
;
228 } catch (Throwable
$e) {
230 'Cannot list members from history! | ' . $e->getMessage(),
236 $actionsSelect = clone $select;
237 $actionsSelect->reset($actionsSelect::COLUMNS
);
238 $actionsSelect->reset($actionsSelect::ORDER
);
239 $actionsSelect->quantifier('DISTINCT')->columns(['action_log']);
240 $actionsSelect->order(['action_log ASC']);
242 $results = $this->zdb
->execute($actionsSelect);
245 foreach ($results as $result) {
246 $this->actions
[] = $result->action_log
;
248 } catch (Throwable
$e) {
250 'Cannot list actions from history! | ' . $e->getMessage(),
258 * Builds the order clause
260 * @return array<int, string> SQL ORDER clauses
262 protected function buildOrderClause(): array
266 switch ($this->filters
->orderby
) {
267 case HistoryList
::ORDERBY_DATE
:
268 $order[] = 'date_log ' . $this->filters
->ordered
;
270 case HistoryList
::ORDERBY_IP
:
271 $order[] = 'ip_log ' . $this->filters
->ordered
;
273 case HistoryList
::ORDERBY_USER
:
274 $order[] = 'adh_log ' . $this->filters
->ordered
;
276 case HistoryList
::ORDERBY_ACTION
:
277 $order[] = 'action_log ' . $this->filters
->ordered
;
285 * Builds where clause, for filtering on simple list mode
287 * @param Select $select Original select
291 private function buildWhereClause(Select
$select): void
294 if ($this->filters
->start_date_filter
!= null) {
295 $d = new \
DateTime($this->filters
->raw_start_date_filter
);
296 $d->setTime(0, 0, 0);
297 $select->where
->greaterThanOrEqualTo(
299 $d->format('Y-m-d H:i:s')
303 if ($this->filters
->end_date_filter
!= null) {
304 $d = new \
DateTime($this->filters
->raw_end_date_filter
);
305 $d->setTime(23, 59, 59);
306 $select->where
->lessThanOrEqualTo(
308 $d->format('Y-m-d H:i:s')
312 //@phpstan-ignore-next-line
313 if ($this->filters
->user_filter
!= null && $this->filters
->user_filter
!= '0') {
314 $select->where
->equalTo(
316 $this->filters
->user_filter
320 //@phpstan-ignore-next-line
321 if ($this->filters
->action_filter
!= null && $this->filters
->action_filter
!= '0') {
322 $select->where
->equalTo(
324 $this->filters
->action_filter
327 } catch (Throwable
$e) {
329 __METHOD__
. ' | ' . $e->getMessage(),
337 * Count history entries from the query
339 * @param Select $select Original select
343 private function proceedCount(Select
$select): void
346 $countSelect = clone $select;
347 $countSelect->reset($countSelect::COLUMNS
);
348 $countSelect->reset($countSelect::JOINS
);
349 $countSelect->reset($countSelect::ORDER
);
350 $countSelect->columns(
352 $this->getPk() => new Expression('COUNT(' . $this->getPk() . ')')
356 $results = $this->zdb
->execute($countSelect);
357 $result = $results->current();
360 $this->count
= (int)$result->$k;
361 $this->filters
->setCounter($this->count
);
362 } catch (Throwable
$e) {
364 'Cannot count history | ' . $e->getMessage(),
372 * Global getter method
374 * @param string $name name of the property we want to retrieve
376 * @return mixed the called property
378 public function __get(string $name): mixed
380 $forbidden = array();
381 if (!in_array($name, $forbidden)) {
385 throw new \
RuntimeException(
387 'Unable to get property "%s::%s"!',
395 * Global isset method
396 * Required for twig to access properties via __get
398 * @param string $name name of the property we want to retrieve
402 public function __isset(string $name): bool
404 if (isset($this->$name)) {
411 * Global setter method
413 * @param string $name name of the property we want to assign a value to
414 * @param mixed $value a relevant value for the property
418 public function __set(string $name, mixed $value): void
421 '[History] Setting property `' . $name . '`',
425 $forbidden = array();
426 if (!in_array($name, $forbidden)) {
429 $this->$name = $value;
434 '[History] Unable to set property `' . $name . '`',
443 * @param boolean $prefixed Whether table name should be prefixed
447 protected function getTableName(bool $prefixed = false): string
449 if ($prefixed === true) {
450 return PREFIX_DB
. self
::TABLE
;
461 protected function getPk(): string
469 * @param HistoryList $filters Filters
473 public function setFilters(HistoryList
$filters): self
475 $this->filters
= $filters;
480 * Get count for current query
484 public function getCount(): int
492 * @return array<int, string>
494 public function getUsersList(): array
502 * @return array<int, string>
504 public function getActionsList(): array
506 return $this->actions
;