1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* PHP 5.4 defines SessionHandlerInterface, but PHP 5.3 doesn't. For backwards compatibility, if it doesn't exist |
5
|
|
|
* (and no other fallback exists in other libraries) then define it. |
6
|
|
|
* |
7
|
|
|
* Then, either way, add a new function "register_sessionhandler" which takes a SessionHandlerInterface and |
8
|
|
|
* registers it (including registering session_write_close as a shutdown function) |
9
|
|
|
*/ |
10
|
|
|
if (!interface_exists('SessionHandlerInterface')) { |
11
|
|
|
interface SessionHandlerInterface |
|
|
|
|
12
|
|
|
{ |
13
|
|
|
/* Methods */ |
14
|
|
|
public function close(); |
15
|
|
|
public function destroy($session_id); |
16
|
|
|
public function gc($maxlifetime); |
17
|
|
|
public function open($save_path, $name); |
18
|
|
|
public function read($session_id); |
19
|
|
|
public function write($session_id, $session_data); |
20
|
|
|
} |
21
|
|
|
} |
22
|
|
|
|
23
|
|
|
if (version_compare(PHP_VERSION, '5.4.0', '<')) { |
24
|
|
|
function register_sessionhandler($handler) |
25
|
|
|
{ |
26
|
|
|
session_set_save_handler( |
27
|
|
|
array($handler, 'open'), |
28
|
|
|
array($handler, 'close'), |
29
|
|
|
array($handler, 'read'), |
30
|
|
|
array($handler, 'write'), |
31
|
|
|
array($handler, 'destroy'), |
32
|
|
|
array($handler, 'gc') |
33
|
|
|
); |
34
|
|
|
|
35
|
|
|
register_shutdown_function('session_write_close'); |
36
|
|
|
} |
37
|
|
|
} else { |
38
|
|
|
function register_sessionhandler($handler) |
|
|
|
|
39
|
|
|
{ |
40
|
|
|
session_set_save_handler($handler, true); |
41
|
|
|
} |
42
|
|
|
} |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* Class HybridSessionStore_Crypto |
46
|
|
|
* Some cryptography used for Session cookie encryption. Requires the mcrypt extension. |
47
|
|
|
* |
48
|
|
|
*/ |
49
|
|
|
class HybridSessionStore_Crypto |
|
|
|
|
50
|
|
|
{ |
51
|
|
|
|
52
|
|
|
private $key; |
53
|
|
|
private $ivSize; |
54
|
|
|
private $keySize; |
55
|
|
|
|
56
|
|
|
public $salt; |
57
|
|
|
private $saltedKey; |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* @param $key a per-site secret string which is used as the base encryption key. |
61
|
|
|
* @param $salt a per-session random string which is used as a salt to generate a per-session key |
62
|
|
|
* |
63
|
|
|
* The base encryption key needs to stay secret. If an attacker ever gets it, they can read their session, |
64
|
|
|
* and even modify & re-sign it. |
65
|
|
|
* |
66
|
|
|
* The salt is a random per-session string that is used with the base encryption key to create a per-session key. |
67
|
|
|
* This (amongst other things) makes sure an attacker can't use a known-plaintext attack to guess the key. |
68
|
|
|
* |
69
|
|
|
* Normally we could create a salt on encryption, send it to the client as part of the session (it doesn't |
70
|
|
|
* need to remain secret), then use the returned salt to decrypt. But we already have the Session ID which makes |
71
|
|
|
* a great salt, so no need to generate & handle another one. |
72
|
|
|
*/ |
73
|
|
|
public function __construct($key, $salt) |
74
|
|
|
{ |
75
|
|
|
$this->ivSize = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC); |
76
|
|
|
$this->keySize = mcrypt_get_key_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC); |
77
|
|
|
|
78
|
|
|
$this->key = $key; |
79
|
|
|
$this->salt = $salt; |
80
|
|
|
$this->saltedKey = hash_pbkdf2('sha256', $this->key, $this->salt, 1000, $this->keySize, true); |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* Encrypt and then sign some cleartext |
85
|
|
|
* |
86
|
|
|
* @param $cleartext - The cleartext to encrypt and sign |
87
|
|
|
* @return string - The encrypted-and-signed message as base64 ASCII. |
88
|
|
|
*/ |
89
|
|
|
public function encrypt($cleartext) |
90
|
|
|
{ |
91
|
|
|
$iv = mcrypt_create_iv($this->ivSize, MCRYPT_DEV_URANDOM); |
92
|
|
|
|
93
|
|
|
$enc = mcrypt_encrypt( |
94
|
|
|
MCRYPT_RIJNDAEL_256, |
95
|
|
|
$this->saltedKey, |
96
|
|
|
$cleartext, |
97
|
|
|
MCRYPT_MODE_CBC, |
98
|
|
|
$iv |
99
|
|
|
); |
100
|
|
|
|
101
|
|
|
$hash = hash_hmac('sha256', $enc, $this->saltedKey); |
102
|
|
|
|
103
|
|
|
return base64_encode($iv.$hash.$enc); |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* Check the signature on an encrypted-and-signed message, and if valid decrypt the content |
108
|
|
|
* |
109
|
|
|
* @param $data - The encrypted-and-signed message as base64 ASCII |
110
|
|
|
* @return bool|string - The decrypted cleartext or false if signature failed |
111
|
|
|
*/ |
112
|
|
|
public function decrypt($data) |
113
|
|
|
{ |
114
|
|
|
$data = base64_decode($data); |
115
|
|
|
|
116
|
|
|
$iv = substr($data, 0, $this->ivSize); |
117
|
|
|
$hash = substr($data, $this->ivSize, 64); |
118
|
|
|
$enc = substr($data, $this->ivSize + 64); |
119
|
|
|
|
120
|
|
|
$cleartext = rtrim(mcrypt_decrypt( |
121
|
|
|
MCRYPT_RIJNDAEL_256, |
122
|
|
|
$this->saltedKey, |
123
|
|
|
$enc, |
124
|
|
|
MCRYPT_MODE_CBC, |
125
|
|
|
$iv |
126
|
|
|
), "\x00"); |
127
|
|
|
|
128
|
|
|
// Needs to be after decrypt so it always runs, to avoid timing attack |
129
|
|
|
$gen_hash = hash_hmac('sha256', $enc, $this->saltedKey); |
130
|
|
|
|
131
|
|
|
if ($gen_hash == $hash) { |
132
|
|
|
return $cleartext; |
133
|
|
|
} |
134
|
|
|
return false; |
135
|
|
|
} |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
abstract class HybridSessionStore_Base implements SessionHandlerInterface |
|
|
|
|
139
|
|
|
{ |
140
|
|
|
|
141
|
|
|
/** |
142
|
|
|
* Session secret key |
143
|
|
|
* |
144
|
|
|
* @var string |
145
|
|
|
*/ |
146
|
|
|
protected $key = null; |
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* Assign a new session secret key |
150
|
|
|
* |
151
|
|
|
* @param string $key |
152
|
|
|
*/ |
153
|
|
|
public function setKey($key) |
154
|
|
|
{ |
155
|
|
|
$this->key = $key; |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
/** |
159
|
|
|
* Get the session secret key |
160
|
|
|
* |
161
|
|
|
* @return string |
162
|
|
|
*/ |
163
|
|
|
protected function getKey() |
164
|
|
|
{ |
165
|
|
|
return $this->key; |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
/** |
169
|
|
|
* Get lifetime in number of seconds |
170
|
|
|
* |
171
|
|
|
* @return int |
172
|
|
|
*/ |
173
|
|
|
protected function getLifetime() |
174
|
|
|
{ |
175
|
|
|
$params = session_get_cookie_params(); |
176
|
|
|
$cookieLifetime = (int)$params['lifetime']; |
177
|
|
|
$gcLifetime = (int)ini_get('session.gc_maxlifetime'); |
178
|
|
|
return $cookieLifetime ? min($cookieLifetime, $gcLifetime) : $gcLifetime; |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
/** |
182
|
|
|
* Gets the current unix timestamp |
183
|
|
|
* |
184
|
|
|
* @return int |
185
|
|
|
*/ |
186
|
|
|
protected function getNow() |
187
|
|
|
{ |
188
|
|
|
return (int)SS_Datetime::now()->Format('U'); |
189
|
|
|
} |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
/** |
193
|
|
|
* Class HybridSessionStore_Cookie |
194
|
|
|
* |
195
|
|
|
* A session store which stores the session data in an encrypted & signed cookie. |
196
|
|
|
* |
197
|
|
|
* This way the server doesn't need to open a database connection or have a shared filesystem for reading |
198
|
|
|
* the session from - the client passes through the session with every request. |
199
|
|
|
* |
200
|
|
|
* This approach does have some limitations - cookies can only be quite small (4K total, but we limit to 1K) |
201
|
|
|
* and can only be set _before_ the server starts sending a response. |
202
|
|
|
* |
203
|
|
|
* So we clear the cookie on Session startup (which should always be before the headers get sent), but just |
204
|
|
|
* fail on Session write if we can't use cookies, assuming there's something watching for that & providing a fallback |
205
|
|
|
*/ |
206
|
|
|
class HybridSessionStore_Cookie extends HybridSessionStore_Base |
|
|
|
|
207
|
|
|
{ |
208
|
|
|
|
209
|
|
|
/** |
210
|
|
|
* Maximum length of a cookie value in characters |
211
|
|
|
* |
212
|
|
|
* @var int |
213
|
|
|
* @config |
214
|
|
|
*/ |
215
|
|
|
private static $max_length = 1024; |
|
|
|
|
216
|
|
|
|
217
|
|
|
/** |
218
|
|
|
* Encryption service |
219
|
|
|
* |
220
|
|
|
* @var HybridSessionStore_Crypto |
221
|
|
|
*/ |
222
|
|
|
protected $crypto; |
223
|
|
|
|
224
|
|
|
/** |
225
|
|
|
* Name of cookie |
226
|
|
|
* |
227
|
|
|
* @var string |
228
|
|
|
*/ |
229
|
|
|
protected $cookie; |
230
|
|
|
|
231
|
|
|
/** |
232
|
|
|
* Known unmodified value of this cookie. If the cookie backend has been read into the application, |
233
|
|
|
* then the backend is unable to verify the modification state of this value internally within the |
234
|
|
|
* system, so this will be left null unless written back. |
235
|
|
|
* |
236
|
|
|
* If the content exceeds max_length then the backend can also not maintain this cookie, also |
237
|
|
|
* setting this variable to null. |
238
|
|
|
* |
239
|
|
|
* @var string |
240
|
|
|
*/ |
241
|
|
|
protected $currentCookieData; |
242
|
|
|
|
243
|
|
|
public function open($save_path, $name) |
244
|
|
|
{ |
245
|
|
|
$this->cookie = $name.'_2'; |
246
|
|
|
// Read the incoming value, then clear the cookie - we might not be able |
247
|
|
|
// to do so later if write() is called after headers are sent |
248
|
|
|
// This is intended to force a failover to the database store if the |
249
|
|
|
// modified session cannot be emitted. |
250
|
|
|
$this->currentCookieData = Cookie::get($this->cookie); |
251
|
|
|
if ($this->currentCookieData) { |
|
|
|
|
252
|
|
|
Cookie::set($this->cookie, ''); |
253
|
|
|
} |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
public function close() |
257
|
|
|
{ |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
/** |
261
|
|
|
* Get the cryptography store for the specified session |
262
|
|
|
* |
263
|
|
|
* @param string $session_id |
264
|
|
|
* @return HybridSessionStore_Crypto |
265
|
|
|
*/ |
266
|
|
|
protected function getCrypto($session_id) |
267
|
|
|
{ |
268
|
|
|
$key = $this->getKey(); |
269
|
|
|
if (!$key) { |
270
|
|
|
return null; |
271
|
|
|
} |
272
|
|
|
if (!$this->crypto || $this->crypto->salt != $session_id) { |
273
|
|
|
$this->crypto = new HybridSessionStore_Crypto($key, $session_id); |
274
|
|
|
} |
275
|
|
|
return $this->crypto; |
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
public function read($session_id) |
279
|
|
|
{ |
280
|
|
|
// Check ability to safely decrypt content |
281
|
|
|
if (!$this->currentCookieData |
282
|
|
|
|| !($crypto = $this->getCrypto($session_id)) |
283
|
|
|
) { |
284
|
|
|
return; |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
// Decrypt and invalidate old data |
288
|
|
|
$cookieData = $crypto->decrypt($this->currentCookieData); |
289
|
|
|
$this->currentCookieData = null; |
290
|
|
|
|
291
|
|
|
// Verify expiration |
292
|
|
|
if ($cookieData) { |
|
|
|
|
293
|
|
|
$expiry = (int)substr($cookieData, 0, 10); |
294
|
|
|
$data = substr($cookieData, 10); |
295
|
|
|
|
296
|
|
|
if ($expiry > $this->getNow()) { |
297
|
|
|
return $data; |
298
|
|
|
} |
299
|
|
|
} |
300
|
|
|
} |
301
|
|
|
|
302
|
|
|
/** |
303
|
|
|
* Determine if the session could be verifably written to cookie storage |
304
|
|
|
* |
305
|
|
|
* @return bool |
306
|
|
|
*/ |
307
|
|
|
protected function canWrite() |
308
|
|
|
{ |
309
|
|
|
return !headers_sent(); |
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
public function write($session_id, $session_data) |
313
|
|
|
{ |
314
|
|
|
// Check ability to safely encrypt and write content |
315
|
|
|
if (!$this->canWrite() |
316
|
|
|
|| (strlen($session_data) > Config::inst()->get(__CLASS__, 'max_length')) |
317
|
|
|
|| !($crypto = $this->getCrypto($session_id)) |
318
|
|
|
) { |
319
|
|
|
return false; |
320
|
|
|
} |
321
|
|
|
|
322
|
|
|
// Prepare content for write |
323
|
|
|
$params = session_get_cookie_params(); |
324
|
|
|
// Total max lifetime, stored internally |
325
|
|
|
$lifetime = $this->getLifetime(); |
326
|
|
|
$expiry = $this->getNow() + $lifetime; |
327
|
|
|
|
328
|
|
|
// Restore the known good cookie value |
329
|
|
|
$this->currentCookieData = $this->crypto->encrypt( |
330
|
|
|
sprintf('%010u', $expiry) . $session_data |
331
|
|
|
); |
332
|
|
|
|
333
|
|
|
// Respect auto-expire on browser close for the session cookie (in case the cookie lifetime is zero) |
334
|
|
|
$cookieLifetime = min((int)$params['lifetime'], $lifetime); |
335
|
|
|
Cookie::set( |
336
|
|
|
$this->cookie, |
337
|
|
|
$this->currentCookieData, |
338
|
|
|
$cookieLifetime / 86400, |
339
|
|
|
$params['path'], |
340
|
|
|
$params['domain'], |
341
|
|
|
$params['secure'], |
342
|
|
|
$params['httponly'] |
343
|
|
|
); |
344
|
|
|
|
345
|
|
|
return true; |
346
|
|
|
} |
347
|
|
|
|
348
|
|
|
public function destroy($session_id) |
349
|
|
|
{ |
350
|
|
|
$this->currentCookieData = null; |
351
|
|
|
Cookie::force_expiry($this->cookie); |
352
|
|
|
} |
353
|
|
|
|
354
|
|
|
public function gc($maxlifetime) |
355
|
|
|
{ |
356
|
|
|
// NOP |
357
|
|
|
} |
358
|
|
|
} |
359
|
|
|
|
360
|
|
|
class HybridSessionStore_Database extends HybridSessionStore_Base |
|
|
|
|
361
|
|
|
{ |
362
|
|
|
|
363
|
|
|
/** |
364
|
|
|
* Determine if the DB is ready to use. |
365
|
|
|
* |
366
|
|
|
* @return bool |
367
|
|
|
* @throws Exception |
368
|
|
|
*/ |
369
|
|
|
protected function isDatabaseReady() |
370
|
|
|
{ |
371
|
|
|
// Such as during setup of testsession prior to DB connection. |
372
|
|
|
if (!DB::isActive()) { |
|
|
|
|
373
|
|
|
return false; |
374
|
|
|
} |
375
|
|
|
|
376
|
|
|
// If we have a DB of the wrong type then complain |
377
|
|
|
if (!(DB::getConn() instanceof MySQLDatabase)) { |
|
|
|
|
378
|
|
|
throw new Exception('HybridSessionStore currently only works with MySQL databases'); |
379
|
|
|
} |
380
|
|
|
|
381
|
|
|
// Prevent freakout during dev/build |
382
|
|
|
return ClassInfo::hasTable('HybridSessionDataObject'); |
383
|
|
|
} |
384
|
|
|
|
385
|
|
|
public function open($save_path, $name) |
386
|
|
|
{ |
387
|
|
|
} |
388
|
|
|
|
389
|
|
|
public function close() |
390
|
|
|
{ |
391
|
|
|
} |
392
|
|
|
|
393
|
|
|
public function read($session_id) |
394
|
|
|
{ |
395
|
|
|
if (!$this->isDatabaseReady()) { |
396
|
|
|
return null; |
397
|
|
|
} |
398
|
|
|
|
399
|
|
|
$result = DB::query(sprintf( |
400
|
|
|
'SELECT "Data" FROM "HybridSessionDataObject" |
401
|
|
|
WHERE "SessionID" = \'%s\' AND "Expiry" >= %u', |
402
|
|
|
Convert::raw2sql($session_id), |
403
|
|
|
$this->getNow() |
404
|
|
|
)); |
405
|
|
|
|
406
|
|
|
if ($result && $result->numRecords()) { |
407
|
|
|
$data = $result->first(); |
408
|
|
|
return $data['Data']; |
409
|
|
|
} |
410
|
|
|
} |
411
|
|
|
|
412
|
|
|
public function write($session_id, $session_data) |
413
|
|
|
{ |
414
|
|
|
if (!$this->isDatabaseReady()) { |
415
|
|
|
return false; |
416
|
|
|
} |
417
|
|
|
|
418
|
|
|
$expiry = $this->getNow() + $this->getLifetime(); |
419
|
|
|
DB::query($str = sprintf( |
420
|
|
|
'INSERT INTO "HybridSessionDataObject" ("SessionID", "Expiry", "Data") |
421
|
|
|
VALUES (\'%1$s\', %2$u, \'%3$s\') |
422
|
|
|
ON DUPLICATE KEY UPDATE "Expiry" = %2$u, "Data" = \'%3$s\'', |
423
|
|
|
Convert::raw2sql($session_id), |
424
|
|
|
$expiry, |
425
|
|
|
Convert::raw2sql($session_data) |
426
|
|
|
)); |
427
|
|
|
|
428
|
|
|
return true; |
429
|
|
|
} |
430
|
|
|
|
431
|
|
|
public function destroy($session_id) |
432
|
|
|
{ |
433
|
|
|
// NOP |
434
|
|
|
} |
435
|
|
|
|
436
|
|
|
public function gc($maxlifetime) |
437
|
|
|
{ |
438
|
|
|
if (!$this->isDatabaseReady()) { |
439
|
|
|
return; |
440
|
|
|
} |
441
|
|
|
DB::query(sprintf( |
442
|
|
|
'DELETE FROM "HybridSessionDataObject" WHERE "Expiry" < %u', |
443
|
|
|
$this->getNow() |
444
|
|
|
)); |
445
|
|
|
} |
446
|
|
|
} |
447
|
|
|
|
448
|
|
|
|
449
|
|
|
class HybridSessionStore extends HybridSessionStore_Base |
|
|
|
|
450
|
|
|
{ |
451
|
|
|
|
452
|
|
|
/** |
453
|
|
|
* List of session handlers |
454
|
|
|
* |
455
|
|
|
* @var array[HybridSessionStore_Base] |
456
|
|
|
*/ |
457
|
|
|
protected $handlers = array(); |
458
|
|
|
|
459
|
|
|
/** |
460
|
|
|
* True if this session store has been initialised |
461
|
|
|
* |
462
|
|
|
* @var bool |
463
|
|
|
*/ |
464
|
|
|
protected static $enabled = false; |
465
|
|
|
|
466
|
|
|
/** |
467
|
|
|
* @param array[HybridSessionStore_Base] |
468
|
|
|
*/ |
469
|
|
|
public function setHandlers($handlers) |
470
|
|
|
{ |
471
|
|
|
$this->handlers = $handlers; |
472
|
|
|
$this->setKey($this->getKey()); |
473
|
|
|
} |
474
|
|
|
|
475
|
|
|
public function setKey($key) |
476
|
|
|
{ |
477
|
|
|
parent::setKey($key); |
478
|
|
|
foreach ($this->handlers as $handler) { |
479
|
|
|
$handler->setKey($key); |
480
|
|
|
} |
481
|
|
|
} |
482
|
|
|
|
483
|
|
|
/** |
484
|
|
|
* @return array[SessionHandlerInterface] |
|
|
|
|
485
|
|
|
*/ |
486
|
|
|
public function getHandlers() |
487
|
|
|
{ |
488
|
|
|
return $this->handlers; |
489
|
|
|
} |
490
|
|
|
|
491
|
|
|
public function open($save_path, $name) |
492
|
|
|
{ |
493
|
|
|
foreach ($this->handlers as $handler) { |
494
|
|
|
$handler->open($save_path, $name); |
495
|
|
|
} |
496
|
|
|
|
497
|
|
|
return true; |
498
|
|
|
} |
499
|
|
|
|
500
|
|
|
public function close() |
501
|
|
|
{ |
502
|
|
|
foreach ($this->handlers as $handler) { |
503
|
|
|
$handler->close(); |
504
|
|
|
} |
505
|
|
|
|
506
|
|
|
return true; |
507
|
|
|
} |
508
|
|
|
|
509
|
|
|
public function read($session_id) |
510
|
|
|
{ |
511
|
|
|
foreach ($this->handlers as $handler) { |
512
|
|
|
if ($data = $handler->read($session_id)) { |
513
|
|
|
return $data; |
514
|
|
|
} |
515
|
|
|
} |
516
|
|
|
|
517
|
|
|
return ''; |
518
|
|
|
} |
519
|
|
|
|
520
|
|
|
public function write($session_id, $session_data) |
521
|
|
|
{ |
522
|
|
|
foreach ($this->handlers as $handler) { |
523
|
|
|
if ($handler->write($session_id, $session_data)) { |
524
|
|
|
return; |
525
|
|
|
} |
526
|
|
|
} |
527
|
|
|
} |
528
|
|
|
|
529
|
|
|
public function destroy($session_id) |
530
|
|
|
{ |
531
|
|
|
foreach ($this->handlers as $handler) { |
532
|
|
|
$handler->destroy($session_id); |
533
|
|
|
} |
534
|
|
|
} |
535
|
|
|
|
536
|
|
|
public function gc($maxlifetime) |
537
|
|
|
{ |
538
|
|
|
foreach ($this->handlers as $handler) { |
539
|
|
|
$handler->gc($maxlifetime); |
540
|
|
|
} |
541
|
|
|
} |
542
|
|
|
|
543
|
|
|
/** |
544
|
|
|
* Register the session handler as the default |
545
|
|
|
* |
546
|
|
|
* @param string $key Desired session key |
547
|
|
|
*/ |
548
|
|
|
public static function init($key = null) |
549
|
|
|
{ |
550
|
|
|
$instance = Injector::inst()->get(__CLASS__); |
551
|
|
|
if (empty($key)) { |
552
|
|
|
user_error( |
553
|
|
|
'HybridSessionStore::init() was not given a $key. Disabling cookie-based storage', |
554
|
|
|
E_USER_WARNING |
555
|
|
|
); |
556
|
|
|
} else { |
557
|
|
|
$instance->setKey($key); |
558
|
|
|
} |
559
|
|
|
register_sessionhandler($instance); |
560
|
|
|
self::$enabled = true; |
561
|
|
|
} |
562
|
|
|
|
563
|
|
|
public static function is_enabled() |
564
|
|
|
{ |
565
|
|
|
return self::$enabled; |
566
|
|
|
} |
567
|
|
|
} |
568
|
|
|
|
569
|
|
|
class HybridSessionStore_RequestFilter implements RequestFilter |
|
|
|
|
570
|
|
|
{ |
571
|
|
|
public function preRequest(SS_HTTPRequest $request, Session $session, DataModel $model) |
572
|
|
|
{ |
573
|
|
|
// NOP |
574
|
|
|
} |
575
|
|
|
|
576
|
|
|
public function postRequest(SS_HTTPRequest $request, SS_HTTPResponse $response, DataModel $model) |
577
|
|
|
{ |
578
|
|
|
if (HybridSessionStore::is_enabled()) { |
579
|
|
|
session_write_close(); |
580
|
|
|
} |
581
|
|
|
} |
582
|
|
|
} |
583
|
|
|
|
You can fix this by adding a namespace to your class:
When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.