• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

codeigniter4 / CodeIgniter4 / 7346990850

28 Dec 2023 10:57AM UTC coverage: 85.044% (-0.006%) from 85.05%
7346990850

push

github

kenjis
Merge remote-tracking branch 'origin/develop' into 4.5

 Conflicts:
	phpstan-baseline.php
	system/BaseModel.php
	system/CodeIgniter.php
	system/Model.php
	system/Test/FeatureTestCase.php
	system/Test/TestResponse.php

87 of 91 new or added lines in 20 files covered. (95.6%)

2 existing lines in 1 file now uncovered.

19368 of 22774 relevant lines covered (85.04%)

193.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

77.59
/system/Session/Session.php
1
<?php
2

3
declare(strict_types=1);
4

5
/**
6
 * This file is part of CodeIgniter 4 framework.
7
 *
8
 * (c) CodeIgniter Foundation <admin@codeigniter.com>
9
 *
10
 * For the full copyright and license information, please view
11
 * the LICENSE file that was distributed with this source code.
12
 */
13

14
namespace CodeIgniter\Session;
15

16
use CodeIgniter\Cookie\Cookie;
17
use CodeIgniter\I18n\Time;
18
use Config\Cookie as CookieConfig;
19
use Config\Services;
20
use Config\Session as SessionConfig;
21
use Psr\Log\LoggerAwareTrait;
22
use SessionHandlerInterface;
23

24
/**
25
 * Implementation of CodeIgniter session container.
26
 *
27
 * Session configuration is done through session variables and cookie related
28
 * variables in app/config/App.php
29
 *
30
 * @property string $session_id
31
 * @see \CodeIgniter\Session\SessionTest
32
 */
