58a4a8b15f6a29fdf7c6a3df3b0abf9729e4327f
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Session / Backend / DatabaseSessionBackend.php
1 <?php
2 declare(strict_types=1);
3 namespace TYPO3\CMS\Core\Session\Backend;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18 use Doctrine\DBAL\DBALException;
19 use TYPO3\CMS\Core\Database\Connection;
20 use TYPO3\CMS\Core\Database\ConnectionPool;
21 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
22 use TYPO3\CMS\Core\Session\Backend\Exception\SessionNotCreatedException;
23 use TYPO3\CMS\Core\Session\Backend\Exception\SessionNotFoundException;
24 use TYPO3\CMS\Core\Session\Backend\Exception\SessionNotUpdatedException;
25 use TYPO3\CMS\Core\Utility\GeneralUtility;
26
27 /**
28 * Class DatabaseSessionBackend
29 *
30 * This session backend requires the 'table' configuration option. If the backend is used to holds non-authenticated
31 * sessions (default if 'TYPO3_MODE' is 'FE'), the 'ses_anonymous' configuration option must be set to true.
32 */
33 class DatabaseSessionBackend implements SessionBackendInterface
34 {
35 /**
36 * @var array
37 */
38 protected $configuration = [];
39
40 /**
41 * @var bool Indicates whether the sessions table has the ses_anonymous column
42 */
43 protected $hasAnonymousSessions = false;
44
45 /**
46 * Initializes the session backend
47 *
48 * @param string $identifier Name of the session type, e.g. FE or BE
49 * @param array $configuration
50 * @internal To be used only by SessionManager
51 */
52 public function initialize(string $identifier, array $configuration)
53 {
54 $this->hasAnonymousSessions = (bool)$configuration['has_anonymous'];
55 $this->configuration = $configuration;
56 }
57
58 /**
59 * Checks if the configuration is valid
60 *
61 * @return bool
62 * @throws \InvalidArgumentException
63 * @internal To be used only by SessionManager
64 */
65 public function validateConfiguration(): bool
66 {
67 if (empty($this->configuration['table'])) {
68 throw new \InvalidArgumentException(
69 'The session backend "' . get_class($this) . '" needs a "table" configuration.',
70 1442996707
71 );
72 }
73 return true;
74 }
75
76 /**
77 * Read session data
78 *
79 * @param string $sessionId
80 * @return array Returns the session data
81 * @throws SessionNotFoundException
82 */
83 public function get(string $sessionId): array
84 {
85 $query = $this->getQueryBuilder();
86
87 $query->select('*')
88 ->from($this->configuration['table'])
89 ->where($query->expr()->eq('ses_id', $query->createNamedParameter($sessionId, \PDO::PARAM_STR)));
90
91 $result = $query->execute()->fetch();
92
93 if (!is_array($result)) {
94 throw new SessionNotFoundException(
95 'The session with identifier ' . $sessionId . ' was not found ',
96 1481885483
97 );
98 }
99 return $result;
100 }
101
102 /**
103 * Delete a session record
104 *
105 * @param string $sessionId
106 * @return bool true if the session was deleted, false it session could not be found
107 */
108 public function remove(string $sessionId): bool
109 {
110 $query = $this->getQueryBuilder();
111 $query->delete($this->configuration['table'])
112 ->where($query->expr()->eq('ses_id', $query->createNamedParameter($sessionId, \PDO::PARAM_STR)));
113
114 return (bool)$query->execute();
115 }
116
117 /**
118 * Write session data. This method prevents overriding existing session data.
119 * ses_id will always be set to $sessionId and overwritten if existing in $sessionData
120 * This method updates ses_tstamp automatically
121 *
122 * @param string $sessionId
123 * @param array $sessionData
124 * @return array The newly created session record.
125 * @throws SessionNotCreatedException
126 */
127 public function set(string $sessionId, array $sessionData): array
128 {
129 $sessionData['ses_id'] = $sessionId;
130 $sessionData['ses_tstamp'] = $GLOBALS['EXEC_TIME'] ?? time();
131
132 try {
133 $this->getConnection()->insert(
134 $this->configuration['table'],
135 $sessionData,
136 ['ses_data' => \PDO::PARAM_LOB]
137 );
138 } catch (DBALException $e) {
139 throw new SessionNotCreatedException(
140 'Session could not be written to database: ' . $e->getMessage(),
141 1481895005,
142 $e
143 );
144 }
145
146 return $sessionData;
147 }
148
149 /**
150 * Updates the session data.
151 * ses_id will always be set to $sessionId and overwritten if existing in $sessionData
152 * This method updates ses_tstamp automatically
153 *
154 * @param string $sessionId
155 * @param array $sessionData The session data to update. Data may be partial.
156 * @return array $sessionData The newly updated session record.
157 * @throws SessionNotUpdatedException
158 */
159 public function update(string $sessionId, array $sessionData): array
160 {
161 $sessionData['ses_id'] = $sessionId;
162 $sessionData['ses_tstamp'] = $GLOBALS['EXEC_TIME'] ?? time();
163
164 try {
165 // allow 0 records to be affected, happens when no columns where changed
166 $this->getConnection()->update(
167 $this->configuration['table'],
168 $sessionData,
169 ['ses_id' => $sessionId],
170 ['ses_data' => \PDO::PARAM_LOB]
171 );
172 } catch (DBALException $e) {
173 throw new SessionNotUpdatedException(
174 'Session with id ' . $sessionId . ' could not be updated: ' . $e->getMessage(),
175 1481889220,
176 $e
177 );
178 }
179 return $sessionData;
180 }
181
182 /**
183 * Garbage Collection
184 *
185 * @param int $maximumLifetime maximum lifetime of authenticated user sessions, in seconds.
186 * @param int $maximumAnonymousLifetime maximum lifetime of non-authenticated user sessions, in seconds. If set to 0, non-authenticated sessions are ignored.
187 */
188 public function collectGarbage(int $maximumLifetime, int $maximumAnonymousLifetime = 0)
189 {
190 $query = $this->getQueryBuilder();
191
192 $query->delete($this->configuration['table'])
193 ->where($query->expr()->lt('ses_tstamp', (int)($GLOBALS['EXEC_TIME'] - (int)$maximumLifetime)))
194 ->andWhere($this->hasAnonymousSessions ? $query->expr()->eq('ses_anonymous', 0) :' 1 = 1');
195 $query->execute();
196
197 if ($maximumAnonymousLifetime > 0 && $this->hasAnonymousSessions) {
198 $query = $this->getQueryBuilder();
199 $query->delete($this->configuration['table'])
200 ->where($query->expr()->lt('ses_tstamp', (int)($GLOBALS['EXEC_TIME'] - (int)$maximumAnonymousLifetime)))
201 ->andWhere($query->expr()->eq('ses_anonymous', 1));
202 $query->execute();
203 }
204 }
205
206 /**
207 * List all sessions
208 *
209 * @return array Return a list of all user sessions. The list may be empty
210 */
211 public function getAll(): array
212 {
213 $query = $this->getQueryBuilder();
214 $query->select('*')->from($this->configuration['table']);
215 return $query->execute()->fetchAll();
216 }
217
218 /**
219 * @return QueryBuilder
220 */
221 protected function getQueryBuilder(): QueryBuilder
222 {
223 return $this->getConnection()->createQueryBuilder();
224 }
225
226 /**
227 * @return Connection
228 */
229 protected function getConnection(): Connection
230 {
231 return GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->configuration['table']);
232 }
233 }