12 Июль 2011

Изменение пароля пользователя. Часть 22

Изменение пароля на Kohana

Приветствую вас, уважаемый читатель! В данной статье я хочу описать процесс создания идеальной формы изменения пароля для проекта на фреймворке Kohana 3.1.

Идеальная форма изменения пароля, на мой взгляд, должна отвечать следующим критериям (внешний вид формы на рисунке выше):

  • Введенный в верхнее поле ввода старый пароль должен быть проверен, посредством ajax-запроса, на отсутствие ошибок. Сразу после ввода старого пароля пользователь должен быть проинформирован о результатах этой проверки.
  • Полей для ввода нового пароля должно быть два, дабы минимизировать возможную ошибку. Значения в полях сравниваются при помощи JavaScript. Информация о соответствии значений полей нового пароля друг-другу выводится до отправки данных формы.
  • Присутствует возможность отключить замену вводимых символов звездочками (это для пущей гламурности).
  • При этом никто не отменял необходимость валидации значений, которые ввел пользователь в нашу форму, на стороне сервера.

Какие знания нам потребуются:

А теперь приступим.

Видеоурок

Скачать видеоурок (67.4 МБ, *.wmv)

Скачать исходники (1.16 МБ, *.wmv)

Для пущей идеальности формы, ко всему сказанному видеоуроке я бы добавил дополнительную валидацию в модели на предмет минимального и максимального количества символов в новом пароле. Максимальное количество не должно превышать 64 символа (это ограничение длины записи в поле password таблицы users). Информацию о валидационных правилах можно почерпнуть из официального руководства по Kohana.
Личный кабинет
Личный кабинет пользователя — оптимальное место для размещения функционала по смене пароля.

Думаю, что ссылка kohana/account будет весьма подходящей для перехода в личный кабинет. Соответственно, нам потребуется новый контроллер kohana\www\application\classes\controller\account.php, в котором разместится код, отвечающий за отображение страницы личного кабинета.

Листинг 1. Контроллер Account

<?php defined('SYSPATH') or die('No direct script access.');

class Controller_Account extends Mycontrollerlogin {

	public $template = 'basic';

	public function action_index()
	{
		$data = array();
		$myuser = new Model_Myuser();
		$data['username'] = $myuser->displayusername();

		if(isset($_POST['btnsubmit']))
		{
			$oldpass = Arr::get($_POST, 'oldpass', '');
			$newpass1 = Arr::get($_POST, 'newpass1', '');
			$newpass2 = Arr::get($_POST, 'newpass2', '');

			if($myuser->saveNewPass($oldpass, $newpass1, $newpass2))
			{
				$data['newpassok'] = '';
			}
			else
			{
				$data['errors'] = $myuser->getErrors();
			}
		}
		$this->template->content =  View::factory('accountview', $data);
	}

}

Пояснение к листингу 1

9-я строка. Создаем экземпляр старой доброй модели Myuser (которую мы создали еще в 11-м видеоуроке по Kohana), которую мы наделим новыми методами. Код обновленной модели Myuser я приведу в листинге 2.

10-я строка. Один из новых методов displayusername(), возвращающий имя авторизованного в данный момент пользователя. Полученное от метода имя пользователя сохраняется в поле username массива data и дальше будет участвовать в формировании внешнего вида страницы личного кабинета.

12-я строка. Если была нажата кнопка btnsubmit (она же «Сохранить новый пароль», см. верхний рисунок) — выполнится код с 13 по 26 строки.

14 — 16 строки. От пользователя глобальным массивом пост передаются значения полей ввода «Старый пароль», «Новый пароль», «Повторите новый пароль» (oldpass, newpass1 и newpass2 соответственно).

18-я строка. Метод saveNewPass (также новый метод модели Myuser) выполняет внутреннюю валидацию введенных пользователем значений и, в случае успешной валидации, сохраняет новый пароль в базу данных. Если метод успешно сохранил новый пароль, он возвратит значение TRUE, иначе — FALSE.

20-я строка. В случае успешного сохранения нового пароля в массиве data будет создано поле newpassok с пустым значением. В виде (листинг 3) мы проверим существование данной переменной и, в случае ее наличия, выведем сообщение об успешной смене пароля.

24-я строка. Если, по той или иной причине, новый пароль сохранить не удалось — в массиве data создается поле errors, которое будет содержать информацию об ошибках, возвращаемых новым методом getErrors() модели Myuser. В дальнейшем содержимое поля будет выведено в вид (см. листинг 3).

27-я строка. В формировании отображения страницы участвует шаблон kohana\www\application\views\accountview.php (листинг 3) и массив data.

Пришло время посмотреть на изменения модели Myuser (kohana\www\application\classes\model\myuser.php).

Листинг 2. Модель Myuser

public function displayusername()
	{
		$auth = Auth::instance();
		$userId = $auth->get_user();

		$usertemp = ORM::factory('myuser', array('id'=>$userId));

		return $usertemp->username;
	}

public function checkOldPass($oldpass)
	{
		$auth = Auth::instance();
		return $auth->check_password($oldpass);
	}

