Brilliant!
Employing your hints, it was a snap to do. For other interested readers, this is the whole story (I hope I did not forget something):
(1) Enrich the model class "User" with two password fields (which are not database fields):
class User extends CActiveRecord
{
public $new_password;
public $new_password_repeat;
[...]
(2) Also in the User model class, set up validation rules for comparing the passwords in case they are entered (that is, in case the scenario is "changePassword" - or "insert" where the password has to be entered in any case):
public function rules()
{
return array(
array('username, salt', 'required'),
array('username', 'length', 'max'=>45),
// THIS IS THE NEW STUFF
array('new_password', 'length', 'max'=>50),
array('new_password', 'compare', 'on'=>'insert, changePassword'),
array('new_password_repeat', 'safe'),
array('new_password, new_password_repeat', 'required', 'on'=>'insert'),
[...]
So, in scenario "update", the new_password will not be validated by the compare validator, but on scenario "changePassword" it will. Now we care about switching the scenario: in the update case, the scenario is "update" by default. In case the user enters something into the "new_password" field, switch the scenario to "changePassword", so that the newly defined password validator will be called by $model->validate():
Furthermore, in case of creating a new user, the new password should be required (last rule in the code above).
(3) Scenario switching:
In the UserController, the function actionUpdate() gets additional logic and looks like this:
public function actionUpdate($id)
{
$model=$this->loadModel($id);
if(isset($_POST['User']))
{
$model->attributes=$_POST['User'];
// if a new password has been entered
if ($model->new_password !== '') {
// set scenario 'changePassword' in order
// for the compare validator to be called
$model->setScenario('changePassword');
}
if ($model->validate())
{
if ($model->new_password !== '') {
$model->password = $model->hashPassword($model->new_password, $model->salt);
}
// the validation has already been done, skipping it with save(false):
if($model->save(false))
$this->redirect(array('view','id'=>$model->user_id));
}
}
$this->render('update',array(
'model'=>$model,
));
}
(4) The hashPassword method could be something like (in the model file User.php):
public function hashPassword($password, $salt)
{
return md5($salt.$password);
}
(5) And finally in the view (user/_form.php), refer to the new password fields (not the database field):
[...]
<div class="row">
<?php echo $form->labelEx($model,'new_password'); ?>
<?php echo $form->passwordField($model,'new_password',array('size'=>50,'maxlength'=>50)); ?>
<?php echo $form->error($model,'new_password'); ?>
</div>
<div class="row">
<?php echo $form->labelEx($model,'new_password_repeat'); ?>
<?php echo $form->passwordField($model,'new_password_repeat',array('size'=>50,'maxlength'=>50)); ?>
<?php echo $form->error($model,'new_password_repeat'); ?>
</div>
[...]
(6) Modify the case "Create new user"
So far we only took care of the update case (Controller: actionUpdate). When creating a new user, the password field would now stay empty because the encryption of the field “new_password” and assignment to the model’s password field takes only place in UserController.actionUpdate(), but not in UserController.actionCreate(). I could now duplicate some code for actionCreate(), but I decided to solve the task with a simple afterValidate routine which only does the job of encrypting/assigning in the “insert” scenario.
I am not sure if this is the most elegant way to do so, but it works. So add the following function to the User model:
public function afterValidate()
{
parent::afterValidate();
if ($this->getScenario() === 'insert')
$this->password = $this->hashPassword($this->new_password, $this->salt);
}
This is it! Now if a user is created, thanks to the scenario "insert", the password is required and gets validated. In the update case, if the user enters a new password, thanks to the scenario "changePassword", the new password will be validated and saved along with all other entered data. In the update case, if the user does not enter a new password, the scenario will remain "update" and the new password is ignored - only the other entered attributes are saved.
Wonderful! Many thanks, lgoss!