jformvalidate extension validate required field even when it shouldn't

Hello…

I have a form that allows to create or update a user.

When the form is in insert mode all works perfect. The problem is when the form is in update mode. The rule says that password field is not required in edit mode and in fact, when the form appears, no asterisk is shown, meaning that YII framework recognize that the field is not required.

When the form is submitted, jformvalidate displays the error telling that password field is required. Is this a bug or there’s something I miss to configure?

This is the whole rule for the model:




public function rules()

	{

        return  array(

    		    array('username, nombres, apellidos, email', 'required'),

    		    array('password, password_repeat', 'required', 'on' => 'insert'),

    			array('password','length','max'=>50, 'min'=>6, 'on' => 'insert'),

			    array('password_repeat','length','max'=>50, 'min'=>6, 'on' => 'insert'),

			    array('password_repeat', 'compare', 'compareAttribute'=>'password', 'on' => 'insert'),

    			array('username','length','max'=>50),

    			array('username', 'filter', 'filter'=>'strtolower'), 

    			array('username, nombres, apellidos', 'filter', 'filter'=>'trim'),

    			array('email','length','max'=>80),

    			array('email','email'));

	}



And this is the part of the form that renders the password fields:




<div class="simple">

<?php echo EHtml::activeLabelEx($model,'password'); ?>

<?php echo EHtml::activePasswordField($model,'password',array('value'=>'', 'size'=>50,'maxlength'=>50)); ?>

</div>

<div class="simple">

<?php echo EHtml::activeLabelEx($model,'password_repeat'); ?>

<?php echo EHtml::activePasswordField($model,'password_repeat',array('size'=>50,'maxlength'=>50)); ?>

</div>



Any help will be greatly appreciated

Thanks

Jaime

Are you sure?

echo EHtml::activePasswordField($model,‘password’,array(‘value’=>’’, ‘size’=>50,‘maxlength’=>50));

Yes… I have to add that because when I am in edit mode, I don’t want the password field to be filled. If that is the cause of the problem, how can I avoid the password to be filled automatically? if I use EHtml::passwordField the field is not filled but it is not connected to the database.

The password field is shown in update page so that a user can change the password. If he doesn’t enter nothing into that field, the password must remain intact.

Jaime

Make password and password_repeat unsafe in update scenario (so your safeAttributes might look like this: array(… ‘insert’=>‘password, password_repeat’, …)).

This way it won’t be overwritten by massive assignment. ($user->attributes=…)

However, you still have to handle the case when these fields are filled.

Hello… I have done it, but safeAttributes function is not called when form appears. The password field is filled with something (actually, the md5 value stored in the database) when the update form appears.

How can avoid that?

Jaime

In this case you should avoid activeTextField.

Use something like: CHtml::textField(‘User[password & password_repeat]’, ‘’, …); so that it fits your active elements (check the generated source if you’re not sure).

Hello… that partially worked… I finally wrote:




<?php if ($update) :?>

<div class="simple">

<?php echo EHtml::activeLabelEx($model,'password'); ?>

<?php echo EHtml::passwordField('Usuario[password]','',array('size'=>50,'maxlength'=>50)); ?>

</div>

<div class="simple">

<?php echo EHtml::activeLabelEx($model,'password_repeat'); ?>

<?php echo EHtml::passwordField('Usuario[password_repeat]','',array('size'=>50,'maxlength'=>50)); ?>

</div>

<?php else: ?>

<div class="simple">

<?php echo EHtml::activeLabelEx($model,'password'); ?>

<?php echo EHtml::activePasswordField($model,'password',array('size'=>50,'maxlength'=>50)); ?>

</div>

<div class="simple">

<?php echo EHtml::activeLabelEx($model,'password_repeat'); ?>

<?php echo EHtml::activePasswordField($model,'password_repeat',array('size'=>50,'maxlength'=>50)); ?>

</div>

<?php endif; ?>



But I have problems again with jformvalidate extension. It validates the passwords to have certain length, but if I enter "password" field, "password_repeat" field is not compared to "password" field if "password_repeat" is empty.