33
class Session implements SessionInterface
34
{
35
    use LoggerAwareTrait;
36

37
    /**
38
     * Instance of the driver to use.
39
     *
40
     * @var SessionHandlerInterface
41
     */
42
    protected $driver;
43

44
    /**
45
     * The storage driver to use: files, database, redis, memcached
46
     *
47
     * @var string
48
     *
49
     * @deprecated Use $this->config->driver.
50
     */
51
    protected $sessionDriverName;
52

53
    /**
54
     * The session cookie name, must contain only [0-9a-z_-] characters.
55
     *
56
     * @var string
57
     *
58
     * @deprecated Use $this->config->cookieName.
59
     */
60
    protected $sessionCookieName = 'ci_session';
61

62
    /**
63
     * The number of SECONDS you want the session to last.
64
     * Setting it to 0 (zero) means expire when the browser is closed.
65
     *
66
     * @var int
67
     *
68
     * @deprecated Use $this->config->expiration.
69
     */
70
    protected $sessionExpiration = 7200;
71

72
    /**
73
     * The location to save sessions to, driver dependent.
74
     *
75
     * For the 'files' driver, it's a path to a writable directory.
76
     * WARNING: Only absolute paths are supported!
77
     *
78
     * For the 'database' driver, it's a table name.
79
     *
80
     * @todo address memcache & redis needs
81
     *
82
     * IMPORTANT: You are REQUIRED to set a valid save path!
83
     *
84
     * @var string
85
     *
86
     * @deprecated Use $this->config->savePath.
87
     */
88
    protected $sessionSavePath;
89

90
    /**
91
     * Whether to match the user's IP address when reading the session data.
92
     *
93
     * WARNING: If you're using the database driver, don't forget to update
94
     * your session table's PRIMARY KEY when changing this setting.
95
     *
96
     * @var bool
97
     *
98
     * @deprecated Use $this->config->matchIP.
99
     */
100
    protected $sessionMatchIP = false;
101

102
    /**
103
     * How many seconds between CI regenerating the session ID.
104
     *
105
     * @var int
106
     *
107
     * @deprecated Use $this->config->timeToUpdate.
108
     */
109
    protected $sessionTimeToUpdate = 300;
110

111
    /**
112
     * Whether to destroy session data associated with the old session ID
113
     * when auto-regenerating the session ID. When set to FALSE, the data
114
     * will be later deleted by the garbage collector.
115
     *
116
     * @var bool
117
     *
118
     * @deprecated Use $this->config->regenerateDestroy.
119
     */
120
    protected $sessionRegenerateDestroy = false;
121

122
    /**
123
     * The session cookie instance.
124
     *
125
     * @var Cookie
126
     */
127
    protected $cookie;
128

129
    /**
130
     * The domain name to use for cookies.
131
     * Set to .your-domain.com for site-wide cookies.
132
     *
133
     * @var string
134
     *
135
     * @deprecated No longer used.
136
     */
137
    protected $cookieDomain = '';
138

139
    /**
140
     * Path used for storing cookies.
141
     * Typically will be a forward slash.
142
     *
143
     * @var string
144
     *
145
     * @deprecated No longer used.
146
     */
147
    protected $cookiePath = '/';
148

149
    /**
150
     * Cookie will only be set if a secure HTTPS connection exists.
151
     *
152
     * @var bool
153
     *
154
     * @deprecated No longer used.
155
     */
156
    protected $cookieSecure = false;
157

158
    /**
159
     * Cookie SameSite setting as described in RFC6265
160
     * Must be 'None', 'Lax' or 'Strict'.
161
     *
162
     * @var string
163
     *
164
     * @deprecated No longer used.
165
     */
166
    protected $cookieSameSite = Cookie::SAMESITE_LAX;
167

168
    /**
169
     * sid regex expression
170
     *
171
     * @var string
172
     */
173
    protected $sidRegexp;
174

175
    /**
176
     * Session Config
177
     */
178
    protected SessionConfig $config;
179

180
    /**
181
     * Constructor.
182
     *
183
     * Extract configuration settings and save them here.
184
     */
185
    public function __construct(SessionHandlerInterface $driver, SessionConfig $config)
186
    {
187
        $this->driver = $driver;
6,145✔
188

189
        $this->config = $config;
6,145✔
190

191
        $cookie = config(CookieConfig::class);
6,145✔
192

193
        $this->cookie = (new Cookie($this->config->cookieName, '', [
6,145✔
194
            'expires'  => $this->config->expiration === 0 ? 0 : Time::now()->getTimestamp() + $this->config->expiration,
6,145✔
195
            'path'     => $cookie->path,
6,145✔
196
            'domain'   => $cookie->domain,
6,145✔
197
            'secure'   => $cookie->secure,
6,145✔
198
            'httponly' => true, // for security
6,145✔
199
            'samesite' => $cookie->samesite ?? Cookie::SAMESITE_LAX,
6,145✔
200
            'raw'      => $cookie->raw ?? false,
6,145✔
201
        ]))->withPrefix(''); // Cookie prefix should be ignored.
6,145✔
202

203
        helper('array');
6,145✔
204
    }
205

206
    /**
207
     * Initialize the session container and starts up the session.
208
     *
209
     * @return $this|void
210
     */
211
    public function start()
212
    {
213
        if (is_cli() && ENVIRONMENT !== 'testing') {
80✔
214
            // @codeCoverageIgnoreStart
215
            $this->logger->debug('Session: Initialization under CLI aborted.');
216

217
            return;
218
            // @codeCoverageIgnoreEnd
219
        }
220

221
        if ((bool) ini_get('session.auto_start')) {
80✔
222
            $this->logger->error('Session: session.auto_start is enabled in php.ini. Aborting.');
×
223

224
            return;
×
225
        }
226

227
        if (session_status() === PHP_SESSION_ACTIVE) {
80✔
228
            $this->logger->warning('Session: Sessions is enabled, and one exists. Please don\'t $session->start();');
×
229

230
            return;
×
231
        }
232

233
        $this->configure();
80✔
234
        $this->setSaveHandler();
80✔
235

236
        // Sanitize the cookie, because apparently PHP doesn't do that for userspace handlers
237
        if (isset($_COOKIE[$this->config->cookieName])
80✔
238
            && (! is_string($_COOKIE[$this->config->cookieName]) || ! preg_match('#\A' . $this->sidRegexp . '\z#', $_COOKIE[$this->config->cookieName]))
80✔
239
        ) {
240
            unset($_COOKIE[$this->config->cookieName]);
×
241
        }
242

243
        $this->startSession();
80✔
244

245
        // Is session ID auto-regeneration configured? (ignoring ajax requests)
246
        if ((! isset($_SERVER['HTTP_X_REQUESTED_WITH']) || strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) !== 'xmlhttprequest')
80✔
247
            && ($regenerateTime = $this->config->timeToUpdate) > 0
80✔
248
        ) {
249
            if (! isset($_SESSION['__ci_last_regenerate'])) {
78✔
250
                $_SESSION['__ci_last_regenerate'] = Time::now()->getTimestamp();
77✔
251
            } elseif ($_SESSION['__ci_last_regenerate'] < (Time::now()->getTimestamp() - $regenerateTime)) {
3✔
252
                $this->regenerate((bool) $this->config->regenerateDestroy);
78✔
253
            }
254
        }
255
        // Another work-around ... PHP doesn't seem to send the session cookie
256
        // unless it is being currently created or regenerated
257
        elseif (isset($_COOKIE[$this->config->cookieName]) && $_COOKIE[$this->config->cookieName] === session_id()) {
2✔
258
            $this->setCookie();
×
259
        }
260

261
        $this->initVars();
80✔
262
        $this->logger->info("Session: Class initialized using '" . $this->config->driver . "' driver.");
80✔
263

264
        return $this;
80✔
265
    }
