Passed
Push — twig ( 8daeb7...5bf58a )
by Paweł
08:25 queued 03:50
created

Session   F

Complexity

Total Complexity 130

Size/Duplication

Total Lines 992
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 130
eloc 266
c 1
b 0
f 0
dl 0
loc 992
rs 2

59 Methods

Rating   Name   Duplication   Size   Complexity  
A removeAll() 0 5 2
A removeFlash() 0 8 2
A setSavePath() 0 7 2
A set() 0 4 1
A getGCProbability() 0 3 1
A offsetUnset() 0 4 1
A getCacheLimiter() 0 3 1
A getCookieParams() 0 3 1
A openSession() 0 3 1
A getUseCookies() 0 9 3
A addFlash() 0 11 4
A setCacheLimiter() 0 5 1
A freeze() 0 8 3
A offsetGet() 0 5 2
A getHasSessionId() 0 15 6
A closeSession() 0 3 1
A readSession() 0 3 1
A offsetSet() 0 4 1
A has() 0 4 1
A getUseTransparentSessionID() 0 3 1
A setTimeout() 0 5 1
A setUseStrictMode() 0 12 5
A getUseCustomStorage() 0 3 1
A gcSession() 0 3 1
A setUseCookies() 0 14 3
A getTimeout() 0 3 1
A getUseStrictMode() 0 7 2
A setGCProbability() 0 11 3
A getName() 0 3 1
A setCookieParams() 0 3 1
A setHasSessionId() 0 3 1
A regenerateID() 0 9 4
A getSavePath() 0 3 1
A getFlash() 0 17 4
A destroy() 0 10 2
A setFlash() 0 6 2
A destroySession() 0 3 1
A getCount() 0 4 1
A remove() 0 11 2
A setId() 0 3 1
A removeAllFlashes() 0 7 2
A getIsActive() 0 3 1
A unfreeze() 0 16 5
A updateFlashCounters() 0 15 5
B open() 0 24 7
A getId() 0 3 1
A offsetExists() 0 5 1
A writeSession() 0 3 1
A count() 0 3 1
A get() 0 4 2
A hasFlash() 0 3 1
A setUseTransparentSessionID() 0 5 2
A close() 0 7 3
A getIterator() 0 4 1
A getAllFlashes() 0 21 5
A init() 0 7 2
A setName() 0 5 1
B registerSessionHandler() 0 41 11
A setCookieParamsInternal() 0 15 4

How to fix   Complexity   

Complex Class

Complex classes like Session often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Session, and based on these observations, apply Extract Interface, too.

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-read array $allFlashes Flash messages (key => message or key => [message1, message2]). This
49
 * property is read-only.
50
 * @property-read string $cacheLimiter Current cache limiter. This property is read-only.
51
 * @property-read array $cookieParams The session cookie parameters. This property is read-only.
52
 * @property-read int $count The number of session variables. This property is read-only.
53
 * @property-write string $flash The key identifying the flash message. Note that flash messages and normal
54
 * session variables share the same name space. If you have a normal session variable using the same name, its
55
 * value will 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.
58
 * @property bool $hasSessionId Whether the current request has sent the session ID.
59
 * @property string $id The current session ID.
60
 * @property-read bool $isActive Whether the session has started. This property is read-only.
61
 * @property-read 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-read bool $useCustomStorage Whether to use custom storage. This property is read-only.
69
 * @property-read bool $useStrictMode Whether strict mode is enabled or not. This property is read-only.
70
 * @property bool $useTransparentSessionID Whether transparent sid support is enabled or not, defaults to
71
 * false.
72
 *
73
 * @author Qiang Xue <[email protected]>
74
 * @since 2.0
75
 */
