Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
73.81% covered (warning)
73.81%
31 / 42
77.78% covered (warning)
77.78%
7 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
Session
73.81% covered (warning)
73.81%
31 / 42
77.78% covered (warning)
77.78%
7 / 9
22.19
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getNumberOfOnlineUsers
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
20
 getTimeFromSessionId
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSessionsByDate
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 getNumberOfSessions
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 deleteSessions
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 deleteAllSessions
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getLast30DaysVisits
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getVisitsForDays
94.44% covered (success)
94.44%
17 / 18
0.00% covered (danger)
0.00%
0 / 1
5.00
1<?php
2
3/**
4 * The Administration Session class.
5 *
6 * This Source Code Form is subject to the terms of the Mozilla Public License,
7 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
8 * obtain one at https://mozilla.org/MPL/2.0/.
9 *
10 * @package   phpMyFAQ
11 * @author    Thorsten Rinne <thorsten@phpmyfaq.de>
12 * @copyright 2024-2026 phpMyFAQ Team
13 * @license   https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
14 * @link      https://www.phpmyfaq.de
15 * @since     2024-10-29
16 */
17
18declare(strict_types=1);
19
20namespace phpMyFAQ\Administration;
21
22use phpMyFAQ\Configuration;
23use stdClass;
24use Symfony\Component\HttpFoundation\Request;
25use Throwable;
26
27readonly class Session
28{
29    private SessionRepository $sessionRepository;
30
31    public function __construct(
32        private Configuration $configuration,
33    ) {
34        $this->sessionRepository = new SessionRepository($configuration);
35    }
36
37    /**
38     * Returns the number of currently online (logged-in) users within the last windowSeconds.
39     */
40    public function getNumberOfOnlineUsers(int $windowSeconds = 600): int
41    {
42        $count = 0;
43
44        try {
45            $minTimestamp = (int) Request::createFromGlobals()->server->get('REQUEST_TIME') - $windowSeconds;
46            if ($this->configuration->get(item: 'main.enableUserTracking')) {
47                $count = $this->sessionRepository->countOnlineUsersFromSessions($minTimestamp
48                - (PMF_AUTH_TIMEOUT * 60));
49            }
50
51            if (!$this->configuration->get(item: 'main.enableUserTracking')) {
52                $count = $this->sessionRepository->countOnlineUsersFromFaqUser($minTimestamp);
53            }
54        } catch (Throwable) {
55            $count = 0;
56        }
57
58        return $count;
59    }
60
61    public function getTimeFromSessionId(int $sessionId): int
62    {
63        return $this->sessionRepository->getTimeBySessionId($sessionId);
64    }
65
66    /**
67     * Returns all sessions from a date.
68     *
69     * @param int $firstHour First hour
70     * @param int $lastHour Last hour
71     *
72     * @return array<int, string[]>
73     */
74    public function getSessionsByDate(int $firstHour, int $lastHour): array
75    {
76        $sessions = [];
77        $rows = $this->sessionRepository->getSessionsByDateRange($firstHour, $lastHour);
78
79        foreach ($rows as $row) {
80            $sessions[(int) $row->sid] = [
81                'ip' => (string) $row->ip,
82                'time' => (string) $row->time,
83            ];
84        }
85
86        return $sessions;
87    }
88
89    /**
90     * Returns the number of sessions.
91     */
92    public function getNumberOfSessions(): int
93    {
94        return $this->sessionRepository->countTotalSessions();
95    }
96
97    /**
98     * Deletes the sessions for a given timespan.
99     *
100     * @param int $first First session ID
101     * @param int $last Last session ID
102     */
103    public function deleteSessions(int $first, int $last): bool
104    {
105        return $this->sessionRepository->deleteSessionsByTimeRange($first, $last);
106    }
107
108    /**
109     * Deletes all entries in the table.
110     */
111    public function deleteAllSessions(): bool
112    {
113        return $this->sessionRepository->deleteAllSessions();
114    }
115
116    /**
117     * Calculates the number of visits per day the last 30 days.
118     *
119     * @return array<int, stdClass>
120     */
121    public function getLast30DaysVisits(int $endDate): array
122    {
123        return $this->getVisitsForDays($endDate, 30);
124    }
125
126    /**
127     * Calculates the number of visits per day for the given number of days.
128     *
129     * @return array<int, stdClass>
130     */
131    public function getVisitsForDays(int $endDate, int $days = 30): array
132    {
133        $days = max(1, min($days, 365));
134        $stats = [];
135        $completeData = [];
136        // Inclusive range: the loop below counts both the start and end day,
137        // so subtract one day to yield exactly $days buckets.
138        $startDate = $endDate - (($days - 1) * 86_400);
139
140        $visits = $this->sessionRepository->getSessionTimestamps($startDate, $endDate);
141
142        for ($date = $startDate; $date <= $endDate; $date += 86_400) {
143            $stats[date(format: 'Y-m-d', timestamp: $date)] = 0;
144        }
145
146        foreach ($visits as $visitDate) {
147            $key = date(format: 'Y-m-d', timestamp: $visitDate);
148            if (!array_key_exists($key, $stats)) {
149                continue;
150            }
151
152            ++$stats[$key];
153        }
154
155        foreach ($stats as $date => $number) {
156            $visit = new stdClass();
157            $visit->date = $date;
158            $visit->number = $number;
159            $completeData[] = $visit;
160        }
161
162        return $completeData;
163    }
164}