mirror of
https://github.com/processwire/processwire.git
synced 2025-08-09 08:17:12 +02:00
Update $input->cookie API variable so that it can now also set cookies (in addition to just getting them). Default cookie settings are controlled from new $config->cookieOptions array.
This commit is contained in:
@@ -901,6 +901,30 @@ $config->wireInputOrder = 'get post';
|
|||||||
*/
|
*/
|
||||||
$config->wireInputLazy = false;
|
$config->wireInputLazy = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for setting cookies from $input->cookie()->set(...)
|
||||||
|
*
|
||||||
|
* Additional details about some of these options can also be found on PHP’s [setcookie](https://www.php.net/manual/en/function.setcookie.php) doc page.
|
||||||
|
*
|
||||||
|
* #property int age Max age of cookies in seconds or 0 to expire with session (3600=1hr, 86400=1day, 604800=1week, 2592000=30days, etc.)
|
||||||
|
* #property string|null Cookie path or null for PW installation’s root URL (default=null).
|
||||||
|
* #property string|null|bool domain Cookie domain: null for current hostname, true for all subdomains of current domain, domain.com for domain and all subdomains, www.domain.com for www subdomain.
|
||||||
|
* #property bool|null secure Transmit cookies only over secure HTTPS connection? (true, false, or null to auto-detect, using true option for cookies set when HTTPS is active).
|
||||||
|
* #property bool httponly When true, cookie is http/server-side and not visible to JS code in most browsers.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
* @since 3.0.141
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
$config->cookieOptions = array(
|
||||||
|
'age' => 604800, // Max age of cookies in seconds or 0 to expire with session (3600=1hr, 86400=1day, 604800=1week, 2592000=30days, etc.)
|
||||||
|
'path' => null, // Cookie path/URL or null for PW installation’s root URL (default=null).
|
||||||
|
'domain' => null, // Cookie domain: null for current hostname, true for all subdomains of current domain, domain.com for domain and all subdomains, www.domain.com for www subdomain.
|
||||||
|
'secure' => null, // Transmit cookies only over secure HTTPS connection? (true, false, or null to auto-detect, substituting true for cookies set when HTTPS is active).
|
||||||
|
'httponly' => false, // When true, cookie is http/server-side only and not visible to client-side JS code.
|
||||||
|
'fallback' => true, // If set cookie fails (perhaps due to output already sent), attempt to set at beginning of next request? (default=true)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
/*** 7. DATABASE ********************************************************************************/
|
/*** 7. DATABASE ********************************************************************************/
|
||||||
|
|
||||||
|
@@ -63,7 +63,7 @@
|
|||||||
* @property bool|callable $sessionAllow Are sessions allowed? Typically boolean true, unless provided a callable function that returns boolean. See /wire/config.php for an example. #pw-group-session
|
* @property bool|callable $sessionAllow Are sessions allowed? Typically boolean true, unless provided a callable function that returns boolean. See /wire/config.php for an example. #pw-group-session
|
||||||
* @property int $sessionExpireSeconds How many seconds of inactivity before session expires? #pw-group-session
|
* @property int $sessionExpireSeconds How many seconds of inactivity before session expires? #pw-group-session
|
||||||
* @property bool $sessionChallenge Should login sessions have a challenge key? (for extra security, recommended) #pw-group-session
|
* @property bool $sessionChallenge Should login sessions have a challenge key? (for extra security, recommended) #pw-group-session
|
||||||
* @property bool $sessionFingerprint Should login sessions be tied to IP and user agent? 0 or false: Fingerprint off. 1 or true: Fingerprint on with default/recommended setting (currently 10). 2: Fingerprint only the remote IP. 4: Fingerprint only the forwarded/client IP (can be spoofed). 8: Fingerprint only the useragent. 10: Fingerprint the remote IP and useragent (default). 12: Fingerprint the forwarded/client IP and useragent. 14: Fingerprint the remote IP, forwarded/client IP and useragent (all). #pw-group-session
|
* @property int|bool $sessionFingerprint Should login sessions be tied to IP and user agent? 0 or false: Fingerprint off. 1 or true: Fingerprint on with default/recommended setting (currently 10). 2: Fingerprint only the remote IP. 4: Fingerprint only the forwarded/client IP (can be spoofed). 8: Fingerprint only the useragent. 10: Fingerprint the remote IP and useragent (default). 12: Fingerprint the forwarded/client IP and useragent. 14: Fingerprint the remote IP, forwarded/client IP and useragent (all). #pw-group-session
|
||||||
* @property int $sessionHistory Number of session entries to keep (default=0, which means off). #pw-group-session
|
* @property int $sessionHistory Number of session entries to keep (default=0, which means off). #pw-group-session
|
||||||
* @property string $sessionForceIP Force the client IP address returned by $session->getIP() to be this rather than auto-detect (useful with load balancer). Use for setting value only. #pw-group-session
|
* @property string $sessionForceIP Force the client IP address returned by $session->getIP() to be this rather than auto-detect (useful with load balancer). Use for setting value only. #pw-group-session
|
||||||
* @property array $loginDisabledRoles Array of role name(s) or ID(s) of roles where login is disallowed. #pw-group-session
|
* @property array $loginDisabledRoles Array of role name(s) or ID(s) of roles where login is disallowed. #pw-group-session
|
||||||
@@ -88,6 +88,7 @@
|
|||||||
* @property int $maxUrlDepth Maximum URL/path slashes (depth) for request URLs. (Min=10, Max=60) #pw-group-URLs
|
* @property int $maxUrlDepth Maximum URL/path slashes (depth) for request URLs. (Min=10, Max=60) #pw-group-URLs
|
||||||
* @property string $wireInputOrder Order that variables with the $input API var are handled when you access $input->var. #pw-group-HTTP-and-input
|
* @property string $wireInputOrder Order that variables with the $input API var are handled when you access $input->var. #pw-group-HTTP-and-input
|
||||||
* @property bool $wireInputLazy Specify true for $input API var to load input data in a lazy fashion and potentially use less memory. Default is false. #pw-group-HTTP-and-input
|
* @property bool $wireInputLazy Specify true for $input API var to load input data in a lazy fashion and potentially use less memory. Default is false. #pw-group-HTTP-and-input
|
||||||
|
* @property array $cookieOptions Options for setting cookies from $input->cookie #pw-group-HTTP-and-input
|
||||||
*
|
*
|
||||||
* @property bool $advanced Special mode for ProcessWire system development. Not recommended for regular site development or production use. #pw-group-system
|
* @property bool $advanced Special mode for ProcessWire system development. Not recommended for regular site development or production use. #pw-group-system
|
||||||
* @property bool $demo Special mode for demonstration use that causes POST requests to be disabled. Applies to core, but may not be safe with 3rd party modules. #pw-group-system
|
* @property bool $demo Special mode for demonstration use that causes POST requests to be disabled. Applies to core, but may not be safe with 3rd party modules. #pw-group-system
|
||||||
|
@@ -65,6 +65,13 @@ class Session extends Wire implements \IteratorAggregate {
|
|||||||
*/
|
*/
|
||||||
const fingerprintUseragent = 8;
|
const fingerprintUseragent = 8;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suffix applied to challenge cookies
|
||||||
|
*
|
||||||
|
* @since 3.0.141
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const challengeSuffix = '_challenge';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reference to ProcessWire $config object
|
* Reference to ProcessWire $config object
|
||||||
@@ -198,7 +205,7 @@ class Session extends Wire implements \IteratorAggregate {
|
|||||||
} else {
|
} else {
|
||||||
$name = $this->config->sessionName;
|
$name = $this->config->sessionName;
|
||||||
}
|
}
|
||||||
if($checkLogin) $name .= "_challenge";
|
if($checkLogin) $name .= self::challengeSuffix;
|
||||||
return !empty($_COOKIE[$name]);
|
return !empty($_COOKIE[$name]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,7 +291,8 @@ class Session extends Wire implements \IteratorAggregate {
|
|||||||
|
|
||||||
// check challenge cookie
|
// check challenge cookie
|
||||||
if($this->config->sessionChallenge) {
|
if($this->config->sessionChallenge) {
|
||||||
if(empty($_COOKIE[$sessionName . "_challenge"]) || ($this->get('_user', 'challenge') != $_COOKIE[$sessionName . "_challenge"])) {
|
$cookieName = $sessionName . self::challengeSuffix;
|
||||||
|
if(empty($_COOKIE[$cookieName]) || ($this->get('_user', 'challenge') != $_COOKIE[$cookieName])) {
|
||||||
$valid = false;
|
$valid = false;
|
||||||
$reason = "Error: Invalid challenge value";
|
$reason = "Error: Invalid challenge value";
|
||||||
}
|
}
|
||||||
@@ -488,6 +496,18 @@ class Session extends Wire implements \IteratorAggregate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all session variables for given namespace and return associative array
|
||||||
|
*
|
||||||
|
* @param string|Wire $ns
|
||||||
|
* @return array
|
||||||
|
* @since 3.0.141 Method added for consistency, but any version can do this with $session->getFor($ns, '');
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function getAllFor($ns) {
|
||||||
|
return $this->getFor($ns, '');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a session variable
|
* Set a session variable
|
||||||
*
|
*
|
||||||
@@ -857,12 +877,12 @@ class Session extends Wire implements \IteratorAggregate {
|
|||||||
|
|
||||||
if($this->config->sessionChallenge) {
|
if($this->config->sessionChallenge) {
|
||||||
// create new challenge
|
// create new challenge
|
||||||
$pass = $this->wire(new Password());
|
$rand = new WireRandom();
|
||||||
$challenge = $pass->randomBase64String(32);
|
$challenge = $rand->base64(32);
|
||||||
$this->set('_user', 'challenge', $challenge);
|
$this->set('_user', 'challenge', $challenge);
|
||||||
$secure = $this->config->sessionCookieSecure ? (bool) $this->config->https : false;
|
$secure = $this->config->sessionCookieSecure ? (bool) $this->config->https : false;
|
||||||
// set challenge cookie to last 30 days (should be longer than any session would feasibly last)
|
// set challenge cookie to last 30 days (should be longer than any session would feasibly last)
|
||||||
setcookie(session_name() . '_challenge', $challenge, time()+60*60*24*30, '/',
|
setcookie(session_name() . self::challengeSuffix, $challenge, time()+60*60*24*30, '/',
|
||||||
$this->config->sessionCookieDomain, $secure, true);
|
$this->config->sessionCookieDomain, $secure, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1055,11 +1075,32 @@ class Session extends Wire implements \IteratorAggregate {
|
|||||||
if(isset($_COOKIE[$sessionName])) {
|
if(isset($_COOKIE[$sessionName])) {
|
||||||
setcookie($sessionName, '', $time, '/', $this->config->sessionCookieDomain, $secure, true);
|
setcookie($sessionName, '', $time, '/', $this->config->sessionCookieDomain, $secure, true);
|
||||||
}
|
}
|
||||||
if(isset($_COOKIE[$sessionName . "_challenge"])) {
|
if(isset($_COOKIE[$sessionName . self::challengeSuffix])) {
|
||||||
setcookie($sessionName . "_challenge", '', $time, '/', $this->config->sessionCookieDomain, $secure, true);
|
setcookie($sessionName . self::challengeSuffix, '', $time, '/', $this->config->sessionCookieDomain, $secure, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the names of all cookies managed by Session
|
||||||
|
*
|
||||||
|
* #pw-internal
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* @since 3.0.141
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function getCookieNames() {
|
||||||
|
$name = $this->config->sessionName;
|
||||||
|
$nameSecure = $this->config->sessionNameSecure;
|
||||||
|
if(empty($nameSecure)) $nameSecure = $this->config->sessionName . 's';
|
||||||
|
$a = array($name, $nameSecure);
|
||||||
|
if($this->config->sessionChallenge) {
|
||||||
|
$a[] = $name . self::challengeSuffix;
|
||||||
|
$a[] = $nameSecure . self::challengeSuffix;
|
||||||
|
}
|
||||||
|
return $a;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logout success method for hooks
|
* Logout success method for hooks
|
||||||
*
|
*
|
||||||
|
@@ -301,9 +301,12 @@ class WireInput extends Wire {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public function cookie($key = '', $valid = null, $fallback = null) {
|
public function cookie($key = '', $valid = null, $fallback = null) {
|
||||||
if(is_null($this->cookieVars)) $this->cookieVars = $this->wire(new WireInputData($_COOKIE, $this->lazy));
|
if($this->cookieVars === null) {
|
||||||
|
$this->cookieVars = $this->wire(new WireInputDataCookie($_COOKIE, $this->lazy));
|
||||||
|
$this->cookieVars->init();
|
||||||
|
}
|
||||||
if(!strlen($key)) return $this->cookieVars;
|
if(!strlen($key)) return $this->cookieVars;
|
||||||
if($valid === null && $fallback === null && !strpos($key, '[]')) return $this->cookieVars->__get($key);
|
if($valid === null && $fallback === null && !strpos($key, '[]')) return $this->cookieVars->get($key);
|
||||||
return $this->getValidInputValue($this->cookieVars, $key, $valid, $fallback);
|
return $this->getValidInputValue($this->cookieVars, $key, $valid, $fallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -159,6 +159,36 @@ class WireInputData extends Wire implements \ArrayAccess, \IteratorAggregate, \C
|
|||||||
if($this->lazy) $this->unlazyKeys[$key] = $key;
|
if($this->lazy) $this->unlazyKeys[$key] = $key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a value
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
* @param string|int|float|array|null $value
|
||||||
|
* @return $this
|
||||||
|
* @param array|int|string $options Options not currently used, but available for descending classes or future use
|
||||||
|
* @since 3.0.141 You can also use __set() or set directly for compatibility with all versions
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function set($key, $value, $options = array()) {
|
||||||
|
if($options) {} // not currently used by this class
|
||||||
|
$this->__set($key, $value);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a value
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
* @param array|int|string $options Options not currently used, but available for descending classes or future use
|
||||||
|
* @return string|int|float|array|null $value
|
||||||
|
* @since 3.0.141 You can also get directly or use __get(), both of which are compatible with all versions
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function get($key, $options = array()) {
|
||||||
|
if($options) {} // not currently used by this class
|
||||||
|
return $this->__get($key);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clean an array of data
|
* Clean an array of data
|
||||||
*
|
*
|
||||||
|
337
wire/core/WireInputDataCookie.php
Normal file
337
wire/core/WireInputDataCookie.php
Normal file
@@ -0,0 +1,337 @@
|
|||||||
|
<?php namespace ProcessWire;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WireInputDataCookie class represents the $input->cookie API variable
|
||||||
|
*
|
||||||
|
* #pw-summary Enables setting and getting cookies from the ProcessWire API using $input->cookie.
|
||||||
|
*
|
||||||
|
* #pw-body =
|
||||||
|
*
|
||||||
|
* - Whether getting or setting, cookie values are always strings.
|
||||||
|
* - Values retrieved from `$input->cookie` are user input (like PHP’s $_COOKIE) and should be sanitized and validated.
|
||||||
|
* - When removing/unsetting cookies, the path, domain, secure, and httponly options must be the same as when the cookie was set,
|
||||||
|
* as a result, it’s good to have these things predefined in `$config->cookieOptions` rather than setting during runtime.
|
||||||
|
* - Note that this class does not manage PW’s session cookies.
|
||||||
|
*
|
||||||
|
* ~~~~~
|
||||||
|
* // setting cookies
|
||||||
|
* $input->cookie->foo = 'bar';
|
||||||
|
* $input->cookie->set('foo', 'bar'); // same as above
|
||||||
|
* $input->cookie['foo'] = 'bar'; // same as above
|
||||||
|
*
|
||||||
|
* // setting cookies, with options
|
||||||
|
* $input->cookie->set('foo', bar', 86400); // live for 1 day
|
||||||
|
* $input->cookie->options('age', 3600); // any further set() cookies live for 1 hour (3600s)
|
||||||
|
* $input->cookie->set('foo', 'bar'); // uses setting from above options() call
|
||||||
|
*
|
||||||
|
* // getting cookies
|
||||||
|
* $bar = $input->cookie->foo;
|
||||||
|
* $bar = $input->cookie['foo']; // same as above
|
||||||
|
* $bar = $input->cookie('foo'); // same as above
|
||||||
|
* $bar = $input->cookie->get('foo'); // same as above
|
||||||
|
* $bar = $input->cookie->text('foo'); // sanitize with text() sanitizer
|
||||||
|
*
|
||||||
|
* // removing cookies
|
||||||
|
* unset($input->cookie->foo);
|
||||||
|
* $input->cookie->remove('foo'); // same as above
|
||||||
|
* $input->cookie->set('foo', null); // same as above
|
||||||
|
* $input->cookie->removeAll(); // remove all cookies
|
||||||
|
*
|
||||||
|
* // to modify default cookie settings, add this to your /site/config.php file and edit:
|
||||||
|
* $config->cookieOptions = [
|
||||||
|
*
|
||||||
|
* // Max age of cookies in seconds or 0 to expire with session
|
||||||
|
* // 3600=1hr, 86400=1day, 604800=1week, 2592000=30days, etc.
|
||||||
|
* 'age' => 604800,
|
||||||
|
*
|
||||||
|
* // Cookie path/URL or null for PW installation’s root URL
|
||||||
|
* 'path' => null,
|
||||||
|
*
|
||||||
|
* // Cookie domain: null for current hostname, true for all subdomains of current domain,
|
||||||
|
* // domain.com for domain and all subdomains, www.domain.com for www subdomain.
|
||||||
|
* 'domain' => null,
|
||||||
|
*
|
||||||
|
* // Transmit cookies only over secure HTTPS connection?
|
||||||
|
* // Specify true, false, or null to auto-detect (uses true for cookies set when HTTPS).
|
||||||
|
* 'secure' => null,
|
||||||
|
*
|
||||||
|
* // Make cookies accessible by HTTP only?
|
||||||
|
* // When true, cookie is http/server-side only and not visible to client-side JS code.
|
||||||
|
* 'httponly' => false,
|
||||||
|
*
|
||||||
|
* // If set cookie fails (perhaps due to output already sent),
|
||||||
|
* // attempt to set at beginning of next request?
|
||||||
|
* 'fallback' => true,
|
||||||
|
* ];
|
||||||
|
* ~~~~~
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* ProcessWire 3.x, Copyright 2019 by Ryan Cramer
|
||||||
|
* https://processwire.com
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
class WireInputDataCookie extends WireInputData {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Are we initialized?
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected $init = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default cookie options
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected $defaultOptions = array(
|
||||||
|
'age' => 0,
|
||||||
|
'path' => null,
|
||||||
|
'domain' => null,
|
||||||
|
'secure' => null,
|
||||||
|
'httponly' => false,
|
||||||
|
'fallback' => true,
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cookie options specifically set at runtime
|
||||||
|
* @var array
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected $options = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cookie names not be allowed to be removed
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected $skipCookies = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct
|
||||||
|
*
|
||||||
|
* @param array $input Associative array of variables to store
|
||||||
|
* @param bool $lazy Use lazy loading?
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function __construct(&$input = array(), $lazy = false) {
|
||||||
|
if($lazy) {} // lazy option not used by cookie
|
||||||
|
parent::__construct($input, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize and set any pending cookies from previous request
|
||||||
|
*
|
||||||
|
* #pw-internal
|
||||||
|
*
|
||||||
|
* @since 3.0.141
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function init() {
|
||||||
|
$this->init = true;
|
||||||
|
/** @var Session $session */
|
||||||
|
$session = $this->wire('session');
|
||||||
|
$cookies = $session->getFor($this, '');
|
||||||
|
if(!empty($cookies)) {
|
||||||
|
$this->setArray($cookies);
|
||||||
|
$session->removeAllFor($this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get or set cookie options
|
||||||
|
*
|
||||||
|
* - Omit all arguments to get current options.
|
||||||
|
* - Specify string for $key (and omit $value) to get the value of one option.
|
||||||
|
* - Specify both $key and $value arguments to set one option.
|
||||||
|
* - Specify associative array for $key (and omit $value) to set multiple options.
|
||||||
|
*
|
||||||
|
* @param string|array|null $key
|
||||||
|
* @param string|array|int|float|null $value
|
||||||
|
* @return string|array|int|float|null|$this
|
||||||
|
* @since 3.0.141
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function options($key = null, $value = null) {
|
||||||
|
if($key === null) {
|
||||||
|
// get all
|
||||||
|
return $this->options;
|
||||||
|
} else if(is_array($key) && $value === null) {
|
||||||
|
// set multiple
|
||||||
|
$this->options = array_merge($this->options, $key);
|
||||||
|
} else if($value === null) {
|
||||||
|
// get one
|
||||||
|
return isset($this->options[$key]) ? $this->options[$key] : null;
|
||||||
|
} else {
|
||||||
|
// set one
|
||||||
|
$this->options[$key] = $value;
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a cookie (directly)
|
||||||
|
*
|
||||||
|
* To set options for setting cookie, use $input->cookie->options(key, value); or $config->cookieOptions(key, value);
|
||||||
|
* Note that options set from $input->cookie->options take precedence over those set to $config.
|
||||||
|
*
|
||||||
|
* @param string $key Cookie name
|
||||||
|
* @param array|float|int|null|string $value Cookie value
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function __set($key, $value) {
|
||||||
|
|
||||||
|
if(!$this->init) {
|
||||||
|
// initial set of existing cookies that are present from constructor
|
||||||
|
parent::__set($key, $value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->setCookie($key, $value, array());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a cookie (optionally with options)
|
||||||
|
*
|
||||||
|
* @param string $key Cookie name
|
||||||
|
* @param string $value Cookie value
|
||||||
|
* @param array|int|string $options Optionally specify max age in seconds (int) or array with any of the following options:
|
||||||
|
* - `age` (int): Max age of cookies in seconds or 0 to expire with session (3600=1hr, 86400=1day, 604800=1week, 2592000=30days, etc.)
|
||||||
|
* - `path` (string|null): Cookie path/URL or null for PW installation’s root URL.
|
||||||
|
* - `domain` (string|bool|null): Cookie domain: null for current hostname, true for all subdomains of current domain, domain.com for domain and all subdomains, www.domain.com for www subdomain.
|
||||||
|
* - `secure` (bool|null): Transmit cookies only over secure HTTPS connection? (true, false, or null to auto-detect, substituting true for cookies set when HTTPS is active).
|
||||||
|
* - `httponly` (bool): When true, cookie is http/server-side only and not visible to client-side JS code.
|
||||||
|
* - `fallback` (bool): If set cookie fails (perhaps due to output already sent), attempt to set at beginning of next request?
|
||||||
|
* @return $this
|
||||||
|
* @since 3.0.141
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function set($key, $value, $options = array()) {
|
||||||
|
|
||||||
|
if(!$this->init) {
|
||||||
|
parent::__set($key, $value);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!is_array($options) && ctype_digit("$options")) {
|
||||||
|
$age = (int) $options;
|
||||||
|
$options = array('age' => $age);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->setCookie($key, $value, $options);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a cookie (internal)
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
* @param string|array|int|float $value
|
||||||
|
* @param array $options Optionally override options from $config->cookieOptions and any previously set from an options() call:
|
||||||
|
* - `age` (int): Max age of cookies in seconds or 0 to expire with session (3600=1hr, 86400=1day, 604800=1week, 2592000=30days, etc.)
|
||||||
|
* - `path` (string|null): Cookie path/URL or null for PW installation’s root URL.
|
||||||
|
* - `domain` (string|bool|null): Cookie domain: null for current hostname, true for all subdomains of current domain, domain.com for domain and all subdomains, www.domain.com for www subdomain.
|
||||||
|
* - `secure` (bool|null): Transmit cookies only over secure HTTPS connection? (true, false, or null to auto-detect, substituting true for cookies set when HTTPS is active).
|
||||||
|
* - `httponly` (bool): When true, cookie is http/server-side only and not visible to client-side JS code.
|
||||||
|
* - `fallback` (bool): If set cookie fails (perhaps due to output already sent), attempt to set at beginning of next request?
|
||||||
|
* @return bool
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected function setCookie($key, $value, array $options) {
|
||||||
|
|
||||||
|
/** @var Config $config */
|
||||||
|
$config = $this->wire('config');
|
||||||
|
$options = array_merge($this->defaultOptions, $config->cookieOptions, $this->options, $options);
|
||||||
|
|
||||||
|
$expires = $options['age'] ? time() + (int) $options['age'] : 0;
|
||||||
|
$path = $options['path'] === null || $options['path'] === true ? $config->urls->root : $options['path'];
|
||||||
|
$secure = $options['secure'] === null ? (bool) $config->https : (bool) $options['secure'];
|
||||||
|
$httponly = (bool) $options['httponly'];
|
||||||
|
$domain = $options['domain'];
|
||||||
|
$remove = $value === null;
|
||||||
|
|
||||||
|
if(!$this->allowSetCookie($key)) return false;
|
||||||
|
|
||||||
|
// determine what to use for the domain argument
|
||||||
|
if($domain === null) {
|
||||||
|
// use current http host
|
||||||
|
$domain = $config->httpHost;
|
||||||
|
} else if($domain === true) {
|
||||||
|
// allow all subdomains off current domain
|
||||||
|
$parts = explode('.', $config->httpHost);
|
||||||
|
$domain = count($parts) > 1 ? implode('.', array_slice($parts, -2)) : $config->httpHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove port from domain, as it is not compatible with setcookie()
|
||||||
|
if(strpos($domain, ':') !== false) list($domain,) = explode(':', $domain, 2);
|
||||||
|
|
||||||
|
// check if cookie should be deleted
|
||||||
|
if($remove) list($value, $expires) = array('', 1);
|
||||||
|
|
||||||
|
// set the cookie
|
||||||
|
$result = setcookie($key, $value, $expires, $path, $domain, $secure, $httponly);
|
||||||
|
|
||||||
|
if($result === false && $options['fallback']) {
|
||||||
|
// output must have already started, set at construct on next request
|
||||||
|
$this->wire('session')->setFor($this, $key, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($remove) {
|
||||||
|
parent::offsetUnset($key);
|
||||||
|
unset($_COOKIE[$key]);
|
||||||
|
} else {
|
||||||
|
parent::__set($key, $value);
|
||||||
|
$_COOKIE[$key] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unset a cookie value
|
||||||
|
*
|
||||||
|
* #pw-internal
|
||||||
|
*
|
||||||
|
* @param mixed $key
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function offsetUnset($key) {
|
||||||
|
if(!$this->allowSetCookie($key)) return;
|
||||||
|
parent::offsetUnset($key);
|
||||||
|
$this->setCookie($key, null, array());
|
||||||
|
unset($_COOKIE[$key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow cookie with given name to be set or unset?
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @return bool
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected function allowSetCookie($name) {
|
||||||
|
if(empty($this->skipCookies)) $this->skipCookies = $this->wire('session')->getCookieNames();
|
||||||
|
return in_array($name, $this->skipCookies) ? false : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove all cookies (other than those required for current session)
|
||||||
|
*
|
||||||
|
* @return $this|WireInputData
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function removeAll() {
|
||||||
|
foreach($this as $key => $value) {
|
||||||
|
$this->offsetUnset($key);
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Reference in New Issue
Block a user