Completed
Push — master ( 5a8c3d...0e2b6c )
by Dmitry
12:47
created

Session::getId()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 1
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\InvalidConfigException;
13
use yii\base\InvalidParamException;
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 array $cookieParams The session cookie parameters. This property is read-only.
51
 * @property int $count The number of session variables. This property is read-only.
52
 * @property string $flash The key identifying the flash message. Note that flash messages and normal session
53
 * variables share the same name space. If you have a normal session variable using the same name, its value will
54
 * be overwritten by this method. This property is write-only.
55
 * @property float $gCProbability The probability (percentage) that the GC (garbage collection) process is
56
 * started on every session initialization, defaults to 1 meaning 1% chance.
57
 * @property bool $hasSessionId Whether the current request has sent the session ID.
58
 * @property string $id The current session ID.
59
 * @property bool $isActive Whether the session has started. This property is read-only.
60
 * @property SessionIterator $iterator An iterator for traversing the session variables. This property is
61
 * read-only.
62
 * @property string $name The current session name.
63
 * @property string $savePath The current session save path, defaults to '/tmp'.
64
 * @property int $timeout The number of seconds after which data will be seen as 'garbage' and cleaned up. The
65
 * default value is 1440 seconds (or the value of "session.gc_maxlifetime" set in php.ini).
66
 * @property bool|null $useCookies The value indicating whether cookies should be used to store session IDs.
67
 * @property bool $useCustomStorage Whether to use custom storage. This property is read-only.
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 the name of the session variable that stores the flash message data.
78
     */
79
    public $flashParam = '__flash';
80
    /**
81
     * @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.
82
     */
83
    public $handler;
84
85
    /**
86
     * @var array parameter-value pairs to override default session cookie parameters that are used for session_set_cookie_params() function
87
     * Array may have the following possible keys: 'lifetime', 'path', 'domain', 'secure', 'httponly'
88
     * @see http://www.php.net/manual/en/function.session-set-cookie-params.php
89
     */
90
    private $_cookieParams = ['httponly' => true];
91
92
93
    /**
94
     * Initializes the application component.
95
     * This method is required by IApplicationComponent and is invoked by application.
96
     */
97 44
    public function init()
98
    {
99 44
        parent::init();
100 44
        register_shutdown_function([$this, 'close']);
101 44
        if ($this->getIsActive()) {
102 5
            Yii::warning('Session is already started', __METHOD__);
103 5
            $this->updateFlashCounters();
104
        }
105 44
    }
106
107
    /**
108
     * Returns a value indicating whether to use custom session storage.
109
     * This method should be overridden to return true by child classes that implement custom session storage.
110
     * To implement custom session storage, override these methods: [[openSession()]], [[closeSession()]],
111
     * [[readSession()]], [[writeSession()]], [[destroySession()]] and [[gcSession()]].
112
     * @return bool whether to use custom storage.
113
     */
114 29
    public function getUseCustomStorage()
115
    {
116 29
        return false;
117
    }
118
119
    /**
120
     * Starts the session.
121
     */
122 34
    public function open()
123
    {
124 34
        if ($this->getIsActive()) {
125 34
            return;
126
        }
127
128 30
        $this->registerSessionHandler();
129
130 30
        $this->setCookieParamsInternal();
131
132 30
        @session_start();
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...
133
134 30
        if ($this->getIsActive()) {
135 30
            Yii::info('Session started', __METHOD__);
136 30
            $this->updateFlashCounters();
137
        } else {
138
            $error = error_get_last();
139
            $message = isset($error['message']) ? $error['message'] : 'Failed to start session.';
140
            Yii::error($message, __METHOD__);
141
        }
142 30
    }
143
144
    /**
145
     * Registers session handler.
146
     * @throws \yii\base\InvalidConfigException
147
     */
148 30
    protected function registerSessionHandler()
