Completed
Push — remove-intl-polyfills ( 129df4...a6e727 )
by Alexander
16:22 queued 12:49
created

Session::destroy()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 10
cts 10
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 9
nc 2
nop 0
crap 2
1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://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 array $allFlashes Flash messages (key => message or key => [message1, message2]). This property
49
 * is read-only.
50
 * @property string $cacheLimiter Current cache limiter. This property is read-only.
51
 * @property array $cookieParams The session cookie parameters. This property is read-only.
52
 * @property int $count The number of session variables. This property is read-only.
53
 * @property string $flash The key identifying the flash message. Note that flash messages and normal session
54
 * variables share the same name space. If you have a normal session variable using the same name, its value will
55
 * be overwritten by this method. This property is write-only.
56
 * @property float $gCProbability The probability (percentage) that the GC (garbage collection) process is
57
 * started on every session initialization, defaults to 1 meaning 1% chance.
58
 * @property bool $hasSessionId Whether the current request has sent the session ID.
59
 * @property string $id The current session ID.
60
 * @property bool $isActive Whether the session has started. This property is read-only.
61
 * @property SessionIterator $iterator An iterator for traversing the session variables. This property is
62
 * read-only.
63
 * @property string $name The current session name.
64
 * @property string $savePath The current session save path, defaults to '/tmp'.
65
 * @property int $timeout The number of seconds after which data will be seen as 'garbage' and cleaned up. The
66
 * default value is 1440 seconds (or the value of "session.gc_maxlifetime" set in php.ini).
67
 * @property bool|null $useCookies The value indicating whether cookies should be used to store session IDs.
68
 * @property bool $useCustomStorage Whether to use custom storage. This property is read-only.
69
 * @property bool $useTransparentSessionID Whether transparent sid support is enabled or not, defaults to
70
 * false.
71
 *
72
 * @author Qiang Xue <[email protected]>
73
 * @since 2.0
74
 */