266

267
    /**
268
     * Destroys the current session.
269
     *
270
     * @deprecated Use destroy() instead.
271
     */
272
    public function stop()
273
    {
274
        $this->destroy();
×
275
    }
276

277
    /**
278
     * Configuration.
279
     *
280
     * Handle input binds and configuration defaults.
281
     */
282
    protected function configure()
283
    {
284
        ini_set('session.name', $this->config->cookieName);
80✔
285

286
        $sameSite = $this->cookie->getSameSite() ?: ucfirst(Cookie::SAMESITE_LAX);
80✔
287

288
        $params = [
80✔
289
            'lifetime' => $this->config->expiration,
80✔
290
            'path'     => $this->cookie->getPath(),
80✔
291
            'domain'   => $this->cookie->getDomain(),
80✔
292
            'secure'   => $this->cookie->isSecure(),
80✔
293
            'httponly' => true, // HTTP only; Yes, this is intentional and not configurable for security reasons.
80✔
294
            'samesite' => $sameSite,
80✔
295
        ];
80✔
296

297
        ini_set('session.cookie_samesite', $sameSite);
80✔
298
        session_set_cookie_params($params);
80✔
299

300
        if ($this->config->expiration > 0) {
80✔
301
            ini_set('session.gc_maxlifetime', (string) $this->config->expiration);
79✔
302
        }
303

304
        if ($this->config->savePath !== '') {
80✔
305
            ini_set('session.save_path', $this->config->savePath);
39✔
306
        }
307

308
        // Security is king
309
        ini_set('session.use_trans_sid', '0');
80✔
310
        ini_set('session.use_strict_mode', '1');
80✔
311
        ini_set('session.use_cookies', '1');
80✔
312
        ini_set('session.use_only_cookies', '1');
80✔
313

314
        $this->configureSidLength();
80✔
315
    }
316

317
    /**
318
     * Configure session ID length
319
     *
320
     * To make life easier, we used to force SHA-1 and 4 bits per
321
     * character on everyone. And of course, someone was unhappy.
322
     *
323
     * Then PHP 7.1 broke backwards-compatibility because ext/session
324
     * is such a mess that nobody wants to touch it with a pole stick,
325
     * and the one guy who does, nobody has the energy to argue with.
326
     *
327
     * So we were forced to make changes, and OF COURSE something was
328
     * going to break and now we have this pile of shit. -- Narf
329
     */
330
    protected function configureSidLength()