149
    {
150 30
        if ($this->handler !== null) {
151
            if (!is_object($this->handler)) {
152
                $this->handler = Yii::createObject($this->handler);
153
            }
154
            if (!$this->handler instanceof \SessionHandlerInterface) {
155
                throw new InvalidConfigException('"' . get_class($this) . '::handler" must implement the SessionHandlerInterface.');
156
            }
157
            YII_DEBUG ? session_set_save_handler($this->handler, false) : @session_set_save_handler($this->handler, false);
158 30
        } elseif ($this->getUseCustomStorage()) {
159 1
            if (YII_DEBUG) {
160 1
                session_set_save_handler(
161 1
                    [$this, 'openSession'],
162 1
                    [$this, 'closeSession'],
163 1
                    [$this, 'readSession'],
164 1
                    [$this, 'writeSession'],
165 1
                    [$this, 'destroySession'],
166 1
                    [$this, 'gcSession']
167
                );
168
            } else {
169
                @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...
170
                    [$this, 'openSession'],
171
                    [$this, 'closeSession'],
172
                    [$this, 'readSession'],
173
                    [$this, 'writeSession'],
174
                    [$this, 'destroySession'],
175
                    [$this, 'gcSession']
176
                );
177
            }
178
        }
179 30
    }
180
181
    /**
182
     * Ends the current session and store session data.
183
     */
184 38
    public function close()
185
    {
186 38
        if ($this->getIsActive()) {
187 30
            YII_DEBUG ? session_write_close() : @session_write_close();
188
        }
189 38
    }
190
191
    /**
192
     * Frees all session variables and destroys all data registered to a session.
193
     *
194
     * This method has no effect when session is not [[getIsActive()|active]].
195
     * Make sure to call [[open()]] before calling it.
196
     * @see open()
197
     * @see isActive
198
     */
199 1
    public function destroy()
200
    {
201 1
        if ($this->getIsActive()) {
202 1
            $sessionId = session_id();
203 1
            $this->close();
204 1
            $this->setId($sessionId);
205 1
            $this->open();
206 1
            session_unset();
207 1
            session_destroy();
208 1
            $this->setId($sessionId);
209
        }
210 1
    }
211
212
    /**
213
     * @return bool whether the session has started
214
     */
215 44
    public function getIsActive()
216
    {
217 44
        return session_status() === PHP_SESSION_ACTIVE;
218
    }
219
220
    private $_hasSessionId;
221
222
    /**
223
     * Returns a value indicating whether the current request has sent the session ID.
224
     * The default implementation will check cookie and $_GET using the session name.
225
     * If you send session ID via other ways, you may need to override this method
226
     * or call [[setHasSessionId()]] to explicitly set whether the session ID is sent.
227
     * @return bool whether the current request has sent the session ID.
228
     */
229 10
    public function getHasSessionId()
230
    {
231 10
        if ($this->_hasSessionId === null) {
232 10
            $name = $this->getName();
233 10
            $request = Yii::$app->getRequest();
234 10
            if (!empty($_COOKIE[$name]) && ini_get('session.use_cookies')) {
235
                $this->_hasSessionId = true;
236 10
            } elseif (!ini_get('session.use_only_cookies') && ini_get('session.use_trans_sid')) {
237
                $this->_hasSessionId = $request->get($name) != '';
238
            } else {
239 10
                $this->_hasSessionId = false;
240
            }
241
        }
242
243 10
        return $this->_hasSessionId;
244
    }
245
246
    /**
247
     * Sets the value indicating whether the current request has sent the session ID.
248
     * This method is provided so that you can override the default way of determining
249
     * whether the session ID is sent.
250
     * @param bool $value whether the current request has sent the session ID.
251
     */
252
    public function setHasSessionId($value)
253
    {
254
        $this->_hasSessionId = $value;
255
    }
256
257
    /**
258
     * Gets the session ID.
259
     * This is a wrapper for [PHP session_id()](http://php.net/manual/en/function.session-id.php).
260
     * @return string the current session ID
261
     */
262 1
    public function getId()
263
    {
264 1
        return session_id();
265
    }
266
267
    /**
268
     * Sets the session ID.
269
     * This is a wrapper for [PHP session_id()](http://php.net/manual/en/function.session-id.php).
270
     * @param string $value the session ID for the current session
271
     */
272 1
    public function setId($value)
273
    {
274 1
        session_id($value);
275 1
    }
276
277
    /**
278
     * Updates the current session ID with a newly generated one.
279
     *
280
     * Please refer to <http://php.net/session_regenerate_id> for more details.
281
     *
282
     * This method has no effect when session is not [[getIsActive()|active]].
283
     * Make sure to call [[open()]] before calling it.
284
     *
285
     * @param bool $deleteOldSession Whether to delete the old associated session file or not.
286
     * @see open()
287
     * @see isActive
288
     */
