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

codeigniter4 / CodeIgniter4 / 14562643483

20 Apr 2025 07:28PM UTC coverage: 84.438%. Remained the same
14562643483

Pull #9536

github

web-flow
Merge 1b0b907da into 8c6da9376
Pull Request #9536: fix: `Session::markAsTempdata()` adding wrong TTL

11 of 12 new or added lines in 1 file covered. (91.67%)

5 existing lines in 1 file now uncovered.

20808 of 24643 relevant lines covered (84.44%)

191.24 hits per line

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

77.38
/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\Session as SessionConfig;
20
use Psr\Log\LoggerAwareTrait;
21
use SessionHandlerInterface;
22

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

188
        $this->config = $config;
6,587✔
189

190
        $cookie = config(CookieConfig::class);
6,587✔
191

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

202
        helper('array');
6,587✔
203
    }
204

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

216
            return null;
×
217
            // @codeCoverageIgnoreEnd
218
        }
219

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

223
            return null;
×
224
        }
225

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

229
            return null;
×
230
        }
231

232
        $this->configure();
82✔
233
        $this->setSaveHandler();
82✔
234

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

243
        $this->startSession();
82✔
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')
82✔
247
            && ($regenerateTime = $this->config->timeToUpdate) > 0
82✔
248
        ) {
249
            if (! isset($_SESSION['__ci_last_regenerate'])) {
80✔
250
                $_SESSION['__ci_last_regenerate'] = Time::now()->getTimestamp();
79✔
251
            } elseif ($_SESSION['__ci_last_regenerate'] < (Time::now()->getTimestamp() - $regenerateTime)) {
3✔
252
                $this->regenerate($this->config->regenerateDestroy);
1✔
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();
82✔
262
        $this->logger->debug("Session: Class initialized using '" . $this->config->driver . "' driver.");
82✔
263

264
        return $this;
82✔
265
    }
266

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

279
    /**
280
     * Configuration.
281
     *
282
     * Handle input binds and configuration defaults.
283
     *
284
     * @return void
285
     */
286
    protected function configure()
287
    {
288
        ini_set('session.name', $this->config->cookieName);
82✔
289

290
        $sameSite = $this->cookie->getSameSite() ?: ucfirst(Cookie::SAMESITE_LAX);
82✔
291

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

301
        ini_set('session.cookie_samesite', $sameSite);
82✔
302
        session_set_cookie_params($params);
82✔
303

304
        if ($this->config->expiration > 0) {
82✔
305
            ini_set('session.gc_maxlifetime', (string) $this->config->expiration);
81✔
306
        }
307

308
        if ($this->config->savePath !== '') {
82✔
309
            ini_set('session.save_path', $this->config->savePath);
40✔
310
        }
311

312
        // Security is king
313
        ini_set('session.use_trans_sid', '0');
82✔
314
        ini_set('session.use_strict_mode', '1');
82✔
315
        ini_set('session.use_cookies', '1');
82✔
316
        ini_set('session.use_only_cookies', '1');
82✔
317

318
        $this->configureSidLength();
82✔
319
    }
320

321
    /**
322
     * Configure session ID length
323
     *
324
     * To make life easier, we force the PHP defaults. Because PHP9 forces them.
325
     * See https://wiki.php.net/rfc/deprecations_php_8_4#sessionsid_length_and_sessionsid_bits_per_character
326
     *
327
     * @return void
328
     */
329
    protected function configureSidLength()
330
    {
331
        $bitsPerCharacter = (int) ini_get('session.sid_bits_per_character');
82✔
332
        $sidLength        = (int) ini_get('session.sid_length');
82✔
333

334
        // We force the PHP defaults.
335
        if (PHP_VERSION_ID < 90000) {
82✔
336
            if ($bitsPerCharacter !== 4) {
82✔
337
                ini_set('session.sid_bits_per_character', '4');
×
338
            }
339
            if ($sidLength !== 32) {
82✔
340
                ini_set('session.sid_length', '32');
×
341
            }
342
        }
343

344
        $this->sidRegexp = '[0-9a-f]{32}';
82✔
345
    }
346

347
    /**
348
     * Handle temporary variables
349
     *
350
     * Clears old "flash" data, marks the new one for deletion and handles
351
     * "temp" data deletion.
352
     *
353
     * @return void
354
     */
355
    protected function initVars()
356
    {
357
        if (! isset($_SESSION['__ci_vars'])) {
82✔
358
            return;
82✔
359
        }
360

361
        $currentTime = Time::now()->getTimestamp();
2✔
362

363
        foreach ($_SESSION['__ci_vars'] as $key => &$value) {
2✔
364
            if ($value === 'new') {
2✔
365
                $_SESSION['__ci_vars'][$key] = 'old';
2✔
366
            }
367
            // DO NOT move this above the 'new' check!
368
            elseif ($value === 'old' || $value < $currentTime) {
1✔
369
                unset($_SESSION[$key], $_SESSION['__ci_vars'][$key]);
1✔
370
            }
371
        }
372

373
        if ($_SESSION['__ci_vars'] === []) {
2✔
374
            unset($_SESSION['__ci_vars']);
1✔
375
        }
376
    }
377

378
    /**
379
     * Regenerates the session ID.
380
     *
381
     * @param bool $destroy Should old session data be destroyed?
382
     *
383
     * @return void
384
     */
385
    public function regenerate(bool $destroy = false)
386
    {
387
        $_SESSION['__ci_last_regenerate'] = Time::now()->getTimestamp();
×
388
        session_regenerate_id($destroy);
×
389

390
        $this->removeOldSessionCookie();
×
391
    }
392

393
    private function removeOldSessionCookie(): void
394
    {
395
        $response              = service('response');
×
396
        $cookieStoreInResponse = $response->getCookieStore();
×
397

398
        if (! $cookieStoreInResponse->has($this->config->cookieName)) {
×
399
            return;
×
400
        }
401

402
        // CookieStore is immutable.
403
        $newCookieStore = $cookieStoreInResponse->remove($this->config->cookieName);
×
404

405
        // But clear() method clears cookies in the object (not immutable).
406
        $cookieStoreInResponse->clear();
×
407

408
        foreach ($newCookieStore as $cookie) {
×
409
            $response->setCookie($cookie);
×
410
        }
411
    }
412

413
    /**
414
     * Destroys the current session.
415
     *
416
     * @return void
417
     */
418
    public function destroy()
419
    {
420
        if (ENVIRONMENT === 'testing') {
×
421
            return;
×
422
        }
423

424
        session_destroy();
×
425
    }
426

427
    /**
428
     * Writes session data and close the current session.
429
     *
430
     * @return void
431
     */
432
    public function close()
433
    {
434
        if (ENVIRONMENT === 'testing') {
×
435
            return;
×
436
        }
437

438
        session_write_close();
×
439
    }
440

441
    /**
442
     * Sets user data into the session.
443
     *
444
     * If $data is a string, then it is interpreted as a session property
445
     * key, and  $value is expected to be non-null.
446
     *
447
     * If $data is an array, it is expected to be an array of key/value pairs
448
     * to be set as session properties.
449
     *
450
     * @param array|string                            $data  Property name or associative array of properties
451
     * @param array|bool|float|int|object|string|null $value Property value if single key provided
452
     *
453
     * @return void
454
     */
455
    public function set($data, $value = null)
456
    {
457
        if (is_array($data)) {
115✔
458
            foreach ($data as $key => &$value) {
13✔
459
                if (is_int($key)) {
13✔
460
                    $_SESSION[$value] = null;
1✔
461
                } else {
462
                    $_SESSION[$key] = $value;
12✔
463
                }
464
            }
465

466
            return;
13✔
467
        }
468

469
        $_SESSION[$data] = $value;
105✔
470
    }
471

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

491
        if (! isset($_SESSION) || $_SESSION === []) {
18✔
492
            return $key === null ? [] : null;
12✔
493
        }
494

495
        if ($key !== null && $key !== '') {
6✔
496
            return null;
5✔
497
        }
498

499
        $userdata = [];
1✔
500
        $_exclude = array_merge(['__ci_vars'], $this->getFlashKeys(), $this->getTempKeys());
1✔
501

502
        $keys = array_keys($_SESSION);
1✔
503

504
        foreach ($keys as $key) {
1✔
505
            if (! in_array($key, $_exclude, true)) {
1✔
506
                $userdata[$key] = $_SESSION[$key];
1✔
507
            }
508
        }
509

510
        return $userdata;
1✔
511
    }