These are the validation rules:




	public function rules()

	{

        return  array(

    		    array('username, nombres, apellidos, email', 'required'),

    		    array('password, password_repeat', 'required', 'on' => 'insert'),

    			array('password','length','max'=>50, 'min'=>6),

			    array('password_repeat','length','max'=>50, 'min'=>6),

			    array('password_repeat', 'compare', 'compareAttribute'=>'password'),

    			array('username','length','max'=>50),

    			array('username', 'filter', 'filter'=>'strtolower'), 

    			array('username, nombres, apellidos', 'filter', 'filter'=>'trim'),

    			array('email','length','max'=>80),

    			array('email','email'));

	}



Then I tried by modifying safeAttributes method this way:




       return array('username, nombres, apellidos, administrador, email, password_repeat',

                     'insert'=> 'password',

                     'login' => 'username, password');



The password_repeat validates false, even if the password and password_repeat are the same.

I think this is the last problem to solve in order to finish the user administration form :rolleyes:

I’m wondering why documentation says nothing about this, although this is a very common task in user administration.

Thanks

Jaime

Hi Jaime,

I will take a look to this issue as soon as possible and if needed, release a new version with a fix.

I’ll keep you inform

ciao

8)

Hi Jaime,

after some investigation, I may have found a small bug on the current jformvalidate version on the way rules are applied depending in the scenario (or more exactly, the absence of scenario). Anyway, I wrote a small sample to reproduce the incorrect behavior you described (see below). With the attached version of the EJFValidate.php file the behaviour was (I think) correct. So please, could you try to replace your version of EJFValidate.php (it is located in protected/extensions/jformvalidate) by the one attached to this post and let me know if it works. If it does, I’ll release a new version this week-end.

ciao

8)

Here is the model (I use a CFormModel based model for the sample) :

[size="4"][color="#0000FF"]models/UserForm.php[/color][/size]




class UserForm extends CFormModel

{

	public $name;

	public $password;

	public $password_repeat;

	

	public function rules()

	{

		return array(

			array('name', 'required'),

			array('password', 'required', 'on' => 'insert'),

			array('password','compare', 'compareAttribute' => 'password_repeat'),

		);

	}

}



…and here are the view files both for create (insert) and update :

[size="4"][color="#0000FF"]views/site/createUser.php[/color][/size]




<h2>Create User</h2>

<?php 

	

	$this->renderPartial('_form', array(

		'user'=>$form,

		'update'=>false,

	)); 

?>



[size="4"][color="#0000FF"]views/site/updateUser.php[/color][/size]




<h2>Update User</h2>

<?php 

	$this->renderPartial('_form', array(

		'user'=>$form,

		'update'=>true,

	)); 

?>



[size="4"][color="#0000FF"]views/site/_form.php[/color][/size]




<div class="yiiForm">

<?php echo EHtml::beginForm(); ?>

<?php		

	

	EHtml::setScenario(($update ? '':'insert'));

	EHtml::setOptions(array(

		'errorContainer'=> "div.container",

		'wrapper' => 'li',

		'errorLabelContainer' => "div.container ul",

		'errorClass' => "invalid",	

		'onkeyup' 				=> false,

		'onfocusout' 			=> false

	));

?>




<?php echo EHtml::errorSummary($user); ?>

<div class="container errorSummary" style="display:none">

	<p>Please fix the following input errors:</p>

	<ul>

	</ul>

</div>

<div class="simple">

<?php echo EHtml::activeLabel($user,'name'); ?>

<?php echo EHtml::activeTextField($user,'name',array('size'=>20,'maxlength'=>128)); ?>

</div>

<div class="simple">

<?php echo EHtml::activeLabel($user,'password'); ?>

<?php echo EHtml::activeTextField($user,'password',array('rows'=>20, 'cols'=>50)); ?>

</div>

<div class="simple">

<?php echo EHtml::activeLabel($user,'password_repeat'); ?>

<?php echo EHtml::activeTextField($user,'password_repeat',array('rows'=>20, 'cols'=>50)); ?>

</div>

<div class="action">