289
    public function regenerateID($deleteOldSession = false)
290
    {
291
        if ($this->getIsActive()) {
292
            // add @ to inhibit possible warning due to race condition
293
            // https://github.com/yiisoft/yii2/pull/1812
294
            if (YII_DEBUG && !headers_sent()) {
295
                session_regenerate_id($deleteOldSession);
296
            } else {
297
                @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...
298
            }
299
        }
300
    }
301
302
    /**
303
     * Gets the name of the current session.
304
     * This is a wrapper for [PHP session_name()](http://php.net/manual/en/function.session-name.php).
305
     * @return string the current session name
306
     */
307 10
    public function getName()
308
    {
309 10
        return session_name();
310
    }
311
312
    /**
313
     * Sets the name for the current session.
314
     * This is a wrapper for [PHP session_name()](http://php.net/manual/en/function.session-name.php).
315
     * @param string $value the session name for the current session, must be an alphanumeric string.
316
     * It defaults to "PHPSESSID".
317
     */
318
    public function setName($value)
319
    {
320
        session_name($value);
321
    }
322
323
    /**
324
     * Gets the current session save path.
325
     * This is a wrapper for [PHP session_save_path()](http://php.net/manual/en/function.session-save-path.php).
326
     * @return string the current session save path, defaults to '/tmp'.
327
     */
328
    public function getSavePath()
329
    {
330
        return session_save_path();
331
    }
332
333
    /**
334
     * Sets the current session save path.
335
     * This is a wrapper for [PHP session_save_path()](http://php.net/manual/en/function.session-save-path.php).
336
     * @param string $value the current session save path. This can be either a directory name or a [path alias](guide:concept-aliases).
337
     * @throws InvalidParamException if the path is not a valid directory
338
     */
339
    public function setSavePath($value)
340
    {
341
        $path = Yii::getAlias($value);
342
        if (is_dir($path)) {
343
            session_save_path($path);
344
        } else {
345
            throw new InvalidParamException("Session save path is not a valid directory: $value");
346
        }
347
    }
348
349
    /**
350
     * @return array the session cookie parameters.
351
     * @see http://php.net/manual/en/function.session-get-cookie-params.php
352
     */
353 30
    public function getCookieParams()
354
    {
355 30
        return array_merge(session_get_cookie_params(), array_change_key_case($this->_cookieParams));
356
    }
357
358
    /**
359
     * Sets the session cookie parameters.
360
     * The cookie parameters passed to this method will be merged with the result
361
     * of `session_get_cookie_params()`.
362
     * @param array $value cookie parameters, valid keys include: `lifetime`, `path`, `domain`, `secure` and `httponly`.
363
     * @throws InvalidParamException if the parameters are incomplete.
364
     * @see http://us2.php.net/manual/en/function.session-set-cookie-params.php
365
     */
366
    public function setCookieParams(array $value)
367
    {
368
        $this->_cookieParams = $value;
369
    }
370
371
    /**
372
     * Sets the session cookie parameters.
373
     * This method is called by [[open()]] when it is about to open the session.
374
     * @throws InvalidParamException if the parameters are incomplete.
375
     * @see http://us2.php.net/manual/en/function.session-set-cookie-params.php
376
     */
377 30
    private function setCookieParamsInternal()
378
    {
379 30
        $data = $this->getCookieParams();
380 30
        if (isset($data['lifetime'], $data['path'], $data['domain'], $data['secure'], $data['httponly'])) {
381 30
            session_set_cookie_params($data['lifetime'], $data['path'], $data['domain'], $data['secure'], $data['httponly']);
382
        } else {
383
            throw new InvalidParamException('Please make sure cookieParams contains these elements: lifetime, path, domain, secure and httponly.');
384
        }
385 30
    }
386
387
    /**
388
     * Returns the value indicating whether cookies should be used to store session IDs.
389
     * @return bool|null the value indicating whether cookies should be used to store session IDs.
390
     * @see setUseCookies()
391
     */
392
    public function getUseCookies()
393
    {
394
        if (ini_get('session.use_cookies') === '0') {
395
            return false;
396
        } elseif (ini_get('session.use_only_cookies') === '1') {
397
            return true;
398
        }
399
400
        return null;
401
    }