public function saveNewPass($oldpass, $newpass1, $newpass2)
	{
		$vData = array("oldpass" => $oldpass, "newpass1" => $newpass1, "newpass2" => $newpass2,);

		$validation = Validation::factory($vData);
                $validation->rule('oldpass', 'not_empty');
                $validation->rule('oldpass', 'alpha_numeric');
		$validation->rule('oldpass', array($this, 'checkOldPass'));
		$validation->rule('newpass1', 'not_empty');
                $validation->rule('newpass1', 'alpha_numeric');
		$validation->rule('newpass1', 'matches', array(':validation', 'newpass1', 'newpass2'));

		if(!$validation->check())
        {
			$this->errors = $validation->errors('catErrors');
			return FALSE;
        }

		$auth = Auth::instance();
		$userId = $auth->get_user();
		
		$usertemp = ORM::factory('myuser', array('id'=>$userId));
		$usertemp->password = $auth->hash_password($newpass1);
		$usertemp->save();

		return TRUE;
	}

public function getErrors()
    {
        return $this->errors;
    }

Под строкой объявления имени таблицы базы данных (protected $_table_name = 'users';) я объявил новый массив errors с модификатором доступа protected (к данному массиву можно будет иметь доступ из самой модели Myuser и из ее наследников). В массиве errors будут храниться ошибки валидации.

protected $errors = array();

Далее идет код четырех новых методов (пояснение ниже).

Пояснение к листингу 2

1-я строка. Объявляется вспомогательный метод displayusername(), возвращающий имя авторизованного в данный момент пользователя.

4-я строка. Метод get_user() класса авторизации Auth, возвращает идентификатор авторизованного в данный момент пользователя. Очень удобный метод.

11-я строка. Объявляется метод checkOldPass, который будет обеспечивать проверку на совпадение пароля залогиненного в данный момент пользователя аргументу $oldpass. Проверка осуществляется посредством метода check_password класса авторизации (14-я строка листинга 2). В качестве аргумента методу checkOldPass мы будем передавать старый пароль пользователя (старый пароль мы получаем в строке 14 листинга 1).

17-я строка. Объявляется основной метод для сохранения нового пароля saveNewPass(). Данный метод будет осуществлять валидацию в модели (с валидацией в модели мы уже знакомились в 17-м видеоуроке по Kohana).

19-я строка. Подготавливается массив, содержащий аргументы, нуждающиеся в валидации.

21 — 27 строки. Валидационные правила. Дополнительное внимание прошу обратить на строку 27, в которой используется новое правило валидации, проверяющее совпадение аргументов newpass1 и newpass2.

29 — 33 строки. Выполняется метод check(), который осуществляет валидацию полей массива vData, согласно валидационным правилам из 21 — 27 строк. В случае ошибки валидации метод сгенерирует сообщение об ошибках, которое сохранятся в свойство errors (errors – массив, объявленный строкой protected $errors = array();) и вернет значение FALSE.

35 — 42 строки. Уже знакомым образом осуществляется загрузка строки из таблицы user, соответствующей авторизованному пользователю, в объект $usertemp. В объекте $usertemp осуществляется замена свойства password значением нового пароля (пароль хешируется). После чего строка с обновленным паролем сохраняется в базу данных и метод возвращает значение TRUE.

45 — 48 строки. Объявляется вспомогательный метод getErrors(), возвращающий сообщение об ошибках, содержащихся в свойстве errors экземпляра класса Myuser. Поскольку свойство errors объявлено с модификатором доступа protected, то возникает необходимость воспользоваться дополнительным методом getErrors(), возвращающим значение данного свойства, иначе из контроллера Account (который не является наследником Myuser) к свойству доступа не получить.

Плавно переходим к логике отображения и рассматриваем листинг файла kohana\www\application\views\accountview.php

Листинг 3. Файл, отвечающий за логику отображения личного кабинета accountview.php

<!--Вызов отображение формы логина-->
<?
$request = Request::factory('auth/form/1');
$response = $request->execute();
echo $response;
?>
<!-- / Вызов отображение формы логина-->

<!-- Вывод сообщения об успешной смене пароля -->
<?if(isset($newpassok)){?>
	<p style="text-align:center; color:green;">
		Новый пароль успешно сохранен
	</p>
<?}?>
<!-- /Вывод сообщения об успешной смене пароля -->

<!-- Вывод ошибок валидации при сохранении нового пароля -->
<? if(isset($errors)){?>
<?foreach($errors as $item){?>
<p style="color:red"><?=$item?></p>
<?}?>
<?}?>
<!-- / Вывод ошибок валидации при сохранении нового пароля -->

