In Web3CMS we thought about we same, and created a way to log in with either "Username" or "Email" or "Username or email". This is configured in params.php with
// which field to log user in with. possible values: username/email/_any_
// reset this on any page with MParams::setUserLoginWithField('username');
'userLoginWithField'=>'username', //'username'
Then in login view we add activeHiddenField($form,‘loginWithField’) to understand what is the account field, and activeTextField($form,$form->getLoginWithField()) to display the account field itself.
Then in UserLoginModel we read the account field and pass it to _CUserIdentity component, which is validating user input agains User db-table according to the account field.
Web3CMS is open source, feel free to download it and try (link is in the bottom).
Listing of user login view:
<?php MParams::setPageLabel(Yii::t('page','Login')); ?>
<?php MUserFlash::setTopError(_CHtml::errorSummary($form)); ?>
<?php MUserFlash::setSidebarInfo(Yii::t('feedback','Hint: You may login with <tt>demo/demo</tt> or <tt>admin/admin</tt>.')); ?>
<?php $this->widget('application.components.WContentHeader',array(
'breadcrumbs'=>array(
array(
'url'=>CHtml::normalizeUrl(array($this->getAction()->getId())),
'active'=>true
),
),
)); ?>
<div class="w3-main-form-wrapper ui-widget-content ui-corner-all">
<?php echo _CHtml::beginForm('','post',array('class'=>'w3-main-form'))."\n"; ?>
<?php echo _CHtml::activeHiddenField($form,'loginWithField')."\n"; ?>
<div class="w3-form-row w3-first">
<div class="w3-form-row-label"><?php echo _CHtml::activeLabelEx($form,$form->getLoginWithField()); ?></div>
<div class="w3-form-row-input">
<?php echo _CHtml::activeTextField($form,$form->getLoginWithField(),array('class'=>'w3-input-text ui-widget-content ui-corner-all'))."\n"; ?>
</div>
<div class="clear"> </div>
</div>
<div class="w3-form-row">
<div class="w3-form-row-label"><?php echo _CHtml::activeLabelEx($form,'password'); ?></div>
<div class="w3-form-row-input">
<?php echo _CHtml::activePasswordField($form,'password',array('class'=>'w3-input-text ui-widget-content ui-corner-all','maxlength'=>64))."\n"; ?>
</div>
<div class="clear"> </div>
</div>
<?php if(Yii::app()->user->allowAutoLogin): ?>
<div class="w3-form-row">
<div class="w3-form-row-label"> </div>
<div class="w3-form-row-input">
<div class="w3-form-row-text">
<?php echo _CHtml::activeCheckBox($form,'rememberMe')."\n"; ?>
<?php echo _CHtml::activeLabelEx($form,'rememberMe')."\n"; ?>
</div>
</div>
<div class="clear"> </div>
</div>
<?php endif; ?>
<div class="w3-form-row">
<div class="w3-form-row-label"> </div>
<div class="w3-form-row-input">
<?php echo _CHtml::submitButton(Yii::t('link','Log in'),array('class'=>'w3-input-button ui-button ui-state-default ui-corner-all'))."\n"; ?>
</div>
<div class="clear"> </div>
</div>
<?php echo _CHtml::endForm(); ?>
</div><!-- w3-main-form-wrapper -->
<?php MClientScript::registerScript('focusOnFormFirstItem'); ?>
<?php MClientScript::registerScript('w3FormButton'); ?>
Listing of UserLoginForm model:
<?php
/**
* UserLoginForm class.
* UserLoginForm is the data structure for keeping
* user login form data. It is used by the 'login' action of 'UserController'.
*/
class UserLoginForm extends CFormModel
{
public $email;
public $password;
public $rememberMe;
public $username;
public $usernameOrEmail;
public static $loginWithField;
public function __construct($attributes=array(),$scenario='')
{
parent::__construct($attributes,$scenario);
self::$loginWithField=$this->getLoginWithField();
}
/**
* Declares the validation rules.
* The rules state that username and password are required,
* and password needs to be authenticated.
*/
public function rules()
{
return array(
// username or email or usernameOrEmail is required
array(self::getLoggingWithField(), 'required'),
// password is required
array('password', 'required'),
// password needs to be authenticated
array('password', 'authenticate'),
);
}
/**
* Declares the attribute labels.
* If an attribute is not delcared here, it will use the default label
* generation algorithm to get its label.
*/
public function attributeLabels()
{
return array(
'rememberMe'=>Yii::t('feedback','Remember my member account on this computer'),
'password'=>Yii::t('t','Password'),
'username'=>Yii::t('t','Username'),
'usernameOrEmail'=>Yii::t('t','Username or email'),
);
}
/**
* Authenticates the password.
* This is the 'authenticate' validator as declared in rules().
*/
public function authenticate($attribute,$params)
{
if(!$this->hasErrors()) // we only want to authenticate when no input errors
{
$identity=new _CUserIdentity($this->{self::getLoggingWithField()},$this->password);
$identity->authenticate();
switch($identity->errorCode)
{
case _CUserIdentity::ERROR_NONE:
// if user is already logged in
if(!Yii::app()->user->isGuest)
{
// log user out from the current account. i want to sleep well, do you? <img src='http://www.yiiframework.com/forum/public/style_emoticons/default/wink.gif' class='bbc_emoticon' alt=';)' />
Yii::app()->user->logout();
if(!Yii::app()->getSession()->getIsStarted())
// restore http session. this is necessary for login
Yii::app()->getSession()->open();
}
// remember for 30 days. makes sence only if auto-login is allowed
$duration=(Yii::app()->user->allowAutoLogin && $this->rememberMe) ? 3600*24*30 : 0;
// log user in and save in session all appended data
Yii::app()->user->login($identity,$duration);
// set user preferences (for welcome message, and so on)
if(isset(Yii::app()->user->interface) && !empty(Yii::app()->user->interface))
// set user preferred interface
W3::setInterface(Yii::app()->user->interface);
if(isset(Yii::app()->user->language) && !empty(Yii::app()->user->language))
// set user preferred language
W3::setLanguage(Yii::app()->user->language);
break;
case _CUserIdentity::ERROR_USERNAME_INVALID:
if(self::getLoggingWithField()==='username')
$this->addError('username',Yii::t('t','Username is incorrect.'));
else if(self::getLoggingWithField()==='email')
$this->addError('email',Yii::t('t','Email is incorrect.'));
else if(self::getLoggingWithField()==='usernameOrEmail')
$this->addError('usernameOrEmail',Yii::t('t','Username or email is incorrect.'));
break;
case _CUserIdentity::ERROR_ACCOUNT_IS_INACTIVE:
// set the error message
MUserFlash::setTopError(Yii::t('feedback',
'We are sorry, but your member account is marked as "inactive". Inactive member accounts are temporarely inaccessible. {contactLink}.',
array('{contactLink}'=>CHtml::link(Yii::t('link','Contact us'),array('site/contact')))
));
// add to username (first field in the login form) error css class
// and make the validate() to fail
$attribute=self::getLoggingWithField();
$attribute!=='username' && $attribute!=='email' && $attribute!=='usernameOrEmail' && ($attribute='username');
$this->addError($attribute,'');
break;
case _CUserIdentity::ERROR_IS_NOT_ADMINISTRATOR:
// set the error message
MUserFlash::setTopError(Yii::t('feedback',
'We are sorry, but your access type is {accessType}. Required access type: {requiredAccessType}.',
array(
'{accessType}'=>Yii::app()->controller->var->userAccessType,
'{requiredAccessType}'=>Yii::t('t',User::ADMINISTRATOR_T)
)
));
unset(Yii::app()->controller->var->userAccessType); // we do not need this any more
// add to username (first field in the login form) error css class
// and make the validate() to fail
$attribute=self::getLoggingWithField();
$attribute!=='username' && $attribute!=='email' && $attribute!=='usernameOrEmail' && ($attribute='username');
$this->addError($attribute,'');
break;
case _CUserIdentity::ERROR_PASSWORD_INVALID:
default:
$this->addError('password',Yii::t('t','Password is incorrect.'));
break;
}
}
}
/**
* Which field to log user in with
*
* @return string
*/
public function getLoginWithField()
{
$array=array(
'username'=>'username',
'email'=>'email',
'_any_'=>'usernameOrEmail'
);
return $array[MParams::getUserLoginWithField()];
}
/**
* Which field is user now trying to login with
* If trying to login with username, but later userLoginWithField param
* was changed to email, still username should be required, not email, right?
*
* @return string
*/
public static function getLoggingWithField()
{
switch(self::$loginWithField)
{
case 'username':
case 'email':
case 'usernameOrEmail':
return self::$loginWithField;
break;
default:
return 'username';
break;
}
}
}
Listing of _CUserIdentity component:
<?php
/**
* _CUserIdentity represents the data needed to identity a user.
* It contains the authentication method that checks if the provided
* data can identity the user.
*/
class _CUserIdentity extends CUserIdentity
{
const ERROR_UNKNOWN_IDENTITY=10;
const ERROR_ACCOUNT_IS_INACTIVE=11;
const ERROR_IS_NOT_ADMINISTRATOR=12;
private $_id;
/**
* @return integer the ID of the user record
*/
public function getId()
{
return $this->_id;
}
/**
* Authenticates a user.
* @return boolean whether authentication succeeds.
*/
public function authenticate()
{
if(UserLoginForm::getLoggingWithField()=='username')
$user=User::model()->findByAttributes(array('username'=>$this->username));
else if(UserLoginForm::getLoggingWithField()=='email')
$user=User::model()->findByAttributes(array('email'=>$this->username));
else if(UserLoginForm::getLoggingWithField()=='usernameOrEmail')
$user=User::model()->find("`username`=? OR `email`=?",array($this->username,$this->username));
if($user===null)
$this->errorCode=self::ERROR_USERNAME_INVALID;
else if(md5($this->password)!==$user->password)
$this->errorCode=self::ERROR_PASSWORD_INVALID;
else if($user->isActive===User::IS_NOT_ACTIVE)
$this->errorCode=self::ERROR_ACCOUNT_IS_INACTIVE;
else if(MArea::isBackend() && !User::isAdministrator($user->accessType))
{
$this->errorCode=self::ERROR_IS_NOT_ADMINISTRATOR;
Yii::app()->controller->var->userAccessType=$user->getAttributeView('accessType');
}
else
{
$this->_id=$user->id;
$this->errorCode=self::ERROR_NONE;
// do not store password or other sensitive data in the persistent storage
// when (config/main.php) allowAutoLogin is true, because
// all these data will be stored in cookie = it is readable
$this->setState('email', $user->email);
$this->setState('interface', $user->interface);
$this->setState('language', $user->language);
$this->setState('screenName', $user->screenName);
}
return !$this->errorCode;
}
/**
* Authenticates a user by cookie.
* Is called by {@link _CWebUser::restoreFromCookie()}.
* @return boolean whether authentication succeeds.
*/
public function authenticateByCookie()
{
$user=User::model()->findByPk($this->username);
if($user===null)
$this->errorCode=self::ERROR_UNKNOWN_IDENTITY;
else if($user->isActive==='0')
$this->errorCode=self::ERROR_ACCOUNT_IS_INACTIVE;
else
{
$this->_id=$user->id;
$this->errorCode=self::ERROR_NONE;
// do not store password or other sensitive data in the persistent storage
// when (config/main.php) allowAutoLogin is true, because
// all these data will be stored in cookie = it is readable
$this->setState('email', $user->email);
$this->setState('interface', $user->interface);
$this->setState('language', $user->language);
$this->setState('screenName', $user->screenName);
}
return !$this->errorCode;
}
}