402
403
    /**
404
     * Sets the value indicating whether cookies should be used to store session IDs.
405
     *
406
     * Three states are possible:
407
     *
408
     * - true: cookies and only cookies will be used to store session IDs.
409
     * - false: cookies will not be used to store session IDs.
410
     * - null: if possible, cookies will be used to store session IDs; if not, other mechanisms will be used (e.g. GET parameter)
411
     *
412
     * @param bool|null $value the value indicating whether cookies should be used to store session IDs.
413
     */
414
    public function setUseCookies($value)
415
    {
416
        if ($value === false) {
417
            ini_set('session.use_cookies', '0');
418
            ini_set('session.use_only_cookies', '0');
419
        } elseif ($value === true) {
420
            ini_set('session.use_cookies', '1');
421
            ini_set('session.use_only_cookies', '1');
422
        } else {
423
            ini_set('session.use_cookies', '1');
424
            ini_set('session.use_only_cookies', '0');
425
        }
426
    }
427
428
    /**
429
     * @return float the probability (percentage) that the GC (garbage collection) process is started on every session initialization, defaults to 1 meaning 1% chance.
430
     */
431
    public function getGCProbability()
432
    {
433
        return (float) (ini_get('session.gc_probability') / ini_get('session.gc_divisor') * 100);
434
    }
435
436
    /**
437
     * @param float $value the probability (percentage) that the GC (garbage collection) process is started on every session initialization.
438
     * @throws InvalidParamException if the value is not between 0 and 100.
439
     */
440
    public function setGCProbability($value)
441
    {
442
        if ($value >= 0 && $value <= 100) {
443
            // percent * 21474837 / 2147483647 ≈ percent * 0.01
444
            ini_set('session.gc_probability', floor($value * 21474836.47));
445
            ini_set('session.gc_divisor', 2147483647);
446
        } else {
447
            throw new InvalidParamException('GCProbability must be a value between 0 and 100.');
448
        }
449
    }
450
451
    /**
452
     * @return bool whether transparent sid support is enabled or not, defaults to false.
453
     */
454
    public function getUseTransparentSessionID()
455
    {
456
        return ini_get('session.use_trans_sid') == 1;
457
    }
458
459
    /**
460
     * @param bool $value whether transparent sid support is enabled or not.
461
     */
462
    public function setUseTransparentSessionID($value)
463
    {
464
        ini_set('session.use_trans_sid', $value ? '1' : '0');
465
    }
466
467
    /**
468
     * @return int the number of seconds after which data will be seen as 'garbage' and cleaned up.
469
     * The default value is 1440 seconds (or the value of "session.gc_maxlifetime" set in php.ini).
470
     */
471 13
    public function getTimeout()
472
    {
473 13
        return (int) ini_get('session.gc_maxlifetime');
474
    }
475
476
    /**
477
     * @param int $value the number of seconds after which data will be seen as 'garbage' and cleaned up
478
     */
479
    public function setTimeout($value)
480
    {
481
        ini_set('session.gc_maxlifetime', $value);
482
    }
483
484
    /**
485
     * Session open handler.
486
     * This method should be overridden if [[useCustomStorage]] returns true.
487
     * @internal Do not call this method directly.
488
     * @param string $savePath session save path
489
     * @param string $sessionName session name
490
     * @return bool whether session is opened successfully
491
     */
492 9
    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...
493
    {
494 9
        return true;
495
    }
496
497
    /**
498
     * Session close handler.
499
     * This method should be overridden if [[useCustomStorage]] returns true.
500
     * @internal Do not call this method directly.
501
     * @return bool whether session is closed successfully
502
     */
503 9
    public function closeSession()
504
    {
505 9
        return true;
506
    }
507
508
    /**
509
     * Session read handler.
510
     * This method should be overridden if [[useCustomStorage]] returns true.
511
     * @internal Do not call this method directly.
512
     * @param string $id session ID
513
     * @return string the session data
514
     */
515
    public function readSession($id)
516
    {
517
        return '';
518
    }
519
520
    /**
521
     * Session write handler.
522
     * This method should be overridden if [[useCustomStorage]] returns true.
523
     * @internal Do not call this method directly.
524
     * @param string $id session ID
525
     * @param string $data session data
526
     * @return bool whether session write is successful
527
     */
