Completed
Branch 2.x (161cec)
by Julián
03:58
created

Session::getInitiatedCookieString()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 17
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 12
nc 2
nop 0
1
<?php
2
3
/*
4
 * sessionware (https://github.com/juliangut/sessionware).
5
 * PSR7 compatible session management.
6
 *
7
 * @license BSD-3-Clause
8
 * @link https://github.com/juliangut/sessionware
9
 * @author Julián Gutiérrez <[email protected]>
10
 */
11
12
declare(strict_types=1);
13
14
namespace Jgut\Sessionware;
15
16
use Jgut\Sessionware\Manager\Manager;
17
use League\Event\EmitterAwareInterface;
18
use League\Event\EmitterTrait;
19
use League\Event\Event;
20
use Psr\Http\Message\ResponseInterface;
21
use Psr\Http\Message\ServerRequestInterface;
22
23
/**
24
 * Session helper.
25
 */
26
class Session implements EmitterAwareInterface
27
{
28
    use EmitterTrait;
29
30
    /**
31
     * Session manager.
32
     *
33
     * @var Manager
34
     */
35
    protected $sessionManager;
36
37
    /**
38
     * Session initial data.
39
     *
40
     * @var array
41
     */
42
    protected $originalData;
43
44
    /**
45
     * Session data.
46
     *
47
     * @var array
48
     */
49
    protected $data;
50
51
    /**
52
     * Session constructor.
53
     *
54
     * @param Manager $sessionManager
55
     * @param array   $initialData
56
     *
57
     * @throws \InvalidArgumentException
58
     */
59
    public function __construct(Manager $sessionManager, array $initialData = [])
60
    {
61
        $this->sessionManager = $sessionManager;
62
        $this->data = [];
63
64
        foreach ($initialData as $key => $value) {
65
            $this->set($key, $value);
66
        }
67
68
        $this->originalData = $this->data;
69
70
        register_shutdown_function([$this, 'close']);
71
    }
72
73
    /**
74
     * Get session manager.
75
     *
76
     * @return Manager
77
     */
78
    public function getManager() : Manager
79
    {
80
        return $this->sessionManager;
81
    }
82
83
    /**
84
     * Start session.
85
     *
86
     * @throws \RuntimeException
87
     */
88
    public function start()
89
    {
90
        if ($this->isActive()) {
91
            return;
92
        }
93
94
        $this->emit(Event::named('preStart'), $this);
95
96
        $this->originalData = $this->data = array_merge($this->data, $this->sessionManager->sessionStart());
97
98
        if ($this->sessionManager->shouldRegenerateId()) {
99
            $this->regenerateId();
100
        }
101
102
        $this->emit(Event::named('postStart'), $this);
103
104
        $this->manageTimeout();
105
    }
106
107
    /**
108
     * Regenerate session identifier keeping parameters.
109
     *
110
     * @throws \RuntimeException
111
     */
112
    public function regenerateId()
113
    {
114
        if (!$this->isActive()) {
115
            throw new \RuntimeException('Cannot regenerate a not started session');
116
        }
117
118
        $this->emit(Event::named('preRegenerateId'), $this);
119
120
        $this->sessionManager->sessionRegenerateId();
121
122
        $this->emit(Event::named('postRegenerateId'), $this);
123
    }
124
125
    /**
126
     * Revert session to its original data.
127
     */
128
    public function reset()
129
    {
130
        if (!$this->isActive()) {
131
            return;
132
        }
133
134
        $this->emit(Event::named('preReset'), $this);
135
136
        $this->data = $this->originalData;
137
138
        $this->emit(Event::named('postReset'), $this);
139
    }
140
141
    /**
142
     * Close session keeping original session data.
143
     */
144
    public function abort()
145
    {
146
        if (!$this->isActive()) {
147
            return;
148
        }
149
150
        $this->emit(Event::named('preAbort'), $this);
151
152
        $this->sessionManager->sessionEnd($this->originalData);
153
154
        $this->emit(Event::named('postAbort'), $this);
155
    }
156
157
    /**
158
     * Close session.
159
     */
160
    public function close()
161
    {
162
        if (!$this->isActive()) {
163
            return;
164
        }
165
166
        $this->emit(Event::named('preClose'), $this);
167
168
        $this->sessionManager->sessionEnd($this->data);
169
170
        $this->emit(Event::named('postClose'), $this);
171
    }
172
173
    /**
174
     * Destroy session.
175
     *
176
     * @throws \RuntimeException
177
     */
178
    public function destroy()
179
    {
180
        if (!$this->isActive()) {
181
            throw new \RuntimeException('Cannot destroy a not started session');
182
        }
183
184
        $this->emit(Event::named('preDestroy'), $this);
185
186
        $this->sessionManager->sessionDestroy();
187
188
        $this->emit(Event::named('postDestroy'), $this);
189
190
        $this->originalData = $this->data = [];
191
    }
192
193
    /**
194
     * Has session been started.
195
     *
196
     * @return bool
197
     */
198
    public function isActive() : bool
199
    {
200
        return $this->sessionManager->isSessionStarted();
201
    }
202
203
    /**
204
     * Has session been destroyed.
205
     *
206
     * @return bool
207
     */
208
    public function isDestroyed() : bool
209
    {
210
        return $this->sessionManager->isSessionDestroyed();
211
    }
212
213
    /**
214
     * Get session identifier.
215
     *
216
     * @return string
217
     */
218
    public function getId() : string
219
    {
220
        return $this->sessionManager->getSessionId();
221
    }
222
223
    /**
224
     * Load session identifier from request.
225
     *
226
     * @param ServerRequestInterface $request
227
     *
228
     * @throws \RuntimeException
229
     */
230
    public function loadIdFromRequest(ServerRequestInterface $request)
231
    {
232
        $requestCookies = $request->getCookieParams();
233
        $sessionName = $this->getConfiguration()->getName();
234
235
        if (array_key_exists($sessionName, $requestCookies) && !empty($requestCookies[$sessionName])) {
236
            $this->setId($requestCookies[$sessionName]);
237
        }
238
    }
239
240
    /**
241
     * Set session identifier.
242
     *
243
     * @param string $sessionId
244
     *
245
     * @throws \RuntimeException
246
     */
247
    public function setId(string $sessionId)
248
    {
249
        if ($this->isActive() || $this->isDestroyed()) {
250
            throw new \RuntimeException('Cannot set session id on started or destroyed sessions');
251
        }
252
253
        $this->sessionManager->setSessionId($sessionId);
254
    }
255
256
    /**
257
     * Session parameter existence.
258
     *
259
     * @param string $key
260
     *
261
     * @return bool
262
     */
263
    public function has(string $key) : bool
264
    {
265
        return array_key_exists($key, $this->data);
266
    }
267
268
    /**
269
     * Retrieve session parameter.
270
     *
271
     * @param string     $key
272
     * @param mixed|null $default
273
     *
274
     * @return mixed
275
     */
276
    public function get(string $key, $default = null)
277
    {
278
        return array_key_exists($key, $this->data) ? $this->data[$key] : $default;
279
    }
280
281
    /**
282
     * Set session parameter.
283
     *
284
     * @param string $key
285
     * @param mixed  $value
286
     *
287
     * @throws \InvalidArgumentException
288
     *
289
     * @return self
290
     */
291
    public function set(string $key, $value) : self
292
    {
293
        $this->verifyScalarValue($value);
294
295
        $this->data[$key] = $value;
296
297
        return $this;
298
    }
299
300
    /**
301
     * Verify only scalar values allowed.
302
     *
303
     * @param string|int|float|bool|array $value
304
     *
305
     * @throws \InvalidArgumentException
306
     */
307
    final protected function verifyScalarValue($value)
308
    {
309
        if (is_array($value)) {
310
            foreach ($value as $val) {
311
                $this->verifyScalarValue($val);
312
            }
313
        }
314
315
        if (!is_scalar($value)) {
316
            throw new \InvalidArgumentException(sprintf('Session values must be scalars, %s given', gettype($value)));
317
        }
318
    }
319
320
    /**
321
     * Remove session parameter.
322
     *
323
     * @param string $key
324
     *
325
     * @return self
326
     */
327
    public function remove(string $key) : self
328
    {
329
        if (array_key_exists($key, $this->data)) {
330
            unset($this->data[$key]);
331
        }
332
333
        return $this;
334
    }
335
336
    /**
337
     * Remove all session parameters.
338
     *
339
     * @return self
340
     */
341
    public function clear() : self
342
    {
343
        $timeoutKey = $this->getConfiguration()->getTimeoutKey();
344
        $sessionTimeout = array_key_exists($timeoutKey, $this->data)
345
            ? $this->data[$timeoutKey]
346
            : time() + $this->getConfiguration()->getLifetime();
347
348
        $this->data = [$timeoutKey => $sessionTimeout];
349
350
        return $this;
351
    }
352
353
    /**
354
     * Manage session timeout.
355
     *
356
     * @throws \RuntimeException
357
     */
358
    protected function manageTimeout()
359
    {
360
        $configuration = $this->getConfiguration();
361
        $timeoutKey = $configuration->getTimeoutKey();
362
363
        if (array_key_exists($timeoutKey, $this->data) && $this->data[$timeoutKey] < time()) {
364
            $this->emit(Event::named('preTimeout'), $this);
365
366
            $this->sessionManager->sessionRegenerateId();
367
368
            $this->emit(Event::named('postTimeout'), $this);
369
        }
370
371
        $this->data[$timeoutKey] = time() + $configuration->getLifetime();
372
    }
373
374
    /**
375
     * Return response with added session cookie.
376
     *
377
     * @param ResponseInterface $response
378
     *
379
     * @return ResponseInterface
380
     */
381
    public function withSessionCookie(ResponseInterface $response) : ResponseInterface
382
    {
383
        $cookieString = $this->getSessionCookieString();
384
385
        if (!empty($cookieString)) {
386
            $response = $response->withAddedHeader('Set-Cookie', $cookieString);
387
        }
388
389
        return $response;
390
    }
391
392
    /**
393
     * Get session cookie content.
394
     *
395
     * @return string
396
     */
397
    public function getSessionCookieString() : string
398
    {
399
        if (empty($this->getId())) {
400
            return '';
401
        }
402
403
        if ($this->isDestroyed()) {
404
            return $this->getExpiredCookieString();
405
        }
406
407
        return $this->getInitiatedCookieString();
408
    }
409
410
    /**
411
     * Get session expired cookie.
412
     *
413
     * @return string
414
     */
415
    protected function getExpiredCookieString() : string
416
    {
417
        $configuration = $this->getConfiguration();
418
419
        return sprintf(
420
            '%s=%s; %s',
421
            urlencode($configuration->getName()),
422
            urlencode($this->getId()),
423
            $this->getCookieParameters(0, 1)
424
        );
425
    }
426
427
    /**
428
     * Get normal session cookie.
429
     *
430
     * @return string
431
     */
432
    protected function getInitiatedCookieString() : string
433
    {
434
        $configuration = $this->getConfiguration();
435
436
        $lifetime = $configuration->getLifetime();
437
        $timeoutKey = $configuration->getTimeoutKey();
438
        $expireTime = array_key_exists($timeoutKey, $this->data)
439
            ? $this->data[$timeoutKey]
440
            : time() + $lifetime;
441
442
        return sprintf(
443
            '%s=%s; %s',
444
            urlencode($configuration->getName()),
445
            urlencode($this->getId()),
446
            $this->getCookieParameters($expireTime, $lifetime)
447
        );
448
    }
449
450
    /**
451
     * Get session cookie parameters.
452
     *
453
     * @param int $expireTime
454
     * @param int $lifetime
455
     *
456
     * @return string
457
     */
458
    protected function getCookieParameters(int $expireTime, int $lifetime) : string
459
    {
460
        $configuration = $this->getConfiguration();
461
462
        $cookieParams = [
463
            sprintf('expires=%s; max-age=%s', gmdate('D, d M Y H:i:s T', $expireTime), $lifetime),
464
        ];
465
466
        if (!empty($configuration->getCookiePath())) {
467
            $cookieParams[] = 'path=' . $configuration->getCookiePath();
468
        }
469
470
        $domain = $this->getCookieDomain();
471
        if (!empty($domain)) {
472
            $cookieParams[] = 'domain=' . $domain;
473
        }
474
475
        if ($configuration->isCookieSecure()) {
476
            $cookieParams[] = 'secure';
477
        }
478
479
        if ($configuration->isCookieHttpOnly()) {
480
            $cookieParams[] = 'httponly';
481
        }
482
483
        $cookieParams[] = 'SameSite=' . $configuration->getCookieSameSite();
484
485
        return implode('; ', $cookieParams);
486
    }
487
488
    /**
489
     * Get normalized cookie domain.
490
     *
491
     * @return string
492
     */
493
    protected function getCookieDomain() : string
494
    {
495
        $configuration = $this->getConfiguration();
496
497
        $domain = $configuration->getCookieDomain();
498
499
        // Current domain for local host names or IP addresses
500
        if (empty($domain)
501
            || strpos($domain, '.') === false
502
            || filter_var($domain, FILTER_VALIDATE_IP) !== false
503
        ) {
504
            return '';
505
        }
506
507
        return $domain[0] === '.' ? $domain : '.' . $domain;
508
    }
509
510
    /**
511
     * Get session configuration.
512
     *
513
     * @return Configuration
514
     */
515
    protected function getConfiguration() : Configuration
516
    {
517
        return $this->sessionManager->getConfiguration();
518
    }
519
}
520