Session::open()   B
last analyzed

Complexity

Conditions 7
Paths 13

Size

Total Lines 24
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 7.392

Importance

Changes 0
Metric Value
cc 7
eloc 15
nc 13
nop 0
dl 0
loc 24
ccs 12
cts 15
cp 0.8
crap 7.392
rs 8.8333
c 0
b 0
f 0
1
<?php
2
/**
3
 * @link https://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license https://www.yiiframework.com/license/
6
 */
7
8
namespace yii\web;
9
10
use Yii;
11
use yii\base\Component;
12
use yii\base\InvalidArgumentException;
13
use yii\base\InvalidConfigException;
14
15
/**
16
 * Session provides session data management and the related configurations.
17
 *
18
 * Session is a Web application component that can be accessed via `Yii::$app->session`.
19
 *
20
 * To start the session, call [[open()]]; To complete and send out session data, call [[close()]];
21
 * To destroy the session, call [[destroy()]].
22
 *
23
 * Session can be used like an array to set and get session data. For example,
24
 *
25
 * ```php
26
 * $session = new Session;
27
 * $session->open();
28
 * $value1 = $session['name1'];  // get session variable 'name1'
29
 * $value2 = $session['name2'];  // get session variable 'name2'
30
 * foreach ($session as $name => $value) // traverse all session variables
31
 * $session['name3'] = $value3;  // set session variable 'name3'
32
 * ```
33
 *
34
 * Session can be extended to support customized session storage.
35
 * To do so, override [[useCustomStorage]] so that it returns true, and
36
 * override these methods with the actual logic about using custom storage:
37
 * [[openSession()]], [[closeSession()]], [[readSession()]], [[writeSession()]],
38
 * [[destroySession()]] and [[gcSession()]].
39
 *
40
 * Session also supports a special type of session data, called *flash messages*.
41
 * A flash message is available only in the current request and the next request.
42
 * After that, it will be deleted automatically. Flash messages are particularly
43
 * useful for displaying confirmation messages. To use flash messages, simply
44
 * call methods such as [[setFlash()]], [[getFlash()]].
45
 *
46
 * For more details and usage information on Session, see the [guide article on sessions](guide:runtime-sessions-cookies).
47
 *
48
 * @property-read array $allFlashes Flash messages (key => message or key => [message1, message2]).
49
 * @property-read string $cacheLimiter Current cache limiter.
50
 * @property-read array $cookieParams The session cookie parameters.
51
 * @property-read int $count The number of session variables.
52
 * @property-write string $flash The key identifying the flash message. Note that flash messages and normal
53
 * session variables share the same name space. If you have a normal session variable using the same name, its
54
 * value will be overwritten by this method.
55
 * @property float $gCProbability The probability (percentage) that the GC (garbage collection) process is
56
 * started on every session initialization.
57
 * @property bool $hasSessionId Whether the current request has sent the session ID.
58
 * @property string $id The current session ID.
59
 * @property-read bool $isActive Whether the session has started.
60
 * @property-read SessionIterator $iterator An iterator for traversing the session variables.
61
 * @property string $name The current session name.
62
 * @property string $savePath The current session save path, defaults to '/tmp'.
63
 * @property int $timeout The number of seconds after which data will be seen as 'garbage' and cleaned up. The
64
 * default value is 1440 seconds (or the value of "session.gc_maxlifetime" set in php.ini).
65
 * @property bool|null $useCookies The value indicating whether cookies should be used to store session IDs.
66
 * @property-read bool $useCustomStorage Whether to use custom storage.
67
 * @property bool $useStrictMode Whether strict mode is enabled or not.
68
 * @property bool $useTransparentSessionID Whether transparent sid support is enabled or not, defaults to
69
 * false.
70
 *
71
 * @author Qiang Xue <[email protected]>
72
 * @since 2.0
73
 */