528
    public function writeSession($id, $data)
529
    {
530
        return true;
531
    }
532
533
    /**
534
     * Session destroy handler.
535
     * This method should be overridden if [[useCustomStorage]] returns true.
536
     * @internal Do not call this method directly.
537
     * @param string $id session ID
538
     * @return bool whether session is destroyed successfully
539
     */
540
    public function destroySession($id)
541
    {
542
        return true;
543
    }
544
545
    /**
546
     * Session GC (garbage collection) handler.
547
     * This method should be overridden if [[useCustomStorage]] returns true.
548
     * @internal Do not call this method directly.
549
     * @param int $maxLifetime the number of seconds after which data will be seen as 'garbage' and cleaned up.
550
     * @return bool whether session is GCed successfully
551
     */
552 1
    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...
553
    {
554 1
        return true;
555
    }
556
557
    /**
558
     * Returns an iterator for traversing the session variables.
559
     * This method is required by the interface [[\IteratorAggregate]].
560
     * @return SessionIterator an iterator for traversing the session variables.
561
     */
562
    public function getIterator()
563
    {
564
        $this->open();
565
        return new SessionIterator();
566
    }
567
568
    /**
569
     * Returns the number of items in the session.
570
     * @return int the number of session variables
571
     */
572
    public function getCount()
573
    {
574
        $this->open();
575
        return count($_SESSION);
576
    }
577
578
    /**
579
     * Returns the number of items in the session.
580
     * This method is required by [[\Countable]] interface.
581
     * @return int number of items in the session.
582
     */
583
    public function count()
584
    {
585
        return $this->getCount();
586
    }
587
588
    /**
589
     * Returns the session variable value with the session variable name.
590
     * If the session variable does not exist, the `$defaultValue` will be returned.
591
     * @param string $key the session variable name
592
     * @param mixed $defaultValue the default value to be returned when the session variable does not exist.
593
     * @return mixed the session variable value, or $defaultValue if the session variable does not exist.
594
     */
595 34
    public function get($key, $defaultValue = null)
596
    {
597 34
        $this->open();
598 34
        return isset($_SESSION[$key]) ? $_SESSION[$key] : $defaultValue;
599
    }
600
601
    /**
602
     * Adds a session variable.
603
     * If the specified name already exists, the old value will be overwritten.
604
     * @param string $key session variable name
605
     * @param mixed $value session variable value
606
     */
607 20
    public function set($key, $value)
608
    {
609 20
        $this->open();
610 20
        $_SESSION[$key] = $value;
611 20
    }
612
613
    /**
614
     * Removes a session variable.
615
     * @param string $key the name of the session variable to be removed
616
     * @return mixed the removed value, null if no such session variable.
617
     */
618 15
    public function remove($key)
619
    {
620 15
        $this->open();
621 15
        if (isset($_SESSION[$key])) {
622 13
            $value = $_SESSION[$key];
623 13
            unset($_SESSION[$key]);
624
625 13
            return $value;
626
        }
627
628 15
        return null;
629
    }
630
631
    /**
632
     * Removes all session variables.
633
     */
634 13
    public function removeAll()
635
    {
636 13
        $this->open();
637 13
        foreach (array_keys($_SESSION) as $key) {
638 13
            unset($_SESSION[$key]);
639
        }
640 13
    }
641
642
    /**
643
     * @param mixed $key session variable name
644
     * @return bool whether there is the named session variable
645
     */
646
    public function has($key)
647
    {
648
        $this->open();
649
        return isset($_SESSION[$key]);
650
    }
651
652
    /**
653
     * Updates the counters for flash messages and removes outdated flash messages.
654
     * This method should only be called once in [[init()]].
655
     */
656 34
    protected function updateFlashCounters()
657
    {
658 34
        $counters = $this->get($this->flashParam, []);
659 34
        if (is_array($counters)) {
660 34
            foreach ($counters as $key => $count) {
661
                if ($count > 0) {
662
                    unset($counters[$key], $_SESSION[$key]);
663
                } elseif ($count == 0) {
664
                    $counters[$key]++;
665
                }
666
            }
667 34
            $_SESSION[$this->flashParam] = $counters;
668
        } else {
669
            // fix the unexpected problem that flashParam doesn't return an array
670
            unset($_SESSION[$this->flashParam]);
671
        }
672 34
    }
