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

codeigniter4 / CodeIgniter4 / 7345642681

28 Dec 2023 07:58AM UTC coverage: 85.244% (+0.007%) from 85.237%
7345642681

push

github

web-flow
Merge pull request #8345 from kenjis/replace-empty

refactor: replace empty() Part 1

90 of 93 new or added lines in 19 files covered. (96.77%)

1 existing line in 1 file now uncovered.

18607 of 21828 relevant lines covered (85.24%)

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

12
namespace CodeIgniter\Session;
13

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

187
        $this->config = $config;
6,058✔
188

189
        $cookie = config(CookieConfig::class);
6,058✔
190

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

201
        helper('array');
6,058✔
202
    }
203

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

215
            return;
216
            // @codeCoverageIgnoreEnd
217
        }
218

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

222
            return;
×
223
        }
224

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

228
            return;
×
229
        }
230

231
        $this->configure();
80✔
232
        $this->setSaveHandler();
80✔
233

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

241
        $this->startSession();
80✔
242

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

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

262
        return $this;
80✔
263
    }
264

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

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

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

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

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

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

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

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

312
        $this->configureSidLength();
80✔
313
    }
314

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

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

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

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

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

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

360
        $this->sidRegexp .= '{' . $sidLength . '}';
80✔
361
    }
362

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

375
        $currentTime = Time::now()->getTimestamp();
2✔
376

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

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

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

402
        $this->removeOldSessionCookie();
×
403
    }
404

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

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

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

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

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

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

434
        session_destroy();
×
435
    }
436

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

448
        session_write_close();
×
449
    }
450

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

474
            return;
12✔
475
        }
476

477
        $_SESSION[$data] = $value;
103✔
478
    }
479

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

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

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

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

510
        $keys = array_keys($_SESSION);
1✔
511

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

518
        return $userdata;
1✔
519
    }
520

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

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

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

560
            return;
1✔
561
        }
562

563
        unset($_SESSION[$key]);
1✔
564
    }
565

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

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

594
        if ($key === 'session_id') {
×
595
            return session_id();
×
596
        }
597

598
        return null;
×
599
    }
600

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

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

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

647
        $flashdata = [];
×
648

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

657
        return $flashdata;
×
658
    }
659

660
    /**
661
     * Keeps a single piece of flash data alive for one more request.
662
     *
663
     * @param array|string $key Property identifier or array of them
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
    public function unmarkFlashdata($key)
708
    {
709
        if (! isset($_SESSION['__ci_vars'])) {
1✔
710
            return;
×
711
        }
712

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

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

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

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

739
        $keys = [];
1✔
740

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

747
        return $keys;
1✔
748
    }
749

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

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

779
        $tempdata = [];
4✔
780

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

789
        return $tempdata;
4✔
790
    }
791

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

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

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

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

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

834
                $temp[$k] = $v;
8✔
835
            }
836

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

839
            return true;
8✔
840
        }
841

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

846
        $_SESSION['__ci_vars'][$key] = $ttl;
1✔
847

848
        return true;
1✔
849
    }
850

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

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

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

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

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

887
        $keys = [];
1✔
888

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

895
        return $keys;
1✔
896
    }
897

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

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

916
            return;
39✔
917
        }
918

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

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

932
        $response = Services::response();
933
        $response->setCookie($this->cookie);
934
    }
935
}
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