Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
78 / 78 |
|
100.00% |
25 / 25 |
CRAP | |
100.00% |
1 / 1 |
Cookie | |
100.00% |
78 / 78 |
|
100.00% |
25 / 25 |
48 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getInstance | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
getOptions | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
1 | |||
setOptions | |
100.00% |
19 / 19 |
|
100.00% |
1 / 1 |
12 | |||
set | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
4 | |||
getExpires | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getPath | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getDomain | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isSecure | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isHttpOnly | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getSamesite | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getIp | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
delete | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
clear | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
4 | |||
count | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getIterator | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
toArray | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
__set | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
1 | |||
__get | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
__isset | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
__unset | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
offsetSet | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
offsetGet | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
offsetExists | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
offsetUnset | |
100.00% |
1 / 1 |
|
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 | */ |
14 | namespace Pop\Cookie; |
15 | |
16 | use 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 | */ |
28 | class 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 | } |