673
674
    /**
675
     * Returns a flash message.
676
     * @param string $key the key identifying the flash message
677
     * @param mixed $defaultValue value to be returned if the flash message does not exist.
678
     * @param bool $delete whether to delete this flash message right after this method is called.
679
     * If false, the flash message will be automatically deleted in the next request.
680
     * @return mixed the flash message or an array of messages if addFlash was used
681
     * @see setFlash()
682
     * @see addFlash()
683
     * @see hasFlash()
684
     * @see getAllFlashes()
685
     * @see removeFlash()
686
     */
687
    public function getFlash($key, $defaultValue = null, $delete = false)
688
    {
689
        $counters = $this->get($this->flashParam, []);
690
        if (isset($counters[$key])) {
691
            $value = $this->get($key, $defaultValue);
692
            if ($delete) {
693
                $this->removeFlash($key);
694
            } elseif ($counters[$key] < 0) {
695
                // mark for deletion in the next request
696
                $counters[$key] = 1;
697
                $_SESSION[$this->flashParam] = $counters;
698
            }
699
700
            return $value;
701
        }
702
703
        return $defaultValue;
704
    }
705
706
    /**
707
     * Returns all flash messages.
708
     *
709
     * You may use this method to display all the flash messages in a view file:
710
     *
711
     * ```php
712
     * <?php
713
     * foreach (Yii::$app->session->getAllFlashes() as $key => $message) {
714
     *     echo '<div class="alert alert-' . $key . '">' . $message . '</div>';
715
     * } ?>
716
     * ```
717
     *
718
     * With the above code you can use the [bootstrap alert][] classes such as `success`, `info`, `danger`
719
     * as the flash message key to influence the color of the div.
720
     *
721
     * Note that if you use [[addFlash()]], `$message` will be an array, and you will have to adjust the above code.
722
     *
723
     * [bootstrap alert]: http://getbootstrap.com/components/#alerts
724
     *
725
     * @param bool $delete whether to delete the flash messages right after this method is called.
726
     * If false, the flash messages will be automatically deleted in the next request.
727
     * @return array flash messages (key => message or key => [message1, message2]).
728
     * @see setFlash()
729
     * @see addFlash()
730
     * @see getFlash()
731
     * @see hasFlash()
732
     * @see removeFlash()
733
     */
734
    public function getAllFlashes($delete = false)
735
    {
736
        $counters = $this->get($this->flashParam, []);
737
        $flashes = [];
738
        foreach (array_keys($counters) as $key) {
739
            if (array_key_exists($key, $_SESSION)) {
740
                $flashes[$key] = $_SESSION[$key];
741
                if ($delete) {
742
                    unset($counters[$key], $_SESSION[$key]);
743
                } elseif ($counters[$key] < 0) {
744
                    // mark for deletion in the next request
745
                    $counters[$key] = 1;
746
                }
747
            } else {
748
                unset($counters[$key]);
749
            }
750
        }
751
752
        $_SESSION[$this->flashParam] = $counters;
753
754
        return $flashes;
755
    }
756
757
    /**
758
     * Sets a flash message.
759
     * A flash message will be automatically deleted after it is accessed in a request and the deletion will happen
760
     * in the next request.
761
     * If there is already an existing flash message with the same key, it will be overwritten by the new one.
762
     * @param string $key the key identifying the flash message. Note that flash messages
763
     * and normal session variables share the same name space. If you have a normal
764
     * session variable using the same name, its value will be overwritten by this method.
765
     * @param mixed $value flash message
766
     * @param bool $removeAfterAccess whether the flash message should be automatically removed only if
767
     * it is accessed. If false, the flash message will be automatically removed after the next request,
768
     * regardless if it is accessed or not. If true (default value), the flash message will remain until after
769
     * it is accessed.
770
     * @see getFlash()
771
     * @see addFlash()
772
     * @see removeFlash()
773
     */
774
    public function setFlash($key, $value = true, $removeAfterAccess = true)
775
    {
776
        $counters = $this->get($this->flashParam, []);
777
        $counters[$key] = $removeAfterAccess ? -1 : 0;
778
        $_SESSION[$key] = $value;
779
        $_SESSION[$this->flashParam] = $counters;
780
    }
