Нам понадобятся следующие страницы:

  • Страница регистрации с формой для регистрации
  • Страница активации аккаунта
  • Страница восстановления пароля
  • Страница сброса пароля

Форма входа на сайт будет размещаться на всех страницах сайта (например, в хедере).

Вот такую страницу регистрации мы хотим получить:

Форма регистрации

Здесь тип учетной записи будет определять, в какую группу мы зарегистрируем пользователя. Так же полем для идентификации (username) будет электронная почта.

Добавим на страницу вызов сниппета Register:

[[!Register?
    &submitVar=`register-btn`
    &activationResourceId=`27`
    &activationEmailTpl=`Email.Activation`
    &activationEmailSubject=`Вы зарегистрированы на сайте example.com`
    &placeholderPrefix=`reg.`
    &successMsg=`<div class="alert alert-success">Спасибо за регистрацию. На вашу электронную почту <b>[[!+reg.email]]</b> отправлено письмо со ссылкой на активацию аккаунта. Пройдите по этой ссылке, чтобы завершить регистрацию. </div>`
    &usernameField=`email`
    &usergroupsField=`reg_type`
    &customValidators=`valueIn`
    &validate=`username:blank,
        reg_type:valueIn=^Readers;Writers;Idlers ^,
        fullname:required:minLength=^6^,
        password:required:minLength=^6^,
        password_confirm:password_confirm=^password^,
        email:required:email`
    ]]
[[!+error.message:default=`[[!$Register.Form]]`]]


Обратите внимание, что все теги, касающиеся регистрации нужно обязательно вызывать некешированными. Такие же правила действуют при обработке форм сниппетом FormIt.

Разберем параметры вызова:

&submitVar=`register-btn` — указывает атрибут name тега input[type=submit]. То есть сниппет сработает, только если отправлена форма кнопкой с определенным именем.

&activationResourceId=`42` — забегая вперед, 42 – это идентификатор страницы, на которой мы будет активировать пользователя.

&activationEmailTpl=`Email.Activation` — чанк с письмом активации, о нем позже.

&placeholderPrefix=`reg.` — указывает, что все плейсхолдеры, за редким исключением (об этом дальше), которые создаются в данном сниппете, должны начинаться с «reg.».

&successMsg – сообщение, которое выведется при успешной отправке формы. Заметьте, что в нем можно указывать значения из формы и любые другие теги. Данное сообщение запишется в плейсхолдер [[!+error.message]]. Довольно странное название, да и в документации на данный момент ошибка. Там написано [[!+reg.error.message]], но из кода компонента следует, что это не так.

&usernameField=`email` — указывает, что в качестве имени пользователя будет использоваться поле email.

&usergroupsField=`reg_type` — определяет поле, устанавливающее группу, в которую будет добавлен новый пользователь.

&customValidators=`valueIn` — указывает дополнительные валидаторы, которые нужно создать вручную.

&validate – валидаторы задаются через запятую для каждого поля, а если требуется несколько валидаторов для одного поля, то они еще разделяются двоеточием. Разберем их отдельно:

username:blank – нехитрая ловушка для спама, означает, что поле username должно остаться пустое.

reg_type:valueIn=^Readers;Writers;Idlers^ — ограничиваем возможные группы тремя указанными. В изначальной поставке такого нет и злые хацкеры могут зарегиться, например, под группой Administrator (если вы ее не переименовали).

fullname:required:minLength=^6^ — поле fullname должно быть не пусто и содержать хотя бы 6 символов.

password:required:minLength=^6^ — аналогично для пароля.

password_confirm:password_confirm=^password^ — пароли должны совпадать.

email:required:email – электронная почта должна быть не пуста и являться собственно почтой.

Конструкция [[!+error.message:default=`[[!$Register.Form]]`]] выводит сообщение об успешной отправке формы или чанк формы, если вы только зашли на страницу или заполнили ее неправильно.

