Некоторое время назад у меня была идея сделать городской сайт (естественно, очередной) для одного небольшого городка на базе Drupal. Как раз незадолго до этого Яндекс расширил функционал своей Почты для доменов и добавил возможность управлять ящиками через API. И в голове зародилась мысль: а почему бы и нет? Почему бы не предоставить пользователям возможность одновременно с регистрацией на городском портале получать почтовый ящик в городском домене? Сама по себе идея, конечно, не удивительна, однако готовых решений не было. Сайт я так и не запустил, а коду без дела лежать грех.
Как известно, Drupal позиционируется как CMS, ориентированная разработчиков и позволяющая благодаря обширной API разработать хоть систему для управления лунной базой. Я не буду вдаваться в своём посте в её восхваление и самые начальные основы разработки модулей под Drupal, однако начинающие Drupal-разработчики наверняка сочтут материал полезным, а опытным буду благодарен за советы и рекомендации.

Естественно, приступая к написанию модуля, нужно решить, каким функционалом он будет обладать. Модуль рассчитан на социальный сайт, где смогут регистрироваться пользователи. Следовательно, он должен:
Конечно, можно было бы добавить ещё какие-нибудь плюшки (например, изменять пароль к ящику при изменении пароля в нашей системе, редактировать пользовательские данные, управлять переадресацией), но это скорее излишества.
Любой модуль Drupal должен содержать .info-файл со служебной информацией о модуле. Минимальный набор — название модуля, краткое описание и версия ядра, для которой он написан. Но в нашем случае необходимо указать ещё одно поле — зависимости. В логике модуля заложено создание ящиков, логин которых совпадает с логином пользователя. Но чтобы не лишать пользователей возможность создавать кириллические логины и не удивлять ими Яндекс, нам нужен модуль транслитерации. Таким образом, файлик yandex_pdd.info у нас будет выглядеть следующим образом:
Естественно, перед установкой модуля нужно не забыть установить у себя этот самый transliteration.
Ещё один обязательный файл — .install (в нашем случае — yandex_pdd.install), который определяет используемые модулем базы данных и необходимые действия при установке и удалении модуля. Для работы нам понадобится одна база данных и две переменных. Для начала определим структуру базы (хук hook_schema())
Первые две колонки, думаю, вопросов не вызывают. В третью колонку мы будем записывать логин самого электронного ящика. Четвёртую колонку стоит разобрать отдельно. В CMS Drupal для активации пользователя необходимо, чтобы он перешёл по ссылке, присланной ему на e-mail, и задал свой пароль. Известный факт, что часть учётных записей никогда не будет активирована (часть спам-ботов и просто странные люди), потому создавать ящик имеет смысл, когда пользователь задаёт новый пароль. Собственно, это заодно позволяет нам создать ящик с этим же паролем. Данный вопрос я ещё затрону ниже.
При удалении модуля описанные в схеме базы удаляются автоматически. А вот переменные, закешированные данные и т.п. следует за собой убрать с помощью hook_uninstall().
Параметры yandex_pdd_domain и yandex_pdd_authtoken задают домен, в котором будут создаваться ящики, и API-ключ соответственно. Расскажу о них подробнее немного позже.
Необходимый фундамент для работы модуля заложен, и мы можем приступать к написанию самого кода модуля. Храниться он у нас будет, как и полагается, в файле с расширением .module (yandex_pdd.module).
Если модулю Drupal необходимо прописываться в меню, создавать какие-либо страницы и т.п., нам нужно описать всю структуру и необходимые действия при переходе по ссылкам с помощью хука hook_menu(). В нашем случае будет использоваться два URL-а: страница настройки модуля и переадресация в почтовый ящик.
Обратите внимание на использование системной функции t(). Она позволяет переводить элементы текста на текущий системный язык. И да, на странице mailbox нам такая возможность не понадобится.
В предыдущем разделе мы рассказали нашей CMS, что страница для ввода необходимых модулю данных будет размещаться по пути admin/config/content/yandex_pdd и обрабатываться функцией main_config. Собственно, CMS на самом деле не в курсе, чем конкретно будет заниматься страница, но знает, что при обращении по данному пути нужно обратиться к данной функции.
Для создания страницы мы получаем друпалопонятный массив полей формы обращением системной функции drupal_get_form() к пользовательской функции pdd_config_form.
Результат передаётся функции drupal_render(), которая собирает массив в готовый html-код страницы.
Форма содержит всего два поля:

Естественно, результаты заполнения формы нам необходимо сохранить. За это отвечает функция pdd_config_form_submit, которая просто сохраняет значения полей в системные переменные.
Ещё небольшой кусочек кода — реализация хука hook_help(), который выводит справочную информацию о модуле в соответствующем разделе админ-части. По поводу этой функции я не заморачивался и пошёл по пути минимализма.
С административной частью разобрались. Пора заняться пользовательской, весь функционал которой можно разделить на этап создания и этап работы.
Не всем нашим пользователям может понадобиться ещё один e-mail, пусть даже и в домене любимого сайта. Да и нам ни к чему плодить огромное количество неиспользуемых ящиков. Потому мы добавим в форму регистрации пользователя чекбокс, который позволит ему выбирать, создавать ящик или нет. Любую форму в Drupal можно изменить с помощью hook_form_alter(). С назначением некоторых элементов массива описания формы мы уже знакомы. Подробнее со всеми остальными можно познакомиться на справочной странице по формам.
Много текста для одного маленького чекбокса.

При регистрации пользователя мы проверяем, отмечен ли чекбокс создания ящика в форме, и если да, вносим в таблицу данные о неактивном ящике.
Создание ящика подвязано к изменению формы с пользовательскими данными хуком hook_field_attach_submit(). Таким образом, до того, как пользователь впервые произведёт редактирование данных (изменение пароля), ящик не создаётся.
Наконец-то наш пользователь получил ящик, хотя не особо знает, как туда попасть. Осталось реализовать функционал перехода на почту.
Собственно, пользовательских функций всего две: отобразить блок и авторизироваться на почте. Блок нужно в первую очередь описать хуком hook_block_info()
Для блока нам ещё понадобится задать темизацию и файл с шаблоном оформления. Темизация описывается хуком hook_theme(), описывающим системе реализацию оформления элементов модуля.
И, собственно, сам файл yandex-pdd-block.tpl.php.
Для блока нам нужно получить по API одно-единственное значение — количество новых писем, после чего отрендерить сам блок хуком hook_block_view()
Сгенерированный блок будет выглядеть примерно так.

Завершающий штрих — системная ссылка mailbox, которая будет перебрасывать пользователя на страницу Яндекс.Почты и авторизировать по одноразовому токену. Как мы помним (или уже не помним), мы ранее задали, что эта ссылка должна обрабатываться функцией mailbox_login. Время жизни токена всего 30 секунд, потому после его получения пользователь тут же должен быть переадресован на страницу авторизации.
Тут и сказу конец. Размещённые куски кода, собранные в соответствии с указанными именами файлов, должны дать Вам готовый модуль. Надеюсь, кому-то он будет полезен, и с удовольствием выслушаю замечания и рекомендации по коду.
Как известно, Drupal позиционируется как CMS, ориентированная разработчиков и позволяющая благодаря обширной API разработать хоть систему для управления лунной базой. Я не буду вдаваться в своём посте в её восхваление и самые начальные основы разработки модулей под Drupal, однако начинающие Drupal-разработчики наверняка сочтут материал полезным, а опытным буду благодарен за советы и рекомендации.

