14d7dd147SGreg Roach<?php 23976b470SGreg Roach 34d7dd147SGreg Roach/** 44d7dd147SGreg Roach * webtrees: online genealogy 5d11be702SGreg Roach * Copyright (C) 2023 webtrees development team 64d7dd147SGreg Roach * This program is free software: you can redistribute it and/or modify 74d7dd147SGreg Roach * it under the terms of the GNU General Public License as published by 84d7dd147SGreg Roach * the Free Software Foundation, either version 3 of the License, or 94d7dd147SGreg Roach * (at your option) any later version. 104d7dd147SGreg Roach * This program is distributed in the hope that it will be useful, 114d7dd147SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of 124d7dd147SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 134d7dd147SGreg Roach * GNU General Public License for more details. 144d7dd147SGreg Roach * You should have received a copy of the GNU General Public License 1589f7189bSGreg Roach * along with this program. If not, see <https://www.gnu.org/licenses/>. 164d7dd147SGreg Roach */ 17fcfa147eSGreg Roach 184d7dd147SGreg Roachdeclare(strict_types=1); 194d7dd147SGreg Roach 204d7dd147SGreg Roachnamespace Fisharebest\Webtrees; 214d7dd147SGreg Roach 224d7dd147SGreg Roachuse Psr\Http\Message\ServerRequestInterface; 234d7dd147SGreg Roachuse SessionHandlerInterface; 244d7dd147SGreg Roach 25f12c148eSGreg Roachuse function date; 26849190baSGreg Roachuse function time; 27f12c148eSGreg Roach 284d7dd147SGreg Roach/** 294d7dd147SGreg Roach * Session handling - stores sessions in the database. 304d7dd147SGreg Roach */ 314d7dd147SGreg Roachclass SessionDatabaseHandler implements SessionHandlerInterface 324d7dd147SGreg Roach{ 33c4943cffSGreg Roach private ServerRequestInterface $request; 344d7dd147SGreg Roach 35*f25fc0f9SGreg Roach private ?object $row = null; 36dfc849d2SGreg Roach 37c4943cffSGreg Roach /** 38c4943cffSGreg Roach * @param ServerRequestInterface $request 39c4943cffSGreg Roach */ 403b267247SGreg Roach public function __construct(ServerRequestInterface $request) 413b267247SGreg Roach { 424d7dd147SGreg Roach $this->request = $request; 434d7dd147SGreg Roach } 444d7dd147SGreg Roach 454d7dd147SGreg Roach /** 46c4943cffSGreg Roach * @param string $path 473976b470SGreg Roach * @param string $name 484d7dd147SGreg Roach * 494d7dd147SGreg Roach * @return bool 504d7dd147SGreg Roach */ 5160df7837SGreg Roach public function open(string $path, string $name): bool 524d7dd147SGreg Roach { 534d7dd147SGreg Roach return true; 544d7dd147SGreg Roach } 554d7dd147SGreg Roach 564d7dd147SGreg Roach /** 574d7dd147SGreg Roach * @return bool 584d7dd147SGreg Roach */ 594d7dd147SGreg Roach public function close(): bool 604d7dd147SGreg Roach { 614d7dd147SGreg Roach return true; 624d7dd147SGreg Roach } 634d7dd147SGreg Roach 644d7dd147SGreg Roach /** 65c4943cffSGreg Roach * @param string $id 664d7dd147SGreg Roach * 674d7dd147SGreg Roach * @return string 684d7dd147SGreg Roach */ 6960df7837SGreg Roach public function read(string $id): string 704d7dd147SGreg Roach { 71dfc849d2SGreg Roach $this->row = DB::table('session') 72c4943cffSGreg Roach ->where('session_id', '=', $id) 73dfc849d2SGreg Roach ->first(); 74dfc849d2SGreg Roach 75dfc849d2SGreg Roach return $this->row->session_data ?? ''; 764d7dd147SGreg Roach } 774d7dd147SGreg Roach 784d7dd147SGreg Roach /** 79c4943cffSGreg Roach * @param string $id 80c4943cffSGreg Roach * @param string $data 814d7dd147SGreg Roach * 824d7dd147SGreg Roach * @return bool 834d7dd147SGreg Roach */ 8460df7837SGreg Roach public function write(string $id, string $data): bool 854d7dd147SGreg Roach { 86b55cbc6bSGreg Roach $ip_address = Validator::attributes($this->request)->string('client-ip'); 87aadd14baSGreg Roach $user_id = (int) Auth::id(); 88bfd75698SGreg Roach $now = Registry::timestampFactory()->now(); 89aadd14baSGreg Roach 90dfc849d2SGreg Roach if ($this->row === null) { 91dfc849d2SGreg Roach DB::table('session')->insert([ 92c4943cffSGreg Roach 'session_id' => $id, 93bfd75698SGreg Roach 'session_time' => $now->toDateTimeString(), 94aadd14baSGreg Roach 'user_id' => $user_id, 95aadd14baSGreg Roach 'ip_address' => $ip_address, 96c4943cffSGreg Roach 'session_data' => $data, 974d7dd147SGreg Roach ]); 98dfc849d2SGreg Roach } else { 99dfc849d2SGreg Roach $updates = []; 100dfc849d2SGreg Roach 101dfc849d2SGreg Roach // The user ID can change if we masquerade as another user. 102aadd14baSGreg Roach if ((int) $this->row->user_id !== $user_id) { 103aadd14baSGreg Roach $updates['user_id'] = $user_id; 104dfc849d2SGreg Roach } 105dfc849d2SGreg Roach 106aadd14baSGreg Roach if ($this->row->ip_address !== $ip_address) { 107aadd14baSGreg Roach $updates['ip_address'] = $ip_address; 108dfc849d2SGreg Roach } 109dfc849d2SGreg Roach 110c4943cffSGreg Roach if ($this->row->session_data !== $data) { 111c4943cffSGreg Roach $updates['session_data'] = $data; 112dfc849d2SGreg Roach } 113dfc849d2SGreg Roach 114bfd75698SGreg Roach // Only update session once a minute to reduce contention on the session table. 115bfd75698SGreg Roach if ($now->subtractMinutes(1)->timestamp() > Registry::timestampFactory()->fromString($this->row->session_time)->timestamp()) { 116bfd75698SGreg Roach $updates['session_time'] = $now->toDateTimeString(); 117dfc849d2SGreg Roach } 118dfc849d2SGreg Roach 119dfc849d2SGreg Roach if ($updates !== []) { 120dfc849d2SGreg Roach DB::table('session') 121c4943cffSGreg Roach ->where('session_id', '=', $id) 122dfc849d2SGreg Roach ->update($updates); 123dfc849d2SGreg Roach } 124dfc849d2SGreg Roach } 1254d7dd147SGreg Roach 1264d7dd147SGreg Roach return true; 1274d7dd147SGreg Roach } 1284d7dd147SGreg Roach 1294d7dd147SGreg Roach /** 130c4943cffSGreg Roach * @param string $id 1314d7dd147SGreg Roach * 1324d7dd147SGreg Roach * @return bool 1334d7dd147SGreg Roach */ 13460df7837SGreg Roach public function destroy(string $id): bool 1354d7dd147SGreg Roach { 1364d7dd147SGreg Roach DB::table('session') 137c4943cffSGreg Roach ->where('session_id', '=', $id) 1384d7dd147SGreg Roach ->delete(); 1394d7dd147SGreg Roach 1404d7dd147SGreg Roach return true; 1414d7dd147SGreg Roach } 1424d7dd147SGreg Roach 1434d7dd147SGreg Roach /** 144c4943cffSGreg Roach * @param int $max_lifetime 1454d7dd147SGreg Roach * 14660df7837SGreg Roach * @return int 1474d7dd147SGreg Roach */ 14860df7837SGreg Roach public function gc(int $max_lifetime): int 1494d7dd147SGreg Roach { 15095edec1eSGreg Roach return DB::table('session') 151f12c148eSGreg Roach ->where('session_time', '<', date('Y-m-d H:i:s', time() - $max_lifetime)) 1524d7dd147SGreg Roach ->delete(); 1534d7dd147SGreg Roach } 1544d7dd147SGreg Roach} 155