331
    {
332
        $bitsPerCharacter = (int) (ini_get('session.sid_bits_per_character') !== false
80✔
333
            ? ini_get('session.sid_bits_per_character')
80✔
334
            : 4);
×
335

336
        $sidLength = (int) (ini_get('session.sid_length') !== false
80✔
337
            ? ini_get('session.sid_length')
80✔
338
            : 40);
×
339

340
        if (($sidLength * $bitsPerCharacter) < 160) {
80✔
341
            $bits = ($sidLength * $bitsPerCharacter);
×
342
            // Add as many more characters as necessary to reach at least 160 bits
343
            $sidLength += (int) ceil((160 % $bits) / $bitsPerCharacter);
×
344
            ini_set('session.sid_length', (string) $sidLength);
×
345
        }
346

347
        // Yes, 4,5,6 are the only known possible values as of 2016-10-27
348
        switch ($bitsPerCharacter) {
349
            case 4:
80✔
350
                $this->sidRegexp = '[0-9a-f]';
×
351
                break;
×
352

353
            case 5:
80✔
354
                $this->sidRegexp = '[0-9a-v]';
80✔
355
                break;
80✔
356

357
            case 6:
×
358
                $this->sidRegexp = '[0-9a-zA-Z,-]';
×
359
                break;
×
360
        }
361

362
        $this->sidRegexp .= '{' . $sidLength . '}';
80✔
363
    }
364

365
    /**
366
     * Handle temporary variables
367
     *
368
     * Clears old "flash" data, marks the new one for deletion and handles
369
     * "temp" data deletion.
370
     */
371
    protected function initVars()
372
    {
373
        if (! isset($_SESSION['__ci_vars'])) {
80✔
374
            return;
80✔
375
        }
376

377
        $currentTime = Time::now()->getTimestamp();
2✔
378

379
        foreach ($_SESSION['__ci_vars'] as $key => &$value) {
2✔
380
            if ($value === 'new') {
2✔
381
                $_SESSION['__ci_vars'][$key] = 'old';
2✔
382
            }
383
            // DO NOT move this above the 'new' check!
384
            elseif ($value === 'old' || $value < $currentTime) {
1✔
385
                unset($_SESSION[$key], $_SESSION['__ci_vars'][$key]);
1✔
386
            }
387
        }
388

389
        if ($_SESSION['__ci_vars'] === []) {
2✔
390
            unset($_SESSION['__ci_vars']);
1✔
391
        }
392
    }
393

394
    /**
395
     * Regenerates the session ID.
396
     *
397
     * @param bool $destroy Should old session data be destroyed?
398
     */
399
    public function regenerate(bool $destroy = false)
400
    {
401
        $_SESSION['__ci_last_regenerate'] = Time::now()->getTimestamp();
×
402
        session_regenerate_id($destroy);
×
403

404
        $this->removeOldSessionCookie();
×
405
    }
406

407
    private function removeOldSessionCookie(): void
408
    {
409
        $response              = Services::response();
×
410
        $cookieStoreInResponse = $response->getCookieStore();
×
411

412
        if (! $cookieStoreInResponse->has($this->config->cookieName)) {
×
413
            return;
×
414
        }
415

416
        // CookieStore is immutable.
417
        $newCookieStore = $cookieStoreInResponse->remove($this->config->cookieName);
×
418

419
        // But clear() method clears cookies in the object (not immutable).
420
        $cookieStoreInResponse->clear();
×
421

422
        foreach ($newCookieStore as $cookie) {
×
423
            $response->setCookie($cookie);
×
424
        }
425
    }
426

427
    /**
428
     * Destroys the current session.
429
     */
430
    public function destroy()
431
    {
432
        if (ENVIRONMENT === 'testing') {
×
433
            return;
×
434
        }
435

436
        session_destroy();
×
437
    }
438

439
    /**
440
     * Writes session data and close the current session.
441
     *
442
     * @return void
443
     */
444
    public function close()
445
    {
446
        if (ENVIRONMENT === 'testing') {
×
447
            return;
×
448
        }
449

450
        session_write_close();
×
451
    }
