2014-05-04 15:34:58 +02:00
|
|
|
<?php
|
|
|
|
|
2016-08-19 00:51:35 +02:00
|
|
|
namespace SilverStripe\Control;
|
2016-06-15 06:03:16 +02:00
|
|
|
|
|
|
|
use SilverStripe\ORM\FieldType\DBDatetime;
|
2016-08-19 00:51:35 +02:00
|
|
|
use LogicException;
|
2015-08-30 07:02:55 +02:00
|
|
|
|
2014-05-04 15:34:58 +02:00
|
|
|
/**
|
|
|
|
* A default backend for the setting and getting of cookies
|
|
|
|
*
|
2015-05-11 14:55:46 +02:00
|
|
|
* This backend allows one to better test Cookie setting and separate cookie
|
2014-05-04 15:34:58 +02:00
|
|
|
* handling from the core
|
|
|
|
*
|
|
|
|
* @todo Create a config array for defaults (eg: httpOnly, secure, path, domain, expiry)
|
|
|
|
* @todo A getter for cookies that haven't been sent to the browser yet
|
|
|
|
* @todo Tests / a way to set the state without hacking with $_COOKIE
|
|
|
|
* @todo Store the meta information around cookie setting (path, domain, secure, etc)
|
|
|
|
*/
|
2016-11-29 00:31:16 +01:00
|
|
|
class CookieJar implements Cookie_Backend
|
|
|
|
{
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Hold the cookies that were existing at time of instantiation (ie: The ones
|
|
|
|
* sent to PHP by the browser)
|
|
|
|
*
|
|
|
|
* @var array Existing cookies sent by the browser
|
|
|
|
*/
|
2020-04-20 19:58:09 +02:00
|
|
|
protected $existing = [];
|
2016-11-29 00:31:16 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Hold the current cookies (ie: a mix of those that were sent to us and we
|
|
|
|
* have set without the ones we've cleared)
|
|
|
|
*
|
|
|
|
* @var array The state of cookies once we've sent the response
|
|
|
|
*/
|
2020-04-20 19:58:09 +02:00
|
|
|
protected $current = [];
|
2016-11-29 00:31:16 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Hold any NEW cookies that were set by the application and will be sent
|
|
|
|
* in the next response
|
|
|
|
*
|
|
|
|
* @var array New cookies set by the application
|
|
|
|
*/
|
2020-04-20 19:58:09 +02:00
|
|
|
protected $new = [];
|
2016-11-29 00:31:16 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* When creating the backend we want to store the existing cookies in our
|
|
|
|
* "existing" array. This allows us to distinguish between cookies we received
|
|
|
|
* or we set ourselves (and didn't get from the browser)
|
|
|
|
*
|
|
|
|
* @param array $cookies The existing cookies to load into the cookie jar.
|
|
|
|
* Omit this to default to $_COOKIE
|
|
|
|
*/
|
2020-04-20 19:58:09 +02:00
|
|
|
public function __construct($cookies = [])
|
2016-11-29 00:31:16 +01:00
|
|
|
{
|
|
|
|
$this->current = $this->existing = func_num_args()
|
2020-04-20 19:58:09 +02:00
|
|
|
? ($cookies ?: []) // Convert empty values to blank arrays
|
2016-11-29 00:31:16 +01:00
|
|
|
: $_COOKIE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set a cookie
|
|
|
|
*
|
|
|
|
* @param string $name The name of the cookie
|
|
|
|
* @param string $value The value for the cookie to hold
|
2020-09-10 02:34:33 +02:00
|
|
|
* @param float $expiry The number of days until expiry; 0 indicates a cookie valid for the current session
|
2016-11-29 00:31:16 +01:00
|
|
|
* @param string $path The path to save the cookie on (falls back to site base)
|
|
|
|
* @param string $domain The domain to make the cookie available on
|
|
|
|
* @param boolean $secure Can the cookie only be sent over SSL?
|
|
|
|
* @param boolean $httpOnly Prevent the cookie being accessible by JS
|
|
|
|
*/
|
|
|
|
public function set($name, $value, $expiry = 90, $path = null, $domain = null, $secure = false, $httpOnly = true)
|
|
|
|
{
|
|
|
|
//are we setting or clearing a cookie? false values are reserved for clearing cookies (see PHP manual)
|
|
|
|
$clear = false;
|
|
|
|
if ($value === false || $value === '' || $expiry < 0) {
|
|
|
|
$clear = true;
|
|
|
|
$value = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//expiry === 0 is a special case where we set a cookie for the current user session
|
|
|
|
if ($expiry !== 0) {
|
|
|
|
//don't do the maths if we are clearing
|
2017-01-26 05:20:08 +01:00
|
|
|
$expiry = $clear ? -1 : DBDatetime::now()->getTimestamp() + (86400 * $expiry);
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
//set the path up
|
|
|
|
$path = $path ? $path : Director::baseURL();
|
|
|
|
//send the cookie
|
|
|
|
$this->outputCookie($name, $value, $expiry, $path, $domain, $secure, $httpOnly);
|
|
|
|
//keep our variables in check
|
|
|
|
if ($clear) {
|
|
|
|
unset($this->new[$name], $this->current[$name]);
|
|
|
|
} else {
|
|
|
|
$this->new[$name] = $this->current[$name] = $value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the cookie value by name
|
|
|
|
*
|
|
|
|
* Cookie names are normalised to work around PHP's behaviour of replacing incoming variable name . with _
|
|
|
|
*
|
|
|
|
* @param string $name The name of the cookie to get
|
|
|
|
* @param boolean $includeUnsent Include cookies we've yet to send when fetching values
|
|
|
|
*
|
|
|
|
* @return string|null The cookie value or null if unset
|
|
|
|
*/
|
|
|
|
public function get($name, $includeUnsent = true)
|
|
|
|
{
|
|
|
|
$cookies = $includeUnsent ? $this->current : $this->existing;
|
|
|
|
if (isset($cookies[$name])) {
|
|
|
|
return $cookies[$name];
|
|
|
|
}
|
|
|
|
|
|
|
|
//Normalise cookie names by replacing '.' with '_'
|
|
|
|
$safeName = str_replace('.', '_', $name);
|
|
|
|
if (isset($cookies[$safeName])) {
|
|
|
|
return $cookies[$safeName];
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get all the cookies
|
|
|
|
*
|
|
|
|
* @param boolean $includeUnsent Include cookies we've yet to send
|
|
|
|
* @return array All the cookies
|
|
|
|
*/
|
|
|
|
public function getAll($includeUnsent = true)
|
|
|
|
{
|
|
|
|
return $includeUnsent ? $this->current : $this->existing;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Force the expiry of a cookie by name
|
|
|
|
*
|
|
|
|
* @param string $name The name of the cookie to expire
|
|
|
|
* @param string $path The path to save the cookie on (falls back to site base)
|
|
|
|
* @param string $domain The domain to make the cookie available on
|
|
|
|
* @param boolean $secure Can the cookie only be sent over SSL?
|
|
|
|
* @param boolean $httpOnly Prevent the cookie being accessible by JS
|
|
|
|
*/
|
|
|
|
public function forceExpiry($name, $path = null, $domain = null, $secure = false, $httpOnly = true)
|
|
|
|
{
|
|
|
|
$this->set($name, false, -1, $path, $domain, $secure, $httpOnly);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The function that actually sets the cookie using PHP
|
|
|
|
*
|
|
|
|
* @see http://uk3.php.net/manual/en/function.setcookie.php
|
|
|
|
*
|
|
|
|
* @param string $name The name of the cookie
|
|
|
|
* @param string|array $value The value for the cookie to hold
|
2020-09-10 02:34:33 +02:00
|
|
|
* @param int $expiry A Unix timestamp indicating when the cookie expires; 0 means it will expire at the end of the session
|
2016-11-29 00:31:16 +01:00
|
|
|
* @param string $path The path to save the cookie on (falls back to site base)
|
|
|
|
* @param string $domain The domain to make the cookie available on
|
|
|
|
* @param boolean $secure Can the cookie only be sent over SSL?
|
|
|
|
* @param boolean $httpOnly Prevent the cookie being accessible by JS
|
|
|
|
* @return boolean If the cookie was set or not; doesn't mean it's accepted by the browser
|
|
|
|
*/
|
|
|
|
protected function outputCookie(
|
|
|
|
$name,
|
|
|
|
$value,
|
|
|
|
$expiry = 90,
|
|
|
|
$path = null,
|
|
|
|
$domain = null,
|
|
|
|
$secure = false,
|
|
|
|
$httpOnly = true
|
|
|
|
) {
|
|
|
|
// if headers aren't sent, we can set the cookie
|
|
|
|
if (!headers_sent($file, $line)) {
|
|
|
|
return setcookie($name, $value, $expiry, $path, $domain, $secure, $httpOnly);
|
|
|
|
}
|
|
|
|
|
2017-02-22 04:14:53 +01:00
|
|
|
if (Cookie::config()->uninherited('report_errors')) {
|
2016-11-29 00:31:16 +01:00
|
|
|
throw new LogicException(
|
|
|
|
"Cookie '$name' can't be set. The site started outputting content at line $line in $file"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2014-05-04 15:34:58 +02:00
|
|
|
}
|