512

513
    /**
514
     * Returns whether an index exists in the session array.
515
     *
516
     * @param string $key Identifier of the session property we are interested in.
517
     */
518
    public function has(string $key): bool
519
    {
520
        return isset($_SESSION[$key]);
38✔
521
    }
522

523
    /**
524
     * Push new value onto session value that is array.
525
     *
526
     * @param string $key  Identifier of the session property we are interested in.
527
     * @param array  $data value to be pushed to existing session key.
528
     *
529
     * @return void
530
     */
531
    public function push(string $key, array $data)
532
    {
533
        if ($this->has($key) && is_array($value = $this->get($key))) {
1✔
534
            $this->set($key, array_merge($value, $data));
1✔
535
        }
536
    }
537

538
    /**
539
     * Remove one or more session properties.
540
     *
541
     * If $key is an array, it is interpreted as an array of string property
542
     * identifiers to remove. Otherwise, it is interpreted as the identifier
543
     * of a specific session property to remove.
544
     *
545
     * @param array|string $key Identifier of the session property or properties to remove.
546
     *
547
     * @return void
548
     */
549
    public function remove($key)
550
    {
551
        if (is_array($key)) {
2✔
552
            foreach ($key as $k) {
1✔
553
                unset($_SESSION[$k]);
1✔
554
            }
555

556
            return;
1✔
557
        }
558

559
        unset($_SESSION[$key]);
1✔
560
    }