452

453
    /**
454
     * Sets user data into the session.
455
     *
456
     * If $data is a string, then it is interpreted as a session property
457
     * key, and  $value is expected to be non-null.
458
     *
459
     * If $data is an array, it is expected to be an array of key/value pairs
460
     * to be set as session properties.
461
     *
462
     * @param array|string                            $data  Property name or associative array of properties
463
     * @param array|bool|float|int|object|string|null $value Property value if single key provided
464
     */
465
    public function set($data, $value = null)
466
    {
467
        if (is_array($data)) {
113✔
468
            foreach ($data as $key => &$value) {
12✔
469
                if (is_int($key)) {
12✔
470
                    $_SESSION[$value] = null;
1✔
471
                } else {
472
                    $_SESSION[$key] = $value;
11✔
473
                }
474
            }
475

476
            return;
12✔
477
        }
478

479
        $_SESSION[$data] = $value;
103✔
480
    }
481

482
    /**
483
     * Get user data that has been set in the session.
484
     *
485
     * If the property exists as "normal", returns it.
486
     * Otherwise, returns an array of any temp or flash data values with the
487
     * property key.
488
     *
489
     * Replaces the legacy method $session->userdata();
490
     *
491
     * @param non-empty-string|null $key Identifier of the session property to retrieve
492
     *
493
     * @return array|bool|float|int|object|string|null The property value(s)
494
     */
495
    public function get(?string $key = null)
496
    {
497
        if ($key !== null && $key !== '' && (null !== ($value = $_SESSION[$key] ?? null) || null !== ($value = dot_array_search($key, $_SESSION ?? [])))) {
67✔
498
            return $value;
51✔
499
        }
500

501
        if ($_SESSION === []) {
18✔
502
            return $key === null ? [] : null;
12✔
503
        }
504

505
        if ($key !== null && $key !== '') {
6✔
506
            return null;
5✔
507
        }
508

509
        $userdata = [];
1✔
510
        $_exclude = array_merge(['__ci_vars'], $this->getFlashKeys(), $this->getTempKeys());
1✔
511

512
        $keys = array_keys($_SESSION);
1✔
513

514
        foreach ($keys as $key) {
1✔
515
            if (! in_array($key, $_exclude, true)) {
1✔
516
                $userdata[$key] = $_SESSION[$key];
1✔
517
            }
518
        }
519

520
        return $userdata;
1✔
521
    }
522

523
    /**
524
     * Returns whether an index exists in the session array.
525
     *
526
     * @param string $key Identifier of the session property we are interested in.
527
     */
528
    public function has(string $key): bool
529
    {
530
        return isset($_SESSION[$key]);
38✔
531
    }
532

533
    /**
534
     * Push new value onto session value that is array.
535
     *
536
     * @param string $key  Identifier of the session property we are interested in.
537
     * @param array  $data value to be pushed to existing session key.
538
     */
539
    public function push(string $key, array $data)
540
    {
541
        if ($this->has($key) && is_array($value = $this->get($key))) {
1✔
542
            $this->set($key, array_merge($value, $data));
1✔
543
        }
544
    }
545

546
    /**
547
     * Remove one or more session properties.
548
     *
549
     * If $key is an array, it is interpreted as an array of string property
550
     * identifiers to remove. Otherwise, it is interpreted as the identifier
551
     * of a specific session property to remove.
552
     *
553
     * @param array|string $key Identifier of the session property or properties to remove.
554
     */
555
    public function remove($key)
556
    {
557
        if (is_array($key)) {
2✔
558
            foreach ($key as $k) {
1✔
559
                unset($_SESSION[$k]);
1✔
560
            }
561

562
            return;
1✔
563
        }
564

565
        unset($_SESSION[$key]);
1✔
566
    }
567

568
    /**
569
     * Magic method to set variables in the session by simply calling
570
     *  $session->foo = bar;
571
     *
572
     * @param string       $key   Identifier of the session property to set.
573
     * @param array|string $value
574
     */