76
class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Countable
77
{
78
    /**
79
     * @var string|null Holds the original session module (before a custom handler is registered) so that it can be
80
     * restored when a Session component without custom handler is used after one that has.
81
     */
82
    static protected $_originalSessionModule = null;
83
    /**
84
     * Polyfill for ini directive session.use-strict-mode for PHP < 5.5.2.
85
     */
86
    static private $_useStrictModePolyfill = false;
87
    /**
88
     * @var string the name of the session variable that stores the flash message data.
89
     */
90
    public $flashParam = '__flash';
91
    /**
92
     * @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.
93
     */
94
    public $handler;
95
96
    /**
97
     * @var string|null Holds the session id in case useStrictMode is enabled and the session id needs to be regenerated
98
     */
99
    protected $_forceRegenerateId = null;
100
101
    /**
102
     * @var array parameter-value pairs to override default session cookie parameters that are used for session_set_cookie_params() function
103
     * Array may have the following possible keys: 'lifetime', 'path', 'domain', 'secure', 'httponly'
104
     * @see https://secure.php.net/manual/en/function.session-set-cookie-params.php
105
     */
106
    private $_cookieParams = ['httponly' => true];
107
    /**
108
     * @var array|null is used for saving session between recreations due to session parameters update.
109
     */
110
    private $frozenSessionData;
111
112
113
    /**
114
     * Initializes the application component.
115
     * This method is required by IApplicationComponent and is invoked by application.
116
     */
117
    public function init()
118
    {
119
        parent::init();
120
        register_shutdown_function([$this, 'close']);
121
        if ($this->getIsActive()) {
122
            Yii::warning('Session is already started', __METHOD__);
123
            $this->updateFlashCounters();
124
        }
125
    }
126
127
    /**
128
     * Returns a value indicating whether to use custom session storage.
129
     * This method should be overridden to return true by child classes that implement custom session storage.
130
     * To implement custom session storage, override these methods: [[openSession()]], [[closeSession()]],
131
     * [[readSession()]], [[writeSession()]], [[destroySession()]] and [[gcSession()]].
132
     * @return bool whether to use custom storage.
133
     */
134
    public function getUseCustomStorage()
135
    {
136
        return false;
137
    }
138
139
    /**
140
     * Starts the session.
141
     */
142
    public function open()
143
    {
144
        if ($this->getIsActive()) {
145
            return;
146
        }
147
148
        $this->registerSessionHandler();
149
150
        $this->setCookieParamsInternal();
151
152
        YII_DEBUG ? session_start() : @session_start();
153
154
        if ($this->getUseStrictMode() && $this->_forceRegenerateId) {
155
            $this->regenerateID();
156
            $this->_forceRegenerateId = null;
157
        }
158
159
        if ($this->getIsActive()) {
160
            Yii::info('Session started', __METHOD__);
161
            $this->updateFlashCounters();
162
        } else {
163
            $error = error_get_last();
164
            $message = isset($error['message']) ? $error['message'] : 'Failed to start session.';
165
            Yii::error($message, __METHOD__);
166
        }
167
    }
168
169
    /**
170
     * Registers session handler.
171
     * @throws \yii\base\InvalidConfigException
172
     */
173
    protected function registerSessionHandler()
174
    {
175
        $sessionModuleName = session_module_name();
176
        if (static::$_originalSessionModule === null) {
177
            static::$_originalSessionModule = $sessionModuleName;
178
        }
179
180
        if ($this->handler !== null) {
181
            if (!is_object($this->handler)) {
182
                $this->handler = Yii::createObject($this->handler);
183
            }
184
            if (!$this->handler instanceof \SessionHandlerInterface) {
185
                throw new InvalidConfigException('"' . get_class($this) . '::handler" must implement the SessionHandlerInterface.');
186
            }
187
            YII_DEBUG ? session_set_save_handler($this->handler, false) : @session_set_save_handler($this->handler, false);
188
        } elseif ($this->getUseCustomStorage()) {
189
            if (YII_DEBUG) {
190
                session_set_save_handler(
191
                    [$this, 'openSession'],
192
                    [$this, 'closeSession'],
193
                    [$this, 'readSession'],
194
                    [$this, 'writeSession'],
195
                    [$this, 'destroySession'],
196
                    [$this, 'gcSession']
197
                );
198
            } else {
199
                @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

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

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

335
                /** @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...
336
            }
337
        }
338
    }
339
340
    /**
341
     * Gets the name of the current session.
342
     * This is a wrapper for [PHP session_name()](https://secure.php.net/manual/en/function.session-name.php).
343
     * @return string the current session name
344
     */
345
    public function getName()
346
    {
347
        return session_name();
348
    }
349
350
    /**
351
     * Sets the name for the current session.
352
     * This is a wrapper for [PHP session_name()](https://secure.php.net/manual/en/function.session-name.php).
353
     * @param string $value the session name for the current session, must be an alphanumeric string.
354
     * It defaults to "PHPSESSID".
355
     */
356
    public function setName($value)
357
    {
358
        $this->freeze();
359
        session_name($value);
360
        $this->unfreeze();
361
    }
362
363
    /**
364
     * Gets the current session save path.
365
     * This is a wrapper for [PHP session_save_path()](https://secure.php.net/manual/en/function.session-save-path.php).
366
     * @return string the current session save path, defaults to '/tmp'.
367
     */
368
    public function getSavePath()
369
    {
370
        return session_save_path();
371
    }
372
373
    /**
374
     * Sets the current session save path.
375
     * This is a wrapper for [PHP session_save_path()](https://secure.php.net/manual/en/function.session-save-path.php).
376
     * @param string $value the current session save path. This can be either a directory name or a [path alias](guide:concept-aliases).
377
     * @throws InvalidArgumentException if the path is not a valid directory
378
     */
379
    public function setSavePath($value)
380
    {
381
        $path = Yii::getAlias($value);
382
        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

382
        if (is_dir(/** @scrutinizer ignore-type */ $path)) {
Loading history...
383
            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

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

595
    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

595
    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...
596
    {
597
        return true;
598
    }
599
600
    /**
601
     * Session close handler.
602
     * This method should be overridden if [[useCustomStorage]] returns true.
603
     * @internal Do not call this method directly.
604
     * @return bool whether session is closed successfully
605
     */
606
    public function closeSession()
607
    {
608
        return true;
609
    }
610
611
    /**
612
     * Session read handler.
613
     * This method should be overridden if [[useCustomStorage]] returns true.
614
     * @internal Do not call this method directly.
615
     * @param string $id session ID
616
     * @return string the session data
617
     */
618
    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

618
    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...
619
    {
620
        return '';
621
    }
622
623
    /**
624
     * Session write handler.
625
     * This method should be overridden if [[useCustomStorage]] returns true.
626
     * @internal Do not call this method directly.
627
     * @param string $id session ID
628
     * @param string $data session data
629
     * @return bool whether session write is successful
630
     */
631
    public function writeSession($id, $data)
0 ignored issues
show
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

631
    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...
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

631
    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...
632
    {
633
        return true;
634
    }
635
636
    /**
637
     * Session destroy handler.
638
     * This method should be overridden if [[useCustomStorage]] returns true.
639
     * @internal Do not call this method directly.
640
     * @param string $id session ID
641
     * @return bool whether session is destroyed successfully
642
     */
643
    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

643
    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...
644
    {
645
        return true;
646
    }
647
648
    /**
649
     * Session GC (garbage collection) handler.
650
     * This method should be overridden if [[useCustomStorage]] returns true.
651
     * @internal Do not call this method directly.
652
     * @param int $maxLifetime the number of seconds after which data will be seen as 'garbage' and cleaned up.
653
     * @return bool whether session is GCed successfully
654
     */
655
    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

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