Делаем стандартную форму регистрации в Друпал 7 многошаговой на AJAX


78

Сегодня я покажу, как можно сделать регистрацию пользователей в Друпал 7 пошаговой, используя стандартную форму регистрации. Данный метод не является универсальным, т.к. в нем идет идет привязка к конкретным полям на форме. Для своей формы придется потанцевать с бубном самостоятельно.  

Итак, чтобы цвет волос окончательно стал седым, усложним задачу:

  • дополнительно ограничим допустимые символы в имени пользователя (оставим только латинский алфавит, цифры и несколько дополнительных символов);
  • позволим пользователю выбирать роль (модуль Auto Assign Role) и в зависимости от выбранного значения сделаем разный набор полей;
  • обеспечим совместимость с модулем Ajax Login/Register (формы авторизации, регистрации в всплывающем окне);
  • добавим Лицензионное соглашение (Ознакомление и согласие с правилами работы нашего интернет-ресурса) - модуль Legal.

Шаг 1. Подготовка

Этот этап нет смысла описывать подробно, поэтому вкратце:

Добавим две дополнительные роли на сайте:

  1. Физическое лицо
  2. Юридическое лицо

С помощью модуля 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;
}
Ну а теперь начинаем делать магию. При помощи hook_form_FORM_ID_alter модернизируем форму регистрации. 
/**
 * 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);
}

Теперь ВСЕ! Самое время допить ледяной кофе и спрятать бубен на полку, до следующих танцев (а они наступят совсем скоро).

Ох же потанцевать пришлось. Жалко, что в примере не было чекбоксов. Пару кружек чая бы сэкономил :) Когда в доп. функции формировал массив значений для чекбоксов через foreach, то все работало, но была ошибка на счет типа. Сделал через цикл for и ошибка пропала.

Забыл сказать.
Спасибо большое.

Добавить комментарий
Может быть интересно

Порядок действий для установки Solr на сервере с Centos 7

4
Модуль Migrate это фреймворк для миграции (импорта) данных в Drupal из любых источников.
1

В данной статье будет теория про механизм, который использует модуль migrate при импорте материалов в Друпал из различных источников.

2
Снова возвращаемся к migrate. Довольно удобный фреймворк для импорта данных в Друпал. Один из распространенных форматов источника для импорта - CSV. Поддерживается migrate из коробки. Описание и примеры работы с классом MigrateSourceCSV можно найти на drupal.org.
2

В общем, проблема старая и известная. Правда не на всех рейсурсах заметная. При использовании модуля Metatag, на форму редактирования сущностей добавляется вкладка для индивидуального изменения метатегов. И на ней используется браузер токенов.

1