575
    public function __set(string $key, $value)
576
    {
577
        $_SESSION[$key] = $value;
1✔
578
    }
579

580
    /**
581
     * Magic method to get session variables by simply calling
582
     *  $foo = $session->foo;
583
     *
584
     * @param string $key Identifier of the session property to remove.
585
     *
586
     * @return string|null
587
     */
588
    public function __get(string $key)
589
    {
590
        // Note: Keep this order the same, just in case somebody wants to
591
        // use 'session_id' as a session data key, for whatever reason
592
        if (isset($_SESSION[$key])) {
1✔
593
            return $_SESSION[$key];
1✔
594
        }
595

596
        if ($key === 'session_id') {
×
597
            return session_id();
×
598
        }
599

600
        return null;
×
601
    }
602

603
    /**
604
     * Magic method to check for session variables.
605
     * Different from has() in that it will validate 'session_id' as well.
606
     * Mostly used by internal PHP functions, users should stick to has()
607
     *
608
     * @param string $key Identifier of the session property to remove.
609
     */
610
    public function __isset(string $key): bool
611
    {
612
        return isset($_SESSION[$key]) || ($key === 'session_id');
2✔
613
    }
614

615
    /**
616
     * Sets data into the session that will only last for a single request.
617
     * Perfect for use with single-use status update messages.
618
     *
619
     * If $data is an array, it is interpreted as an associative array of
620
     * key/value pairs for flashdata properties.
621
     * Otherwise, it is interpreted as the identifier of a specific
622
     * flashdata property, with $value containing the property value.
623
     *
624
     * @param array|string                            $data  Property identifier or associative array of properties
625
     * @param array|bool|float|int|object|string|null $value Property value if $data is a scalar
626
     */
627
    public function setFlashdata($data, $value = null)
628
    {
629
        $this->set($data, $value);
11✔
630
        $this->markAsFlashdata(is_array($data) ? array_keys($data) : $data);
11✔
631
    }
632

633
    /**
634
     * Retrieve one or more items of flash data from the session.
635
     *
636
     * If the item key is null, return all flashdata.
637
     *
638
     * @param string $key Property identifier
639
     *
640
     * @return array|null The requested property value, or an associative array  of them
641
     */
642
    public function getFlashdata(?string $key = null)
643
    {
644
        if (isset($key)) {
×
645
            return (isset($_SESSION['__ci_vars'], $_SESSION['__ci_vars'][$key], $_SESSION[$key])
×
646
                && ! is_int($_SESSION['__ci_vars'][$key])) ? $_SESSION[$key] : null;
×
647
        }
648

649
        $flashdata = [];
×
650

NEW
651
        if (isset($_SESSION['__ci_vars'])) {
×
652
            foreach ($_SESSION['__ci_vars'] as $key => &$value) {
×
653
                if (! is_int($value)) {
×
654
                    $flashdata[$key] = $_SESSION[$key];
×
655
                }
656
            }
657
        }
658

659
        return $flashdata;
×
660
    }
661

662
    /**
663
     * Keeps a single piece of flash data alive for one more request.
664
     *
665
     * @param array|string $key Property identifier or array of them
666
     */
667
    public function keepFlashdata($key)
668
    {
669
        $this->markAsFlashdata($key);
1✔
670
    }
671

672
    /**
673
     * Mark a session property or properties as flashdata.
674
     *
675
     * @param array|string $key Property identifier or array of them
676
     *
677
     * @return bool False if any of the properties are not already set
678
     */
679
    public function markAsFlashdata($key): bool
680
    {
681
        if (is_array($key)) {
11✔
682
            foreach ($key as $sessionKey) {
1✔
683
                if (! isset($_SESSION[$sessionKey])) {
1✔
684
                    return false;
×
685
                }
686
            }
687

688
            $new = array_fill_keys($key, 'new');
1✔
689

690
            $_SESSION['__ci_vars'] = isset($_SESSION['__ci_vars']) ? array_merge($_SESSION['__ci_vars'], $new) : $new;
1✔
691

692
            return true;
1✔
693
        }
694

695
        if (! isset($_SESSION[$key])) {
10✔
696
            return false;
×
697
        }
698

699
        $_SESSION['__ci_vars'][$key] = 'new';
10✔
700

701
        return true;
10✔
702
    }