<?php echo EHtml::submitButton($update ? 'Save' : 'Create', array('name'=>'submitPost')); ?>

</div>


<?php echo EHtml::endForm(); ?>

</div>

…and eventually the controller Action :




	public function actionCreateUser()

	{

		$form=new UserForm;

		$form->scenario = 'insert';

		// collect user input data

		if(isset($_POST['UserForm']))

		{

			$form->attributes=$_POST['UserForm'];

			// validate user input and redirect to previous page if valid

			if($form->validate())

				$this->redirect(Yii::app()->user->returnUrl);

		}

		// display the login form

		$this->render('createUser',array('form'=>$form));

	}

	public function actionUpdateUser()

	{

		$form=new UserForm;

		//$form->scenario = 'update';

		$form->name     = 'theUserName';

		

		// collect user input data

		if(isset($_POST['UserForm']))

		{

			$form->attributes=$_POST['UserForm'];

			// validate user input and redirect to previous page if valid

			if($form->validate())

				$this->redirect(Yii::app()->user->returnUrl);

		}

		// display the login form

		$this->render('updateUser',array('form'=>$form));

	}	



Hi Raoul !! and thanks for answering.

That problem was solved after I replaced the PHP file with the file you modified. But I got another problem, however this is not related to jformvalidation, I think, but maybe you have the answer :rolleyes:

After the form is submitted and if I leave password fields empty, the password is also cleared in the database table.

Trying to solve it, I used this in safeAttributes method:




return array('username, nombres, apellidos, administrador, email, password_repeat',

             'insert'=> 'password',

             'login' => 'username, password');



If I do that, form validation thinks that password_repeat is not equal to password, so it throws the corresponding error.

The original safeAttributes was:




return array('username, nombres, apellidos, administrador, email, password, password_repeat',

             'login' => 'username, password');



but in this case, validation succeeds, but if password is left blank, it is cleared also in database.

Do you know how to solve this?

Thanks a lot

Jaime

Hola Jaime,

safeAttribute is used to define which ones are the attributes that can be massively assigned depending on the scenario. In your case, it seems to me that if scenario can be used for validation, it is not appropriated for attribute update. The fact that the password should be saved to DB does not only depend on the scenario, but also on the password value.

If I understand well, here is what we’ve got :

  • scenario : [color="#0000FF"]insert [/color]- password is always saved to DB

  • scenario : [color="#0000FF"]update [/color]- password is saved to DB only if not empty

So, to implement this, when in update scenario, I would test if the password is empty, and if yes, call the CActiveRecord.save() method, otherwise call the CActiveRecord.saveAttributes() method in order to save all updated attribute except the password.

If there’s a better way to do this, I’ll be glad to learn …

Hope this helps

ciao

8)

Usually passwords should never be saved unencrypted in DB for security reasons. Therefore i would split the job up. Lets say we have a field crypted_password in DB. This can never be assigned from a form, so never use it in safeAttributes().

  1. Instead we add a "virtual attribute" $password to the model (not a DB field!):

 class User extends CActiveRecord {

    public $password;

   ...

 

We can now use scenarios to define when this attribute can be massively assigned and which validation rules should apply.

  1. Use beforeSafe() to only set/overwrite crypted_password when $password was set:

public function beforeSave() {

     if (!empty($this->password))

         $this->crypted_password=md5($this->password);

     return true;

 }

 

That way, the old value of crypted_password stays unchanged if $password was not set.

That is indeed a much cleaner and better way to handle this case.

thanks Mike

8)

Hello guys i am fairly new to the yii framework. So, i have a beforeSave function to my user.php file that hashes the password when a new user is created and it works fine.


protected function beforeSave()

        {

            if(parent::beforeSave())

            {

                if($this->isNewRecord)

                {

                    $this->salt = utf8_encode( mcrypt_create_iv(30) );

                    $newPassword = utf8_encode( crypt($this->password, $this->salt) );

                    $this->password = $newPassword;

                }

                return true;

            }

            else

                return false;

        }

But when i update an existing user’s password, it is stored to the database unhashed… what can i do?