Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
78 / 78
100.00% covered (success)
100.00%
25 / 25
CRAP
100.00% covered (success)
100.00%
1 / 1
Cookie
100.00% covered (success)
100.00%
78 / 78
100.00% covered (success)
100.00%
25 / 25
48
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getInstance
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getOptions
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 setOptions
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
12
 set
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 getExpires
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPath
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDomain
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isSecure
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isHttpOnly
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSamesite
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getIp
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 delete
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 clear
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 count
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getIterator
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 toArray
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __set
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 __get
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 __isset
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __unset
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 offsetSet
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 offsetGet
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 offsetExists
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 offsetUnset
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Pop PHP Framework (http://www.popphp.org/)
4 *
5 * @link       https://github.com/popphp/popphp-framework
6 * @author     Nick Sagona, III <dev@nolainteractive.com>
7 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
8 * @license    http://www.popphp.org/license     New BSD License
9 */
10
11/**
12 * @namespace
13 */
14namespace Pop\Cookie;
15
16use ArrayIterator;
17
18/**
19 * Cookie class
20 *
21 * @category   Pop
22 * @package    Pop\Cookie
23 * @author     Nick Sagona, III <dev@nolainteractive.com>
24 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
25 * @license    http://www.popphp.org/license     New BSD License
26 * @version    4.0.0
27 */
28class Cookie implements \ArrayAccess, \Countable, \IteratorAggregate
29{
30
31    /**
32     * Instance of the cookie object
33     * @var Cookie
34     */
35    static private Cookie $instance;
36
37    /**
38     * Cookie IP
39     * @var ?string
40     */
41    private ?string $ip = null;
42
43    /**
44     * Cookie Expiration
45     * @var int
46     */
47    private int $expires = 0;
48
49    /**
50     * Cookie Path
51     * @var string
52     */
53    private string $path = '/';
54
55    /**
56     * Cookie Domain
57     * @var ?string
58     */
59    private ?string $domain = null;
60
61    /**
62     * Cookie Secure Flag
63     * @var bool
64     */
65    private bool $secure = false;
66
67    /**
68     * Cookie HTTP Only Flag
69     * @var bool
70     */
71    private bool $httponly = false;
72
73    /**
74     * Cookie SameSite Flag (None, Lax, Strict)
75     * @var string
76     */
77    private string $samesite = 'Lax';
78
79    /**
80     * Constructor
81     *
82     * Private method to instantiate the cookie object
83     *
84     * @param  array $options
85     */
86    private function __construct(array $options = [])
87    {
88        $this->setOptions($options);
89    }
90
91    /**
92     * Determine whether or not an instance of the cookie object exists
93     * already, and instantiate the object if it does not exist.
94     *
95     * @param  array $options
96     * @return Cookie
97     */
98    public static function getInstance(array $options = []): Cookie
99    {
100        if (empty(self::$instance)) {
101            self::$instance = new Cookie($options);
102        }
103
104        return self::$instance;
105    }
106
107    /**
108     * Method to create options array
109     *
110     * @return array
111     */
112    public function getOptions(): array
113    {
114        return [
115            'expires'  => $this->expires,
116            'path'     => $this->path,
117            'domain'   => $this->domain,
118            'secure'   => $this->secure,
119            'httponly' => $this->httponly,
120            'samesite' => $this->samesite
121        ];
122    }
123
124    /**
125     * Private method to set options
126     *
127     * @param  array $options
128     * @return Cookie
129     */
130    public function setOptions(array $options = []): Cookie
131    {
132        // Set the cookie owner's IP address and domain.
133        $this->ip = $_SERVER['REMOTE_ADDR'] ?? null;
134
135        if (isset($_SERVER['SERVER_NAME'])) {
136            $this->domain = $_SERVER['SERVER_NAME'];
137        } else if (isset($_SERVER['HTTP_HOST'])) {
138            $this->domain = $_SERVER['HTTP_HOST'];
139        }
140
141        if (isset($options['expires'])) {
142            $this->expires = (int)$options['expires'];
143        }
144        if (isset($options['path'])) {
145            $this->path = $options['path'];
146        }
147        if (isset($options['domain'])) {
148            $this->domain = $options['domain'];
149        }
150        if (isset($options['secure'])) {
151            $this->secure = (bool)$options['secure'];
152        }
153        if (isset($options['httponly'])) {
154            $this->httponly = (bool)$options['httponly'];
155        }
156        if (isset($options['samesite'])) {
157            if (($options['samesite'] == 'None') || ($options['samesite'] == 'Lax') || ($options['samesite'] == 'Strict')) {
158                $this->samesite = $options['samesite'];
159            }
160        }
161
162        return $this;
163    }
164
165    /**
166     * Set a cookie
167     *
168     * @param  string  $name
169     * @param  mixed   $value
170     * @param  array   $options
171     * @return Cookie
172     */
173    public function set(string $name, mixed $value, array $options = []): Cookie
174    {
175        if (!empty($options)) {
176            $this->setOptions($options);
177        }
178
179        if (!is_string($value) && !is_numeric($value)) {
180            $value = json_encode($value);
181        }
182
183        setcookie($name, $value, $this->getOptions());
184        return $this;
185    }
186
187    /**
188     * Return the current cookie expiration
189     *
190     * @return int
191     */
192    public function getExpires(): int
193    {
194        return $this->expires;
195    }
196
197    /**
198     * Return the current cookie path.
199     *
200     * @return string
201     */
202    public function getPath(): string
203    {
204        return $this->path;
205    }
206
207    /**
208     * Return the current cookie domain
209     *
210     * @return string|null
211     */
212    public function getDomain(): string|null
213    {
214        return $this->domain;
215    }
216
217    /**
218     * Return if the cookie is secure
219     *
220     * @return bool
221     */
222    public function isSecure(): bool
223    {
224        return $this->secure;
225    }
226
227    /**
228     * Return if the cookie is HTTP only
229     *
230     * @return bool
231     */
232    public function isHttpOnly(): bool
233    {
234        return $this->httponly;
235    }
236
237    /**
238     * Return if the cookie's samesite flag
239     *
240     * @return string
241     */
242    public function getSamesite(): string
243    {
244        return $this->samesite;
245    }
246
247    /**
248     * Return the current IP address.
249     *
250     * @return string|null
251     */
252    public function getIp(): string|null
253    {
254        return $this->ip;
255    }
256
257    /**
258     * Delete a cookie
259     *
260     * @param  string $name
261     * @param  array  $options
262     * @return void
263     */
264    public function delete(string $name, array $options = []): void
265    {
266        if (!empty($options)) {
267            $this->setOptions($options);
268        }
269        if (isset($_COOKIE[$name])) {
270            $this->expires = time() - 3600;
271            setcookie($name, $_COOKIE[$name], $this->getOptions());
272        }
273    }
274
275    /**
276     * Clear (delete) all cookies
277     *
278     * @param  array $options
279     * @return void
280     */
281    public function clear(array $options = []): void
282    {
283        if (!empty($options)) {
284            $this->setOptions($options);
285        }
286
287        $this->expires = time() - 3600;
288
289        foreach ($_COOKIE as $name => $value) {
290            if (isset($_COOKIE[$name])) {
291                setcookie($name, $_COOKIE[$name], $this->getOptions());
292            }
293        }
294    }
295
296    /**
297     * Method to get the count of cookie data
298     *
299     * @return int
300     */
301    public function count(): int
302    {
303        return count($this->toArray());
304    }
305    /**
306     * Method to iterate over the cookie
307     *
308     * @return ArrayIterator
309     */
310    public function getIterator(): ArrayIterator
311    {
312        return new ArrayIterator($this->toArray());
313    }
314    /**
315     * Get the cookie values as an array
316     *
317     * @return array
318     */
319    public function toArray(): array
320    {
321        return $_COOKIE;
322    }
323
324    /**
325     * Set method to set the value of the $_COOKIE global variable
326     *
327     * @param  string $name
328     * @param  mixed $value
329     * @return void
330     */
331    public function __set(string $name, mixed $value)
332    {
333        $options = [
334            'expires'  => $this->expires,
335            'path'     => $this->path,
336            'domain'   => $this->domain,
337            'secure'   => $this->secure,
338            'httponly' => $this->httponly
339        ];
340        $this->set($name, $value, $options);
341    }
342
343    /**
344     * Get method to return the value of the $_COOKIE global variable
345     *
346     * @param  string $name
347     * @return mixed
348     */
349    public function __get(string $name): mixed
350    {
351        $value = null;
352        if (isset($_COOKIE[$name])) {
353            $value = (str_starts_with($_COOKIE[$name], '{')) ? json_decode($_COOKIE[$name], true) : $_COOKIE[$name];
354        }
355        return $value;
356    }
357
358    /**
359     * Return the isset value of the $_COOKIE global variable
360     *
361     * @param  string $name
362     * @return bool
363     */
364    public function __isset(string $name): bool
365    {
366        return isset($_COOKIE[$name]);
367    }
368
369    /**
370     * Unset the value in the $_COOKIE global variable
371     *
372     * @param  string $name
373     * @return void
374     */
375    public function __unset(string $name): void
376    {
377        if (isset($_COOKIE[$name])) {
378            $this->expires = time() - 3600;
379            setcookie($name, $_COOKIE[$name], $this->getOptions());
380        }
381    }
382
383    /**
384     * ArrayAccess offsetSet
385     *
386     * @param  mixed $offset
387     * @param  mixed $value
388     * @return void
389     */
390    public function offsetSet(mixed $offset, mixed $value): void
391    {
392        $this->__set($offset, $value);
393    }
394
395    /**
396     * ArrayAccess offsetGet
397     *
398     * @param  mixed $offset
399     * @return mixed
400     */
401    public function offsetGet(mixed $offset): mixed
402    {
403        return $this->__get($offset);
404    }
405
406    /**
407     * ArrayAccess offsetExists
408     *
409     * @param  mixed $offset
410     * @return bool
411     */
412    public function offsetExists(mixed $offset): bool
413    {
414        return $this->__isset($offset);
415    }
416
417    /**
418     * ArrayAccess offsetUnset
419     *
420     * @param  mixed $offset
421     * @return void
422     */
423    public function offsetUnset(mixed $offset): void
424    {
425        $this->__unset($offset);
426    }
427
428}