1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace SilverStripe\Control; |
4
|
|
|
|
5
|
|
|
use SilverStripe\ORM\FieldType\DBDatetime; |
6
|
|
|
use LogicException; |
7
|
|
|
|
8
|
|
|
/** |
9
|
|
|
* A default backend for the setting and getting of cookies |
10
|
|
|
* |
11
|
|
|
* This backend allows one to better test Cookie setting and separate cookie |
12
|
|
|
* handling from the core |
13
|
|
|
* |
14
|
|
|
* @todo Create a config array for defaults (eg: httpOnly, secure, path, domain, expiry) |
15
|
|
|
* @todo A getter for cookies that haven't been sent to the browser yet |
16
|
|
|
* @todo Tests / a way to set the state without hacking with $_COOKIE |
17
|
|
|
* @todo Store the meta information around cookie setting (path, domain, secure, etc) |
18
|
|
|
*/ |
19
|
|
|
class CookieJar implements Cookie_Backend |
20
|
|
|
{ |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* Hold the cookies that were existing at time of instantiation (ie: The ones |
24
|
|
|
* sent to PHP by the browser) |
25
|
|
|
* |
26
|
|
|
* @var array Existing cookies sent by the browser |
27
|
|
|
*/ |
28
|
|
|
protected $existing = array(); |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* Hold the current cookies (ie: a mix of those that were sent to us and we |
32
|
|
|
* have set without the ones we've cleared) |
33
|
|
|
* |
34
|
|
|
* @var array The state of cookies once we've sent the response |
35
|
|
|
*/ |
36
|
|
|
protected $current = array(); |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* Hold any NEW cookies that were set by the application and will be sent |
40
|
|
|
* in the next response |
41
|
|
|
* |
42
|
|
|
* @var array New cookies set by the application |
43
|
|
|
*/ |
44
|
|
|
protected $new = array(); |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* When creating the backend we want to store the existing cookies in our |
48
|
|
|
* "existing" array. This allows us to distinguish between cookies we received |
49
|
|
|
* or we set ourselves (and didn't get from the browser) |
50
|
|
|
* |
51
|
|
|
* @param array $cookies The existing cookies to load into the cookie jar. |
52
|
|
|
* Omit this to default to $_COOKIE |
53
|
|
|
*/ |
54
|
|
|
public function __construct($cookies = array()) |
55
|
|
|
{ |
56
|
|
|
$this->current = $this->existing = func_num_args() |
57
|
|
|
? ($cookies ?: array()) // Convert empty values to blank arrays |
58
|
|
|
: $_COOKIE; |
59
|
|
|
} |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* Set a cookie |
63
|
|
|
* |
64
|
|
|
* @param string $name The name of the cookie |
65
|
|
|
* @param string $value The value for the cookie to hold |
66
|
|
|
* @param int $expiry The number of days until expiry; 0 indicates a cookie valid for the current session |
67
|
|
|
* @param string $path The path to save the cookie on (falls back to site base) |
68
|
|
|
* @param string $domain The domain to make the cookie available on |
69
|
|
|
* @param boolean $secure Can the cookie only be sent over SSL? |
70
|
|
|
* @param boolean $httpOnly Prevent the cookie being accessible by JS |
71
|
|
|
*/ |
72
|
|
|
public function set($name, $value, $expiry = 90, $path = null, $domain = null, $secure = false, $httpOnly = true) |
73
|
|
|
{ |
74
|
|
|
//are we setting or clearing a cookie? false values are reserved for clearing cookies (see PHP manual) |
75
|
|
|
$clear = false; |
76
|
|
|
if ($value === false || $value === '' || $expiry < 0) { |
77
|
|
|
$clear = true; |
78
|
|
|
$value = false; |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
//expiry === 0 is a special case where we set a cookie for the current user session |
82
|
|
|
if ($expiry !== 0) { |
83
|
|
|
//don't do the maths if we are clearing |
84
|
|
|
$expiry = $clear ? -1 : DBDatetime::now()->getTimestamp() + (86400 * $expiry); |
85
|
|
|
} |
86
|
|
|
//set the path up |
87
|
|
|
$path = $path ? $path : Director::baseURL(); |
88
|
|
|
//send the cookie |
89
|
|
|
$this->outputCookie($name, $value, $expiry, $path, $domain, $secure, $httpOnly); |
|
|
|
|
90
|
|
|
//keep our variables in check |
91
|
|
|
if ($clear) { |
92
|
|
|
unset($this->new[$name], $this->current[$name]); |
93
|
|
|
} else { |
94
|
|
|
$this->new[$name] = $this->current[$name] = $value; |
95
|
|
|
} |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* Get the cookie value by name |
100
|
|
|
* |
101
|
|
|
* Cookie names are normalised to work around PHP's behaviour of replacing incoming variable name . with _ |
102
|
|
|
* |
103
|
|
|
* @param string $name The name of the cookie to get |
104
|
|
|
* @param boolean $includeUnsent Include cookies we've yet to send when fetching values |
105
|
|
|
* |
106
|
|
|
* @return string|null The cookie value or null if unset |
107
|
|
|
*/ |
108
|
|
|
public function get($name, $includeUnsent = true) |
109
|
|
|
{ |
110
|
|
|
$cookies = $includeUnsent ? $this->current : $this->existing; |
111
|
|
|
if (isset($cookies[$name])) { |
112
|
|
|
return $cookies[$name]; |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
//Normalise cookie names by replacing '.' with '_' |
116
|
|
|
$safeName = str_replace('.', '_', $name); |
117
|
|
|
if (isset($cookies[$safeName])) { |
118
|
|
|
return $cookies[$safeName]; |
119
|
|
|
} |
120
|
|
|
return null; |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
/** |
124
|
|
|
* Get all the cookies |
125
|
|
|
* |
126
|
|
|
* @param boolean $includeUnsent Include cookies we've yet to send |
127
|
|
|
* @return array All the cookies |
128
|
|
|
*/ |
129
|
|
|
public function getAll($includeUnsent = true) |
130
|
|
|
{ |
131
|
|
|
return $includeUnsent ? $this->current : $this->existing; |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
/** |
135
|
|
|
* Force the expiry of a cookie by name |
136
|
|
|
* |
137
|
|
|
* @param string $name The name of the cookie to expire |
138
|
|
|
* @param string $path The path to save the cookie on (falls back to site base) |
139
|
|
|
* @param string $domain The domain to make the cookie available on |
140
|
|
|
* @param boolean $secure Can the cookie only be sent over SSL? |
141
|
|
|
* @param boolean $httpOnly Prevent the cookie being accessible by JS |
142
|
|
|
*/ |
143
|
|
|
public function forceExpiry($name, $path = null, $domain = null, $secure = false, $httpOnly = true) |
144
|
|
|
{ |
145
|
|
|
$this->set($name, false, -1, $path, $domain, $secure, $httpOnly); |
|
|
|
|
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* The function that actually sets the cookie using PHP |
150
|
|
|
* |
151
|
|
|
* @see http://uk3.php.net/manual/en/function.setcookie.php |
152
|
|
|
* |
153
|
|
|
* @param string $name The name of the cookie |
154
|
|
|
* @param string|array $value The value for the cookie to hold |
155
|
|
|
* @param int $expiry The number of days until expiry |
156
|
|
|
* @param string $path The path to save the cookie on (falls back to site base) |
157
|
|
|
* @param string $domain The domain to make the cookie available on |
158
|
|
|
* @param boolean $secure Can the cookie only be sent over SSL? |
159
|
|
|
* @param boolean $httpOnly Prevent the cookie being accessible by JS |
160
|
|
|
* @return boolean If the cookie was set or not; doesn't mean it's accepted by the browser |
161
|
|
|
*/ |
162
|
|
|
protected function outputCookie( |
163
|
|
|
$name, |
164
|
|
|
$value, |
165
|
|
|
$expiry = 90, |
166
|
|
|
$path = null, |
167
|
|
|
$domain = null, |
168
|
|
|
$secure = false, |
169
|
|
|
$httpOnly = true |
170
|
|
|
) { |
171
|
|
|
// if headers aren't sent, we can set the cookie |
172
|
|
|
if (!headers_sent($file, $line)) { |
173
|
|
|
return setcookie($name, $value, $expiry, $path, $domain, $secure, $httpOnly); |
|
|
|
|
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
if (Cookie::config()->uninherited('report_errors')) { |
177
|
|
|
throw new LogicException( |
178
|
|
|
"Cookie '$name' can't be set. The site started outputting content at line $line in $file" |
179
|
|
|
); |
180
|
|
|
} |
181
|
|
|
return false; |
182
|
|
|
} |
183
|
|
|
} |
184
|
|
|
|