Постановка задачи
Естественно, приступая к написанию модуля, нужно решить, каким функционалом он будет обладать. Модуль рассчитан на социальный сайт, где смогут регистрироваться пользователи. Следовательно, он должен:
- при регистрации пользователя предлагать ему возможность создать ящик на Яндексе;
- при подтверждении регистрации инициировать создание ящика;
- отображать авторизированному пользователю блок с информацией о количестве новых писем и возможностью перейти в ящик;
- создавать админ-часть для ввода необходимых параметров.
Конечно, можно было бы добавить ещё какие-нибудь плюшки (например, изменять пароль к ящику при изменении пароля в нашей системе, редактировать пользовательские данные, управлять переадресацией), но это скорее излишества.
.info-файл
Любой модуль Drupal должен содержать .info-файл со служебной информацией о модуле. Минимальный набор — название модуля, краткое описание и версия ядра, для которой он написан. Но в нашем случае необходимо указать ещё одно поле — зависимости. В логике модуля заложено создание ящиков, логин которых совпадает с логином пользователя. Но чтобы не лишать пользователей возможность создавать кириллические логины и не удивлять ими Яндекс, нам нужен модуль транслитерации. Таким образом, файлик yandex_pdd.info у нас будет выглядеть следующим образом:
name = Yandex PDD
description = Yandex PDD mailboxes autocreation.
core = 7.x
dependencies[] = transliteration
Естественно, перед установкой модуля нужно не забыть установить у себя этот самый transliteration.
Файл установки и удаления
Ещё один обязательный файл — .install (в нашем случае — yandex_pdd.install), который определяет используемые модулем базы данных и необходимые действия при установке и удалении модуля. Для работы нам понадобится одна база данных и две переменных. Для начала определим структуру базы (хук hook_schema())
function yandex_pdd_schema() {
$schema['yandex_pdd'] = array(
'fields' => array(
'id' => array('type' => 'serial', 'not null' => TRUE), // Идентификатор строки
'uid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0,), // Идентификатор пользователя
'login' => array('type' => 'varchar', 'length' => 100, 'not null' => TRUE), // Транслитерированный логин для создания ящика
'activated' => array('type' => 'varchar', 'length' => 1, 'not null' => TRUE) // Флаг активации ящика
),
'primary key' => array('id'),
);
return $schema;
}
Первые две колонки, думаю, вопросов не вызывают. В третью колонку мы будем записывать логин самого электронного ящика. Четвёртую колонку стоит разобрать отдельно. В CMS Drupal для активации пользователя необходимо, чтобы он перешёл по ссылке, присланной ему на e-mail, и задал свой пароль. Известный факт, что часть учётных записей никогда не будет активирована (часть спам-ботов и просто странные люди), потому создавать ящик имеет смысл, когда пользователь задаёт новый пароль. Собственно, это заодно позволяет нам создать ящик с этим же паролем. Данный вопрос я ещё затрону ниже.
При удалении модуля описанные в схеме базы удаляются автоматически. А вот переменные, закешированные данные и т.п. следует за собой убрать с помощью hook_uninstall().
function yandex_pdd_uninstall() {
variable_del('yandex_pdd_domain');
variable_del('yandex_pdd_authtoken');
cache_clear_all('yandex_pdd','cache',TRUE);
menu_rebuild();
}
Параметры yandex_pdd_domain и yandex_pdd_authtoken задают домен, в котором будут создаваться ящики, и API-ключ соответственно. Расскажу о них подробнее немного позже.
Необходимый фундамент для работы модуля заложен, и мы можем приступать к написанию самого кода модуля. Храниться он у нас будет, как и полагается, в файле с расширением .module (yandex_pdd.module).
Элементы меню
Если модулю Drupal необходимо прописываться в меню, создавать какие-либо страницы и т.п., нам нужно описать всю структуру и необходимые действия при переходе по ссылкам с помощью хука hook_menu(). В нашем случае будет использоваться два URL-а: страница настройки модуля и переадресация в почтовый ящик.
function yandex_pdd_menu(){
$items = array();
$items['admin/config/content/yandex_pdd'] = array( // Ключ массива - системный путь к странице
'title' => t('Yandex PDD'), // <title> страницы
'page callback' => 'main_config', // Функция, которая отвечает за генерацию страницы
'type' => MENU_NORMAL_ITEM, // Способ подвязки страницы в систему меню CMS
'access callback' => TRUE, // Определяет возможность доступа к странице
);
$items['mailbox'] = array(
'title' => 'Yandex PDD login',
'page callback' => 'mailbox_login',
'type' => MENU_CALLBACK,
'access callback' => TRUE,
);
return $items;
}
Обратите внимание на использование системной функции t(). Она позволяет переводить элементы текста на текущий системный язык. И да, на странице mailbox нам такая возможность не понадобится.
Страница настройки
В предыдущем разделе мы рассказали нашей CMS, что страница для ввода необходимых модулю данных будет размещаться по пути admin/config/content/yandex_pdd и обрабатываться функцией main_config. Собственно, CMS на самом деле не в курсе, чем конкретно будет заниматься страница, но знает, что при обращении по данному пути нужно обратиться к данной функции.
function main_config(){
$form = drupal_get_form('pdd_config_form'); // Получаем поля формы
$form = drupal_render($form); // Рендерим форму
return $form;
}
Для создания страницы мы получаем друпалопонятный массив полей формы обращением системной функции drupal_get_form() к пользовательской функции pdd_config_form.
function pdd_config_form($form, &$form_state){
$form=array();
$form['pdd_domain'] = array( // Ключ массива - имя поля в форме
'#type' => 'textfield', // Тип поля
'#title' => t('Domain zone'), // Название поля, понятное пользователю
'#description' => t('A domain zone in which email should be created.'), // Описание поля
'#default_value' => variable_get('yandex_pdd_domain'), // Значение по умолчанию
'#required' => 1, // Флаг обязательности
);
$form['authtoken'] = array(
'#type' => 'textfield',
'#title' => t('Auth Token'),
'#description' => t('Authorization token obtained at Yandex.PDD service.'),
'#default_value' => variable_get('yandex_pdd_authtoken'),
'#required' => 1,
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
Результат передаётся функции drupal_render(), которая собирает массив в готовый html-код страницы.
Форма содержит всего два поля:
- pdd_domain — подвязанный к Яндекс.ПДД домен, в котором будут создаваться ящики;
- authtoken — авторизационный токен, полученный в настройках API Почты для домена.

Естественно, результаты заполнения формы нам необходимо сохранить. За это отвечает функция pdd_config_form_submit, которая просто сохраняет значения полей в системные переменные.
function pdd_config_form_submit($form, &$form_state){
variable_set('yandex_pdd_domain', $form_state['values']['pdd_domain']);
variable_set('yandex_pdd_authtoken', $form_state['values']['authtoken']);
}
Ещё небольшой кусочек кода — реализация хука hook_help(), который выводит справочную информацию о модуле в соответствующем разделе админ-части. По поводу этой функции я не заморачивался и пошёл по пути минимализма.
function yandex_pdd_help($path, $arg) {
switch ($path) {
case "admin/help#yandex_pdd":
return '<p>'. t("Yandex PDD mailboxes management module.") .'</p>';
break;
}
}
С административной частью разобрались. Пора заняться пользовательской, весь функционал которой можно разделить на этап создания и этап работы.
Создание ящика
Не всем нашим пользователям может понадобиться ещё один e-mail, пусть даже и в домене любимого сайта. Да и нам ни к чему плодить огромное количество неиспользуемых ящиков. Потому мы добавим в форму регистрации пользователя чекбокс, который позволит ему выбирать, создавать ящик или нет. Любую форму в Drupal можно изменить с помощью hook_form_alter(). С назначением некоторых элементов массива описания формы мы уже знакомы. Подробнее со всеми остальными можно познакомиться на справочной странице по формам.
function yandex_pdd_form_alter(&$form, &$form_state, $form_id){
if($form_id=='user_register_form'){ // Идентификатор изменяемой формы
$form['account']['createmail']=array( // Имя дополнительного поля
'#type' => 'checkbox',
'#title' => t('Create a mailbox'),
'#description' => t('Check this box if you want to create a mailbox @'.variable_get('yandex_pdd_domain').'.'),
'#required' => 0,
'#access' => 1, // Доступно ли поле пользователям
'#weight' => 10 // Вес (критерий сортировки)
);
}
}
Много текста для одного маленького чекбокса.

При регистрации пользователя мы проверяем, отмечен ли чекбокс создания ящика в форме, и если да, вносим в таблицу данные о неактивном ящике.
function yandex_pdd_user_insert(&$edit, $account, $category){
if($account->createmail){
$transliterated = transliteration_get($account->name, '_'); // Транслитерируем логин
$pattern = '/[^a-zA-Z0-9]/'; // Задаём шаблон для замены всего, кроме букв и цифер
$transliterated = preg_replace($pattern, '-', $transliterated); // Заменяем все не alphanumeric знаки на дефисы
$newbox = db_insert('yandex_pdd'); // Инициируем вставку в базу данных
$newbox->fields(array('uid' => $account->uid, 'login' => strtolower($transliterated), 'activated' => '0')); // Задаём данные для вставки
$res = $newbox->execute(); // Выполняем запрос
watchdog('yandex_pdd',print_r('Res: '.$res,1)); // Для отладки записываем в системный журнал CMS результат
}
}
Создание ящика подвязано к изменению формы с пользовательскими данными хуком hook_field_attach_submit(). Таким образом, до того, как пользователь впервые произведёт редактирование данных (изменение пароля), ящик не создаётся.
function yandex_pdd_field_attach_submit($entity_type, $entity, $form, &$form_state) {
global $user;
if($entity_type == 'user' and $user->uid > 0) { // Обработка при изменении сущности типа user
$select = db_select('yandex_pdd','ypdd');
$select->addField('ypdd', 'id');
$select->addField('ypdd', 'login');
$select->addField('ypdd', 'activated');
$select->condition('uid', $user->uid);
$entries = $select->execute()->fetchAssoc(); // Получаем логин для ящика и статус активации
if (array_key_exists('login', $entries) and $entries['login'] != '' and $entries['activated'] == 0) { // Если пользователю нужен ящик и он не создан
$mailboxcreate = simplexml_load_file('https://pddimp.yandex.ru/reg_user_token.xml?token='.variable_get('yandex_pdd_authtoken').'&u_login='.$entries['login'].'&u_password='.$form["#user"]->pass); // Создаём ящик и парсим XML-ответ
if ($mailboxcreate->ok[0]) { // Если создался
$num_updated = db_update('yandex_pdd');
$num_updated->fields(array('activated' => '1'));
$num_updated->condition('uid', $user->uid);
$res = $num_updated->execute(); // Отмечаем флаг активации ящика
} elseif ($mailboxcreate->error[0]) { // Если ошибка API
foreach($mailboxcreate->error[0]->attributes() as $key => $value) {
$mbc[$key] = (string)$value;
}
watchdog('yandex_pdd',"Can't create new mailbox. Reason: ".$mbc['reason']); // Записываем в лог сообщение
} else { // Если вообще неясно что случилось
watchdog('yandex_pdd','Unknown error while creating mailbox.'); // Так и запишем
}
}
}
}
Наконец-то наш пользователь получил ящик, хотя не особо знает, как туда попасть. Осталось реализовать функционал перехода на почту.
Рабочий функционал
Собственно, пользовательских функций всего две: отобразить блок и авторизироваться на почте. Блок нужно в первую очередь описать хуком hook_block_info()
function yandex_pdd_block_info() {
$blocks['mailbox_status'] = array( // Системное имя блока
'info' => t('Mailbox status'), // Административное название блока
'cache' => DRUPAL_CACHE_PER_ROLE, // Режим кеширования
);
return $blocks;
}
Для блока нам ещё понадобится задать темизацию и файл с шаблоном оформления. Темизация описывается хуком hook_theme(), описывающим системе реализацию оформления элементов модуля.
function yandex_pdd_theme() {
return array(
'yandex_pdd_block' => array( // Системное имя шаблона
'variables' => array( // Используемые шаблоном переменные
'newmail' => NULL
),
'template' => 'yandex-pdd-block', // Имя файла шаблона
)
);
}
И, собственно, сам файл yandex-pdd-block.tpl.php.
<div class="yandexpdd"><?php print t('You have ').'<a href="/mailbox" target="_blank">'.$newmail.t(' new messages').'</a>.'; ?></div>
Для блока нам нужно получить по API одно-единственное значение — количество новых писем, после чего отрендерить сам блок хуком hook_block_view()
function yandex_pdd_block_view($delta = '') {
global $user;
if ($user->uid > 0) { // Проверяем, авторизирован ли пользователь
$select = db_select('yandex_pdd','ypdd');
$select->addField('ypdd', 'login');
$select->condition('uid', $user->uid);
$entries = $select->execute()->fetchAssoc(); // Получаем логин ящика
$unreadmailxml = simplexml_load_file('https://pddimp.yandex.ru/get_mail_info.xml?token='.variable_get('yandex_pdd_authtoken').'&login='.$entries['login']); // Получаем от Яндекса информацию о ящике
if ($unreadmailxml->ok[0]) { // Если запрос успешен
foreach($unreadmailxml->ok[0]->attributes() as $key => $value) { // Парсим ответ
$unreadmail[$key] = (string)$value;
}
$blocks = array();
$blocks['subject'] = null;
$blocks['content'] = theme('yandex_pdd_block', array('newmail' => $unreadmail['new_messages']));
return $blocks; // Возвращаем блок
} elseif ($unreadmailxml->error[0]) {
foreach($unreadmailxml->error[0]->attributes() as $key => $value) {
$unreadmail[$key] = (string)$value;
}
watchdog('yandex_pdd',"Can't get new mail info. Reason: ".$unreadmail['reason']);
} else {
watchdog('yandex_pdd','Unknown error while loading new mail info');
}
}
}
Сгенерированный блок будет выглядеть примерно так.

Завершающий штрих — системная ссылка mailbox, которая будет перебрасывать пользователя на страницу Яндекс.Почты и авторизировать по одноразовому токену. Как мы помним (или уже не помним), мы ранее задали, что эта ссылка должна обрабатываться функцией mailbox_login. Время жизни токена всего 30 секунд, потому после его получения пользователь тут же должен быть переадресован на страницу авторизации.
function mailbox_login(){
global $user;
global $base_url;
if ($user->uid > 0) {
$select = db_select('yandex_pdd','ypdd');
$select->addField('ypdd', 'login');
$select->condition('uid', $user->uid);
$entries = $select->execute()->fetchAssoc();
$tokenxml = simplexml_load_file('https://pddimp.yandex.ru/api/user_oauth_token.xml?token='.variable_get('yandex_pdd_authtoken').'&domain='.variable_get('yandex_pdd_domain').'&login='.$entries['login']); // Получаем одноразовый токен авторизации
if ($tokenxml->xpath('status/success')) {
$tokenarr = $tokenxml->xpath('domains/domain/email/oauth-token');
header('Location: http://passport.yandex.ru/passport?mode=oauth&type=trusted-pdd-partner&error_retpath='.urlencode($base_url.'/').'&access_token='.(string)$tokenarr[0]); // Перебрасываем пользователя на Яндекс.Почту
drupal_exit(); // Прекращаем любые другие действия CMS
} elseif ($tokenxml->xpath('status/error')) {
watchdog('yandex_pdd',"Can't get short-term auth token info. Reason: ".(string)$tokenxml->xpath('action/status/error'));
}
}
}
Заключение
Тут и сказу конец. Размещённые куски кода, собранные в соответствии с указанными именами файлов, должны дать Вам готовый модуль. Надеюсь, кому-то он будет полезен, и с удовольствием выслушаю замечания и рекомендации по коду.