xref: /webtrees/app/SessionDatabaseHandler.php (revision 1e1d9b146831490ecdbb8fc6ef54546ccc0b270e)
1<?php
2
3/**
4 * webtrees: online genealogy
5 * Copyright (C) 2023 webtrees development team
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17
18declare(strict_types=1);
19
20namespace Fisharebest\Webtrees;
21
22use Psr\Http\Message\ServerRequestInterface;
23use SessionHandlerInterface;
24
25use function date;
26use function time;
27
28/**
29 * Session handling - stores sessions in the database.
30 */
31class SessionDatabaseHandler implements SessionHandlerInterface
32{
33    private ServerRequestInterface $request;
34
35    private ?object $row = null;
36
37    public function __construct(ServerRequestInterface $request)
38    {
39        $this->request = $request;
40    }
41
42    public function open(string $path, string $name): bool
43    {
44        return true;
45    }
46
47    public function close(): bool
48    {
49        return true;
50    }
51
52    public function read(string $id): string
53    {
54        $this->row = DB::table('session')
55            ->where('session_id', '=', $id)
56            ->first();
57
58        return $this->row->session_data ?? '';
59    }
60
61    public function write(string $id, string $data): bool
62    {
63        $ip_address = Validator::attributes($this->request)->string('client-ip');
64        $user_id    = (int) Auth::id();
65
66        if ($this->row === null) {
67            DB::table('session')->insert([
68                'session_id'   => $id,
69                'session_time' => date('Y-m-d H:i:s'),
70                'user_id'      => $user_id,
71                'ip_address'   => $ip_address,
72                'session_data' => $data,
73            ]);
74        } else {
75            $updates = [];
76
77            // The user ID can change if we masquerade as another user.
78            if ((int) $this->row->user_id !== $user_id) {
79                $updates['user_id'] = $user_id;
80            }
81
82            if ($this->row->ip_address !== $ip_address) {
83                $updates['ip_address'] = $ip_address;
84            }
85
86            if ($this->row->session_data !== $data) {
87                $updates['session_data'] = $data;
88            }
89
90            // Only update session once a minute to reduce contention on the session table.
91            if (date('Y-m-d H:i:s', time() - 60) > $this->row->session_time) {
92                $updates['session_time'] =  date('Y-m-d H:i:s');
93            }
94
95            if ($updates !== []) {
96                DB::table('session')
97                    ->where('session_id', '=', $id)
98                    ->update($updates);
99            }
100        }
101
102        return true;
103    }
104
105    public function destroy(string $id): bool
106    {
107        DB::table('session')
108            ->where('session_id', '=', $id)
109            ->delete();
110
111        return true;
112    }
113
114    public function gc(int $max_lifetime): int
115    {
116        return DB::table('session')
117            ->where('session_time', '<', date('Y-m-d H:i:s', time() - $max_lifetime))
118            ->delete();
119    }
120}
121