Создадим вышеупомянутый валидатор valueIn. Для этого создаем сниппет с названием valueIn и следующим кодом:

$valueIn = explode(';', $param);
return in_array($value, $valueIn);

Теперь требуется создать чанк Register.Form. В данном случае он будет следующим (используется Bootstrap 3):

<form action="[[~[[*id]]]]" method="POST" class="form-horizontal" id="register_form">
    <div class="form-group">
        <label class="col-xs-4 control-label">Выберите тип учетной записи</label>
        <div class="col-xs-8">
            <div class="radio">
                <label>
<input type="radio" name="reg_type" id="reg_type1" value="Readers" [[!+reg.reg_type:is=`Readers`:then=`checked`:else=``:default=`checked`]]>
					Чукча-Читатель
                </label>
            </div>
            <div class="radio">
                <label>
<input type="radio" name="reg_type" id="reg_type2" value="Writers" [[!+reg.reg_type:is=`Writers`:then=`checked`:else=``]]>
					Пейсатель
                </label>
            </div>
            <div class="radio">
                <label>
<input type="radio" name="reg_type" id="reg_type3" value="Idlers" [[!+reg.reg_type:is=`Idlers`:then=`checked`:else=``]]>
					Бездельник
                </label>
            </div>
        </div>
    </div>
    <div class="form-group" id="reg_name">
        <label class="col-xs-4 control-label" for="name">Представьтесь:</label>
        <div class="col-xs-8">
            <input type="text" name="fullname" class="form-control" value="[[!+reg.fullname]]">
            [[!+reg.error.fullname:notempty=`<div class="alert alert-danger">[[!+reg.error.fullname]]</div>`]]
        </div>
    </div>
    <div class="form-group" id="reg_email">
        <label class="col-xs-4 control-label" for="email">Электронная почта:</label>
        <div class="col-xs-8">
            <input type="text" name="email" class="form-control" value="[[!+reg.email]]">
            [[!+reg.error.email:notempty=`<div class="alert alert-danger">[[!+reg.error.email]]</div>`]]
        </div>
    </div>
    <div class="form-group" id="reg_password">
        <label class="col-xs-4 control-label" for="password">Пароль:</label>
        <div class="col-xs-8">
            <input type="password" name="password" class="form-control">
            [[!+reg.error.password:notempty=`<div class="alert alert-danger">[[!+reg.error.password]]</div>`]]
        </div>
    </div>
    <div class="form-group" id="reg_password_confirm">
        <label class="col-xs-4 control-label" for="name">Повторите пароль:</label>
        <div class="col-xs-8">
            <input type="password" name="password_confirm" class="form-control">
            [[!+reg.error.password_confirm:notempty=`<div class="alert alert-danger">[[!+reg.error.password_confirm]]</div>`]]
            <input type="hidden" name="username">
        </div>
    </div>
    <div class="form-group">
        <div class="col-xs-12">
            <input type="submit" name="register-btn" class="btn btn-success pull-right" value="Зарегистрироваться">
        </div>
    </div>
    <div class="form-group">
        <div class="col-xs-12">
            <p class="pull-right">Все поля обязательны для заполнения</p>
        </div>
    </div>
</form>


В этой форме замечу несколько вещей, касающихся MODX:


<form action="[[~[[*id]]]]" – форма обрабатывается на той же странице, на которой показывается.

— установка значения из полученных с формы для того, чтобы в случае провала пользователю не пришлось вводить все заново.

[[!+reg.error.email:notempty=`[[!+reg.error.email]]`]] – опять же в случае провала под полем отобразится сообщение об ошибке.

<input type=«submit» name=«register-btn» – обязательно указываем имя кнопки, если ранее устанавливали свойство &submitVar.


Теперь осталось создать чанк Email.Activation с письмом, которое сайт отправляет пользователю:

<p>Спасибо за регистрацию! Чтобы активировать ваш аккаунт, пожалуйста, перейдите по следующей ссылке:</p>
<p><a href="[[+confirmUrl]]">Активировать аккаунт на сайте Example.Com</a></p>
<p>После активации, вы сможете войти, указав электронную почту и пароль:</p>
<p><strong>Логин:</strong> [[+email]]</p>
<p><strong>Пароль:</strong> [[+password]]</p>

Здесь можно использовать плейсхолдеры с именами полей формы. Заметьте, что они пишутся уже без «reg.». Так же добавляется плейсхолдер [[+confirmUrl]], в котором уже сгенерирована ссылка для активации, даже ничего не нужно делать.


Последним штрихом в искусстве регистрации нового аккаунта с помощью компонента Login будет создание страницы активации. Используем для этой страницы пустой шаблон, а в контенте страницы нужно указать всего лишь вызов тега:

[[!ConfirmRegister? &redirectTo=`1`]]

где 1 – идентификатор страницы, на которую пользователь перейдет в случае успешной активации. При этом он уже будет залогиненным.


Приступим к настройке входа в профиль пользователя. Форма авторизации будет простая:

Форма логина

Добавим ее, вызвав в нужном месте:

[[!Login? &loginTpl=`Auth.Login` &logoutTpl=`Auth.Logout` &errTpl=`Auth.Login.Error`

    &actionKey=`action` &loginKey=`login` &redirectToPrior=`1` &logoutResourceId=`1`]]

Здесь мы указываем чанк с формой для входа, нарисованной выше (&loginTpl=`Auth.Login`), чанк с кодом, показывающимся авторизованным пользователям (&logoutTpl=`Auth.Logout`), небольшой чанк с выводом ошибки входа (&errTpl=`Auth.Login.Error`). Далее следуют параметры:

&actionKey=`action` и &loginKey=`login` — основные идентификаторы для обработки запроса. Первый означает имя параметра в POST-запросе, а второй его значение. То есть форма должна передать значение $_POST['action']='login', чтобы сниппет Login ее обработал.

&redirectToPrior=`1` — означает, что после входа мы переместимся на ту же страницу, с которой входили.

&logoutResourceId=`1` — при выходе из профиля мы перейдем на страницу с идентификатором 1.


Чанк Auth.Login:

<form method="post" action="[[~[[*id]]]]" class="form-inline">
    <div class="row">
        <div class="form-group col-lg-5">
            <label for="authLogin">Логин</label>
            <input type="email" name="username" class="form-control" id="authLogin">
            <div class="row registerLink">
                <a class="strong" href="[[~14]]">Регистрация</a>
            </div>
        </div>
        <div class="form-group col-lg-5">
            <label for="authPass">Пароль</label>
            <input type="password" name="password" class="form-control" id="authPass">
            <div class="row registerLink">
                <a class="strong" href="[[~28]]">Забыли пароль?</a>
            </div>
        </div>
        <div class="form-group col-lg-2">
            <label for="authPass">Вход</label>
            <input type="hidden" name="action" value="login">
            <button type="submit" class="authButton"><img src="img/enter-button.png" alt="Войти">
            </button>
        </div>
    </div>
</form>
[[!+errors]]

Форма обрабатывается на этой же странице. Если случилась ошибка, то она будет выведена ниже формы в плейсхолдере [[!+errors]]. Так же нужно не забыть ссылки на ресурсы с Регистрацией и восстановлением пароля. Заметьте, что в поле для электронной почты name=«username» – именно в это поле продублировал почту сниппет Register, и оно является для пользователей уникальным.


Чанк Auth.Logout:

[[pdoUsers? &users=`[[+modx.user.id]]` &tpl=`User.HeaderBadge`
    &innerJoin=`{"modUserGroupMember":{"alias":"modUserGroupMember","on":"modUser.id = modUserGroupMember.member"}, "modUserGroup":{"alias":"modUserGroup", "on":"modUserGroupMember.user_group = modUserGroup.id"}}`
    &select=`{"modUserGroup":{"group_name": "modUserGroup.name"}}`
]]
<div class="row">
    <div class="col-md-6 col-md-offset-6">
        <a href="[[+logoutUrl]]">Выход из профиля</a>
    </div>