<h2>Личный кабинет пользователя</h2>
<p>Имя пользователя и адрес эл. почты: <?=$username?></p>
<p><b>Если хотите сменить пароль:</b> <input type="checkbox" id="showpassbtn"><label for="showpassbtn"> Не прятать пароль за звездочки</label></p>
<form action="" method="post">
	<table class="changepassword">
		<tr>
			<td style="text-align: right">Старый пароль:</td>
			<td><input type="password" name="oldpass" id="oldpass"></td>
			<td>
				<span style="display: none" id="oldpassok"><img src="/img/ok.png" title="Старый пароль введен правильно" alt="Старый пароль введен правильно"></span>
				<span style="display: none" id="oldpasserror"><img src="/img/error.png" title="Ошибка в старом пароле" alt="Ошибка в старом пароле"></span>
			</td>
		</tr>
		<tr>
			<td style="text-align: right">Новый пароль:</td>
			<td><input type="password" name="newpass1" id="newpass1"></td>
			<td>&nbsp;</td>
		</tr>
		<tr>
			<td style="text-align: right">Повторите новый пароль:</td>
			<td><input type="password" name="newpass2" id="newpass2"></td>
			<td>
				<span style="display: none" id="newpassmatchesok"><img src="/img/ok.png" title="Пароли совпадают" alt="Пароли совпадают"></span>
				<span style="display: none" id="newpassmatcheserror"><img src="/img/error.png" title="Пароли несовпадают" alt="Пароли несовпадают"></span>
			</td>
		</tr>
		<tr>
			<td>&nbsp;</td>
			<td><input type="submit" value="Сохранить новый пароль" name="btnsubmit"></td>
		</tr>
	</table>
</form>

Пояснения к листингу 3

27-я строка. Хочу особое ваше внимание, уважаемый читатель, обратить на строку 27. В верстке чекбокса «Не прятать пароль за звездочки» я применил тег <label>, который, благодаря атрибуту for, позволяет кликать по надписи в теге <label> и, тем самым, ставить/снимать галочку в связанном чекбоксе.

34 — 35 строка. Файл ok.png содержит изображение зеленой галочки, а файл error.png — восклицательного знака на красном фоне.

JavaScript, формирующий Ajax-запрос для проверки правильности ввода старого пароля и проверки совпадения новых паролей приведен в листинге 4.

Листинг 4. JavaScript

<script type="text/javascript">
	function checkOldPass()
	{
		var oldpass = $("#oldpass").val();

		$.ajax({
		  type: "POST",
		  data: "oldpass=" + oldpass,
		  url: "/ajax/checkOldPass",
		  dataType: "json",
		  success: function(data)
		  {
			if(data.result)
			{
				$("#oldpassok").css('display','inline');
				$("#oldpasserror").css('display','none');
			}
			else
			{
				$("#oldpasserror").css('display','inline');
				$("#oldpassok").css('display','none');
			}
		  }
		})
	}

	function showPass()
	{
		var checked = $("#showpassbtn").attr('checked');

		if(checked == "checked")
		{
			document.getElementById('oldpass').type = 'text';
			document.getElementById('newpass1').type = 'text';
			document.getElementById('newpass2').type = 'text';
		}
		else
		{
			document.getElementById('oldpass').type = 'password';
			document.getElementById('newpass1').type = 'password';
			document.getElementById('newpass2').type = 'password';
		}
	}

	function matchesPass()
	{
		if($("#newpass1").val() == $("#newpass2").val())
		{
			$("#newpassmatchesok").css('display','inline');
			$("#newpassmatcheserror").css('display','none');
		}
		else
		{
			$("#newpassmatcheserror").css('display','inline');
			$("#newpassmatchesok").css('display','none');
		}
	}

	$(document).ready(function(){
		$("#oldpass").blur(checkOldPass);
		$("#showpassbtn").click(showPass);
		$("#newpass2").keyup(matchesPass);
	});
</script>

Метод checkOldPass, возвращающий результат проверки правильности ввода старого пароля Ajax-запросу приведен в листинге 5.

Листинг 5. Метод checkOldPass, проверяющий правильность ввода старого пароля (файл kohana\www\application\classes\controller\ajax.php, данный файл мы создавали в видеоуроке по прикручиванию Ajax к Kohana 3).

public function action_checkOldPass()
	{
		$oldpass = Arr::get($_POST, 'oldpass', '');

		$myuser = new Model_Myuser();
		$res = $myuser->checkOldPass($oldpass);

		echo json_encode(array('result' => $res));
	}

Для замены стандартного сообщения об ошибках валидации я дополнил файл kohana\www\application\messages\catErrors.php (данный файл мы создавали в 17-м видеоуроке по PHP).

Листинг 6. Файл валидационных сообщений catErrors.php

<?
return array(
    'name' => array(
        'not_empty' => 'Вы не ввели название категории.',
 ),
	'parentId' => array(
		'check_id' => 'Мухлеж.',	
),
	'oldpass' => array(
		'not_empty' => 'Вы не ввели старый пароль',
		'checkOldPass' => 'Ошибка в старом пароле',
),
	'newpass1' => array(
		'not_empty' => 'Вы не ввели новый пароль',
		'matches' => 'Новые пароли должны совпадать',
),
);

Постовой на сегодня (как сюда попасть):

Эффективное поисковое продвижение сайтов - MaxTops.ru - Мурманск

С уважением, Андрей Морковин.

<< Предыдущий видеоурок курса Следующий видеоурок курса >>

MAXCACHE: 0.87MB/0.00030 sec