703

704
    /**
705
     * Unmark data in the session as flashdata.
706
     *
707
     * @param array|string $key Property identifier or array of them
708
     */
709
    public function unmarkFlashdata($key)
710
    {
711
        if (! isset($_SESSION['__ci_vars'])) {
1✔
712
            return;
×
713
        }
714

715
        if (! is_array($key)) {
1✔
716
            $key = [$key];
1✔
717
        }
718

719
        foreach ($key as $k) {
1✔
720
            if (isset($_SESSION['__ci_vars'][$k]) && ! is_int($_SESSION['__ci_vars'][$k])) {
1✔
721
                unset($_SESSION['__ci_vars'][$k]);
1✔
722
            }
723
        }
724

725
        if ($_SESSION['__ci_vars'] === []) {
1✔
726
            unset($_SESSION['__ci_vars']);
1✔
727
        }
728
    }
729

730
    /**
731
     * Retrieve all of the keys for session data marked as flashdata.
732
     *
733
     * @return array The property names of all flashdata
734
     */
735
    public function getFlashKeys(): array
736
    {
737
        if (! isset($_SESSION['__ci_vars'])) {
2✔
738
            return [];
1✔
739
        }
740

741
        $keys = [];
1✔
742

743
        foreach (array_keys($_SESSION['__ci_vars']) as $key) {
1✔
744
            if (! is_int($_SESSION['__ci_vars'][$key])) {
1✔
745
                $keys[] = $key;
1✔
746
            }
747
        }
748

749
        return $keys;
1✔
750
    }
751

752
    /**
753
     * Sets new data into the session, and marks it as temporary data
754
     * with a set lifespan.
755
     *
756
     * @param array|string                            $data  Session data key or associative array of items
757
     * @param array|bool|float|int|object|string|null $value Value to store
758
     * @param int                                     $ttl   Time-to-live in seconds
759
     */
760
    public function setTempdata($data, $value = null, int $ttl = 300)
761
    {
762
        $this->set($data, $value);
9✔
763
        $this->markAsTempdata($data, $ttl);
9✔
764
    }
765

766
    /**
767
     * Returns either a single piece of tempdata, or all temp data currently
768
     * in the session.
769
     *
770
     * @param string $key Session data key
771
     *
772
     * @return array|bool|float|int|object|string|null Session data value or null if not found.
773
     */
774
    public function getTempdata(?string $key = null)
775
    {
776
        if (isset($key)) {
5✔
777
            return (isset($_SESSION['__ci_vars'], $_SESSION['__ci_vars'][$key], $_SESSION[$key])
1✔
778
                    && is_int($_SESSION['__ci_vars'][$key])) ? $_SESSION[$key] : null;
1✔
779
        }
780

781
        $tempdata = [];
4✔
782

783
        if (isset($_SESSION['__ci_vars'])) {
4✔
784
            foreach ($_SESSION['__ci_vars'] as $key => &$value) {
3✔
785
                if (is_int($value)) {
3✔
786
                    $tempdata[$key] = $_SESSION[$key];
3✔
787
                }
788
            }
789
        }
790

791
        return $tempdata;
4✔
792
    }
793

794
    /**
795
     * Removes a single piece of temporary data from the session.
796
     *
797
     * @param string $key Session data key
798
     */
799
    public function removeTempdata(string $key)
800
    {
801
        $this->unmarkTempdata($key);
1✔
802
        unset($_SESSION[$key]);
1✔
803
    }
804

805
    /**
806
     * Mark one of more pieces of data as being temporary, meaning that
807
     * it has a set lifespan within the session.
808
     *
809
     * @param array|string $key Property identifier or array of them
810
     * @param int          $ttl Time to live, in seconds
811
     *
812
     * @return bool False if any of the properties were not set
813
     */
