Сегодня я покажу, как можно сделать регистрацию пользователей в Друпал 7 пошаговой, используя стандартную форму регистрации. Данный метод не является универсальным, т.к. в нем идет идет привязка к конкретным полям на форме. Для своей формы придется потанцевать с бубном самостоятельно.
Итак, чтобы цвет волос окончательно стал седым, усложним задачу:
- дополнительно ограничим допустимые символы в имени пользователя (оставим только латинский алфавит, цифры и несколько дополнительных символов);
- позволим пользователю выбирать роль (модуль Auto Assign Role) и в зависимости от выбранного значения сделаем разный набор полей;
- обеспечим совместимость с модулем Ajax Login/Register (формы авторизации, регистрации в всплывающем окне);
- добавим Лицензионное соглашение (Ознакомление и согласие с правилами работы нашего интернет-ресурса) - модуль Legal.
Шаг 1. Подготовка
Этот этап нет смысла описывать подробно, поэтому вкратце:
Добавим две дополнительные роли на сайте:
- Физическое лицо
- Юридическое лицо
С помощью модуля Auto Assign Role разрешим пользователям выбирать свою роль при регистрации. На форме регистрации должны появится соответствующие радиокнопки.
Установим и настроим модуль Ajax Login/Register.
Установим и настроим модуль Legal. На форме регистрации должны появиться поле с Правилами и чекбокс "Я согласен".
Используя стандартный интерфейс Друпала укомплектуем форму регистрации необходимыми полями.
Совет для тех, кто не знает, где можно добавить свои поля для профиля пользователя - может сейчас самое время остановиться и не читать дальше?
Я создал на своей форме следующие дополнительные поля:
Имя поля | Тип | Описание |
---|---|---|
Для роли "Физическое лицо" | ||
field_user_fio | Текстовое поле | Поле для ввода ФИО пользователя |
Для роли "Юридическое лицо" | ||
field_company_name | Текстовое поле | Поле для ввода Названия компании |
field_company_number | Текстовое поле | Поле для ввода Номера свидетельства о регистрации |
Для всех ролей | ||
field_user_tel | Текстовое поле | Поле для ввода Номера телефона |
field_adres_index | Текстовое поле | Поле для ввода Почтового индекса |
field_city | Ссылка на термин | Поле для выбора или ввода города из словаря таксономии |
field_adres_street | Текстовое поле | Поле для ввода Улицы |
field_adres_dom | Текстовое поле | Поле для ввода Номера дома и квартиры |
Естественно, Ваш набор полей будет другим. Но нам главное понять принцип работы.
Шаг 2. Магия
Для начала заварим кружку горячего кофе и зарегистрируем адрес для ajax запроса. По этому пути будем обрабатывать форму после заполнения всех шагов. Если у Вас не установлен модуль Ajax Login/Register - то можно обойтись и без этого.
/**
* Implements hook_menu().
*/
function multistep_register_menu() {
$items = array();
$items['ajax_multistep_register'] = array(
'page callback' => '_multistep_register_ajax_callback',
'access callback' => TRUE,
'type' => MENU_CALLBACK,
'delivery callback' => 'ajax_deliver',
'theme callback' => 'ajax_base_page_theme',
);
return $items;
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function multistep_register_form_user_register_form_alter(&$form, &$form_state, $form_id){
// Оборачиваем форму в div
$form['#prefix'] = '';
$form['#suffix'] = '';
// Добавляем форме CSS класс (необязательно)
$form['#attributes'] = array('class' => array('user-register-form'));
// Добавим свой валидатор формы
// Обратите внимание - он ОБЯЗАТЕЛЬНО должен быть ПЕРВЫМ
array_unshift($form['#validate'], 'multistep_register_myvalidate');
// Добавим новое свойство '#step' к полям на форме.
// Свойство обозначает номер шага, на котором поле должно отображаться
// Для первого шага:
$form['account']['#step']=
$form['autoassignrole_user']['#step']=1;
// Для второго шага:
$form['field_user_fio']['#step']=
$form['field_user_tel']['#step']=
$form['field_company_name']['#step']=
$form['field_company_number']['#step']=
$form['field_adres_index']['#step']=
$form['field_city']['#step']=
$form['field_adres_dom']['#step']=
$form['field_adres_street']['#step']=2;
// Для третьего шага:
$form['legal']['#step']=3;
// Сохраним список переменных из $form_state['values']
// сгруппировав их по шагам
$form_state['storage']['fields_step']=array(
'step1'=>array(
'name',
'mail',
'user_roles',
),
'step2'=>array(
'field_user_fio',
'field_user_tel',
'field_adres_index',
'field_city',
'field_adres_street',
'field_adres_dom',
'field_company_name',
'field_company_number',
),
'step3'=>array(
'legal_accept',
),
);
// Узнаем текущий шаг и общее количество шагов
$step = empty($form_state['storage']['step']) ? 1 : $form_state['storage']['step'];
$current_step = 'step'.$step;
$form_state['storage']['step'] = $step;
$step_count=count($form_state['storage']['fields_step']);
// Контейнер для кнопок должен отображаться на любом шаге:
$form['actions']['#step']=$step;
// Обозначим вес контейнера для кнопок (необязательно).
$form['actions']['#weight']=48;
// Теперь пройдемся по элементам формы
// и отключим все лишние поля.
// Обратите внимание, элементы 'hidden' будем выводить на всех шагах
foreach ($form as $key => $element){
if (substr($key, 0, 1) != '#' && isset($element['#type']) && $element['#type']!='hidden'){
if(isset($element['#step']) && $element['#step']==$step){
$form[$key]['#access']=TRUE;
}else{
$form[$key]['#access']=FALSE;
}
}
}
// Вывод всех элементов, которые мы будем добавлять ниже
// уже не будет зависить от свойства '#step'
// например, добавляем div, куда будем ложить сообщения, в самый верх формы
// он будет выведен на всех шагах
$form['messages'] = array(
'#markup' => '',
'#weight' => -1000,
);
// Дальше идет настройка по конкретным шагам
// Основные действия - обработка значений по умочанию
switch ($step) {
case 1:
// На первом шаге выводим текст приветствия, информацию
// о текущем шаге, меняем описание поля 'name'(Необязательно)
$form['register_info']=array(
'#markup'=>'Текст приветствия',
'#weight'=>-100);
$form['step_info']=array(
'#markup'=>'Шаг '.$step.' из '.$step_count.'
',
'#weight'=>-99);
$form['account']['name']['#description']='Имя для авторизации на сайте. Допустимые символы: латинские буквы, цифры, тире и знак подчеркивания' ;
// Если пользователь уже был на этом шаге,
// вытащим введенные им значения на форму
if (isset($form_state['storage']['values'][$current_step])) {
$step_values=$form_state['storage']['values'][$current_step];
$form['account']['name']['#default_value'] = $step_values['name'];
$form['account']['mail']['#default_value'] = $step_values['mail'];
$form['autoassignrole_user']['user_roles']['#default_value']= $step_values['user_roles'];
}
break;
case 2:
// Добавляем информацию о текущем шаге
$form['step_info']=array(
'#markup'=>'Шаг '.$step.' из '.$step_count.': Дополнительная информация
',
'#weight'=>-99);
// Теперь ограничим вывод полей в зависимости
// от выбранной на первом шаге роли
if($form_state['storage']['values']['step1']['user_roles']==5){
$form['field_user_fio']['#access']=
$form['field_user_tel']['#access']=FALSE;
}else{
$form['field_company_name']['#access']=
$form['field_company_number']['#access']=FALSE;
}
// Если пользователь уже был на этом шаге,
// вытащим введенные им значения на форму.
// Как добраться до нужного поля в этом случае - тут Вам только Devel поможет!
if (isset($form_state['storage']['values'][$current_step])) {
$step_values=$form_state['storage']['values'][$current_step];
$form['field_user_fio']['und'][0]['value']['#default_value'] = $step_values['field_user_fio']['und'][0]['value'];
$form['field_user_tel']['und'][0]['value']['#default_value'] = $step_values['field_user_tel']['und'][0]['value'];
$form['field_company_name']['und'][0]['value']['#default_value'] = $step_values['field_company_name']['und'][0]['value'];
$form['field_company_number']['und'][0]['value']['#default_value'] = $step_values['field_company_number']['und'][0]['value'];
$form['field_adres_index']['und'][0]['value']['#default_value'] = $step_values['field_adres_index']['und'][0]['value'];
$form['field_adres_street']['und'][0]['value']['#default_value'] = $step_values['field_adres_street']['und'][0]['value'];
$form['field_adres_dom']['und'][0]['value']['#default_value'] = $step_values['field_adres_dom']['und'][0]['value'];
$form['field_city']['und']['#default_value'] = $step_values['field_city']['und'][0]['name'];
}
break;
case 3:
// Добавляем информацию о текущем шаге
$form['step_info']=array(
'#markup'=>'Шаг '.$step.' из '.$step_count.': Правила участия
',
'#weight'=>-99);
//Третий шаг - последний. Поэтому никаких значений восстанавливать не будем
break;
}
// Осталось совсем немного - кнопки
// Если мы не на последнем шаге, то
// скроем кнопку отправить и ЗАМЕНИМ основной обработчик формы
if ($step != $step_count) {
$form['actions']['submit']['#access']=FALSE;
$form['#submit'] = array('multistep_register_form_submit');
}else{
// на последнем шаге добавим кнопке отправить ajax обработку
// это необходимо для совместимости с модулем Ajax Login/Register
// если Вы не используете этот модуль, то должно работать и без этого
$form['actions']['submit']['#ajax'] = array(
'path' => 'ajax_multistep_register',
);
// Задаем вес кнопки
$form['actions']['submit']['#weight']=51;
}
// Если мы не достигли последнего шага, то
// добавляем кнопку "Далее".
if ($step < 3) {
$form['actions']['next'] = array(
'#type' => 'submit',
'#value' => 'Далее',
'#weight'=>50,
'#ajax' => array(
'wrapper' => 'multistep-register-form-wrapper',
'callback' => 'multistep_register_ajax_callback',
),
);
}
// Если мы ушли с первого шага, то покажем кнопку "Назад".
if ($step > 1) {
$form['actions']['prev'] = array(
'#type' => 'submit',
'#value' => 'Назад',
'#limit_validation_errors' => array(),
'#submit' => array('multistep_register_form_submit'),
'#weight'=>49,
'#ajax' => array(
'wrapper' => 'multistep-register-form-wrapper',
'callback' => 'multistep_register_ajax_callback',
),
);
}
}
Самое время глотнуть слегка остывший кофе и подумать о том, что Мы с Вами сделали:
- Условно создали разделение полей по шагам;
- Определили порядок вывода элементов формы в зависимости от текущего шага;
- Позаботились о выводе введенных ранее значений
- Создали необходимые элементы для навигации (информация о текущем щаге и кнопки "Назад" и "Далее")
Осталось самое простое - ajax, проверка и обработка формы.
Шаг 3. Ajax-обработчик формы.
Тут даже комментировать ничего не буду.
function multistep_register_ajax_callback($form, &$form_state) {
return $form;
}
Шаг 4. Дополнительный валидатор формы
У формы регистрации есть свой набор функций для проверки введенных значений.
Есть несколько вариантов, как реализовать валидацию формы в данном случае:
- Удалить стандартные валидаторы и написать один свой, который будет проверять значения в зависимости от текущего шага;
- Написать отдельные валидаторы для каждого шага и вешать их в hook_form_FORM_ID_alter;
- Написать валидаторы для полей и подцеплять их к полям;
- Использовать стандартные валидаторы формы + добавить дополнительную проверку по шагам, если вдруг стандартных Вам не хватает.
Основная проблема заключается в том, что стандартные валидаторы работают со всей формой сразу, а у нас после каждого шага заполнен только определенный набор значений, остальные поля - пустые.
Что бы решить эту проблему, будем перед проверкой дополнять массив $form_state['values'] значениями с предыдущих шагов. Конечно, для нашей формы поля Имя пользователя и E-mail обязательно должны быть на первом шаге, иначе, придется отключать стандартные проверки для начальных шагов.
function multistep_register_myvalidate($form, &$form_state) {
$current_step = empty($form_state['storage']['step']) ? 1 : $form_state['storage']['step'];
if ($current_step>1) {
for($i=1;$i<$current_step;$i++){
if(isset($form_state['storage']['values']['step'.$i])){
$form_state['values']=array_merge($form_state['values'], $form_state['storage']['values']['step'.$i]);
}
}
}
//Теперь добавим свою проверку в зависимости от текущего шага
switch ($current_step) {
case 1:
if (!preg_match("/^[0-9a-zA-Z-_]+$/i", $form_state['values']['name'])) {
form_set_error('name', 'Недопустимые символы в поле "Имя пользователя"');
}
break;
}
}
Шаг 5. Обработчик формы
function multistep_register_form_submit($form, &$form_state) {
//Узнаем текущий шаг
$current_step = 'step' . $form_state['storage']['step'];
// Если перешли на следующий шаг - то увеличиваем счётчик шагов и
// сохраняем состояние формы, полученное при переходе на новый шаг.
if (isset($form['actions']['next']['#value']) && $form_state['triggering_element']['#value'] == $form['actions']['next']['#value']) {
$form_state['storage']['step']++;
// для сохранения текущих значений будет написана отдельная функция
$form_state['storage']['values'][$current_step] = _multistep_register_update_values_step($form_state['storage']['fields_step'][$current_step], $form_state['values']);
}
// Если вернулись на шаг назад - уменьшаем счётчик шагов.
//
if (isset($form['actions']['prev']['#value']) && $form_state['triggering_element']['#value'] == $form['actions']['prev']['#value']) {
$form_state['storage']['step']--;
}
$form_state['rebuild'] = TRUE;
}
function _multistep_register_update_values_step($fields, $values) {
$items=array();
foreach($fields as $name){
if(isset($values[$name])){
$items[$name]=$values[$name];
}
}
return $items;
}
Шаг 6. Добиваем popup
Уже сейчас все должно работать как задумано. Кроме совместимости с модулем Ajax Login/Register. У меня возникало много непонятных проблем, вот самый оптимальный вариант решения - переопределить ajax обработчик для кнопки Отправить. Основа тут - Заставляем любую форму выполняться через AJAX в Drupal 7
/**
* Provides ajax callback for form submit
*/
function _multistep_register_ajax_callback() {
module_load_include('pages.inc', 'user');
list($form, $form_state) = ajax_get_form();
drupal_process_form($form['#form_id'], $form, $form_state);
$commands = array();
// Собираем сообщения, которые вылезли в процессе выполнения формы
// Вот тут основыне изменения
$sms=theme('status_messages');
// Проверяем, если нет ошибок, то ВМЕСТО формы выводим сообщения
// об успешной регистрации
// и о том, что письмо отправлено на почтовый ящик пользователя
// Иначе, записываем сообщение в div, который был добавлен вверху формы
if(strpos($sms, 'error')===FALSE){
$commands[] = ajax_command_html('#multistep-register-form-wrapper', 'На Ваш почтовый адрес отправлено письмо с дальнейшими инструкциями. ');
}else{
$commands[] = ajax_command_html('#multistep-register-ajax-forms-messages', $sms);
}
return array('#type' => 'ajax', '#commands' => $commands);
}
Теперь ВСЕ! Самое время допить ледяной кофе и спрятать бубен на полку, до следующих танцев (а они наступят совсем скоро).
- 23.01.2014
- 206 просмотров
Забыл сказать.
Спасибо большое.
Ох же потанцевать пришлось. Жалко, что в примере не было чекбоксов. Пару кружек чая бы сэкономил :) Когда в доп. функции формировал массив значений для чекбоксов через foreach, то все работало, но была ошибка на счет типа. Сделал через цикл for и ошибка пропала.