781
782
    /**
783
     * Adds a flash message.
784
     * If there are existing flash messages with the same key, the new one will be appended to the existing message array.
785
     * @param string $key the key identifying the flash message.
786
     * @param mixed $value flash message
787
     * @param bool $removeAfterAccess whether the flash message should be automatically removed only if
788
     * it is accessed. If false, the flash message will be automatically removed after the next request,
789
     * regardless if it is accessed or not. If true (default value), the flash message will remain until after
790
     * it is accessed.
791
     * @see getFlash()
792
     * @see setFlash()
793
     * @see removeFlash()
794
     */
795
    public function addFlash($key, $value = true, $removeAfterAccess = true)
796
    {
797
        $counters = $this->get($this->flashParam, []);
798
        $counters[$key] = $removeAfterAccess ? -1 : 0;
799
        $_SESSION[$this->flashParam] = $counters;
800
        if (empty($_SESSION[$key])) {
801
            $_SESSION[$key] = [$value];
802
        } else {
803
            if (is_array($_SESSION[$key])) {
804
                $_SESSION[$key][] = $value;
805
            } else {
806
                $_SESSION[$key] = [$_SESSION[$key], $value];
807
            }
808
        }
809
    }
810
811
    /**
812
     * Removes a flash message.
813
     * @param string $key the key identifying the flash message. Note that flash messages
814
     * and normal session variables share the same name space.  If you have a normal
815
     * session variable using the same name, it will be removed by this method.
816
     * @return mixed the removed flash message. Null if the flash message does not exist.
817
     * @see getFlash()
818
     * @see setFlash()
819
     * @see addFlash()
820
     * @see removeAllFlashes()
821
     */
822
    public function removeFlash($key)
823
    {
824
        $counters = $this->get($this->flashParam, []);
825
        $value = isset($_SESSION[$key], $counters[$key]) ? $_SESSION[$key] : null;
826
        unset($counters[$key], $_SESSION[$key]);
827
        $_SESSION[$this->flashParam] = $counters;
828
829
        return $value;
830
    }
831
832
    /**
833
     * Removes all flash messages.
834
     * Note that flash messages and normal session variables share the same name space.
835
     * If you have a normal session variable using the same name, it will be removed
836
     * by this method.
837
     * @see getFlash()
838
     * @see setFlash()
839
     * @see addFlash()
840
     * @see removeFlash()
841
     */
842
    public function removeAllFlashes()
843
    {
844
        $counters = $this->get($this->flashParam, []);
845
        foreach (array_keys($counters) as $key) {
846
            unset($_SESSION[$key]);
847
        }
848
        unset($_SESSION[$this->flashParam]);
849
    }
850
851
    /**
852
     * Returns a value indicating whether there are flash messages associated with the specified key.
853
     * @param string $key key identifying the flash message type
854
     * @return bool whether any flash messages exist under specified key
855
     */
856
    public function hasFlash($key)
857
    {
858
        return $this->getFlash($key) !== null;
859
    }
860
861
    /**
862
     * This method is required by the interface [[\ArrayAccess]].
863
     * @param mixed $offset the offset to check on
864
     * @return bool
865
     */
866
    public function offsetExists($offset)
867
    {
868
        $this->open();
869
870
        return isset($_SESSION[$offset]);
871
    }
872
873
    /**
874
     * This method is required by the interface [[\ArrayAccess]].
875
     * @param int $offset the offset to retrieve element.
876
     * @return mixed the element at the offset, null if no element is found at the offset
877
     */
878
    public function offsetGet($offset)
879
    {
880
        $this->open();
881
882
        return isset($_SESSION[$offset]) ? $_SESSION[$offset] : null;
883
    }
884
885
    /**
886
     * This method is required by the interface [[\ArrayAccess]].
887
     * @param int $offset the offset to set element
888
     * @param mixed $item the element value
889
     */
890
    public function offsetSet($offset, $item)
891
    {
892
        $this->open();
893
        $_SESSION[$offset] = $item;
894
    }
895
896
    /**
897
     * This method is required by the interface [[\ArrayAccess]].
898
     * @param mixed $offset the offset to unset element
899
     */
900
    public function offsetUnset($offset)
901
    {
902
        $this->open();
903
        unset($_SESSION[$offset]);
904
    }
905
}
906