mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
commit
7a8b4a7f63
5
_config/cookie.yml
Normal file
5
_config/cookie.yml
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
Name: cookie
|
||||
---
|
||||
Injector:
|
||||
Cookie_Backend: CookieJar
|
@ -14,17 +14,12 @@ class Cookie {
|
||||
private static $report_errors = true;
|
||||
|
||||
/**
|
||||
* @var string cookie class
|
||||
* Fetch the current instance of the cookie backend
|
||||
*
|
||||
* @return Cookie_Backend The cookie backend
|
||||
*/
|
||||
static $cookie_class = 'Cookie';
|
||||
|
||||
private static $inst = null;
|
||||
|
||||
public static function get_inst() {
|
||||
if(is_null(self::$inst)) {
|
||||
self::$inst = new self::$cookie_class();
|
||||
}
|
||||
return self::$inst;
|
||||
return Injector::inst()->get('Cookie_Backend');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -41,105 +36,37 @@ class Cookie {
|
||||
public static function set($name, $value, $expiry = 90, $path = null, $domain = null, $secure = false,
|
||||
$httpOnly = true
|
||||
) {
|
||||
return self::get_inst()->inst_set($name, $value, $expiry, $path, $domain, $secure, $httpOnly);
|
||||
return self::get_inst()->set($name, $value, $expiry, $path, $domain, $secure, $httpOnly);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a cookie variable.
|
||||
* Get the cookie value by name
|
||||
*
|
||||
* @param string
|
||||
* @return mixed
|
||||
*/
|
||||
public static function get($name) {
|
||||
return self::get_inst()->inst_get($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string
|
||||
* @param string
|
||||
* @param string
|
||||
*/
|
||||
public static function force_expiry($name, $path = null, $domain = null) {
|
||||
return self::get_inst()->inst_force_expiry($name, $path, $domain);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 3.2 Use "Cookie.report_errors" config setting instead
|
||||
* @param bool
|
||||
*/
|
||||
public static function set_report_errors($reportErrors) {
|
||||
Deprecation::notice('3.2', 'Use "Cookie.report_errors" config setting instead');
|
||||
self::get_inst()->inst_set_report_errors($reportErrors);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 3.2 Use "Cookie.report_errors" config setting instead
|
||||
* @return bool
|
||||
*/
|
||||
public static function report_errors() {
|
||||
Deprecation::notice('3.2', 'Use "Cookie.report_errors" config setting instead');
|
||||
return self::get_inst()->inst_report_errors();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a cookie variable
|
||||
* @param string $name The name of the cookie to get
|
||||
* @param boolean $includeUnsent Include cookies we've yet to send when fetching values
|
||||
*
|
||||
* @param string $name The variable name
|
||||
* @param mixed $value The variable value.
|
||||
* @param int $expiry The expiry time, in days. Defaults to 90.
|
||||
* @param string $path See http://php.net/set_session
|
||||
* @param string $domain See http://php.net/set_session
|
||||
* @param boolean $secure See http://php.net/set_session
|
||||
* @param boolean $httpOnly See http://php.net/set_session
|
||||
* @return string|null The cookie value or null if unset
|
||||
*/
|
||||
protected function inst_set($name, $value, $expiry = 90, $path = null,
|
||||
$domain = null, $secure = false, $httpOnly = true
|
||||
) {
|
||||
if(!headers_sent($file, $line)) {
|
||||
$expiry = $expiry > 0 ? time()+(86400*$expiry) : $expiry;
|
||||
$path = ($path) ? $path : Director::baseURL();
|
||||
setcookie($name, $value, $expiry, $path, $domain, $secure, $httpOnly);
|
||||
$_COOKIE[$name] = $value;
|
||||
} else {
|
||||
if(Config::inst()->get('Cookie', 'report_errors')) {
|
||||
user_error("Cookie '$name' can't be set. The site started outputting content at line $line in $file",
|
||||
E_USER_WARNING);
|
||||
}
|
||||
}
|
||||
public static function get($name, $includeUnsent = true) {
|
||||
return self::get_inst()->get($name, $includeUnsent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the cookies
|
||||
*
|
||||
* @param boolean $includeUnsent Include cookies we've yet to send
|
||||
* @return array All the cookies
|
||||
*/
|
||||
public static function get_all($includeUnsent = true) {
|
||||
return self::get_inst()->getAll($includeUnsent);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string
|
||||
* @return mixed
|
||||
*/
|
||||
protected function inst_get($name) {
|
||||
return isset($_COOKIE[$name]) ? $_COOKIE[$name] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string
|
||||
* @param string
|
||||
*/
|
||||
protected function inst_force_expiry($name, $path = null, $domain = null) {
|
||||
if(!headers_sent($file, $line)) {
|
||||
self::set($name, null, -20, $path, $domain);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 3.2 Use the "Cookie.report_errors" config setting instead
|
||||
* @param bool
|
||||
*/
|
||||
protected function inst_set_report_errors($reportErrors) {
|
||||
Deprecation::notice('3.2', 'Use the "Cookie.report_errors" config setting instead');
|
||||
Config::inst()->update('Cookie', 'report_errors', $reportErrors);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 3.2 Use the "Cookie.report_errors" config setting instead
|
||||
* @return bool
|
||||
*/
|
||||
protected function inst_report_errors() {
|
||||
Deprecation::notice('3.2', 'Use the "Cookie.report_errors" config setting instead');
|
||||
return Config::inst()->get('Cookie', 'report_errors');
|
||||
public static function force_expiry($name, $path = null, $domain = null, $secure = false, $httpOnly = true) {
|
||||
return self::get_inst()->forceExpiry($name, $path, $domain, $secure, $httpOnly);
|
||||
}
|
||||
}
|
||||
|
157
control/CookieJar.php
Normal file
157
control/CookieJar.php
Normal file
@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* A default backend for the setting and getting of cookies
|
||||
*
|
||||
* This backend allows one to better test Cookie setting and seperate cookie
|
||||
* 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)
|
||||
*/
|
||||
class CookieJar implements Cookie_Backend {
|
||||
|
||||
/**
|
||||
* Hold the cookies that were existing at time of instatiation (ie: The ones
|
||||
* sent to PHP by the browser)
|
||||
*
|
||||
* @var array Existing cookies sent by the browser
|
||||
*/
|
||||
protected $existing = array();
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
protected $current = array();
|
||||
|
||||
/**
|
||||
* Hold any NEW cookies that were set by the appliation and will be sent
|
||||
* in the next response
|
||||
*
|
||||
* @var array New cookies set by the application
|
||||
*/
|
||||
protected $new = array();
|
||||
|
||||
/**
|
||||
* When creating the backend we want to store the existing cookies in our
|
||||
* "existing" array. This allows us to distinguish between cookies we recieved
|
||||
* 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
|
||||
*/
|
||||
public function __construct($cookies = array()) {
|
||||
$this->current = $this->existing = func_num_args()
|
||||
? ($cookies ?: array()) // Convert empty values to blank arrays
|
||||
: $_COOKIE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a cookie
|
||||
*
|
||||
* @param string $name The name of the cookie
|
||||
* @param string $value The value for the cookie to hold
|
||||
* @param int $expiry The number of days until expiry; 0 indicates a cookie valid for the current session
|
||||
* @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
|
||||
$expiry = $clear ? -1 : SS_Datetime::now()->Format('U') + (86400 * $expiry);
|
||||
}
|
||||
//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
|
||||
*
|
||||
* @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];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param int $expiry The number of days until expiry
|
||||
* @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);
|
||||
} else if(Config::inst()->get('Cookie', 'report_errors')) {
|
||||
throw new LogicException(
|
||||
"Cookie '$name' can't be set. The site started outputting content at line $line in $file"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
61
control/Cookie_Backend.php
Normal file
61
control/Cookie_Backend.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* The Cookie_Backend interface for use with `Cookie::$inst`.
|
||||
*
|
||||
* See Cookie_DefaultBackend and Cookie
|
||||
*/
|
||||
interface Cookie_Backend {
|
||||
|
||||
/**
|
||||
* When creating the backend we want to store the existing cookies in our
|
||||
* "existing" array. This allows us to distinguish between cookies we recieved
|
||||
* or we set ourselves (and didn't get from the browser)
|
||||
*
|
||||
* @param array $cookies The existing cookies to load into the cookie jar
|
||||
*/
|
||||
public function __construct($cookies = array());
|
||||
|
||||
/**
|
||||
* Set a cookie
|
||||
*
|
||||
* @param string $name The name of the cookie
|
||||
* @param string $value The value for the cookie to hold
|
||||
* @param int $expiry The number of days until expiry
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* Get the cookie value by name
|
||||
*
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* Get all the cookies
|
||||
*
|
||||
* @param boolean $includeUnsent Include cookies we've yet to send
|
||||
* @return array All the cookies
|
||||
*/
|
||||
public function getAll($includeUnsent = true);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
}
|
@ -207,17 +207,18 @@ class Director implements TemplateGlobalProvider {
|
||||
* GET otherwise. Overwritten by $postVars['_method'] if present.
|
||||
* @param string $body The HTTP body
|
||||
* @param array $headers HTTP headers with key-value pairs
|
||||
* @param array $cookies to populate $_COOKIE
|
||||
* @param array|Cookie_Backend $cookies to populate $_COOKIE
|
||||
* @param HTTP_Request $request The {@see HTTP_Request} object generated as a part of this request
|
||||
* @return SS_HTTPResponse
|
||||
*
|
||||
* @uses getControllerForURL() The rule-lookup logic is handled by this.
|
||||
* @uses Controller::run() Controller::run() handles the page logic for a Director::direct() call.
|
||||
*/
|
||||
public static function test($url, $postVars = null, $session = null, $httpMethod = null, $body = null,
|
||||
$headers = null, $cookies = null, &$request = null) {
|
||||
public static function test($url, $postVars = null, $session = array(), $httpMethod = null, $body = null,
|
||||
$headers = array(), $cookies = array(), &$request = null) {
|
||||
|
||||
Config::nest();
|
||||
Injector::nest();
|
||||
|
||||
// These are needed so that calling Director::test() doesnt muck with whoever is calling it.
|
||||
// Really, it's some inappropriate coupling and should be resolved by making less use of statics
|
||||
@ -227,6 +228,9 @@ class Director implements TemplateGlobalProvider {
|
||||
if(!$httpMethod) $httpMethod = ($postVars || is_array($postVars)) ? "POST" : "GET";
|
||||
|
||||
if(!$session) $session = Injector::inst()->create('Session', array());
|
||||
$cookieJar = $cookies instanceof Cookie_Backend
|
||||
? $cookies
|
||||
: Injector::inst()->createWithArgs('Cookie_Backend', array($cookies ?: array()));
|
||||
|
||||
// Back up the current values of the superglobals
|
||||
$existingRequestVars = isset($_REQUEST) ? $_REQUEST : array();
|
||||
@ -241,6 +245,29 @@ class Director implements TemplateGlobalProvider {
|
||||
Config::inst()->update('Cookie', 'report_errors', false);
|
||||
Requirements::set_backend(new Requirements_Backend());
|
||||
|
||||
// Set callback to invoke prior to return
|
||||
$onCleanup = function() use(
|
||||
$existingRequestVars, $existingGetVars, $existingPostVars, $existingSessionVars,
|
||||
$existingCookies, $existingServer, $existingRequirementsBackend, $oldStage
|
||||
) {
|
||||
// Restore the superglobals
|
||||
$_REQUEST = $existingRequestVars;
|
||||
$_GET = $existingGetVars;
|
||||
$_POST = $existingPostVars;
|
||||
$_SESSION = $existingSessionVars;
|
||||
$_COOKIE = $existingCookies;
|
||||
$_SERVER = $existingServer;
|
||||
|
||||
Requirements::set_backend($existingRequirementsBackend);
|
||||
|
||||
// These are needed so that calling Director::test() doesnt muck with whoever is calling it.
|
||||
// Really, it's some inappropriate coupling and should be resolved by making less use of statics
|
||||
Versioned::reading_stage($oldStage);
|
||||
|
||||
Injector::unnest(); // Restore old CookieJar, etc
|
||||
Config::unnest();
|
||||
};
|
||||
|
||||
if (strpos($url, '#') !== false) {
|
||||
$url = substr($url, 0, strpos($url, '#'));
|
||||
}
|
||||
@ -269,7 +296,8 @@ class Director implements TemplateGlobalProvider {
|
||||
$_GET = (array)$getVars;
|
||||
$_POST = (array)$postVars;
|
||||
$_SESSION = $session ? $session->inst_getAll() : array();
|
||||
$_COOKIE = (array) $cookies;
|
||||
$_COOKIE = $cookieJar->getAll(false);
|
||||
Injector::inst()->registerService($cookieJar, 'Cookie_Backend');
|
||||
$_SERVER['REQUEST_URI'] = Director::baseURL() . $urlWithQuerystring;
|
||||
|
||||
$request = new SS_HTTPRequest($httpMethod, $url, $getVars, $postVars, $body);
|
||||
@ -280,7 +308,7 @@ class Director implements TemplateGlobalProvider {
|
||||
$model = DataModel::inst();
|
||||
$output = Injector::inst()->get('RequestProcessor')->preRequest($request, $session, $model);
|
||||
if ($output === false) {
|
||||
// @TODO Need to NOT proceed with the request in an elegant manner
|
||||
$onCleanup();
|
||||
throw new SS_HTTPResponse_Exception(_t('Director.INVALID_REQUEST', 'Invalid request'), 400);
|
||||
}
|
||||
|
||||
@ -300,25 +328,12 @@ class Director implements TemplateGlobalProvider {
|
||||
|
||||
$output = Injector::inst()->get('RequestProcessor')->postRequest($request, $result, $model);
|
||||
if ($output === false) {
|
||||
$onCleanup();
|
||||
throw new SS_HTTPResponse_Exception("Invalid response");
|
||||
}
|
||||
|
||||
// Restore the superglobals
|
||||
$_REQUEST = $existingRequestVars;
|
||||
$_GET = $existingGetVars;
|
||||
$_POST = $existingPostVars;
|
||||
$_SESSION = $existingSessionVars;
|
||||
$_COOKIE = $existingCookies;
|
||||
$_SERVER = $existingServer;
|
||||
|
||||
Requirements::set_backend($existingRequirementsBackend);
|
||||
|
||||
// These are needed so that calling Director::test() doesnt muck with whoever is calling it.
|
||||
// Really, it's some inappropriate coupling and should be resolved by making less use of statics
|
||||
Versioned::reading_stage($oldStage);
|
||||
|
||||
Config::unnest();
|
||||
|
||||
// Return valid response
|
||||
$onCleanup();
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
@ -395,18 +395,11 @@ class Session {
|
||||
public function inst_destroy($removeCookie = true) {
|
||||
if(session_id()) {
|
||||
if($removeCookie) {
|
||||
$path = Config::inst()->get('Session', 'cookie_path');
|
||||
if(!$path) $path = Director::baseURL();
|
||||
$path = Config::inst()->get('Session', 'cookie_path') ?: Director::baseURL();
|
||||
$domain = Config::inst()->get('Session', 'cookie_domain');
|
||||
$secure = Config::inst()->get('Session', 'cookie_secure');
|
||||
|
||||
if($domain) {
|
||||
Cookie::set(session_name(), '', null, $path, $domain, $secure, true);
|
||||
} else {
|
||||
Cookie::set(session_name(), '', null, $path, null, $secure, true);
|
||||
}
|
||||
|
||||
unset($_COOKIE[session_name()]);
|
||||
Cookie::force_expiry(session_name(), $path, $domain, $secure, true);
|
||||
}
|
||||
|
||||
session_destroy();
|
||||
|
@ -746,6 +746,7 @@ class Injector {
|
||||
* @deprecated since 3.1.1
|
||||
*/
|
||||
public function registerNamedService($name, $service) {
|
||||
Deprecation::notice('3.1.1', 'registerNamedService is deprecated, use registerService instead');
|
||||
return $this->registerService($service, $name);
|
||||
}
|
||||
|
||||
@ -882,4 +883,4 @@ class Injector {
|
||||
public function createWithArgs($name, $constructorArgs) {
|
||||
return $this->get($name, false, $constructorArgs);
|
||||
}
|
||||
}
|
||||
}
|
@ -12,6 +12,11 @@ class TestSession {
|
||||
* @var Session
|
||||
*/
|
||||
private $session;
|
||||
|
||||
/**
|
||||
* @var Cookie_Backend
|
||||
*/
|
||||
private $cookies;
|
||||
|
||||
/**
|
||||
* @var SS_HTTPResponse
|
||||
@ -36,6 +41,7 @@ class TestSession {
|
||||
|
||||
public function __construct() {
|
||||
$this->session = Injector::inst()->create('Session', array());
|
||||
$this->cookies = Injector::inst()->create('Cookie_Backend');
|
||||
$this->controller = new Controller();
|
||||
$this->controller->setSession($this->session);
|
||||
$this->controller->pushCurrent();
|
||||
@ -62,8 +68,15 @@ class TestSession {
|
||||
public function get($url, $session = null, $headers = null, $cookies = null) {
|
||||
$headers = (array) $headers;
|
||||
if($this->lastUrl && !isset($headers['Referer'])) $headers['Referer'] = $this->lastUrl;
|
||||
$this->lastResponse
|
||||
= Director::test($url, null, $session ? $session : $this->session, null, null, $headers, $cookies);
|
||||
$this->lastResponse = Director::test(
|
||||
$url,
|
||||
null,
|
||||
$session ?: $this->session,
|
||||
null,
|
||||
null,
|
||||
$headers,
|
||||
$cookies ?: $this->cookies
|
||||
);
|
||||
$this->lastUrl = $url;
|
||||
if(!$this->lastResponse) user_error("Director::test($url) returned null", E_USER_WARNING);
|
||||
return $this->lastResponse;
|
||||
@ -84,8 +97,15 @@ class TestSession {
|
||||
public function post($url, $data, $headers = null, $session = null, $body = null, $cookies = null) {
|
||||
$headers = (array) $headers;
|
||||
if($this->lastUrl && !isset($headers['Referer'])) $headers['Referer'] = $this->lastUrl;
|
||||
$this->lastResponse
|
||||
= Director::test($url, $data, $session ? $session : $this->session, null, $body, $headers, $cookies);
|
||||
$this->lastResponse = Director::test(
|
||||
$url,
|
||||
$data,
|
||||
$session ?: $this->session,
|
||||
null,
|
||||
$body,
|
||||
$headers,
|
||||
$cookies ?: $this->cookies
|
||||
);
|
||||
$this->lastUrl = $url;
|
||||
if(!$this->lastResponse) user_error("Director::test($url) returned null", E_USER_WARNING);
|
||||
return $this->lastResponse;
|
||||
|
117
docs/en/reference/cookies.md
Normal file
117
docs/en/reference/cookies.md
Normal file
@ -0,0 +1,117 @@
|
||||
# Cookies
|
||||
|
||||
## Accessing and Manipulating Cookies
|
||||
|
||||
Cookies can be set/get/expired using the `Cookie` class and its static methods
|
||||
|
||||
setting:
|
||||
|
||||
:::php
|
||||
Cookie::set('CookieName', 'CookieValue');
|
||||
|
||||
getting:
|
||||
|
||||
:::php
|
||||
Cookie::get('CookieName'); //returns null if not set or the value if set
|
||||
|
||||
expiring / removing / clearing:
|
||||
|
||||
:::php
|
||||
Cookie::force_expiry('CookieName');
|
||||
|
||||
## The `Cookie_Backend`
|
||||
|
||||
The `Cookie` class manipulates and sets cookies using a `Cookie_Backend`. The backend is in charge of the logic
|
||||
that fetches, sets and expires cookies. By default we use a the `CookieJar` backend which uses PHP's
|
||||
[setcookie](http://www.php.net/manual/en/function.setcookie.php) function.
|
||||
|
||||
The `CookieJar` keeps track of cookies that have been set by the current process as well as those that were recieved
|
||||
from the browser.
|
||||
|
||||
By default the `Cookie` class will load the `$_COOKIE` superglobal into the `Cookie_Backend`. If you want to change
|
||||
the initial state of the `Cookie_Backend` you can load your own backend into the `CookieJar` service registered with
|
||||
the `Injector`.
|
||||
|
||||
eg:
|
||||
|
||||
:::php
|
||||
$myCookies = array(
|
||||
'cookie1' => 'value1',
|
||||
);
|
||||
|
||||
$newBackend = new CookieJar($myCookies);
|
||||
|
||||
Injector::inst()->registerService($newBackend, 'Cookie_Backend');
|
||||
|
||||
Cookie::get('cookie1'); //will return 'value1'
|
||||
|
||||
### Resetting the Cookie_Backend state
|
||||
|
||||
Assuming that your application hasn't messed around with the `$_COOKIE` superglobal, you can reset the state of your
|
||||
`Cookie_Backend` by simply unregistering the `CookieJar` service with `Injector`. Next time you access `Cookie` it'll
|
||||
create a new service for you using the `$_COOKIE` superglobal.
|
||||
|
||||
eg:
|
||||
|
||||
:::php
|
||||
Injector::inst()->unregisterNamedObject('Cookie_Backend');
|
||||
|
||||
Cookie::get('cookiename'); // will return $_COOKIE['cookiename'] if set
|
||||
|
||||
|
||||
Alternatively, if you know that the superglobal has been changed (or you aren't sure it hasn't) you can attempt to use
|
||||
the current `CookieJar` service to tell you what it was like when it was registered.
|
||||
|
||||
eg:
|
||||
|
||||
:::php
|
||||
//store the cookies that were loaded into the `CookieJar`
|
||||
$recievedCookie = Cookie::get_inst()->getAll(false);
|
||||
|
||||
//set a new `CookieJar`
|
||||
Injector::inst()->registerService(new CookieJar($recievedCookie), 'CookieJar');
|
||||
|
||||
|
||||
### Using your own Cookie_Backend
|
||||
|
||||
If you need to implement your own Cookie_Backend you can use the injector system to force a different class to be used.
|
||||
|
||||
example:
|
||||
|
||||
:::yml
|
||||
---
|
||||
Name: mycookie
|
||||
After: '#cookie'
|
||||
---
|
||||
Injector:
|
||||
Cookie_Backend:
|
||||
class: MyCookieJar
|
||||
|
||||
To be a valid backend your class must implement the `Cookie_Backend` interface.
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Sent vs Received Cookies
|
||||
|
||||
Sometimes it's useful to be able to tell if a cookie was set by the process (thus will be sent to the browser) or if it
|
||||
came from the browser as part of the request.
|
||||
|
||||
Using the `Cookie_Backend` we can do this like such:
|
||||
|
||||
:::php
|
||||
Cookie::set('CookieName', 'CookieVal');
|
||||
|
||||
Cookie::get('CookieName'); //gets the cookie as we set it
|
||||
|
||||
//will return the cookie as it was when it was sent in the request
|
||||
Cookie::get('CookieName', false);
|
||||
|
||||
|
||||
### Accessing all the cookies at once
|
||||
|
||||
One can also access all of the cookies in one go using the `Cookie_Backend`
|
||||
|
||||
:::php
|
||||
Cookie::get_inst()->getAll(); //returns all the cookies including ones set during the current process
|
||||
|
||||
Cookie::get_inst()->getAll(false); //returns all the cookies in the request
|
@ -162,8 +162,8 @@ class DB {
|
||||
Cookie::set("alternativeDatabaseName", base64_encode($encrypted), 0, null, null, false, true);
|
||||
Cookie::set("alternativeDatabaseNameIv", base64_encode($iv), 0, null, null, false, true);
|
||||
} else {
|
||||
Cookie::set("alternativeDatabaseName", null, 0, null, null, false, true);
|
||||
Cookie::set("alternativeDatabaseNameIv", null, 0, null, null, false, true);
|
||||
Cookie::force_expiry("alternativeDatabaseName", null, null, false, true);
|
||||
Cookie::force_expiry("alternativeDatabaseNameIv", null, null, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1032,15 +1032,13 @@ class Versioned extends DataExtension implements TemplateGlobalProvider {
|
||||
if(!headers_sent() && !Director::is_cli()) {
|
||||
if(Versioned::current_stage() == 'Live') {
|
||||
// clear the cookie if it's set
|
||||
if(!empty($_COOKIE['bypassStaticCache'])) {
|
||||
Cookie::set('bypassStaticCache', null, 0, null, null, false, true /* httponly */);
|
||||
unset($_COOKIE['bypassStaticCache']);
|
||||
if(Cookie::get('bypassStaticCache')) {
|
||||
Cookie::force_expiry('bypassStaticCache', null, null, false, true /* httponly */);
|
||||
}
|
||||
} else {
|
||||
// set the cookie if it's cleared
|
||||
if(empty($_COOKIE['bypassStaticCache'])) {
|
||||
if(!Cookie::get('bypassStaticCache')) {
|
||||
Cookie::set('bypassStaticCache', '1', 0, null, null, false, true /* httponly */);
|
||||
$_COOKIE['bypassStaticCache'] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -394,7 +394,6 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
Cookie::set('alc_enc', $this->ID . ':' . $token, 90, null, null, null, true);
|
||||
} else {
|
||||
$this->RememberLoginToken = null;
|
||||
Cookie::set('alc_enc', null);
|
||||
Cookie::force_expiry('alc_enc');
|
||||
}
|
||||
|
||||
@ -489,7 +488,6 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
$this->extend('memberLoggedOut');
|
||||
|
||||
$this->RememberLoginToken = null;
|
||||
Cookie::set('alc_enc', null); // // Clear the Remember Me cookie
|
||||
Cookie::force_expiry('alc_enc');
|
||||
|
||||
// Switch back to live in order to avoid infinite loops when
|
||||
|
135
tests/control/CookieJarTest.php
Normal file
135
tests/control/CookieJarTest.php
Normal file
@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Testing CookieJar
|
||||
*
|
||||
* Testing the CookieJar acts as expected and keeps track of Cookies it is loaded
|
||||
* with as well as new cookies that are set during the running of an application
|
||||
*
|
||||
*/
|
||||
class CookieJarTest extends SapphireTest {
|
||||
|
||||
/**
|
||||
* Test that the construction argument is stored and returned as expected
|
||||
*/
|
||||
public function testConstruct() {
|
||||
|
||||
//some default cookies to load
|
||||
$defaultCookies = array(
|
||||
'cookie1' => 1,
|
||||
'cookie2' => 'cookies',
|
||||
'cookie3' => 'test',
|
||||
);
|
||||
|
||||
$cookieJar = new CookieJar($defaultCookies);
|
||||
|
||||
//make sure all the "recieved" cookies are as expected
|
||||
$this->assertEquals($defaultCookies, $cookieJar->getAll(false));
|
||||
|
||||
//make sure there are no "phantom" cookies
|
||||
$this->assertEquals($defaultCookies, $cookieJar->getAll(true));
|
||||
|
||||
//check an empty array is accepted
|
||||
$cookieJar = new CookieJar(array());
|
||||
$this->assertEmpty($cookieJar->getAll(false));
|
||||
|
||||
|
||||
//check no argument is accepted
|
||||
$cookieJar = new CookieJar();
|
||||
$this->assertEmpty($cookieJar->getAll(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that we can set and get cookies
|
||||
*/
|
||||
public function testSetAndGet() {
|
||||
$cookieJar = new CookieJar();
|
||||
|
||||
$this->assertEmpty($cookieJar->get('testCookie'));
|
||||
|
||||
//set a test cookie
|
||||
$cookieJar->set('testCookie', 'testVal');
|
||||
|
||||
//make sure it was set
|
||||
$this->assertEquals('testVal', $cookieJar->get('testCookie'));
|
||||
|
||||
//make sure we can distinguise it from ones that were "existing"
|
||||
$this->assertEmpty($cookieJar->get('testCookie', false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that we can distinguish between vars that were loaded on instantiation
|
||||
* and those added later
|
||||
*/
|
||||
public function testExistingVersusNew() {
|
||||
//load with a cookie
|
||||
$cookieJar = new CookieJar(array(
|
||||
'cookieExisting' => 'i woz here',
|
||||
));
|
||||
|
||||
//set a new cookie
|
||||
$cookieJar->set('cookieNew', 'i am new');
|
||||
|
||||
//check we can fetch new and old cookie values
|
||||
$this->assertEquals('i woz here', $cookieJar->get('cookieExisting'));
|
||||
$this->assertEquals('i woz here', $cookieJar->get('cookieExisting', false));
|
||||
$this->assertEquals('i am new', $cookieJar->get('cookieNew'));
|
||||
//there should be no original value for the new cookie
|
||||
$this->assertEmpty($cookieJar->get('cookieNew', false));
|
||||
|
||||
//change the existing cookie, can we fetch the new and old value
|
||||
$cookieJar->set('cookieExisting', 'i woz changed');
|
||||
|
||||
$this->assertEquals('i woz changed', $cookieJar->get('cookieExisting'));
|
||||
$this->assertEquals('i woz here', $cookieJar->get('cookieExisting', false));
|
||||
|
||||
//check we can get all cookies
|
||||
$this->assertEquals(array(
|
||||
'cookieExisting' => 'i woz changed',
|
||||
'cookieNew' => 'i am new',
|
||||
), $cookieJar->getAll());
|
||||
|
||||
//check we can get all original cookies
|
||||
$this->assertEquals(array(
|
||||
'cookieExisting' => 'i woz here',
|
||||
), $cookieJar->getAll(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check we can remove cookies and we can access their original values
|
||||
*/
|
||||
public function testForceExpiry() {
|
||||
//load an existing cookie
|
||||
$cookieJar = new CookieJar(array(
|
||||
'cookieExisting' => 'i woz here',
|
||||
));
|
||||
|
||||
//make sure it's available
|
||||
$this->assertEquals('i woz here', $cookieJar->get('cookieExisting'));
|
||||
|
||||
//remove the cookie
|
||||
$cookieJar->forceExpiry('cookieExisting');
|
||||
|
||||
//check it's gone
|
||||
$this->assertEmpty($cookieJar->get('cookieExisting'));
|
||||
|
||||
//check we can get it's original value
|
||||
$this->assertEquals('i woz here', $cookieJar->get('cookieExisting', false));
|
||||
|
||||
|
||||
//check we can add a new cookie and remove it and it doesn't leave any phantom values
|
||||
$cookieJar->set('newCookie', 'i am new');
|
||||
|
||||
//check it's set by not recieved
|
||||
$this->assertEquals('i am new', $cookieJar->get('newCookie'));
|
||||
$this->assertEmpty($cookieJar->get('newCookie', false));
|
||||
|
||||
//remove it
|
||||
$cookieJar->forceExpiry('newCookie');
|
||||
|
||||
//check it's neither set nor reveived
|
||||
$this->assertEmpty($cookieJar->get('newCookie'));
|
||||
$this->assertEmpty($cookieJar->get('newCookie', false));
|
||||
}
|
||||
|
||||
}
|
188
tests/control/CookieTest.php
Normal file
188
tests/control/CookieTest.php
Normal file
@ -0,0 +1,188 @@
|
||||
<?php
|
||||
|
||||
class CookieTest extends SapphireTest {
|
||||
|
||||
private $cookieInst;
|
||||
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
Injector::nest();
|
||||
Injector::inst()->registerService(new CookieJar($_COOKIE), 'Cookie_Backend');
|
||||
}
|
||||
|
||||
public function tearDown() {
|
||||
//restore the cookie_backend
|
||||
Injector::unnest();
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check a new cookie inst will be loaded with the superglobal by default
|
||||
*/
|
||||
public function testCheckNewInstTakesSuperglobal() {
|
||||
//store the superglobal state
|
||||
$existingCookies = $_COOKIE;
|
||||
|
||||
//set a mock state for the superglobal
|
||||
$_COOKIE = array(
|
||||
'cookie1' => 1,
|
||||
'cookie2' => 'cookies',
|
||||
'cookie3' => 'test',
|
||||
);
|
||||
|
||||
Injector::inst()->unregisterNamedObject('Cookie_Backend');
|
||||
|
||||
$this->assertEquals($_COOKIE['cookie1'], Cookie::get('cookie1'));
|
||||
$this->assertEquals($_COOKIE['cookie2'], Cookie::get('cookie2'));
|
||||
$this->assertEquals($_COOKIE['cookie3'], Cookie::get('cookie3'));
|
||||
|
||||
//for good measure check the CookieJar hasn't stored anything extra
|
||||
$this->assertEquals($_COOKIE, Cookie::get_inst()->getAll(false));
|
||||
|
||||
//restore the superglobal state
|
||||
$_COOKIE = $existingCookies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check we don't mess with super globals when manipulating cookies
|
||||
*
|
||||
* State should be managed sperately to the super global
|
||||
*/
|
||||
public function testCheckSuperglobalsArentTouched() {
|
||||
|
||||
//store the current state
|
||||
$before = $_COOKIE;
|
||||
|
||||
//change some cookies
|
||||
Cookie::set('cookie', 'not me');
|
||||
Cookie::force_expiry('cookie2');
|
||||
|
||||
//assert it hasn't changed
|
||||
$this->assertEquals($before, $_COOKIE);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check we can actually change a backend
|
||||
*/
|
||||
public function testChangeBackend() {
|
||||
|
||||
Cookie::set('test', 'testvalue');
|
||||
|
||||
$this->assertEquals('testvalue', Cookie::get('test'));
|
||||
|
||||
Injector::inst()->registerService(new CookieJar(array()), 'Cookie_Backend');
|
||||
|
||||
$this->assertEmpty(Cookie::get('test'));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check we can actually get the backend inst out
|
||||
*/
|
||||
public function testGetInst() {
|
||||
|
||||
$inst = new CookieJar(array('test' => 'testvalue'));
|
||||
|
||||
Injector::inst()->registerService($inst, 'Cookie_Backend');
|
||||
|
||||
$this->assertEquals($inst, Cookie::get_inst());
|
||||
|
||||
$this->assertEquals('testvalue', Cookie::get('test'));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that we can set and get cookies
|
||||
*/
|
||||
public function testSetAndGet() {
|
||||
$this->assertEmpty(Cookie::get('testCookie'));
|
||||
|
||||
//set a test cookie
|
||||
Cookie::set('testCookie', 'testVal');
|
||||
|
||||
//make sure it was set
|
||||
$this->assertEquals('testVal', Cookie::get('testCookie'));
|
||||
|
||||
//make sure we can distinguise it from ones that were "existing"
|
||||
$this->assertEmpty(Cookie::get('testCookie', false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that we can distinguish between vars that were loaded on instantiation
|
||||
* and those added later
|
||||
*/
|
||||
public function testExistingVersusNew() {
|
||||
//load with a cookie
|
||||
$cookieJar = new CookieJar(array(
|
||||
'cookieExisting' => 'i woz here',
|
||||
));
|
||||
Injector::inst()->registerService($cookieJar, 'Cookie_Backend');
|
||||
|
||||
//set a new cookie
|
||||
Cookie::set('cookieNew', 'i am new');
|
||||
|
||||
//check we can fetch new and old cookie values
|
||||
$this->assertEquals('i woz here', Cookie::get('cookieExisting'));
|
||||
$this->assertEquals('i woz here', Cookie::get('cookieExisting', false));
|
||||
$this->assertEquals('i am new', Cookie::get('cookieNew'));
|
||||
//there should be no original value for the new cookie
|
||||
$this->assertEmpty(Cookie::get('cookieNew', false));
|
||||
|
||||
//change the existing cookie, can we fetch the new and old value
|
||||
Cookie::set('cookieExisting', 'i woz changed');
|
||||
|
||||
$this->assertEquals('i woz changed', Cookie::get('cookieExisting'));
|
||||
$this->assertEquals('i woz here', Cookie::get('cookieExisting', false));
|
||||
|
||||
//check we can get all cookies
|
||||
$this->assertEquals(array(
|
||||
'cookieExisting' => 'i woz changed',
|
||||
'cookieNew' => 'i am new',
|
||||
), Cookie::get_all());
|
||||
|
||||
//check we can get all original cookies
|
||||
$this->assertEquals(array(
|
||||
'cookieExisting' => 'i woz here',
|
||||
), Cookie::get_all(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check we can remove cookies and we can access their original values
|
||||
*/
|
||||
public function testForceExpiry() {
|
||||
//load an existing cookie
|
||||
$cookieJar = new CookieJar(array(
|
||||
'cookieExisting' => 'i woz here',
|
||||
));
|
||||
Injector::inst()->registerService($cookieJar, 'Cookie_Backend');
|
||||
|
||||
//make sure it's available
|
||||
$this->assertEquals('i woz here', Cookie::get('cookieExisting'));
|
||||
|
||||
//remove the cookie
|
||||
Cookie::force_expiry('cookieExisting');
|
||||
|
||||
//check it's gone
|
||||
$this->assertEmpty(Cookie::get('cookieExisting'));
|
||||
|
||||
//check we can get it's original value
|
||||
$this->assertEquals('i woz here', Cookie::get('cookieExisting', false));
|
||||
|
||||
|
||||
//check we can add a new cookie and remove it and it doesn't leave any phantom values
|
||||
Cookie::set('newCookie', 'i am new');
|
||||
|
||||
//check it's set by not recieved
|
||||
$this->assertEquals('i am new', Cookie::get('newCookie'));
|
||||
$this->assertEmpty(Cookie::get('newCookie', false));
|
||||
|
||||
//remove it
|
||||
Cookie::force_expiry('newCookie');
|
||||
|
||||
//check it's neither set nor reveived
|
||||
$this->assertEmpty(Cookie::get('newCookie'));
|
||||
$this->assertEmpty(Cookie::get('newCookie', false));
|
||||
}
|
||||
|
||||
}
|
@ -240,6 +240,7 @@ class DirectorTest extends SapphireTest {
|
||||
unset($_SESSION['isLive']);
|
||||
unset($_GET['isTest']);
|
||||
unset($_GET['isDev']);
|
||||
$_SESSION = $_SESSION ?: array();
|
||||
|
||||
// Test isDev=1
|
||||
$_GET['isDev'] = '1';
|
||||
@ -271,8 +272,13 @@ class DirectorTest extends SapphireTest {
|
||||
$_POST = array('somekey' => 'postvalue');
|
||||
$_COOKIE = array('somekey' => 'cookievalue');
|
||||
|
||||
$cookies = Injector::inst()->createWithArgs(
|
||||
'Cookie_Backend',
|
||||
array(array('somekey' => 'sometestcookievalue'))
|
||||
);
|
||||
|
||||
$getresponse = Director::test('errorpage?somekey=sometestgetvalue', array('somekey' => 'sometestpostvalue'),
|
||||
null, null, null, null, array('somekey' => 'sometestcookievalue'));
|
||||
null, null, null, null, $cookies);
|
||||
|
||||
$this->assertEquals('getvalue', $_GET['somekey'],
|
||||
'$_GET reset to original value after Director::test()');
|
||||
@ -288,7 +294,16 @@ class DirectorTest extends SapphireTest {
|
||||
foreach(array('return%sValue', 'returnRequestValue', 'returnCookieValue') as $testfunction) {
|
||||
$url = 'DirectorTestRequest_Controller/' . sprintf($testfunction, ucfirst($method))
|
||||
. '?' . http_build_query($fixture);
|
||||
$getresponse = Director::test($url, $fixture, null, strtoupper($method), null, null, $fixture);
|
||||
|
||||
$getresponse = Director::test(
|
||||
$url,
|
||||
$fixture,
|
||||
null,
|
||||
strtoupper($method),
|
||||
null,
|
||||
null,
|
||||
Injector::inst()->createWithArgs('Cookie_Backend', array($fixture))
|
||||
);
|
||||
|
||||
$this->assertInstanceOf('SS_HTTPResponse', $getresponse, 'Director::test() returns SS_HTTPResponse');
|
||||
$this->assertEquals($fixture['somekey'], $getresponse->getBody(), 'Director::test() ' . $testfunction);
|
||||
|
@ -558,7 +558,7 @@ class InjectorTest extends SapphireTest {
|
||||
$injector = new Injector();
|
||||
$service = new stdClass();
|
||||
|
||||
$injector->registerNamedService('NamedService', $service);
|
||||
$injector->registerService($service, 'NamedService');
|
||||
$this->assertEquals($service, $injector->get('NamedService'));
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user