Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.63% covered (success)
98.63%
72 / 73
88.89% covered (success)
88.89%
8 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
SessionRepository
98.63% covered (success)
98.63%
72 / 73
88.89% covered (success)
88.89%
8 / 9
26
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
 countOnlineUsersFromSessions
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
4
 countOnlineUsersFromFaqUser
90.91% covered (success)
90.91%
10 / 11
0.00% covered (danger)
0.00%
0 / 1
4.01
 getTimeBySessionId
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 getSessionsByDateRange
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
4
 countTotalSessions
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 deleteSessionsByTimeRange
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 deleteAllSessions
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getSessionTimestamps
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2
3/**
4 * Session Repository.
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 2025-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     2025-12-02
16 */
17
18declare(strict_types=1);
19
20namespace phpMyFAQ\Administration;
21
22use phpMyFAQ\Configuration;
23use phpMyFAQ\Database;
24
25readonly class SessionRepository
26{
27    public function __construct(
28        private Configuration $configuration,
29    ) {
30    }
31
32    /**
33     * Counts distinct user IDs from sessions within a time window.
34     */
35    public function countOnlineUsersFromSessions(int $minTimestamp): int
36    {
37        $query = sprintf(
38            'SELECT COUNT(DISTINCT user_id) AS cnt FROM %sfaqsessions WHERE time >= %d AND user_id > 0',
39            Database::getTablePrefix(),
40            $minTimestamp,
41        );
42
43        $result = $this->configuration->getDb()->query($query);
44        if ($result) {
45            $row = $this->configuration->getDb()->fetchObject($result);
46            if (is_object($row) && property_exists($row, 'cnt')) {
47                return (int) $row->cnt;
48            }
49        }
50
51        return 0;
52    }
53
54    /**
55     * Counts users with active sessions from the faquser table.
56     */
57    public function countOnlineUsersFromFaqUser(int $minTimestamp): int
58    {
59        $query = sprintf(
60            'SELECT COUNT(*) AS cnt FROM %sfaquser WHERE session_id IS NOT NULL AND session_timestamp >= %d AND success = 1',
61            Database::getTablePrefix(),
62            $minTimestamp,
63        );
64
65        $result = $this->configuration->getDb()->query($query);
66        if ($result) {
67            $row = $this->configuration->getDb()->fetchObject($result);
68            if (is_object($row) && property_exists($row, 'cnt')) {
69                return (int) $row->cnt;
70            }
71        }
72
73        return 0;
74    }
75
76    /**
77     * Fetches the timestamp for a specific session ID.
78     */
79    public function getTimeBySessionId(int $sessionId): int
80    {
81        $query = sprintf('SELECT time FROM %sfaqsessions WHERE sid = %d', Database::getTablePrefix(), $sessionId);
82
83        $result = $this->configuration->getDb()->query($query);
84
85        if ($result) {
86            $res = $this->configuration->getDb()->fetchObject($result);
87            if (is_object($res) && property_exists($res, 'time')) {
88                return (int) $res->time;
89            }
90        }
91
92        return 0;
93    }
94
95    /**
96     * Fetches all sessions within a time range.
97     *
98     * @return array<int, \stdClass>
99     */
100    public function getSessionsByDateRange(int $firstHour, int $lastHour): array
101    {
102        $query = sprintf(
103            'SELECT sid, ip, time FROM %sfaqsessions WHERE time > %d AND time < %d ORDER BY time',
104            Database::getTablePrefix(),
105            $firstHour,
106            $lastHour,
107        );
108
109        $result = $this->configuration->getDb()->query($query);
110        $sessions = [];
111
112        while (true) {
113            $row = $this->configuration->getDb()->fetchObject($result);
114            if ($row === null || $row === false) {
115                break;
116            }
117
118            $sessions[] = $row;
119        }
120
121        return $sessions;
122    }
123
124    /**
125     * Counts the total number of sessions.
126     */
127    public function countTotalSessions(): int
128    {
129        $query = sprintf('SELECT COUNT(sid) as num_sessions FROM %sfaqsessions', Database::getTablePrefix());
130
131        $result = $this->configuration->getDb()->query($query);
132        if ($result) {
133            $row = $this->configuration->getDb()->fetchObject($result);
134            return $row instanceof \stdClass ? (int) $row->num_sessions : 0;
135        }
136
137        return 0;
138    }
139
140    /**
141     * Deletes sessions within a time range.
142     */
143    public function deleteSessionsByTimeRange(int $first, int $last): bool
144    {
145        $query = sprintf(
146            'DELETE FROM %sfaqsessions WHERE time >= %d AND time <= %d',
147            Database::getTablePrefix(),
148            $first,
149            $last,
150        );
151
152        return (bool) $this->configuration->getDb()->query($query);
153    }
154
155    /**
156     * Deletes all sessions.
157     */
158    public function deleteAllSessions(): bool
159    {
160        $query = sprintf('DELETE FROM %sfaqsessions', Database::getTablePrefix());
161
162        return (bool) $this->configuration->getDb()->query($query);
163    }
164
165    /**
166     * Fetches all session timestamps within a time range.
167     *
168     * @return array<int, int>
169     */
170    public function getSessionTimestamps(int $startDate, int $endDate): array
171    {
172        $query = sprintf(
173            'SELECT time FROM %sfaqsessions WHERE time > %d AND time < %d;',
174            Database::getTablePrefix(),
175            $startDate,
176            $endDate,
177        );
178
179        $result = $this->configuration->getDb()->query($query);
180        $timestamps = [];
181
182        while (true) {
183            $row = $this->configuration->getDb()->fetchObject($result);
184            if ($row === null || $row === false) {
185                break;
186            }
187
188            $timestamps[] = (int) $row->time;
189        }
190
191        return $timestamps;
192    }
193}