814
    public function markAsTempdata($key, int $ttl = 300): bool
815
    {
816
        $ttl += Time::now()->getTimestamp();
9✔
817

818
        if (is_array($key)) {
9✔
819
            $temp = [];
8✔
820

821
            foreach ($key as $k => $v) {
8✔
822
                // Do we have a key => ttl pair, or just a key?
823
                if (is_int($k)) {
8✔
824
                    $k = $v;
1✔
825
                    $v = $ttl;
1✔
826
                } elseif (is_string($v)) {
7✔
827
                    $v = Time::now()->getTimestamp() + $ttl;
6✔
828
                } else {
829
                    $v += Time::now()->getTimestamp();
1✔
830
                }
831

832
                if (! array_key_exists($k, $_SESSION)) {
8✔
833
                    return false;
×
834
                }
835

836
                $temp[$k] = $v;
8✔
837
            }
838

839
            $_SESSION['__ci_vars'] = isset($_SESSION['__ci_vars']) ? array_merge($_SESSION['__ci_vars'], $temp) : $temp;
8✔
840

841
            return true;
8✔
842
        }
843

844
        if (! isset($_SESSION[$key])) {
1✔
845
            return false;
×
846
        }
847

848
        $_SESSION['__ci_vars'][$key] = $ttl;
1✔
849

850
        return true;
1✔
851
    }
852

853
    /**
854
     * Unmarks temporary data in the session, effectively removing its
855
     * lifespan and allowing it to live as long as the session does.
856
     *
857
     * @param array|string $key Property identifier or array of them
858
     */
859
    public function unmarkTempdata($key)
860
    {
861
        if (! isset($_SESSION['__ci_vars'])) {
3✔
862
            return;
×
863
        }
864

865
        if (! is_array($key)) {
3✔
866
            $key = [$key];
2✔
867
        }
868

869
        foreach ($key as $k) {
3✔
870
            if (isset($_SESSION['__ci_vars'][$k]) && is_int($_SESSION['__ci_vars'][$k])) {
3✔
871
                unset($_SESSION['__ci_vars'][$k]);
3✔
872
            }
873
        }
874

875
        if ($_SESSION['__ci_vars'] === []) {
3✔
876
            unset($_SESSION['__ci_vars']);
1✔
877
        }
878
    }
879

880
    /**
881
     * Retrieve the keys of all session data that have been marked as temporary data.
882
     */
883
    public function getTempKeys(): array
884
    {
885
        if (! isset($_SESSION['__ci_vars'])) {
2✔
886
            return [];
1✔
887
        }
888

889
        $keys = [];
1✔
890

891
        foreach (array_keys($_SESSION['__ci_vars']) as $key) {
1✔
892
            if (is_int($_SESSION['__ci_vars'][$key])) {
1✔
893
                $keys[] = $key;
1✔
894
            }
895
        }
896

897
        return $keys;
1✔
898
    }
899

900
    /**
901
     * Sets the driver as the session handler in PHP.
902
     * Extracted for easier testing.
903
     */
904
    protected function setSaveHandler()
905
    {
906
        session_set_save_handler($this->driver, true);
39✔
907
    }
908

909
    /**
910
     * Starts the session.
911
     * Extracted for testing reasons.
912
     */
913
    protected function startSession()
914
    {
915
        if (ENVIRONMENT === 'testing') {
39✔
916
            $_SESSION = [];
39✔
917

918
            return;
39✔
919
        }
920

921
        session_start(); // @codeCoverageIgnore
922
    }
923

924
    /**
925
     * Takes care of setting the cookie on the client side.
926
     *
927
     * @codeCoverageIgnore
928
     */
929
    protected function setCookie()
930
    {
931
        $expiration   = $this->config->expiration === 0 ? 0 : Time::now()->getTimestamp() + $this->config->expiration;
932
        $this->cookie = $this->cookie->withValue(session_id())->withExpires($expiration);
933

934
        $response = Services::response();
935
        $response->setCookie($this->cookie);
936
    }
937
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc