Merge pull request #3511 from tractorcow/pr/3098

Pr/3098
This commit is contained in:
Sean Harvey 2014-10-10 10:16:54 +13:00
commit 7a8b4a7f63
16 changed files with 774 additions and 144 deletions

5
_config/cookie.yml Normal file
View File

@ -0,0 +1,5 @@
---
Name: cookie
---
Injector:
Cookie_Backend: CookieJar

View File

@ -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
View 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"
);
}
}
}

View 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);
}

View File

@ -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;
}

View File

@ -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();

View File

@ -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);
}
}
}

View File

@ -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;

View 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

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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

View 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));
}
}

View 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));
}
}

View File

@ -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);

View File

@ -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'));
}