</div>

Эта часть не обязательна, если все пользователи в одной группе. Но, чтобы отобразить группу пользователя, стандартных возможностей сниппетов, входящих в компонент Login, недостаточно. Можно написать простенький сниппет для получения названия группы на xPDO, а можно воспользоваться уже готовым сниппетом pdoUsers, входящим в пакет pdoTools. Параметры, указанные в этом сниппете:

&users=`[[+modx.user.id]]` — выбираем только текущего авторизованного пользователя.

&tpl=`User.HeaderBadge` — чанк, в котором мы выведем краткую информацию о пользователе.

&innerJoin – JSON с джойнами таблиц групп пользователей, описание выходит за рамки статьи. Главное это работает J.

&select – JSON, добавляющий в выборку поле modUserGroup.name с алиасом group_name.


Так же за создание ссылки на выход из профиля отвечает плейсхолдер [[+logoutUrl]]. Другой вариант – создать ссылку на главную страницу с параметрами [[~1? &action=`logout`]].


Чанк с бейджиком пользователя User.HeaderBadge:

<div class="row">
    <div class="col-md-2 col-md-offset-4">
        <img src="[[+photo:phpthumbon=`&w=50&h=50`]]" alt="Аватар пользователя">
    </div>
    <div class="col-md-6">
        Вы вошли как <b>[[+group_name]]</b>
        [[+fullname]]
        <a href="[[~4? &id=`[[+id]]`]]">Личный кабинет</a>
    </div>
</div>

Если бы нам не нужна была группа пользователя, то содержимое этого чанка можно было бы вставить сразу в чанк Auth.Logout. Здесь можно выводить плейсхолдеры с любыми полями modUser и modUserProfile плюс c помощью pdoUsers добавилось поле group_name.


В чанке Auth.Login.Error простой вывод ошибки:

<div class="alert alert-danger">[[+msg]]</div>


С логином мы закончили. На этом этапе пользователь может зарегистрироваться и успешно войти. Но что, если он забыл пароль? В этом случае он жмякает на ссылку «Забыли пароль?» и переходит на страницу восстановления пароля, которую мы предварительно создаем и помещаем туда вызов:

[[!ForgotPassword? &tpl=`Auth.ForgotPass.Form` &submitVar=`forgotpass` &errTpl=`Auth.Login.Error` &sentTpl=`Auth.ForgotPass.Sent`
&emailTpl=`Email.ForgotPass` &emailSubject=`Восстановление доступа к аккаунту на сайте Example.Com` &resetResourceId=`29`]]

Разберем параметры этого вызова:

&tpl=`Auth.ForgotPass.Form` — чанк формы, в которой пользователь введет свой email.

&submitVar=`forgotpass` — в случае сниппета ForgotPassword достаточно, чтобы параметр с таким названием был передан на сервер и не важно с каким непустым значением.

&errTpl=`Auth.Login.Error` — вывод ошибки аналогично сниппету Login

&sentTpl=`Auth.ForgotPass.Sent` — в этом чанке содержится контент, который будет выведен в случае успешной отправки письма на смену пароля.

&emailTpl=`Email.ForgotPass` — собственно письмо содержится здесь.

&emailSubject=`Восстановление доступа к аккаунту на сайте Example.Com` — заголовок письма.

&resetResourceId=`29` — идентификатор ресурса, на котором будет производиться сброс пароля на новый.


Чанк Auth.ForgotPass.Form:

<form method="post" action="[[~[[*id]]]]" class="form-inline">
    <div class="row">
        <div class="form-group col-lg-5">
            <label for="authLogin">Электронная почта</label>
            <input type="email" name="username" class="form-control">
            [[+loginfp.errors]]
        </div>
        <div class="form-group col-lg-7">
            <label>Нажмите кнопку, и на выбранную электронную почту придет письмо для восстановления пароля.</label>
            <input type="hidden" name="forgotpass" value="1">
            <input type="submit" class="btn btn-info" name="login_fp" value="Сбросить пароль" />
        </div>
    </div>
</form>
<p>Нового здесь только другой способ вывода ошибок в плейсхолдере <strong>[[+loginfp.errors]]</strong> и передача параметра, что именно эта форма сбрасывает пароль: <strong><input type="hidden" name="forgotpass" value="1">.</strong></p>
<p> </p>
<p><strong><em>Auth.ForgotPass.Sent:</em></strong></p>
<code>
<div class="alert alert-info">Информация по восстановлению аккаунта выслана на указанный электронный ящик: <b>[[+email]]</b>.</div>

Здесь можно использовать данные из формы выше.


Email.ForgotPass:

<p>[[+fullname]],</p>
<p>Чтобы активировать свой новый пароль, пожалуйста, перейдите по следующей ссылке:</p>
<p><a href="[[+confirmUrl]]">Хочу новый пароль</a></p>
<p>Если все прошло успешно, вы сможете войти в свой профиль с помощью следующих данных:</p>
<p><strong>Логин:</strong> [[+username]]</p>
<p><strong>Пароль:</strong> [[+password]]</p>
<p>Не переходите по ссылке, если вы не заказывали смену пароля.</p>
<p>Спасибо,<br />
<em>Администрация сайта Example.Com</em></p>


Все очень похоже на чанк письма активации, только пароль здесь сгенерированный в сниппете.


Завершающим штрихом осталось создать ресурс, на который пользователь будет переходить из письма для обновления пароля. В этом ресурсе нам нужен вызов:

[[!ResetPassword:empty=`<div class="alert alert-warning">
<p>Вы не заказывали сброс пароля. Возможно вы ошиблись адресом. Вы можете перейти на <a href="[[~1]]">Главную</a> страницу сайта или воспользоваться меню выше.</div>`? &tpl=`Auth.ForgotPass.Reset`]]</p>
<p>Этот код выведет сообщение, если вдруг кто-то забредет на данную страницу повторно или просто случайно. А если пароль успешно сброшен, выведется сообщение из чанка <strong><em>Auth.ForgotPass.Reset</em></strong>:</p>
<div class="alert alert-info">
Ваш пароль успешно сброшен на указанный в письме. Теперь вы можете войти под этим паролем. Не забудьте сменить его в профиле.
</div>


Теперь мы имеем полностью рабочую систему авторизации и регистрации пользователей. Изменение профиля авторизованных пользователей оставим за рамками статьи.


Итак краткий план настройки авторизации и регистрации:

1. Создаем страницу регистрации и добавляем на нее вызов сниппета Register.

2. Создаем чанки с формой регистрации Register.Form и письмом активации Email.Activation.

3. Создаем страницу подтверждения регистрации и размещаем на ней вызов сниппета ConfirmRegister.

4. Добавляем вызов сниппета Login туда, где хотим разместить форму входа и бейджик авторизованного пользователя.

5. Создаем чанк с формой входа Auth.Login, чанк с информацией об авторизованном пользователе Auth.Logout, чанк с сообщением об ошибке Auth.Login.Error.

6. Создаем страницу восстановления пароля и размещаем на ней вызов сниппета ForgotPassword.

7. Создаем чанк Auth.ForgotPass.Form с формой для восстановления пароля, чанк Auth.ForgotPass.Sent с сообщением об успешной отправке письма, чанк Email.ForgotPass с письмом на сброс пароля.

8. Создаем ресурс с окончательным сбросом пароля и размещаем в нем вызов сниппета ResetPassword.

9. Создаем чанк Auth.ForgotPass.Reset с сообщением об успешном сбросе пароля.

На этом все. Буду рад любым дополнениям и замечаниям.