Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
77.78% covered (warning)
77.78%
56 / 72
61.54% covered (warning)
61.54%
8 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
CookieManager
77.78% covered (warning)
77.78%
56 / 72
61.54% covered (warning)
61.54%
8 / 13
28.81
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 getCookies
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
 getPath
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isSecure
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isHttpOnly
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSessionName
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
 proxySetCookie
14.29% covered (danger)
14.29%
2 / 14
0.00% covered (danger)
0.00%
0 / 1
4.52
 setGlobalCookie
96.67% covered (success)
96.67%
29 / 30
0.00% covered (danger)
0.00%
0 / 1
6
 set
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 clear
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
4.03
 get
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3/**
4 * Cookie Manager
5 *
6 * PHP version 8
7 *
8 * Copyright (C) Villanova University 2015.
9 * Copyright (C) The National Library of Finland 2020.
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2,
13 * as published by the Free Software Foundation.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
23 *
24 * @category VuFind
25 * @package  Cookie
26 * @author   Demian Katz <demian.katz@villanova.edu>
27 * @author   Ere Maijala <ere.maijala@helsinki.fi>
28 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
29 * @link     https://vufind.org/wiki/development Wiki
30 */
31
32namespace VuFind\Cookie;
33
34use function is_array;
35
36/**
37 * Cookie Manager
38 *
39 * @category VuFind
40 * @package  Cookie
41 * @author   Demian Katz <demian.katz@villanova.edu>
42 * @author   Ere Maijala <ere.maijala@helsinki.fi>
43 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
44 * @link     https://vufind.org/wiki/development Wiki
45 */
46class CookieManager
47{
48    /**
49     * Cookie array to work with
50     *
51     * @var array
52     */
53    protected $cookies;
54
55    /**
56     * Cookie base path
57     *
58     * @var string
59     */
60    protected $path;
61
62    /**
63     * Cookie domain
64     *
65     * @var string
66     */
67    protected $domain;
68
69    /**
70     * Are cookies secure only?
71     *
72     * @var bool
73     */
74    protected $secure;
75
76    /**
77     * Are cookies HTTP only?
78     *
79     * @var bool
80     */
81    protected $httpOnly;
82
83    /**
84     * The name of the session cookie
85     *
86     * @var ?string
87     */
88    protected $sessionName;
89
90    /**
91     * Default SameSite attribute
92     *
93     * @var string
94     */
95    protected $sameSite;
96
97    /**
98     * Constructor
99     *
100     * @param array   $cookies     Cookie array to manipulate (e.g. $_COOKIE)
101     * @param string  $path        Cookie base path (default = /)
102     * @param string  $domain      Cookie domain
103     * @param bool    $secure      Are cookies secure only? (default = false)
104     * @param ?string $sessionName Session cookie name (if null defaults to PHP
105     * settings)
106     * @param bool    $httpOnly    Are cookies HTTP only? (default = true)
107     * @param string  $sameSite    Default SameSite attribute (defaut = 'Lax')
108     */
109    public function __construct(
110        $cookies,
111        $path = '/',
112        $domain = null,
113        $secure = false,
114        $sessionName = null,
115        $httpOnly = true,
116        $sameSite = 'Lax'
117    ) {
118        $this->cookies = $cookies;
119        $this->path = $path;
120        $this->domain = $domain;
121        $this->secure = $secure;
122        $this->httpOnly = $httpOnly;
123        $this->sessionName = $sessionName;
124        $this->sameSite = $sameSite;
125    }
126
127    /**
128     * Get all cookie values.
129     *
130     * @return array
131     */
132    public function getCookies()
133    {
134        return $this->cookies;
135    }
136
137    /**
138     * Get the cookie domain.
139     *
140     * @return string
141     */
142    public function getDomain()
143    {
144        return $this->domain;
145    }
146
147    /**
148     * Get the cookie path.
149     *
150     * @return string
151     */
152    public function getPath()
153    {
154        return $this->path;
155    }
156
157    /**
158     * Are cookies set to "secure only" mode?
159     *
160     * @return bool
161     */
162    public function isSecure()
163    {
164        return $this->secure;
165    }
166
167    /**
168     * Are cookies set to "HTTP only" mode?
169     *
170     * @return bool
171     */
172    public function isHttpOnly()
173    {
174        return $this->httpOnly;
175    }
176
177    /**
178     * Get the name of the cookie
179     *
180     * @return ?string
181     */
182    public function getSessionName()
183    {
184        return $this->sessionName;
185    }
186
187    /**
188     * Get the cookie SameSite attribute.
189     *
190     * @return string
191     */
192    public function getSameSite()
193    {
194        return $this->sameSite;
195    }
196
197    /**
198     * Support method for setGlobalCookie -- proxy PHP's setcookie() function
199     * for compatibility with unit testing.
200     *
201     * @param string $key      Name of cookie to set
202     * @param mixed  $value    Value to set
203     * @param int    $expire   Cookie expiration time
204     * @param string $path     Path
205     * @param string $domain   Domain
206     * @param bool   $secure   Whether the cookie is secure only
207     * @param bool   $httpOnly Whether the cookie should be "HTTP only"
208     * @param string $sameSite SameSite attribute to use (Lax, Strict or None)
209     *
210     * @return bool
211     */
212    public function proxySetCookie(
213        $key,
214        $value,
215        $expire,
216        $path,
217        $domain,
218        $secure,
219        $httpOnly,
220        $sameSite
221    ) {
222        // Special case: in CLI -- don't actually write headers!
223        if ('cli' === PHP_SAPI) {
224            return true;
225        }
226        return setcookie(
227            $key,
228            $value ?? '',
229            [
230                'expires' => $expire,
231                'path' => $path,
232                'domain' => $domain,
233                'samesite' => $sameSite,
234                'secure' => $secure,
235                'httponly' => $httpOnly,
236            ]
237        );
238    }
239
240    /**
241     * Support method for set() -- set the actual cookie in PHP.
242     *
243     * @param string    $key      Name of cookie to set
244     * @param mixed     $value    Value to set
245     * @param int       $expire   Cookie expiration time
246     * @param null|bool $httpOnly Whether the cookie should be "HTTP only"
247     * @param string    $sameSite SameSite attribute to use (Lax, Strict or None)
248     *
249     * @return bool
250     */
251    public function setGlobalCookie(
252        $key,
253        $value,
254        $expire,
255        $httpOnly = null,
256        $sameSite = null
257    ) {
258        if (null === $httpOnly) {
259            $httpOnly = $this->httpOnly;
260        }
261        if (null === $sameSite) {
262            $sameSite = $this->sameSite;
263        }
264        // Simple case: flat value.
265        if (!is_array($value)) {
266            return $this->proxySetCookie(
267                $key,
268                $value,
269                $expire,
270                $this->path,
271                $this->domain,
272                $this->secure,
273                $httpOnly,
274                $sameSite
275            );
276        }
277
278        // Complex case: array of values.
279        $success = true;
280        foreach ($value as $i => $curr) {
281            $lastSuccess = $this->proxySetCookie(
282                $key . '[' . $i . ']',
283                $curr,
284                $expire,
285                $this->path,
286                $this->domain,
287                $this->secure,
288                $httpOnly,
289                $sameSite
290            );
291            if (!$lastSuccess) {
292                $success = false;
293            }
294        }
295        return $success;
296    }
297
298    /**
299     * Set a cookie.
300     *
301     * @param string    $key      Name of cookie to set
302     * @param mixed     $value    Value to set
303     * @param int       $expire   Cookie expiration time
304     * @param null|bool $httpOnly Whether the cookie should be "HTTP only"
305     * @param string    $sameSite SameSite attribute to use (Lax, Strict or None)
306     *
307     * @return bool
308     */
309    public function set(
310        $key,
311        $value,
312        $expire = 0,
313        $httpOnly = null,
314        $sameSite = null
315    ) {
316        $success = $this
317            ->setGlobalCookie($key, $value, $expire, $httpOnly, $sameSite);
318        if ($success) {
319            $this->cookies[$key] = $value;
320        }
321        return $success;
322    }
323
324    /**
325     * Clear a cookie.
326     *
327     * @param string $key Name of cookie to unset
328     *
329     * @return bool
330     */
331    public function clear($key)
332    {
333        $value = $this->get($key);
334        if (is_array($value)) {
335            $success = true;
336            foreach (array_keys($value) as $i) {
337                if (!$this->clear($key . '[' . $i . ']')) {
338                    $success = false;
339                }
340            }
341            return $success;
342        }
343        return $this->set($key, null, time() - 3600);
344    }
345
346    /**
347     * Retrieve a cookie value (or null if unset).
348     *
349     * @param string $key Name of cookie to retrieve
350     *
351     * @return mixed
352     */
353    public function get($key)
354    {
355        return $this->cookies[$key] ?? null;
356    }
357}