561

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

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

590
        if ($key === 'session_id') {
×
591
            return session_id();
×
592
        }
593

594
        return null;
×
595
    }
596

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

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

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

645
        $flashdata = [];
×
646

647
        if (isset($_SESSION['__ci_vars'])) {
×
648
            foreach ($_SESSION['__ci_vars'] as $key => &$value) {
×
649
                if (! is_int($value)) {
×
650
                    $flashdata[$key] = $_SESSION[$key];
×
651
                }
652
            }
653
        }
654

655
        return $flashdata;
×
656
    }
657

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

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

686
            $new = array_fill_keys($key, 'new');
1✔
687

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

690
            return true;
1✔
691
        }
692

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

697
        $_SESSION['__ci_vars'][$key] = 'new';
10✔
698

699
        return true;
10✔
700
    }
701

702
    /**
703
     * Unmark data in the session as flashdata.
704
     *
705
     * @param array|string $key Property identifier or array of them
706
     *
707
     * @return void
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
     * @return void
761
     */
762
    public function setTempdata($data, $value = null, int $ttl = 300)
763
    {
764
        $this->set($data, $value);
10✔
765
        $this->markAsTempdata($data, $ttl);
10✔
766
    }
767

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

783
        $tempdata = [];
4✔
784

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

793
        return $tempdata;
4✔
794
    }
795

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

809
    /**
810
     * Mark one of more pieces of data as being temporary, meaning that
811
     * it has a set lifespan within the session.
812
     *
813
     * @param array|string $key Property identifier or array of them
814
     * @param int          $ttl Time to live, in seconds
815
     *
816
     * @return bool False if any of the properties were not set
817
     */
818
    public function markAsTempdata($key, int $ttl = 300): bool
819
    {
820
        $_SESSION['__ci_vars'] ??= [];
10✔
821
        $time = Time::now()->getTimestamp();
10✔
822
        $keys = is_array($key) ? $key : [$key];
10✔
823

824
        if (array_is_list($keys)) {
10✔
825
            $keys = array_fill_keys($keys, $ttl);
3✔
826
        }
827

828
        foreach ($keys as $sessionKey => $timeToLive) {
10✔
829
            if (! array_key_exists($sessionKey, $_SESSION)) {
10✔
NEW
830
                return false;
×
831
            }
832

833
            if (is_int($timeToLive)) {
10✔
834
                $timeToLive += $time;
4✔
835
            } else {
836
                $timeToLive = $time + $ttl;
7✔
837
            }
838

839
            $_SESSION['__ci_vars'][$sessionKey] = $timeToLive;
10✔
840
        }
841

842
        return true;
10✔
843
    }
844

845
    /**
846
     * Unmarks temporary data in the session, effectively removing its
847
     * lifespan and allowing it to live as long as the session does.
848
     *
849
     * @param array|string $key Property identifier or array of them
850
     *
851
     * @return void
852
     */
853
    public function unmarkTempdata($key)
854
    {
855
        if (! isset($_SESSION['__ci_vars'])) {
3✔
UNCOV
856
            return;
×
857
        }
858

859
        if (! is_array($key)) {
3✔
860
            $key = [$key];
2✔
861
        }
862

863
        foreach ($key as $k) {
3✔
864
            if (isset($_SESSION['__ci_vars'][$k]) && is_int($_SESSION['__ci_vars'][$k])) {
3✔
865
                unset($_SESSION['__ci_vars'][$k]);
3✔
866
            }
867
        }
868

869
        if ($_SESSION['__ci_vars'] === []) {
3✔
870
            unset($_SESSION['__ci_vars']);
1✔
871
        }
872
    }
873

874
    /**
875
     * Retrieve the keys of all session data that have been marked as temporary data.
876
     */
877
    public function getTempKeys(): array
878
    {
879
        if (! isset($_SESSION['__ci_vars'])) {
2✔
880
            return [];
1✔
881
        }
882

883
        $keys = [];
1✔
884

885
        foreach (array_keys($_SESSION['__ci_vars']) as $key) {
1✔
886
            if (is_int($_SESSION['__ci_vars'][$key])) {
1✔
887
                $keys[] = $key;
1✔
888
            }
889
        }
890

891
        return $keys;
1✔
892
    }
893

894
    /**
895
     * Sets the driver as the session handler in PHP.
896
     * Extracted for easier testing.
897
     *
898
     * @return void
899
     */
900
    protected function setSaveHandler()
901
    {
902
        session_set_save_handler($this->driver, true);
40✔
903
    }
904

905
    /**
906
     * Starts the session.
907
     * Extracted for testing reasons.
908
     *
909
     * @return void
910
     */
911
    protected function startSession()
912
    {
913
        if (ENVIRONMENT === 'testing') {
40✔
914
            $_SESSION = [];
40✔
915

916
            return;
40✔
917
        }
918

UNCOV
919
        session_start(); // @codeCoverageIgnore
×
920
    }
921

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

UNCOV
934
        $response = service('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