75
class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Countable
76
{
77
    /**
78
     * @var string the name of the session variable that stores the flash message data.
79
     */
80
    public $flashParam = '__flash';
81
    /**
82
     * @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.
83
     */
84
    public $handler;
85
86
    /**
87
     * @var array parameter-value pairs to override default session cookie parameters that are used for session_set_cookie_params() function
88
     * Array may have the following possible keys: 'lifetime', 'path', 'domain', 'secure', 'httponly'
89
     * @see http://www.php.net/manual/en/function.session-set-cookie-params.php
90
     */
91
    private $_cookieParams = ['httponly' => true];
92
    /**
93
     * @var $frozenSessionData array|null is used for saving session between recreations due to session parameters update.
94
     */
95
    private $frozenSessionData;
96
97
98
    /**
99
     * Initializes the application component.
100
     * This method is required by IApplicationComponent and is invoked by application.
101
     */
102 69
    public function init()
103
    {
104 69
        parent::init();
105 69
        register_shutdown_function([$this, 'close']);
106 69
        if ($this->getIsActive()) {
107 3
            Yii::warning('Session is already started', __METHOD__);
108 3
            $this->updateFlashCounters();
109
        }
110 69
    }
111
112
    /**
113
     * Returns a value indicating whether to use custom session storage.
114
     * This method should be overridden to return true by child classes that implement custom session storage.
115
     * To implement custom session storage, override these methods: [[openSession()]], [[closeSession()]],
116
     * [[readSession()]], [[writeSession()]], [[destroySession()]] and [[gcSession()]].
117
     * @return bool whether to use custom storage.
118
     */
119 37
    public function getUseCustomStorage()
120
    {
121 37
        return false;
122
    }
123
124
    /**
125
     * Starts the session.
126
     */
127 38
    public function open()
128
    {
129 38
        if ($this->getIsActive()) {
130 37
            return;
131
        }
132
133 38
        $this->registerSessionHandler();
134
135 38
        $this->setCookieParamsInternal();
136
137 38
        YII_DEBUG ? session_start() : @session_start();
138
139 38
        if ($this->getIsActive()) {
140 38
            Yii::info('Session started', __METHOD__);
141 38
            $this->updateFlashCounters();
142
        } else {
143
            $error = error_get_last();
144
            $message = $error['message'] ?? 'Failed to start session.';
145
            Yii::error($message, __METHOD__);
146
        }
147 38
    }
148
149
    /**
150
     * Registers session handler.
151
     * @throws \yii\base\InvalidConfigException
152
     */
153 38
    protected function registerSessionHandler()
154
    {
155 38
        if ($this->handler !== null) {
156
            if (!is_object($this->handler)) {
157
                $this->handler = Yii::createObject($this->handler);
158
            }
159
            if (!$this->handler instanceof \SessionHandlerInterface) {
160
                throw new InvalidConfigException('"' . get_class($this) . '::handler" must implement the SessionHandlerInterface.');
161
            }
162
            YII_DEBUG ? session_set_save_handler($this->handler, false) : @session_set_save_handler($this->handler, false);
163 38
        } elseif ($this->getUseCustomStorage()) {
164 1
            if (YII_DEBUG) {
165 1
                session_set_save_handler(
166 1
                    [$this, 'openSession'],
167 1
                    [$this, 'closeSession'],
168 1
                    [$this, 'readSession'],
169 1
                    [$this, 'writeSession'],
170 1
                    [$this, 'destroySession'],
171 1
                    [$this, 'gcSession']
172
                );
173
            } else {
174
                @session_set_save_handler(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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...
175
                    [$this, 'openSession'],
176
                    [$this, 'closeSession'],
177
                    [$this, 'readSession'],
178
                    [$this, 'writeSession'],
179
                    [$this, 'destroySession'],
180
                    [$this, 'gcSession']
181
                );
182
            }
183
        }
184 38
    }
185
186
    /**
187
     * Ends the current session and store session data.
188
     */
189 51
    public function close()
190
    {
191 51
        if ($this->getIsActive()) {
192 37
            YII_DEBUG ? session_write_close() : @session_write_close();
193
        }
194 51
    }
195
196
    /**
197
     * Frees all session variables and destroys all data registered to a session.
198
     *
199
     * This method has no effect when session is not [[getIsActive()|active]].
200
     * Make sure to call [[open()]] before calling it.
201
     * @see open()
202
     * @see isActive
203
     */
204 2
    public function destroy()
205
    {
206 2
        if ($this->getIsActive()) {
207 2
            $sessionId = session_id();
208 2
            $this->close();
209 2
            $this->setId($sessionId);
210 2
            $this->open();
211 2
            session_unset();
212 2
            session_destroy();
213 2
            $this->setId($sessionId);
214
        }
215 2
    }
216
217
    /**
218
     * @return bool whether the session has started
219
     */
220 69
    public function getIsActive()
221
    {
222 69
        return session_status() === PHP_SESSION_ACTIVE;
223
    }
224
225
    private $_hasSessionId;
226
227
    /**
228
     * Returns a value indicating whether the current request has sent the session ID.
229
     * The default implementation will check cookie and $_GET using the session name.
230
     * If you send session ID via other ways, you may need to override this method
231
     * or call [[setHasSessionId()]] to explicitly set whether the session ID is sent.
232
     * @return bool whether the current request has sent the session ID.
233
     */
234 15
    public function getHasSessionId()
235
    {
236 15
        if ($this->_hasSessionId === null) {
237 15
            $name = $this->getName();
238 15
            $request = Yii::$app->getRequest();
239
            // unable to use `Request::$cookies` since CSRF protection feature exclude the session one from them
240 15
            if (!empty($_COOKIE[$name]) && ini_get('session.use_cookies')) {
241
                $this->_hasSessionId = true;
242 15
            } elseif (!ini_get('session.use_only_cookies') && ini_get('session.use_trans_sid')) {
243
                $this->_hasSessionId = $request->get($name) != '';
244
            } else {
245 15
                $this->_hasSessionId = false;
246
            }
247
        }
248
249 15
        return $this->_hasSessionId;
250
    }
251
252
    /**
253
     * Sets the value indicating whether the current request has sent the session ID.
254
     * This method is provided so that you can override the default way of determining
255
     * whether the session ID is sent.
256
     * @param bool $value whether the current request has sent the session ID.
257
     */
258
    public function setHasSessionId($value)
259
    {
260
        $this->_hasSessionId = $value;
261
    }
262
263
    /**
264
     * Gets the session ID.
265
     * This is a wrapper for [PHP session_id()](http://php.net/manual/en/function.session-id.php).
266
     * @return string the current session ID
267
     */
268 1
    public function getId()
269
    {
270 1
        return session_id();
271
    }
272
273
    /**
274
     * Sets the session ID.
275
     * This is a wrapper for [PHP session_id()](http://php.net/manual/en/function.session-id.php).
276
     * @param string $value the session ID for the current session
277
     */
278 2
    public function setId($value)
279
    {
280 2
        session_id($value);
281 2
    }
282
283
    /**
284
     * Updates the current session ID with a newly generated one.
285
     *
286
     * Please refer to <http://php.net/session_regenerate_id> for more details.
287
     *
288
     * This method has no effect when session is not [[getIsActive()|active]].
289
     * Make sure to call [[open()]] before calling it.
290
     *
291
     * @param bool $deleteOldSession Whether to delete the old associated session file or not.
292
     * @see open()
293
     * @see isActive
294
     */
295
    public function regenerateID($deleteOldSession = false)
296
    {
297
        if ($this->getIsActive()) {
298
            // add @ to inhibit possible warning due to race condition
299
            // https://github.com/yiisoft/yii2/pull/1812
300
            if (YII_DEBUG && !headers_sent()) {
301
                session_regenerate_id($deleteOldSession);
302
            } else {
303
                @session_regenerate_id($deleteOldSession);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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...
304
            }
305
        }
306
    }
307
308
    /**
309
     * Gets the name of the current session.
310
     * This is a wrapper for [PHP session_name()](http://php.net/manual/en/function.session-name.php).
311
     * @return string the current session name
312
     */
313 16
    public function getName()
314
    {
315 16
        return session_name();
316
    }
317
318
    /**
319
     * Sets the name for the current session.
320
     * This is a wrapper for [PHP session_name()](http://php.net/manual/en/function.session-name.php).
321
     * @param string $value the session name for the current session, must be an alphanumeric string.
322
     * It defaults to "PHPSESSID".
323
     */
324 1
    public function setName($value)
325
    {
326 1
        $this->freeze();
327 1
        session_name($value);
328 1
        $this->unfreeze();
329 1
    }
330
331
    /**
332
     * Gets the current session save path.
333
     * This is a wrapper for [PHP session_save_path()](http://php.net/manual/en/function.session-save-path.php).
334
     * @return string the current session save path, defaults to '/tmp'.
335
     */
336
    public function getSavePath()
337
    {
338
        return session_save_path();
339
    }
340
341
    /**
342
     * Sets the current session save path.
343
     * This is a wrapper for [PHP session_save_path()](http://php.net/manual/en/function.session-save-path.php).
344
     * @param string $value the current session save path. This can be either a directory name or a [path alias](guide:concept-aliases).
345
     * @throws InvalidArgumentException if the path is not a valid directory
346
     */
347
    public function setSavePath($value)
348
    {
349
        $path = Yii::getAlias($value);
350
        if (is_dir($path)) {
351
            session_save_path($path);
352
        } else {
353
            throw new InvalidArgumentException("Session save path is not a valid directory: $value");
354
        }
355
    }
356
357
    /**
358
     * @return array the session cookie parameters.
359
     * @see http://php.net/manual/en/function.session-get-cookie-params.php
360
     */
361 38
    public function getCookieParams()
362
    {
363 38
        return array_merge(session_get_cookie_params(), array_change_key_case($this->_cookieParams));
364
    }
365
366
    /**
367
     * Sets the session cookie parameters.
368
     * The cookie parameters passed to this method will be merged with the result
369
     * of `session_get_cookie_params()`.
370
     * @param array $value cookie parameters, valid keys include: `lifetime`, `path`, `domain`, `secure` and `httponly`.
371
     * @throws InvalidArgumentException if the parameters are incomplete.
372
     * @see http://us2.php.net/manual/en/function.session-set-cookie-params.php
373
     */
374
    public function setCookieParams(array $value)
375
    {
376
        $this->_cookieParams = $value;
377
    }
378
379
    /**
380
     * Sets the session cookie parameters.
381
     * This method is called by [[open()]] when it is about to open the session.
382
     * @throws InvalidArgumentException if the parameters are incomplete.
383
     * @see http://us2.php.net/manual/en/function.session-set-cookie-params.php
384
     */
385 38
    private function setCookieParamsInternal()
386
    {
387 38
        $data = $this->getCookieParams();
388 38
        if (isset($data['lifetime'], $data['path'], $data['domain'], $data['secure'], $data['httponly'])) {
389 38
            session_set_cookie_params($data['lifetime'], $data['path'], $data['domain'], $data['secure'], $data['httponly']);
390
        } else {
391
            throw new InvalidArgumentException('Please make sure cookieParams contains these elements: lifetime, path, domain, secure and httponly.');
392
        }
393 38
    }
394
395
    /**
396
     * Returns the value indicating whether cookies should be used to store session IDs.
397
     * @return bool|null the value indicating whether cookies should be used to store session IDs.
398
     * @see setUseCookies()
399
     */
400 1
    public function getUseCookies()
401
    {
402 1
        if (ini_get('session.use_cookies') === '0') {
403 1
            return false;
404 1
        } elseif (ini_get('session.use_only_cookies') === '1') {
405 1
            return true;
406
        }
407
408
        return null;
409
    }
410
411
    /**
412
     * Sets the value indicating whether cookies should be used to store session IDs.
413
     *
414
     * Three states are possible:
415
     *
416
     * - true: cookies and only cookies will be used to store session IDs.
417
     * - false: cookies will not be used to store session IDs.
418
     * - null: if possible, cookies will be used to store session IDs; if not, other mechanisms will be used (e.g. GET parameter)
419
     *
420
     * @param bool|null $value the value indicating whether cookies should be used to store session IDs.
421
     */
422 4
    public function setUseCookies($value)
423
    {
424 4
        $this->freeze();
425 4
        if ($value === false) {
426 1
            ini_set('session.use_cookies', '0');
427 1
            ini_set('session.use_only_cookies', '0');
428 3
        } elseif ($value === true) {
429 3
            ini_set('session.use_cookies', '1');
430 3
            ini_set('session.use_only_cookies', '1');
431
        } else {
432
            ini_set('session.use_cookies', '1');
433
            ini_set('session.use_only_cookies', '0');
434
        }
435 4
        $this->unfreeze();
436 4
    }
437
438
    /**
439
     * @return float the probability (percentage) that the GC (garbage collection) process is started on every session initialization, defaults to 1 meaning 1% chance.
440
     */
441 1
    public function getGCProbability()
442
    {
443 1
        return (float) (ini_get('session.gc_probability') / ini_get('session.gc_divisor') * 100);
444
    }
445
446
    /**
447
     * @param float $value the probability (percentage) that the GC (garbage collection) process is started on every session initialization.
448
     * @throws InvalidArgumentException if the value is not between 0 and 100.
449
     */
450 1
    public function setGCProbability($value)
451
    {
452 1
        $this->freeze();
453 1
        if ($value >= 0 && $value <= 100) {
454
            // percent * 21474837 / 2147483647 ≈ percent * 0.01
455 1
            ini_set('session.gc_probability', floor($value * 21474836.47));
456 1
            ini_set('session.gc_divisor', 2147483647);
457
        } else {
458
            throw new InvalidArgumentException('GCProbability must be a value between 0 and 100.');
459
        }
460 1
        $this->unfreeze();
461 1
    }
462
463
    /**
464
     * @return bool whether transparent sid support is enabled or not, defaults to false.
465
     */
466 1
    public function getUseTransparentSessionID()
467
    {
468 1
        return ini_get('session.use_trans_sid') == 1;
469
    }
470
471
    /**
472
     * @param bool $value whether transparent sid support is enabled or not.
473
     */
474 1
    public function setUseTransparentSessionID($value)
475
    {
476 1
        $this->freeze();
477 1
        ini_set('session.use_trans_sid', $value ? '1' : '0');
478 1
        $this->unfreeze();
479 1
    }
480
481
    /**
482
     * @return int the number of seconds after which data will be seen as 'garbage' and cleaned up.
483
     * The default value is 1440 seconds (or the value of "session.gc_maxlifetime" set in php.ini).
484
     */
485 22
    public function getTimeout()
486
    {
487 22
        return (int) ini_get('session.gc_maxlifetime');
488
    }
489
490
    /**
491
     * @param int $value the number of seconds after which data will be seen as 'garbage' and cleaned up
492
     */
493 4
    public function setTimeout($value)
494
    {
495 4
        $this->freeze();
496 4
        ini_set('session.gc_maxlifetime', $value);
497 4
        $this->unfreeze();
498 4
    }
499
500
    /**
501
     * Session open handler.
502
     * This method should be overridden if [[useCustomStorage]] returns true.
503
     * @internal Do not call this method directly.
504
     * @param string $savePath session save path
505
     * @param string $sessionName session name
506
     * @return bool whether session is opened successfully
507
     */
508 4
    public function openSession($savePath, $sessionName)
0 ignored issues
show
Unused Code introduced by
The parameter $savePath is not used and could be removed.

This check looks from 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 $sessionName is not used and could be removed.

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

Loading history...
509
    {
510 4
        return true;
511
    }
512
513
    /**
514
     * Session close handler.
515
     * This method should be overridden if [[useCustomStorage]] returns true.
516
     * @internal Do not call this method directly.
517
     * @return bool whether session is closed successfully
518
     */
519 3
    public function closeSession()
520
    {
521 3
        return true;
522
    }
523
524
    /**
525
     * Session read handler.
526
     * This method should be overridden if [[useCustomStorage]] returns true.
527
     * @internal Do not call this method directly.
528
     * @param string $id session ID
529
     * @return string the session data
530
     */
531
    public function readSession($id)
532
    {
533
        return '';
534
    }
535
536
    /**
537
     * Session write handler.
538
     * This method should be overridden if [[useCustomStorage]] returns true.
539
     * @internal Do not call this method directly.
540
     * @param string $id session ID
541
     * @param string $data session data
542
     * @return bool whether session write is successful
543
     */
544
    public function writeSession($id, $data)
545
    {
546
        return true;
547
    }
548
549
    /**
550
     * Session destroy handler.
551
     * This method should be overridden if [[useCustomStorage]] returns true.
552
     * @internal Do not call this method directly.
553
     * @param string $id session ID
554
     * @return bool whether session is destroyed successfully
555
     */
556
    public function destroySession($id)
557
    {
558
        return true;
559
    }
560
561
    /**
562
     * Session GC (garbage collection) handler.
563
     * This method should be overridden if [[useCustomStorage]] returns true.
564
     * @internal Do not call this method directly.
565
     * @param int $maxLifetime the number of seconds after which data will be seen as 'garbage' and cleaned up.
566
     * @return bool whether session is GCed successfully
567
     */
568 2
    public function gcSession($maxLifetime)
0 ignored issues
show
Unused Code introduced by
The parameter $maxLifetime is not used and could be removed.

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

Loading history...
569
    {
570 2
        return true;
571
    }
572
573
    /**
574
     * Returns an iterator for traversing the session variables.
575
     * This method is required by the interface [[\IteratorAggregate]].
576
     * @return SessionIterator an iterator for traversing the session variables.
577
     */
578
    public function getIterator()
579
    {
580
        $this->open();
581
        return new SessionIterator();
582
    }
583
584
    /**
585
     * Returns the number of items in the session.
586
     * @return int the number of session variables
587
     */
588
    public function getCount()
589
    {
590
        $this->open();
591
        return count($_SESSION);
592
    }
593
594
    /**
595
     * Returns the number of items in the session.
596
     * This method is required by [[\Countable]] interface.
597
     * @return int number of items in the session.
598
     */
599
    public function count()
600
    {
601
        return $this->getCount();
602
    }
603
604
    /**
605
     * Returns the session variable value with the session variable name.
606
     * If the session variable does not exist, the `$defaultValue` will be returned.
607
     * @param string $key the session variable name
608
     * @param mixed $defaultValue the default value to be returned when the session variable does not exist.
609
     * @return mixed the session variable value, or $defaultValue if the session variable does not exist.
610
     */
611 37
    public function get($key, $defaultValue = null)
612
    {
613 37
        $this->open();
614 37
        return $_SESSION[$key] ?? $defaultValue;
615
    }
616
617
    /**
618
     * Adds a session variable.
619
     * If the specified name already exists, the old value will be overwritten.
620
     * @param string $key session variable name
621
     * @param mixed $value session variable value
622
     */
623 22
    public function set($key, $value)
624
    {
625 22
        $this->open();
626 22
        $_SESSION[$key] = $value;
627 22
    }
628
629
    /**
630
     * Removes a session variable.
631
     * @param string $key the name of the session variable to be removed
632
     * @return mixed the removed value, null if no such session variable.
633
     */
634 18
    public function remove($key)
635
    {
636 18
        $this->open();
637 18
        if (isset($_SESSION[$key])) {
638 16
            $value = $_SESSION[$key];
639 16
            unset($_SESSION[$key]);
640
641 16
            return $value;
642
        }
643
644 18
        return null;
645
    }
646
647
    /**
648
     * Removes all session variables.
649
     */
650 15
    public function removeAll()
651
    {
652 15
        $this->open();
653 15
        foreach (array_keys($_SESSION) as $key) {
654 14
            unset($_SESSION[$key]);
655
        }
656 15
    }
657
658
    /**
659
     * @param mixed $key session variable name
660
     * @return bool whether there is the named session variable
661
     */
662
    public function has($key)
663
    {
664
        $this->open();
665
        return isset($_SESSION[$key]);
666
    }
667
668
    /**
669
     * Updates the counters for flash messages and removes outdated flash messages.
670
     * This method should only be called once in [[init()]].
671
     */
672 38
    protected function updateFlashCounters()
673
    {
674 38
        $counters = $this->get($this->flashParam, []);
675 38
        if (is_array($counters)) {
676 37
            foreach ($counters as $key => $count) {
677
                if ($count > 0) {
678
                    unset($counters[$key], $_SESSION[$key]);
679
                } elseif ($count == 0) {
680
                    $counters[$key]++;
681
                }
682
            }
683 37
            $_SESSION[$this->flashParam] = $counters;
684
        } else {
685
            // fix the unexpected problem that flashParam doesn't return an array
686 1
            unset($_SESSION[$this->flashParam]);
687
        }
688 38
    }
689
690
    /**
691
     * Returns a flash message.
692
     * @param string $key the key identifying the flash message
693
     * @param mixed $defaultValue value to be returned if the flash message does not exist.
694
     * @param bool $delete whether to delete this flash message right after this method is called.
695
     * If false, the flash message will be automatically deleted in the next request.
696
     * @return mixed the flash message or an array of messages if addFlash was used
697
     * @see setFlash()
698
     * @see addFlash()
699
     * @see hasFlash()
700
     * @see getAllFlashes()
701
     * @see removeFlash()
702
     */
703
    public function getFlash($key, $defaultValue = null, $delete = false)
704
    {
705
        $counters = $this->get($this->flashParam, []);
706
        if (isset($counters[$key])) {
707
            $value = $this->get($key, $defaultValue);
708
            if ($delete) {
709
                $this->removeFlash($key);
710
            } elseif ($counters[$key] < 0) {
711
                // mark for deletion in the next request
712
                $counters[$key] = 1;
713
                $_SESSION[$this->flashParam] = $counters;
714
            }
715
716
            return $value;
717
        }
718
719
        return $defaultValue;
720
    }
721
722
    /**
723
     * Returns all flash messages.
724
     *
725
     * You may use this method to display all the flash messages in a view file:
726
     *
727
     * ```php
728
     * <?php
729
     * foreach (Yii::$app->session->getAllFlashes() as $key => $message) {
730
     *     echo '<div class="alert alert-' . $key . '">' . $message . '</div>';
731
     * } ?>
732
     * ```
733
     *
734
     * With the above code you can use the [bootstrap alert][] classes such as `success`, `info`, `danger`
735
     * as the flash message key to influence the color of the div.
736
     *
737
     * Note that if you use [[addFlash()]], `$message` will be an array, and you will have to adjust the above code.
738
     *
739
     * [bootstrap alert]: http://getbootstrap.com/components/#alerts
740
     *
741
     * @param bool $delete whether to delete the flash messages right after this method is called.
742
     * If false, the flash messages will be automatically deleted in the next request.
743
     * @return array flash messages (key => message or key => [message1, message2]).
744
     * @see setFlash()
745
     * @see addFlash()
746
     * @see getFlash()
747
     * @see hasFlash()
748
     * @see removeFlash()
749
     */
750
    public function getAllFlashes($delete = false)
751
    {
752
        $counters = $this->get($this->flashParam, []);
753
        $flashes = [];
754
        foreach (array_keys($counters) as $key) {
755
            if (array_key_exists($key, $_SESSION)) {
756
                $flashes[$key] = $_SESSION[$key];
757
                if ($delete) {
758
                    unset($counters[$key], $_SESSION[$key]);
759
                } elseif ($counters[$key] < 0) {
760
                    // mark for deletion in the next request
761
                    $counters[$key] = 1;
762
                }
763
            } else {
764
                unset($counters[$key]);
765
            }
766
        }
767
768
        $_SESSION[$this->flashParam] = $counters;
769
770
        return $flashes;
771
    }
772
773
    /**
774
     * Sets a flash message.
775
     * A flash message will be automatically deleted after it is accessed in a request and the deletion will happen
776
     * in the next request.
777
     * If there is already an existing flash message with the same key, it will be overwritten by the new one.
778
     * @param string $key the key identifying the flash message. Note that flash messages
779
     * and normal session variables share the same name space. If you have a normal
780
     * session variable using the same name, its value will be overwritten by this method.
781
     * @param mixed $value flash message
782
     * @param bool $removeAfterAccess whether the flash message should be automatically removed only if
783
     * it is accessed. If false, the flash message will be automatically removed after the next request,
784
     * regardless if it is accessed or not. If true (default value), the flash message will remain until after
785
     * it is accessed.
786
     * @see getFlash()
787
     * @see addFlash()
788
     * @see removeFlash()
789
     */
790
    public function setFlash($key, $value = true, $removeAfterAccess = true)
791
    {
792
        $counters = $this->get($this->flashParam, []);
793
        $counters[$key] = $removeAfterAccess ? -1 : 0;
794
        $_SESSION[$key] = $value;
795
        $_SESSION[$this->flashParam] = $counters;
796
    }
797
798
    /**
799
     * Adds a flash message.
800
     * If there are existing flash messages with the same key, the new one will be appended to the existing message array.
801
     * @param string $key the key identifying the flash message.
802
     * @param mixed $value flash message
803
     * @param bool $removeAfterAccess whether the flash message should be automatically removed only if
804
     * it is accessed. If false, the flash message will be automatically removed after the next request,
805
     * regardless if it is accessed or not. If true (default value), the flash message will remain until after
806
     * it is accessed.
807
     * @see getFlash()
808
     * @see setFlash()
809
     * @see removeFlash()
810
     */
811
    public function addFlash($key, $value = true, $removeAfterAccess = true)
812
    {
813
        $counters = $this->get($this->flashParam, []);
814
        $counters[$key] = $removeAfterAccess ? -1 : 0;
815
        $_SESSION[$this->flashParam] = $counters;
816
        if (empty($_SESSION[$key])) {
817
            $_SESSION[$key] = [$value];
818
        } else {
819
            if (is_array($_SESSION[$key])) {
820
                $_SESSION[$key][] = $value;
821
            } else {
822
                $_SESSION[$key] = [$_SESSION[$key], $value];
823
            }
824
        }
825
    }
826
827
    /**
828
     * Removes a flash message.
829
     * @param string $key the key identifying the flash message. Note that flash messages
830
     * and normal session variables share the same name space.  If you have a normal
831
     * session variable using the same name, it will be removed by this method.
832
     * @return mixed the removed flash message. Null if the flash message does not exist.
833
     * @see getFlash()
834
     * @see setFlash()
835
     * @see addFlash()
836
     * @see removeAllFlashes()
837
     */
838
    public function removeFlash($key)
839
    {
840
        $counters = $this->get($this->flashParam, []);
841
        $value = isset($_SESSION[$key], $counters[$key]) ? $_SESSION[$key] : null;
842
        unset($counters[$key], $_SESSION[$key]);
843
        $_SESSION[$this->flashParam] = $counters;
844
845
        return $value;
846
    }
847
848
    /**
849
     * Removes all flash messages.
850
     * Note that flash messages and normal session variables share the same name space.
851
     * If you have a normal session variable using the same name, it will be removed
852
     * by this method.
853
     * @see getFlash()
854
     * @see setFlash()
855
     * @see addFlash()
856
     * @see removeFlash()
857
     */
858
    public function removeAllFlashes()
859
    {
860
        $counters = $this->get($this->flashParam, []);
861
        foreach (array_keys($counters) as $key) {
862
            unset($_SESSION[$key]);
863
        }
864
        unset($_SESSION[$this->flashParam]);
865
    }
866
867
    /**
868
     * Returns a value indicating whether there are flash messages associated with the specified key.
869
     * @param string $key key identifying the flash message type
870
     * @return bool whether any flash messages exist under specified key
871
     */
872
    public function hasFlash($key)
873
    {
874
        return $this->getFlash($key) !== null;
875
    }
876
877
    /**
878
     * This method is required by the interface [[\ArrayAccess]].
879
     * @param mixed $offset the offset to check on
880
     * @return bool
881
     */
882
    public function offsetExists($offset)
883
    {
884
        $this->open();
885
886
        return isset($_SESSION[$offset]);
887
    }
888
889
    /**
890
     * This method is required by the interface [[\ArrayAccess]].
891
     * @param int $offset the offset to retrieve element.
892
     * @return mixed the element at the offset, null if no element is found at the offset
893
     */
894
    public function offsetGet($offset)
895
    {
896
        $this->open();
897
898
        return $_SESSION[$offset] ?? null;
899
    }
900
901
    /**
902
     * This method is required by the interface [[\ArrayAccess]].
903
     * @param int $offset the offset to set element
904
     * @param mixed $item the element value
905
     */
906
    public function offsetSet($offset, $item)
907
    {
908
        $this->open();
909
        $_SESSION[$offset] = $item;
910
    }
911
912
    /**
913
     * This method is required by the interface [[\ArrayAccess]].
914
     * @param mixed $offset the offset to unset element
915
     */
916
    public function offsetUnset($offset)
917
    {
918
        $this->open();
919
        unset($_SESSION[$offset]);
920
    }
921
922
    /**
923
     * If session is started it's not possible to edit session ini settings. In PHP7.2+ it throws exception.
924
     * This function saves session data to temporary variable and stop session.
925
     * @since 2.0.14
926
     */
927 9
    protected function freeze()
928
    {
929 9
        if ($this->getIsActive()) {
930 2
            if (isset($_SESSION)) {
931 2
                $this->frozenSessionData = $_SESSION;
932
            }
933 2
            $this->close();
934 2
            Yii::info('Session frozen', __METHOD__);
935
        }
936 9
    }
937
938
    /**
939
     * Starts session and restores data from temporary variable
940
     * @since 2.0.14
941
     */
942 9
    protected function unfreeze()
943
    {
944 9
        if (null !== $this->frozenSessionData) {
945
946 2
            YII_DEBUG ? session_start() : @session_start();
947
948 2
            if ($this->getIsActive()) {
949 2
                Yii::info('Session unfrozen', __METHOD__);
950
            } else {
951
                $error = error_get_last();
952
                $message = $error['message'] ?? 'Failed to unfreeze session.';
953
                Yii::error($message, __METHOD__);
954
            }
955
956 2
            $_SESSION = $this->frozenSessionData;
957 2
            $this->frozenSessionData = null;
958
        }
959 9
    }
960
961
    /**
962
     * Set cache limiter
963
     *
964
     * @param string $cacheLimiter
965
     * @since 2.0.14
966
     */
967 1
    public function setCacheLimiter($cacheLimiter)
968
    {
969 1
        $this->freeze();
970 1
        session_cache_limiter($cacheLimiter);
971 1
        $this->unfreeze();
972 1
    }
973
974
    /**
975
     * Returns current cache limiter
976
     *
977
     * @return string current cache limiter
978
     * @since 2.0.14
979
     */
980
    public function getCacheLimiter()
981
    {
982
        return session_cache_limiter();
983
    }
984
}
985