74
class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Countable
75
{
76
    /**
77
     * @var string|null Holds the original session module (before a custom handler is registered) so that it can be
78
     * restored when a Session component without custom handler is used after one that has.
79
     */
80
    protected static $_originalSessionModule = null;
81
    /**
82
     * Polyfill for ini directive session.use-strict-mode for PHP < 5.5.2.
83
     */
84
    private static $_useStrictModePolyfill = false;
85
    /**
86
     * @var string the name of the session variable that stores the flash message data.
87
     */
88
    public $flashParam = '__flash';
89
    /**
90
     * @var \SessionHandlerInterface|array an object implementing the SessionHandlerInterface or a configuration array. If set, will be used to provide persistency instead of build-in methods.
91
     */
92
    public $handler;
93
94
    /**
95
     * @var string|null Holds the session id in case useStrictMode is enabled and the session id needs to be regenerated
96
     */
97
    protected $_forceRegenerateId = null;
98
99
    /**
100
     * @var array parameter-value pairs to override default session cookie parameters that are used for session_set_cookie_params() function
101
     * Array may have the following possible keys: 'lifetime', 'path', 'domain', 'secure', 'httponly'
102
     * @see https://www.php.net/manual/en/function.session-set-cookie-params.php
103
     */
104
    private $_cookieParams = ['httponly' => true];
105
    /**
106
     * @var array|null is used for saving session between recreations due to session parameters update.
107
     */
108
    private $_frozenSessionData;
109
110
111
    /**
112
     * Initializes the application component.
113
     * This method is required by IApplicationComponent and is invoked by application.
114
     */
115 124
    public function init()
116
    {
117 124
        parent::init();
118 124
        register_shutdown_function([$this, 'close']);
119 124
        if ($this->getIsActive()) {
120 5
            Yii::warning('Session is already started', __METHOD__);
121 5
            $this->updateFlashCounters();
122
        }
123
    }
124
125
    /**
126
     * Returns a value indicating whether to use custom session storage.
127
     * This method should be overridden to return true by child classes that implement custom session storage.
128
     * To implement custom session storage, override these methods: [[openSession()]], [[closeSession()]],
129
     * [[readSession()]], [[writeSession()]], [[destroySession()]] and [[gcSession()]].
130
     * @return bool whether to use custom storage.
131
     */
132 68
    public function getUseCustomStorage()
133
    {
134 68
        return false;
135
    }
136
137
    /**
138
     * Starts the session.
139
     */
140 77
    public function open()
141
    {
142 77
        if ($this->getIsActive()) {
143 77
            return;
144
        }
145
146 76
        $this->registerSessionHandler();
147
148 76
        $this->setCookieParamsInternal();
149
150 76
        YII_DEBUG ? session_start() : @session_start();
151
152 76
        if ($this->getUseStrictMode() && $this->_forceRegenerateId) {
153 7
            $this->regenerateID();
154 7
            $this->_forceRegenerateId = null;
155
        }
156
157 76
        if ($this->getIsActive()) {
158 76
            Yii::info('Session started', __METHOD__);
159 76
            $this->updateFlashCounters();
160
        } else {
161
            $error = error_get_last();
162
            $message = isset($error['message']) ? $error['message'] : 'Failed to start session.';
163
            Yii::error($message, __METHOD__);
164
        }
165
    }
166
167
    /**
168
     * Registers session handler.
169
     * @throws \yii\base\InvalidConfigException
170
     */
171 76
    protected function registerSessionHandler()
172
    {
173 76
        $sessionModuleName = session_module_name();
174 76
        if (static::$_originalSessionModule === null) {
175 1
            static::$_originalSessionModule = $sessionModuleName;
176
        }
177
178 76
        if ($this->handler !== null) {
179
            if (!is_object($this->handler)) {
180
                $this->handler = Yii::createObject($this->handler);
181
            }
182
            if (!$this->handler instanceof \SessionHandlerInterface) {
183
                throw new InvalidConfigException('"' . get_class($this) . '::handler" must implement the SessionHandlerInterface.');
184
            }
185
            YII_DEBUG ? session_set_save_handler($this->handler, false) : @session_set_save_handler($this->handler, false);
186 76
        } elseif ($this->getUseCustomStorage()) {
187 8
            if (YII_DEBUG) {
188 8
                session_set_save_handler(
189 8
                    [$this, 'openSession'],
190 8
                    [$this, 'closeSession'],
191 8
                    [$this, 'readSession'],
192 8
                    [$this, 'writeSession'],
193 8
                    [$this, 'destroySession'],
194 8
                    [$this, 'gcSession']
195 8
                );
196
            } else {
197 8
                @session_set_save_handler(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for session_set_save_handler(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

197
                /** @scrutinizer ignore-unhandled */ @session_set_save_handler(

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
198 8
                    [$this, 'openSession'],
199 8
                    [$this, 'closeSession'],
200 8
                    [$this, 'readSession'],
201 8
                    [$this, 'writeSession'],
202 8
                    [$this, 'destroySession'],
203 8
                    [$this, 'gcSession']
204 8
                );
205
            }
206
        } elseif (
207 68
            $sessionModuleName !== static::$_originalSessionModule
208 68
            && static::$_originalSessionModule !== null
209 68
            && static::$_originalSessionModule !== 'user'
210
        ) {
211 1
            session_module_name(static::$_originalSessionModule);
212
        }
213
    }
214
215
    /**
216
     * Ends the current session and store session data.
217
     */
218 93
    public function close()
219
    {
220 93
        if ($this->getIsActive()) {
221 70
            YII_DEBUG ? session_write_close() : @session_write_close();
222
        }
223
224 93
        $this->_forceRegenerateId = null;
225
    }
226
227
    /**
228
     * Frees all session variables and destroys all data registered to a session.
229
     *
230
     * This method has no effect when session is not [[getIsActive()|active]].
231
     * Make sure to call [[open()]] before calling it.
232
     * @see open()
233
     * @see isActive
234
     */
235 7
    public function destroy()
236
    {
237 7
        if ($this->getIsActive()) {
238 7
            $sessionId = session_id();
239 7
            $this->close();
240 7
            $this->setId($sessionId);
241 7
            $this->open();
242 7
            session_unset();
243 7
            session_destroy();
244 7
            $this->setId($sessionId);
245
        }
246
    }
247
248
    /**
249
     * @return bool whether the session has started
250
     */
251 124
    public function getIsActive()
252
    {
253 124
        return session_status() === PHP_SESSION_ACTIVE;
254
    }
255
256
    private $_hasSessionId;
257
258
    /**
259
     * Returns a value indicating whether the current request has sent the session ID.
260
     * The default implementation will check cookie and $_GET using the session name.
261
     * If you send session ID via other ways, you may need to override this method
262
     * or call [[setHasSessionId()]] to explicitly set whether the session ID is sent.
263
     * @return bool whether the current request has sent the session ID.
264
     */
265 37
    public function getHasSessionId()
266
    {
267 37
        if ($this->_hasSessionId === null) {
268 37
            $name = $this->getName();
269 37
            $request = Yii::$app->getRequest();
270 37
            if (!empty($_COOKIE[$name]) && ini_get('session.use_cookies')) {
271
                $this->_hasSessionId = true;
272 37
            } elseif (!ini_get('session.use_only_cookies') && ini_get('session.use_trans_sid')) {
273
                $this->_hasSessionId = $request->get($name) != '';
0 ignored issues
show
Bug introduced by
The method get() does not exist on yii\console\Request. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

273
                $this->_hasSessionId = $request->/** @scrutinizer ignore-call */ get($name) != '';
Loading history...
274
            } else {
275 37
                $this->_hasSessionId = false;
276
            }
277
        }
278
279 37
        return $this->_hasSessionId;
280
    }
281
282
    /**
283
     * Sets the value indicating whether the current request has sent the session ID.
284
     * This method is provided so that you can override the default way of determining
285
     * whether the session ID is sent.
286
     * @param bool $value whether the current request has sent the session ID.
287
     */
288
    public function setHasSessionId($value)
289
    {
290
        $this->_hasSessionId = $value;
291
    }
292
293
    /**
294
     * Gets the session ID.
295
     * This is a wrapper for [PHP session_id()](https://www.php.net/manual/en/function.session-id.php).
296
     * @return string the current session ID
297
     */
298 21
    public function getId()
299
    {
300 21
        return session_id();
301
    }
302
303
    /**
304
     * Sets the session ID.
305
     * This is a wrapper for [PHP session_id()](https://www.php.net/manual/en/function.session-id.php).
306
     * @param string $value the session ID for the current session
307
     */
308 12
    public function setId($value)
309
    {
310 12
        session_id($value);
311
    }
312
313
    /**
314
     * Updates the current session ID with a newly generated one.
315
     *
316
     * Please refer to <https://www.php.net/session_regenerate_id> for more details.
317
     *
318
     * This method has no effect when session is not [[getIsActive()|active]].
319
     * Make sure to call [[open()]] before calling it.
320
     *
321
     * @param bool $deleteOldSession Whether to delete the old associated session file or not.
322
     * @see open()
323
     * @see isActive
324
     */
325 52
    public function regenerateID($deleteOldSession = false)
326
    {
327 52
        if ($this->getIsActive()) {
328
            // add @ to inhibit possible warning due to race condition
329
            // https://github.com/yiisoft/yii2/pull/1812
330 39
            if (YII_DEBUG && !headers_sent()) {
331 39
                session_regenerate_id($deleteOldSession);
332
            } else {
333
                @session_regenerate_id($deleteOldSession);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for session_regenerate_id(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

333
                /** @scrutinizer ignore-unhandled */ @session_regenerate_id($deleteOldSession);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
334
            }
335
        }
336
    }
337
338
    /**
339
     * Gets the name of the current session.
340
     * This is a wrapper for [PHP session_name()](https://www.php.net/manual/en/function.session-name.php).
341
     * @return string the current session name
342
     */
343 38
    public function getName()
344
    {
345 38
        return session_name();
346
    }
347
348
    /**
349
     * Sets the name for the current session.
350
     * This is a wrapper for [PHP session_name()](https://www.php.net/manual/en/function.session-name.php).
351
     * @param string $value the session name for the current session, must be an alphanumeric string.
352
     * It defaults to "PHPSESSID".
353
     */
354 1
    public function setName($value)
355
    {
356 1
        $this->freeze();
357 1
        session_name($value);
358 1
        $this->unfreeze();
359
    }
360
361
    /**
362
     * Gets the current session save path.
363
     * This is a wrapper for [PHP session_save_path()](https://www.php.net/manual/en/function.session-save-path.php).
364
     * @return string the current session save path, defaults to '/tmp'.
365
     */
366
    public function getSavePath()
367
    {
368
        return session_save_path();
369
    }
370
371
    /**
372
     * Sets the current session save path.
373
     * This is a wrapper for [PHP session_save_path()](https://www.php.net/manual/en/function.session-save-path.php).
374
     * @param string $value the current session save path. This can be either a directory name or a [path alias](guide:concept-aliases).
375
     * @throws InvalidArgumentException if the path is not a valid directory
376
     */
377
    public function setSavePath($value)
378
    {
379
        $path = Yii::getAlias($value);
380
        if (is_dir($path)) {
0 ignored issues
show
Bug introduced by
It seems like $path can also be of type false; however, parameter $filename of is_dir() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

380
        if (is_dir(/** @scrutinizer ignore-type */ $path)) {
Loading history...
381
            session_save_path($path);
0 ignored issues
show
Bug introduced by
It seems like $path can also be of type false; however, parameter $path of session_save_path() does only seem to accept null|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

381
            session_save_path(/** @scrutinizer ignore-type */ $path);
Loading history...
382
        } else {
383
            throw new InvalidArgumentException("Session save path is not a valid directory: $value");
384
        }
385
    }
386
387
    /**
388
     * @return array the session cookie parameters.
389
     * @see https://www.php.net/manual/en/function.session-get-cookie-params.php
390
     */
391 76
    public function getCookieParams()
392
    {
393 76
        return array_merge(session_get_cookie_params(), array_change_key_case($this->_cookieParams));
394
    }
395
396
    /**
397
     * Sets the session cookie parameters.
398
     * The cookie parameters passed to this method will be merged with the result
399
     * of `session_get_cookie_params()`.
400
     * @param array $value cookie parameters, valid keys include: `lifetime`, `path`, `domain`, `secure` and `httponly`.
401
     * Starting with Yii 2.0.21 `sameSite` is also supported. It requires PHP version 7.3.0 or higher.
402
     * For security, an exception will be thrown if `sameSite` is set while using an unsupported version of PHP.
403
     * To use this feature across different PHP versions check the version first. E.g.
404
     * ```php
405
     * [
406
     *     'sameSite' => PHP_VERSION_ID >= 70300 ? yii\web\Cookie::SAME_SITE_LAX : null,
407
     * ]
408
     * ```
409
     * See https://owasp.org/www-community/SameSite for more information about `sameSite`.
410
     *
411
     * @throws InvalidArgumentException if the parameters are incomplete.
412
     * @see https://www.php.net/manual/en/function.session-set-cookie-params.php
413
     */
414
    public function setCookieParams(array $value)
415
    {
416
        $this->_cookieParams = $value;
417
    }
418
419
    /**
420
     * Sets the session cookie parameters.
421
     * This method is called by [[open()]] when it is about to open the session.
422
     * @throws InvalidArgumentException if the parameters are incomplete.
423
     * @see https://www.php.net/manual/en/function.session-set-cookie-params.php
424
     */
425 76
    private function setCookieParamsInternal()
426
    {
427 76
        $data = $this->getCookieParams();
428 76
        if (isset($data['lifetime'], $data['path'], $data['domain'], $data['secure'], $data['httponly'])) {
429 76
            if (PHP_VERSION_ID >= 70300) {
430 76
                session_set_cookie_params($data);
431
            } else {
432
                if (!empty($data['samesite'])) {
433
                    $data['path'] .= '; samesite=' . $data['samesite'];
434
                }
435 76
                session_set_cookie_params($data['lifetime'], $data['path'], $data['domain'], $data['secure'], $data['httponly']);
436
            }
437
        } else {
438
            throw new InvalidArgumentException('Please make sure cookieParams contains these elements: lifetime, path, domain, secure and httponly.');
439
        }
440
    }
441
442
    /**
443
     * Returns the value indicating whether cookies should be used to store session IDs.
444
     * @return bool|null the value indicating whether cookies should be used to store session IDs.
445
     * @see setUseCookies()
446
     */
447 1
    public function getUseCookies()
448
    {
449 1
        if (ini_get('session.use_cookies') === '0') {
450 1
            return false;
451 1
        } elseif (ini_get('session.use_only_cookies') === '1') {
452 1
            return true;
453
        }
454
455
        return null;
456
    }
457
458
    /**
459
     * Sets the value indicating whether cookies should be used to store session IDs.
460
     *
461
     * Three states are possible:
462
     *
463
     * - true: cookies and only cookies will be used to store session IDs.
464
     * - false: cookies will not be used to store session IDs.
465
     * - null: if possible, cookies will be used to store session IDs; if not, other mechanisms will be used (e.g. GET parameter)
466
     *
467
     * @param bool|null $value the value indicating whether cookies should be used to store session IDs.
468
     */
469 4
    public function setUseCookies($value)
470
    {
471 4
        $this->freeze();
472 4
        if ($value === false) {
473 1
            ini_set('session.use_cookies', '0');
474 1
            ini_set('session.use_only_cookies', '0');
475 3
        } elseif ($value === true) {
0 ignored issues
show
introduced by
The condition $value === true is always true.
Loading history...
476 3
            ini_set('session.use_cookies', '1');
477 3
            ini_set('session.use_only_cookies', '1');
478
        } else {
479
            ini_set('session.use_cookies', '1');
480
            ini_set('session.use_only_cookies', '0');
481
        }
482 4
        $this->unfreeze();
483
    }
484
485
    /**
486
     * @return float the probability (percentage) that the GC (garbage collection) process is started on every session initialization.
487
     */
488 1
    public function getGCProbability()
489
    {
490 1
        return (float) (ini_get('session.gc_probability') / ini_get('session.gc_divisor') * 100);
491
    }
492
493
    /**
494
     * @param float $value the probability (percentage) that the GC (garbage collection) process is started on every session initialization.
495
     * @throws InvalidArgumentException if the value is not between 0 and 100.
496
     */
497 1
    public function setGCProbability($value)
498
    {
499 1
        $this->freeze();
500 1
        if ($value >= 0 && $value <= 100) {
501
            // percent * 21474837 / 2147483647 ≈ percent * 0.01
502 1
            ini_set('session.gc_probability', floor($value * 21474836.47));
503 1
            ini_set('session.gc_divisor', 2147483647);
504
        } else {
505
            throw new InvalidArgumentException('GCProbability must be a value between 0 and 100.');
506
        }
507 1
        $this->unfreeze();
508
    }
509
510
    /**
511
     * @return bool whether transparent sid support is enabled or not, defaults to false.
512
     */
513 1
    public function getUseTransparentSessionID()
514
    {
515 1
        return ini_get('session.use_trans_sid') == 1;
516
    }
517
518
    /**
519
     * @param bool $value whether transparent sid support is enabled or not.
520
     */
521 1
    public function setUseTransparentSessionID($value)
522
    {
523 1
        $this->freeze();
524 1
        ini_set('session.use_trans_sid', $value ? '1' : '0');
525 1
        $this->unfreeze();
526
    }
527
528
    /**
529
     * @return int the number of seconds after which data will be seen as 'garbage' and cleaned up.
530
     * The default value is 1440 seconds (or the value of "session.gc_maxlifetime" set in php.ini).
531
     */
532 28
    public function getTimeout()
533
    {
534 28
        return (int) ini_get('session.gc_maxlifetime');
535
    }
536
537
    /**
538
     * @param int $value the number of seconds after which data will be seen as 'garbage' and cleaned up
539
     */
540 4
    public function setTimeout($value)
541
    {
542 4
        $this->freeze();
543 4
        ini_set('session.gc_maxlifetime', $value);
544 4
        $this->unfreeze();
545
    }
546
547
    /**
548
     * @param bool $value Whether strict mode is enabled or not.
549
     * When `true` this setting prevents the session component to use an uninitialized session ID.
550
     * Note: Enabling `useStrictMode` on PHP < 5.5.2 is only supported with custom storage classes.
551
     * Warning! Although enabling strict mode is mandatory for secure sessions, the default value of 'session.use-strict-mode' is `0`.
552
     * @see https://www.php.net/manual/en/session.configuration.php#ini.session.use-strict-mode
553
     * @since 2.0.38
554
     */
555 10
    public function setUseStrictMode($value)
556
    {
557 10
        if (PHP_VERSION_ID < 50502) {
558
            if ($this->getUseCustomStorage() || !$value) {
559
                self::$_useStrictModePolyfill = $value;
560
            } else {
561
                throw new InvalidConfigException('Enabling `useStrictMode` on PHP < 5.5.2 is only supported with custom storage classes.');
562
            }
563
        } else {
564 10
            $this->freeze();
565 10
            ini_set('session.use_strict_mode', $value ? '1' : '0');
566 10
            $this->unfreeze();
567
        }
568
    }
569
570
    /**
571
     * @return bool Whether strict mode is enabled or not.
572
     * @see setUseStrictMode()
573
     * @since 2.0.38
574
     */
575 97
    public function getUseStrictMode()
576
    {
577 97
        if (PHP_VERSION_ID < 50502) {
578
            return self::$_useStrictModePolyfill;
579
        }
580
581 97
        return (bool)ini_get('session.use_strict_mode');
582
    }
583
584
    /**
585
     * Session open handler.
586
     * This method should be overridden if [[useCustomStorage]] returns true.
587
     * @internal Do not call this method directly.
588
     * @param string $savePath session save path
589
     * @param string $sessionName session name
590
     * @return bool whether session is opened successfully
591
     */
592 9
    public function openSession($savePath, $sessionName)
0 ignored issues
show
Unused Code introduced by
The parameter $sessionName is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

592
    public function openSession($savePath, /** @scrutinizer ignore-unused */ $sessionName)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $savePath is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

592
    public function openSession(/** @scrutinizer ignore-unused */ $savePath, $sessionName)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
593
    {
594 9
        return true;
595
    }
596
597
    /**
598
     * Session close handler.
599
     * This method should be overridden if [[useCustomStorage]] returns true.
600
     * @internal Do not call this method directly.
601
     * @return bool whether session is closed successfully
602
     */
603 8
    public function closeSession()
604
    {
605 8
        return true;
606
    }
607
608
    /**
609
     * Session read handler.
610
     * This method should be overridden if [[useCustomStorage]] returns true.
611
     * @internal Do not call this method directly.
612
     * @param string $id session ID
613
     * @return string the session data
614
     */
615
    public function readSession($id)
0 ignored issues
show
Unused Code introduced by
The parameter $id is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

615
    public function readSession(/** @scrutinizer ignore-unused */ $id)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
616
    {
617
        return '';
618
    }
619
620
    /**
621
     * Session write handler.
622
     * This method should be overridden if [[useCustomStorage]] returns true.
623
     * @internal Do not call this method directly.
624
     * @param string $id session ID
625
     * @param string $data session data
626
     * @return bool whether session write is successful
627
     */
628
    public function writeSession($id, $data)
0 ignored issues
show
Unused Code introduced by
The parameter $id is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

628
    public function writeSession(/** @scrutinizer ignore-unused */ $id, $data)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $data is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

628
    public function writeSession($id, /** @scrutinizer ignore-unused */ $data)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
629
    {
630
        return true;
631
    }
632
633
    /**
634
     * Session destroy handler.
635
     * This method should be overridden if [[useCustomStorage]] returns true.
636
     * @internal Do not call this method directly.
637
     * @param string $id session ID
638
     * @return bool whether session is destroyed successfully
639
     */
640 1
    public function destroySession($id)
0 ignored issues
show
Unused Code introduced by
The parameter $id is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

640
    public function destroySession(/** @scrutinizer ignore-unused */ $id)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
641
    {
642 1
        return true;
643
    }
644
645
    /**
646
     * Session GC (garbage collection) handler.
647
     * This method should be overridden if [[useCustomStorage]] returns true.
648
     * @internal Do not call this method directly.
649
     * @param int $maxLifetime the number of seconds after which data will be seen as 'garbage' and cleaned up.
650
     * @return bool whether session is GCed successfully
651
     */
652
    public function gcSession($maxLifetime)
0 ignored issues
show
Unused Code introduced by
The parameter $maxLifetime is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

652
    public function gcSession(/** @scrutinizer ignore-unused */ $maxLifetime)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
653
    {
654
        return true;
655
    }
656
657
    /**
658
     * Returns an iterator for traversing the session variables.
659
     * This method is required by the interface [[\IteratorAggregate]].
660
     * @return SessionIterator an iterator for traversing the session variables.
661
     */
662
    #[\ReturnTypeWillChange]
663
    public function getIterator()
664
    {
665
        $this->open();
666
        return new SessionIterator();
667
    }
668
669
    /**
670
     * Returns the number of items in the session.
671
     * @return int the number of session variables
672
     */
673
    public function getCount()
674
    {
675
        $this->open();
676
        return count($_SESSION);
677
    }
678
679
    /**
680
     * Returns the number of items in the session.
681
     * This method is required by [[\Countable]] interface.
682
     * @return int number of items in the session.
683
     */
684
    #[\ReturnTypeWillChange]
685
    public function count()
686
    {
687
        return $this->getCount();
688
    }
689
690
    /**
691
     * Returns the session variable value with the session variable name.
692
     * If the session variable does not exist, the `$defaultValue` will be returned.
693
     * @param string $key the session variable name
694
     * @param mixed $defaultValue the default value to be returned when the session variable does not exist.
695
     * @return mixed the session variable value, or $defaultValue if the session variable does not exist.
696
     */
697 77
    public function get($key, $defaultValue = null)
698
    {
699 77
        $this->open();
700 77
        return isset($_SESSION[$key]) ? $_SESSION[$key] : $defaultValue;
701
    }
702
703
    /**
704
     * Adds a session variable.
705
     * If the specified name already exists, the old value will be overwritten.
706
     * @param string $key session variable name
707
     * @param mixed $value session variable value
708
     */
709 60
    public function set($key, $value)
710
    {
711 60
        $this->open();
712 60
        $_SESSION[$key] = $value;
713
    }
714
715
    /**
716
     * Removes a session variable.
717
     * @param string $key the name of the session variable to be removed
718
     * @return mixed the removed value, null if no such session variable.
719
     */
720 45
    public function remove($key)
721
    {
722 45
        $this->open();
723 45
        if (isset($_SESSION[$key])) {
724 37
            $value = $_SESSION[$key];
725 37
            unset($_SESSION[$key]);
726
727 37
            return $value;
728
        }
729
730 45
        return null;
731
    }
732
733
    /**
734
     * Removes all session variables.
735
     */
736 22
    public function removeAll()
737
    {
738 22
        $this->open();
739 22
        foreach (array_keys($_SESSION) as $key) {
740 22
            unset($_SESSION[$key]);
741
        }
742
    }
743
744
    /**
745
     * @param mixed $key session variable name
746
     * @return bool whether there is the named session variable
747
     */
748
    public function has($key)
749
    {
750
        $this->open();
751
        return isset($_SESSION[$key]);
752
    }
753
754
    /**
755
     * Updates the counters for flash messages and removes outdated flash messages.
756
     * This method should only be called once in [[init()]].
757
     */
758 77
    protected function updateFlashCounters()
759
    {
760 77
        $counters = $this->get($this->flashParam, []);
761 77
        if (is_array($counters)) {
762 77
            foreach ($counters as $key => $count) {
763
                if ($count > 0) {
764
                    unset($counters[$key], $_SESSION[$key]);
765
                } elseif ($count == 0) {
766
                    $counters[$key]++;
767
                }
768
            }
769 77
            $_SESSION[$this->flashParam] = $counters;
770
        } else {
771
            // fix the unexpected problem that flashParam doesn't return an array
772
            unset($_SESSION[$this->flashParam]);
773
        }
774
    }
775
776
    /**
777
     * Returns a flash message.
778
     * @param string $key the key identifying the flash message
779
     * @param mixed $defaultValue value to be returned if the flash message does not exist.
780
     * @param bool $delete whether to delete this flash message right after this method is called.
781
     * If false, the flash message will be automatically deleted in the next request.
782
     * @return mixed the flash message or an array of messages if addFlash was used
783
     * @see setFlash()
784
     * @see addFlash()
785
     * @see hasFlash()
786
     * @see getAllFlashes()
787
     * @see removeFlash()
788
     */
789
    public function getFlash($key, $defaultValue = null, $delete = false)
790
    {
791
        $counters = $this->get($this->flashParam, []);
792
        if (isset($counters[$key])) {
793
            $value = $this->get($key, $defaultValue);
794
            if ($delete) {
795
                $this->removeFlash($key);
796
            } elseif ($counters[$key] < 0) {
797
                // mark for deletion in the next request
798
                $counters[$key] = 1;
799
                $_SESSION[$this->flashParam] = $counters;
800
            }
801
802
            return $value;
803
        }
804
805
        return $defaultValue;
806
    }
807
808
    /**
809
     * Returns all flash messages.
810
     *
811
     * You may use this method to display all the flash messages in a view file:
812
     *
813
     * ```php
814
     * <?php
815
     * foreach (Yii::$app->session->getAllFlashes() as $key => $message) {
816
     *     echo '<div class="alert alert-' . $key . '">' . $message . '</div>';
817
     * } ?>
818
     * ```
819
     *
820
     * With the above code you can use the [bootstrap alert][] classes such as `success`, `info`, `danger`
821
     * as the flash message key to influence the color of the div.
822
     *
823
     * Note that if you use [[addFlash()]], `$message` will be an array, and you will have to adjust the above code.
824
     *
825
     * [bootstrap alert]: https://getbootstrap.com/docs/3.4/components/#alerts
826
     *
827
     * @param bool $delete whether to delete the flash messages right after this method is called.
828
     * If false, the flash messages will be automatically deleted in the next request.
829
     * @return array flash messages (key => message or key => [message1, message2]).
830
     * @see setFlash()
831
     * @see addFlash()
832
     * @see getFlash()
833
     * @see hasFlash()
834
     * @see removeFlash()
835
     */
836
    public function getAllFlashes($delete = false)
837
    {
838
        $counters = $this->get($this->flashParam, []);
839
        $flashes = [];
840
        foreach (array_keys($counters) as $key) {
841
            if (array_key_exists($key, $_SESSION)) {
842
                $flashes[$key] = $_SESSION[$key];
843
                if ($delete) {
844
                    unset($counters[$key], $_SESSION[$key]);
845
                } elseif ($counters[$key] < 0) {
846
                    // mark for deletion in the next request
847
                    $counters[$key] = 1;
848
                }
849
            } else {
850
                unset($counters[$key]);
851
            }
852
        }
853
854
        $_SESSION[$this->flashParam] = $counters;
855
856
        return $flashes;
857
    }
858
859
    /**
860
     * Sets a flash message.
861
     * A flash message will be automatically deleted after it is accessed in a request and the deletion will happen
862
     * in the next request.
863
     * If there is already an existing flash message with the same key, it will be overwritten by the new one.
864
     * @param string $key the key identifying the flash message. Note that flash messages
865
     * and normal session variables share the same name space. If you have a normal
866
     * session variable using the same name, its value will be overwritten by this method.
867
     * @param mixed $value flash message
868
     * @param bool $removeAfterAccess whether the flash message should be automatically removed only if
869
     * it is accessed. If false, the flash message will be automatically removed after the next request,
870
     * regardless if it is accessed or not. If true (default value), the flash message will remain until after
871
     * it is accessed.
872
     * @see getFlash()
873
     * @see addFlash()
874
     * @see removeFlash()
875
     */
876
    public function setFlash($key, $value = true, $removeAfterAccess = true)
877
    {
878
        $counters = $this->get($this->flashParam, []);
879
        $counters[$key] = $removeAfterAccess ? -1 : 0;
880
        $_SESSION[$key] = $value;
881
        $_SESSION[$this->flashParam] = $counters;
882
    }
883
884
    /**
885
     * Adds a flash message.
886
     * If there are existing flash messages with the same key, the new one will be appended to the existing message array.
887
     * @param string $key the key identifying the flash message.
888
     * @param mixed $value flash message
889
     * @param bool $removeAfterAccess whether the flash message should be automatically removed only if
890
     * it is accessed. If false, the flash message will be automatically removed after the next request,
891
     * regardless if it is accessed or not. If true (default value), the flash message will remain until after
892
     * it is accessed.
893
     * @see getFlash()
894
     * @see setFlash()
895
     * @see removeFlash()
896
     */
897
    public function addFlash($key, $value = true, $removeAfterAccess = true)
898
    {
899
        $counters = $this->get($this->flashParam, []);
900
        $counters[$key] = $removeAfterAccess ? -1 : 0;
901
        $_SESSION[$this->flashParam] = $counters;
902
        if (empty($_SESSION[$key])) {
903
            $_SESSION[$key] = [$value];
904
        } elseif (is_array($_SESSION[$key])) {
905
            $_SESSION[$key][] = $value;
906
        } else {
907
            $_SESSION[$key] = [$_SESSION[$key], $value];
908
        }
909
    }
910
911
    /**
912
     * Removes a flash message.
913
     * @param string $key the key identifying the flash message. Note that flash messages
914
     * and normal session variables share the same name space.  If you have a normal
915
     * session variable using the same name, it will be removed by this method.
916
     * @return mixed the removed flash message. Null if the flash message does not exist.
917
     * @see getFlash()
918
     * @see setFlash()
919
     * @see addFlash()
920
     * @see removeAllFlashes()
921
     */
922
    public function removeFlash($key)
923
    {
924
        $counters = $this->get($this->flashParam, []);
925
        $value = isset($_SESSION[$key], $counters[$key]) ? $_SESSION[$key] : null;
926
        unset($counters[$key], $_SESSION[$key]);
927
        $_SESSION[$this->flashParam] = $counters;
928
929
        return $value;
930
    }
931
932
    /**
933
     * Removes all flash messages.
934
     * Note that flash messages and normal session variables share the same name space.
935
     * If you have a normal session variable using the same name, it will be removed
936
     * by this method.
937
     * @see getFlash()
938
     * @see setFlash()
939
     * @see addFlash()
940
     * @see removeFlash()
941
     */
942
    public function removeAllFlashes()
943
    {
944
        $counters = $this->get($this->flashParam, []);
945
        foreach (array_keys($counters) as $key) {
946
            unset($_SESSION[$key]);
947
        }
948
        unset($_SESSION[$this->flashParam]);
949
    }
950
951
    /**
952
     * Returns a value indicating whether there are flash messages associated with the specified key.
953
     * @param string $key key identifying the flash message type
954
     * @return bool whether any flash messages exist under specified key
955
     */
956
    public function hasFlash($key)
957
    {
958
        return $this->getFlash($key) !== null;
959
    }
960
961
    /**
962
     * This method is required by the interface [[\ArrayAccess]].
963
     * @param int|string $offset the offset to check on
964
     * @return bool
965
     */
966 3
    #[\ReturnTypeWillChange]
967
    public function offsetExists($offset)
968
    {
969 3
        $this->open();
970
971 3
        return isset($_SESSION[$offset]);
972
    }
973
974
    /**
975
     * This method is required by the interface [[\ArrayAccess]].
976
     * @param int|string $offset the offset to retrieve element.
977
     * @return mixed the element at the offset, null if no element is found at the offset
978
     */
979 3
    #[\ReturnTypeWillChange]
980
    public function offsetGet($offset)
981
    {
982 3
        $this->open();
983
984 3
        return isset($_SESSION[$offset]) ? $_SESSION[$offset] : null;
985
    }
986
987
    /**
988
     * This method is required by the interface [[\ArrayAccess]].
989
     * @param int|string $offset the offset to set element
990
     * @param mixed $item the element value
991
     */
992
    #[\ReturnTypeWillChange]
993
    public function offsetSet($offset, $item)
994
    {
995
        $this->open();
996
        $_SESSION[$offset] = $item;
997
    }
998
999
    /**
1000
     * This method is required by the interface [[\ArrayAccess]].
1001
     * @param int|string $offset the offset to unset element
1002
     */
1003
    #[\ReturnTypeWillChange]
1004
    public function offsetUnset($offset)
1005
    {
1006
        $this->open();
1007
        unset($_SESSION[$offset]);
1008
    }
1009
1010
    /**
1011
     * If session is started it's not possible to edit session ini settings. In PHP7.2+ it throws exception.
1012
     * This function saves session data to temporary variable and stop session.
1013
     * @since 2.0.14
1014
     */
1015 19
    protected function freeze()
1016
    {
1017 19
        if ($this->getIsActive()) {
1018 4
            if (isset($_SESSION)) {
1019 4
                $this->_frozenSessionData = $_SESSION;
1020
            }
1021 4
            $this->close();
1022 4
            Yii::info('Session frozen', __METHOD__);
1023
        }
1024
    }
1025
1026
    /**
1027
     * Starts session and restores data from temporary variable
1028
     * @since 2.0.14
1029
     */
1030 19
    protected function unfreeze()
1031
    {
1032 19
        if (null !== $this->_frozenSessionData) {
1033 4
            YII_DEBUG ? session_start() : @session_start();
1034
1035 4
            if ($this->getIsActive()) {
1036 4
                Yii::info('Session unfrozen', __METHOD__);
1037
            } else {
1038
                $error = error_get_last();
1039
                $message = isset($error['message']) ? $error['message'] : 'Failed to unfreeze session.';
1040
                Yii::error($message, __METHOD__);
1041
            }
1042
1043 4
            $_SESSION = $this->_frozenSessionData;
1044 4
            $this->_frozenSessionData = null;
1045
        }
1046
    }
1047
1048
    /**
1049
     * Set cache limiter
1050
     *
1051
     * @param string $cacheLimiter
1052
     * @since 2.0.14
1053
     */
1054 1
    public function setCacheLimiter($cacheLimiter)
1055
    {
1056 1
        $this->freeze();
1057 1
        session_cache_limiter($cacheLimiter);
1058 1
        $this->unfreeze();
1059
    }
1060
1061
    /**
1062
     * Returns current cache limiter
1063
     *
1064
     * @return string current cache limiter
1065
     * @since 2.0.14
1066
     */
1067
    public function getCacheLimiter()
1068
    {
1069
        return session_cache_limiter();
1070
    }
1071
}
1072