Commit c03a3ff8 by Qiang Xue

Finishes flash feature.

parent cd1b3d32
......@@ -148,7 +148,7 @@ class Dictionary extends Object implements \IteratorAggregate, \ArrayAccess, \Co
* Defaults to false, meaning all items in the dictionary will be cleared directly
* without calling [[remove]].
*/
public function clear($safeClear = false)
public function removeAll($safeClear = false)
{
if ($safeClear) {
foreach (array_keys($this->_d) as $key) {
......@@ -164,7 +164,7 @@ class Dictionary extends Object implements \IteratorAggregate, \ArrayAccess, \Co
* @param mixed $key the key
* @return boolean whether the dictionary contains an item with the specified key
*/
public function contains($key)
public function has($key)
{
return isset($this->_d[$key]) || array_key_exists($key, $this->_d);
}
......@@ -188,7 +188,7 @@ class Dictionary extends Object implements \IteratorAggregate, \ArrayAccess, \Co
{
if (is_array($data) || $data instanceof \Traversable) {
if ($this->_d !== array()) {
$this->clear();
$this->removeAll();
}
if ($data instanceof self) {
$data = $data->_d;
......@@ -252,7 +252,7 @@ class Dictionary extends Object implements \IteratorAggregate, \ArrayAccess, \Co
*/
public function offsetExists($offset)
{
return $this->contains($offset);
return $this->has($offset);
}
/**
......
......@@ -191,7 +191,7 @@ class Vector extends Object implements \IteratorAggregate, \ArrayAccess, \Counta
* Defaults to false, meaning all items in the vector will be cleared directly
* without calling [[removeAt]].
*/
public function clear($safeClear = false)
public function removeAll($safeClear = false)
{
if ($safeClear) {
for ($i = $this->_c - 1; $i >= 0; --$i) {
......@@ -209,7 +209,7 @@ class Vector extends Object implements \IteratorAggregate, \ArrayAccess, \Counta
* @param mixed $item the item
* @return boolean whether the vector contains the item
*/
public function contains($item)
public function has($item)
{
return $this->indexOf($item) >= 0;
}
......@@ -246,7 +246,7 @@ class Vector extends Object implements \IteratorAggregate, \ArrayAccess, \Counta
{
if (is_array($data) || $data instanceof \Traversable) {
if ($this->_c > 0) {
$this->clear();
$this->removeAll();
}
if ($data instanceof self) {
$data = $data->_d;
......
......@@ -12,13 +12,15 @@ use yii\base\Component;
use yii\base\InvalidParamException;
/**
* Session provides session-level data management and the related configurations.
* Session provides session data management and the related configurations.
*
* Session is a Web application component that can be accessed via `Yii::$app->session`.
* To start the session, call [[open()]]; To complete and send out session data, call [[close()]];
* To destroy the session, call [[destroy()]].
*
* If [[autoStart]] is set true, the session will be started automatically
* when the application component is initialized by the application.
* By default, [[autoStart]] is true which means the session will be started automatically
* when the session component is accessed the first time.
*
* Session can be used like an array to set and get session data. For example,
*
......@@ -37,22 +39,11 @@ use yii\base\InvalidParamException;
* [[openSession()]], [[closeSession()]], [[readSession()]], [[writeSession()]],
* [[destroySession()]] and [[gcSession()]].
*
* Session is a Web application component that can be accessed via
* `Yii::$app->session`.
*
* @property boolean $useCustomStorage read-only. Whether to use custom storage.
* @property boolean $isActive Whether the session has started.
* @property string $id The current session ID.
* @property string $name The current session name.
* @property string $savePath The current session save path, defaults to '/tmp'.
* @property array $cookieParams The session cookie parameters.
* @property string $cookieMode How to use cookie to store session ID. Defaults to 'Allow'.
* @property float $gcProbability The probability (percentage) that the gc (garbage collection) process is started on every session initialization.
* @property boolean $useTransparentSessionID Whether transparent sid support is enabled or not, defaults to false.
* @property integer $timeout The number of seconds after which data will be seen as 'garbage' and cleaned up, defaults to 1440 seconds.
* @property SessionIterator $iterator An iterator for traversing the session variables.
* @property integer $count The number of session variables.
* @property array $keys The list of session variable names.
* Session also supports a special type of session data, called *flash messages*.
* A flash message is available only in the current request and the next request.
* After that, it will be deleted automatically. Flash messages are particularly
* useful for displaying confirmation messages. To use flash messages, simply
* call methods such as [[setFlash()]], [[getFlash()]].
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
......@@ -63,6 +54,10 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
* @var boolean whether the session should be automatically started when the session component is initialized.
*/
public $autoStart = true;
/**
* @var string the name of the session variable that stores the flash message data.
*/
public $flashVar = '__flash';
/**
* Initializes the application component.
......@@ -117,7 +112,9 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
if (session_id() == '') {
$error = error_get_last();
$message = isset($error['message']) ? $error['message'] : 'Failed to start session.';
Yii::warning($message, __CLASS__);
Yii::error($message, __CLASS__);
} else {
$this->updateFlashCounters();
}
}
......@@ -462,18 +459,18 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
/**
* Adds a session variable.
* Note, if the specified name already exists, the old value will be removed first.
* @param mixed $key session variable name
* If the specified name already exists, the old value will be overwritten.
* @param string $key session variable name
* @param mixed $value session variable value
*/
public function add($key, $value)
public function set($key, $value)
{
$_SESSION[$key] = $value;
}
/**
* Removes a session variable.
* @param mixed $key the name of the session variable to be removed
* @param string $key the name of the session variable to be removed
* @return mixed the removed value, null if no such session variable.
*/
public function remove($key)
......@@ -490,7 +487,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
/**
* Removes all session variables
*/
public function clear()
public function removeAll()
{
foreach (array_keys($_SESSION) as $key) {
unset($_SESSION[$key]);
......@@ -501,7 +498,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
* @param mixed $key session variable name
* @return boolean whether there is the named session variable
*/
public function contains($key)
public function has($key)
{
return isset($_SESSION[$key]);
}
......@@ -515,6 +512,114 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
}
/**
* Updates the counters for flash messages and removes outdated flash messages.
* This method should only be called once in [[init()]].
*/
protected function updateFlashCounters()
{
$counters = $this->get($this->flashVar, array());
if (is_array($counters)) {
foreach ($counters as $key => $count) {
if ($count) {
unset($counters[$key], $_SESSION[$key]);
} else {
$counters[$key]++;
}
}
$_SESSION[$this->flashVar] = $counters;
} else {
// fix the unexpected problem that flashVar doesn't return an array
unset($_SESSION[$this->flashVar]);
}
}
/**
* Returns a flash message.
* A flash message is available only in the current request and the next request.
* @param string $key the key identifying the flash message
* @param mixed $defaultValue value to be returned if the flash message does not exist.
* @return mixed the flash message
*/
public function getFlash($key, $defaultValue = null)
{
$counters = $this->get($this->flashVar, array());
return isset($counters[$key]) ? $this->get($key, $defaultValue) : $defaultValue;
}
/**
* Returns all flash messages.
* @return array flash messages (key => message).
*/
public function getAllFlashes()
{
$counters = $this->get($this->flashVar, array());
$flashes = array();
foreach (array_keys($counters) as $key) {
if (isset($_SESSION[$key])) {
$flashes[$key] = $_SESSION[$key];
}
}
return $flashes;
}
/**
* Stores a flash message.
* A flash message is available only in the current request and the next request.
* @param string $key the key identifying the flash message. Note that flash messages
* and normal session variables share the same name space. If you have a normal
* session variable using the same name, its value will be overwritten by this method.
* @param mixed $value flash message
*/
public function setFlash($key, $value)
{
$counters = $this->get($this->flashVar, array());
$counters[$key] = 0;
$_SESSION[$key] = $value;
$_SESSION[$this->flashVar] = $counters;
}
/**
* Removes a flash message.
* Note that flash messages will be automatically removed after the next request.
* @param string $key the key identifying the flash message. Note that flash messages
* and normal session variables share the same name space. If you have a normal
* session variable using the same name, it will be removed by this method.
* @return mixed the removed flash message. Null if the flash message does not exist.
*/
public function removeFlash($key)
{
$counters = $this->get($this->flashVar, array());
$value = isset($_SESSION[$key], $counters[$key]) ? $_SESSION[$key] : null;
unset($counters[$key], $_SESSION[$key]);
$_SESSION[$this->flashVar] = $counters;
return $value;
}
/**
* Removes all flash messages.
* Note that flash messages and normal session variables share the same name space.
* If you have a normal session variable using the same name, it will be removed
* by this method.
*/
public function removeAllFlashes()
{
$counters = $this->get($this->flashVar, array());
foreach (array_keys($counters) as $key) {
unset($_SESSION[$key]);
}
unset($_SESSION[$this->flashVar]);
}
/**
* @param string $key key identifying the flash message
* @return boolean whether the specified flash message exists
*/
public function hasFlash($key)
{
return $this->getFlash($key) !== null;
}
/**
* This method is required by the interface ArrayAccess.
* @param mixed $offset the offset to check on
* @return boolean
......
......@@ -40,7 +40,7 @@ use yii\base\Component;
* Both {@link id} and {@link name} are persistent during the user session.
* Besides, an identity may have additional persistent data which can
* be accessed by calling {@link getState}.
* Note, when {@link allowAutoLogin cookie-based authentication} is enabled,
* Note, when {@link enableAutoLogin cookie-based authentication} is enabled,
* all these persistent data will be stored in cookie. Therefore, do not
* store password or other sensitive data in the persistent storage. Instead,
* you should store them directly in session on the server side if needed.
......@@ -50,67 +50,54 @@ use yii\base\Component;
* @property string $name The user name. If the user is not logged in, this will be {@link guestName}.
* @property string $returnUrl The URL that the user should be redirected to after login.
* @property string $stateKeyPrefix A prefix for the name of the session variables storing user session data.
* @property array $flashes Flash messages (key => message).
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class User extends Component
{
const FLASH_KEY_PREFIX = 'Yii.CWebUser.flash.';
const FLASH_COUNTERS = 'Yii.CWebUser.flashcounters';
const STATES_VAR = '__states';
const AUTH_TIMEOUT_VAR = '__timeout';
/**
* @var boolean whether to enable cookie-based login. Defaults to false.
*/
public $allowAutoLogin = false;
public $enableAutoLogin = false;
/**
* @var string the name for a guest user. Defaults to 'Guest'.
* This is used by {@link getName} when the current user is a guest (not authenticated).
*/
public $guestName = 'Guest';
/**
* @var string|array the URL for login. If using array, the first element should be
* the route to the login action, and the rest name-value pairs are GET parameters
* to construct the login URL (e.g. array('/site/login')). If this property is null,
* a 403 HTTP exception will be raised instead.
* @see CController::createUrl
* @var string|array the URL for login when [[loginRequired()]] is called.
* If an array is given, [[UrlManager::createUrl()]] will be called to create the corresponding URL.
* The first element of the array should be the route to the login action, and the rest of
* the name-value pairs are GET parameters used to construct the login URL. For example,
*
* ~~~
* array('site/login', 'ref' => 1)
* ~~~
*
* If this property is null, a 403 HTTP exception will be raised when [[loginRequired()]] is called.
*/
public $loginUrl = array('/site/login');
public $loginUrl = array('site/login');
/**
* @var array the property values (in name-value pairs) used to initialize the identity cookie.
* Any property of {@link CHttpCookie} may be initialized.
* This property is effective only when {@link allowAutoLogin} is true.
* @var array the configuration of the identity cookie. This property is used only when [[enableAutoLogin]] is true.
* @see Cookie
*/
public $identityCookie;
/**
* @var integer timeout in seconds after which user is logged out if inactive.
* If this property is not set, the user will be logged out after the current session expires
* (c.f. {@link CHttpSession::timeout}).
* @since 1.1.7
* @var integer the number of seconds in which the user will be logged out automatically if he
* remains inactive. If this property is not set, the user will be logged out after
* the current session expires (c.f. [[Session::timeout]]).
*/
public $authTimeout;
/**
* @var boolean whether to automatically renew the identity cookie each time a page is requested.
* Defaults to false. This property is effective only when {@link allowAutoLogin} is true.
* Defaults to false. This property is effective only when {@link enableAutoLogin} is true.
* When this is false, the identity cookie will expire after the specified duration since the user
* is initially logged in. When this is true, the identity cookie will expire after the specified duration
* since the user visits the site the last time.
* @see allowAutoLogin
* @see enableAutoLogin
* @since 1.1.0
*/
public $autoRenewCookie = false;
/**
* @var boolean whether to automatically update the validity of flash messages.
* Defaults to true, meaning flash messages will be valid only in the current and the next requests.
* If this is set false, you will be responsible for ensuring a flash message is deleted after usage.
* (This can be achieved by calling {@link getFlash} with the 3rd parameter being true).
* @since 1.1.7
*/
public $autoUpdateFlash = true;
/**
* @var string value that will be echoed in case that user session has expired during an ajax call.
* When a request is made and user session has expired, {@link loginRequired} redirects to {@link loginUrl} for login.
* If that happens during an ajax call, the complete HTML login page is returned as the result of that ajax call. That could be
......@@ -129,22 +116,16 @@ class User extends Component
/**
* Initializes the application component.
* This method overrides the parent implementation by starting session,
* performing cookie-based authentication if enabled, and updating the flash variables.
*/
public function init()
{
parent::init();
Yii::app()->getSession()->open();
if ($this->getIsGuest() && $this->allowAutoLogin) {
if ($this->getIsGuest() && $this->enableAutoLogin) {
$this->restoreFromCookie();
} elseif ($this->autoRenewCookie && $this->allowAutoLogin) {
} elseif ($this->autoRenewCookie && $this->enableAutoLogin) {
$this->renewCookie();
}
if ($this->autoUpdateFlash) {
$this->updateFlash();
}
$this->updateAuthStatus();
}
......@@ -156,12 +137,12 @@ class User extends Component
* the session storage. If the duration parameter is greater than 0,
* a cookie will be sent to prepare for cookie-based login in future.
*
* Note, you have to set {@link allowAutoLogin} to true
* Note, you have to set {@link enableAutoLogin} to true
* if you want to allow user to be authenticated based on the cookie information.
*
* @param IUserIdentity $identity the user identity (which should already be authenticated)
* @param integer $duration number of seconds that the user can remain in logged-in status. Defaults to 0, meaning login till the user closes the browser.
* If greater than 0, cookie-based login will be used. In this case, {@link allowAutoLogin}
* If greater than 0, cookie-based login will be used. In this case, {@link enableAutoLogin}
* must be set true, otherwise an exception will be thrown.
* @return boolean whether the user is logged in
*/
......@@ -173,10 +154,10 @@ class User extends Component
$this->changeIdentity($id, $identity->getName(), $states);
if ($duration > 0) {
if ($this->allowAutoLogin) {
if ($this->enableAutoLogin) {
$this->saveToCookie($duration);
} else {
throw new CException(Yii::t('yii', '{class}.allowAutoLogin must be set true in order to use cookie-based authentication.',
throw new CException(Yii::t('yii', '{class}.enableAutoLogin must be set true in order to use cookie-based authentication.',
array('{class}' => get_class($this))));
}
}
......@@ -196,7 +177,7 @@ class User extends Component
public function logout($destroySession = true)
{
if ($this->beforeLogout()) {
if ($this->allowAutoLogin) {
if ($this->enableAutoLogin) {
Yii::app()->getRequest()->getCookies()->remove($this->getStateKeyPrefix());
if ($this->identityCookie !== null) {
$cookie = $this->createIdentityCookie($this->getStateKeyPrefix());
......@@ -377,7 +358,7 @@ class User extends Component
/**
* Populates the current user object with the information obtained from cookie.
* This method is used when automatic login ({@link allowAutoLogin}) is enabled.
* This method is used when automatic login ({@link enableAutoLogin}) is enabled.
* The user identity information is recovered from cookie.
* Sufficient security measures are used to prevent cookie data from being tampered.
* @see saveToCookie
......@@ -425,7 +406,7 @@ class User extends Component
/**
* Saves necessary user data into a cookie.
* This method is used when automatic login ({@link allowAutoLogin}) is enabled.
* This method is used when automatic login ({@link enableAutoLogin}) is enabled.
* This method saves user ID, username, other identity states and a validation key to cookie.
* These information are used to do authentication next time when user visits the application.
* @param integer $duration number of seconds that the user can remain in logged-in status. Defaults to 0, meaning login till the user closes the browser.
......@@ -555,81 +536,6 @@ class User extends Component
}
/**
* Returns all flash messages.
* This method is similar to {@link getFlash} except that it returns all
* currently available flash messages.
* @param boolean $delete whether to delete the flash messages after calling this method.
* @return array flash messages (key => message).
* @since 1.1.3
*/
public function getFlashes($delete = true)
{
$flashes = array();
$prefix = $this->getStateKeyPrefix() . self::FLASH_KEY_PREFIX;
$keys = array_keys($_SESSION);
$n = strlen($prefix);
foreach ($keys as $key) {
if (!strncmp($key, $prefix, $n)) {
$flashes[substr($key, $n)] = $_SESSION[$key];
if ($delete) {
unset($_SESSION[$key]);
}
}
}
if ($delete) {
$this->setState(self::FLASH_COUNTERS, array());
}
return $flashes;
}
/**
* Returns a flash message.
* A flash message is available only in the current and the next requests.
* @param string $key key identifying the flash message
* @param mixed $defaultValue value to be returned if the flash message is not available.
* @param boolean $delete whether to delete this flash message after accessing it.
* Defaults to true.
* @return mixed the message message
*/
public function getFlash($key, $defaultValue = null, $delete = true)
{
$value = $this->getState(self::FLASH_KEY_PREFIX . $key, $defaultValue);
if ($delete) {
$this->setFlash($key, null);
}
return $value;
}
/**
* Stores a flash message.
* A flash message is available only in the current and the next requests.
* @param string $key key identifying the flash message
* @param mixed $value flash message
* @param mixed $defaultValue if this value is the same as the flash message, the flash message
* will be removed. (Therefore, you can use setFlash('key',null) to remove a flash message.)
*/
public function setFlash($key, $value, $defaultValue = null)
{
$this->setState(self::FLASH_KEY_PREFIX . $key, $value, $defaultValue);
$counters = $this->getState(self::FLASH_COUNTERS, array());
if ($value === $defaultValue) {
unset($counters[$key]);
} else {
$counters[$key] = 0;
}
$this->setState(self::FLASH_COUNTERS, $counters, array());
}
/**
* @param string $key key identifying the flash message
* @return boolean whether the specified flash message exists
*/
public function hasFlash($key)
{
return $this->getFlash($key, null, false) !== null;
}
/**
* Changes the current user with the specified identity information.
* This method is called by {@link login} and {@link restoreFromCookie}
* when the current user needs to be populated with the corresponding
......@@ -678,28 +584,6 @@ class User extends Component
}
/**
* Updates the internal counters for flash messages.
* This method is internally used by {@link CWebApplication}
* to maintain the availability of flash messages.
*/
protected function updateFlash()
{
$counters = $this->getState(self::FLASH_COUNTERS);
if (!is_array($counters)) {
return;
}
foreach ($counters as $key => $count) {
if ($count) {
unset($counters[$key]);
$this->setState(self::FLASH_KEY_PREFIX . $key, null);
} else {
$counters[$key]++;
}
}
$this->setState(self::FLASH_COUNTERS, $counters, array());
}
/**
* Updates the authentication status according to {@link authTimeout}.
* If the user has been inactive for {@link authTimeout} seconds,
* he will be automatically logged out.
......
......@@ -61,7 +61,7 @@ class DictionaryTest extends \yiiunit\TestCase
{
$this->dictionary->add('key3',$this->item3);
$this->assertEquals(3,$this->dictionary->getCount());
$this->assertTrue($this->dictionary->contains('key3'));
$this->assertTrue($this->dictionary->has('key3'));
$this->dictionary[] = 'test';
}
......@@ -70,28 +70,28 @@ class DictionaryTest extends \yiiunit\TestCase
{
$this->dictionary->remove('key1');
$this->assertEquals(1,$this->dictionary->getCount());
$this->assertTrue(!$this->dictionary->contains('key1'));
$this->assertTrue(!$this->dictionary->has('key1'));
$this->assertTrue($this->dictionary->remove('unknown key')===null);
}
public function testClear()
public function testRemoveAll()
{
$this->dictionary->add('key3',$this->item3);
$this->dictionary->clear();
$this->dictionary->removeAll();
$this->assertEquals(0,$this->dictionary->getCount());
$this->assertTrue(!$this->dictionary->contains('key1') && !$this->dictionary->contains('key2'));
$this->assertTrue(!$this->dictionary->has('key1') && !$this->dictionary->has('key2'));
$this->dictionary->add('key3',$this->item3);
$this->dictionary->clear(true);
$this->dictionary->removeAll(true);
$this->assertEquals(0,$this->dictionary->getCount());
$this->assertTrue(!$this->dictionary->contains('key1') && !$this->dictionary->contains('key2'));
$this->assertTrue(!$this->dictionary->has('key1') && !$this->dictionary->has('key2'));
}
public function testContains()
public function testHas()
{
$this->assertTrue($this->dictionary->contains('key1'));
$this->assertTrue($this->dictionary->contains('key2'));
$this->assertFalse($this->dictionary->contains('key3'));
$this->assertTrue($this->dictionary->has('key1'));
$this->assertTrue($this->dictionary->has('key2'));
$this->assertFalse($this->dictionary->has('key3'));
}
public function testFromArray()
......@@ -162,7 +162,7 @@ class DictionaryTest extends \yiiunit\TestCase
unset($this->dictionary['key2']);
$this->assertEquals(2,$this->dictionary->getCount());
$this->assertTrue(!$this->dictionary->contains('key2'));
$this->assertTrue(!$this->dictionary->has('key2'));
unset($this->dictionary['unknown key']);
}
......
......@@ -101,26 +101,26 @@ class VectorTest extends \yiiunit\TestCase
$this->vector->removeAt(2);
}
public function testClear()
public function testRemoveAll()
{
$this->vector->add($this->item3);
$this->vector->clear();
$this->vector->removeAll();
$this->assertEquals(0,$this->vector->getCount());
$this->assertEquals(-1,$this->vector->indexOf($this->item1));
$this->assertEquals(-1,$this->vector->indexOf($this->item2));
$this->vector->add($this->item3);
$this->vector->clear(true);
$this->vector->removeAll(true);
$this->assertEquals(0,$this->vector->getCount());
$this->assertEquals(-1,$this->vector->indexOf($this->item1));
$this->assertEquals(-1,$this->vector->indexOf($this->item2));
}
public function testContains()
public function testHas()
{
$this->assertTrue($this->vector->contains($this->item1));
$this->assertTrue($this->vector->contains($this->item2));
$this->assertFalse($this->vector->contains($this->item3));
$this->assertTrue($this->vector->has($this->item1));
$this->assertTrue($this->vector->has($this->item2));
$this->assertFalse($this->vector->has($this->item3));
}
public function testIndexOf()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment