Предисловие
Совсем недавно получил задание научиться писать модули под Drupal 7. Начал рыскать в поисках различных статей и мануалов, и понял, что их довольно мало, и информация совсем минимальная. Официальная документация так же не дала мне исчерпывающей информации. С горем пополам собрал некоторую информацию с нескольких источников и решил поделиться ей с Вами.
Не буду рассказывать, что такое Drupal, его структуру. Статья рассчитана на тех, кто минимально знает принцип работы хуков и отображения Drupal. Все это можно прочитать на официальном сайте API Drupal.
Начало разработки модуля
Я покажу как создать модуль, позволяющий добавлять RSS ленты и выводить их контент на отдельной странице.
Для начала необходимо выбрать «краткое имя» модуля. Оно будет использоваться во всех файлах и именах функций модуля. Оно должно начинаться только с буквы и содержать только символы нижнего регистра и знак "_". Я назвал свой модуль «rss_feeds».
Создаем папку: sites/all/modules/rss_feeds (все новые модули, которые Вы хотите добавить, должны находиться в этой папке). В ней создаем файл rss_feeds.info, который содержит META-информацию о нашем модуле:
name = RSS Feeds
description = Makes a compact page to navigate on RSS feeds.
package = «RSS»
core = 7.x
version = «7.x-1.0»
configure = admin/config/content/rss_feeds
files[]= rss_feeds.module
- name — имя модуля, которое будет отображаться в админке;
- description — описание модуля, которое подскажет администратору, что делает этот модуль;
- package — указывает категорию, в которой будет отображен модуль на странице модулей;
- core — версия Drupal, под которую он разрабатывался;
- version — версия нашего модуля;
- configure — путь, по которому будет доступна страница настроек модуля;
- files[] — массив подключаемых модулем файлов;
Есть еще некоторые поля, о которых вы можете прочитать тут.
Наш модуль будет использовать БД, в которой будет хранить данные о RSS (его имя, и URL). Создаем файл rss_feeds.install:
<?php
function rss_feeds_uninstall()
{
cache_clear_all('rss_feeds', 'cache', TRUE);
drupal_uninstall_schema('rssfeeds');
menu_rebuild();
}
function rss_feeds_schema()
{
$schema['rssfeeds'] = array(
'fields' => array(
'id' => array('type' => 'serial', 'not null' => TRUE),
'name' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE),
'url' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE),
'created_at' => array('type' => 'int', 'not null' => TRUE),
'updated_at' => array('type' => 'int', 'not null' => TRUE),
),
'primary key' => array('id'),
);
}
В хуке schema() мы создаем нужную нам таблицу. Хук uninstall() чистит кэш, удаляет таблицу и перестраивает меню при удалении модуля (закрывающих тег "?>" в Drupal принято не ставить).
Создание файла .module
Далее создаем непосредственно файл модуля - rss_feeds.module:
<?php
function rss_feeds_block_info()
{
$blocks['rss_feeds'] = array(
'info' => t('RSS Feeds'),
'cache' => DRUPAL_CACHE_PER_ROLE, // по умолчанию
);
return $blocks;
}
В этом хуке мы описываем используемые блоки. У нас он будет только один и называться 'rss_feeds'. 'info' — это краткая информация о блоке, а 'cache' — правило кеширования. Подробнее см hook_block_info().
Далее описываем hook_menu():
function rss_feeds_menu()
{
$items = array();
$items['admin/config/content/rss_feeds'] = array(
'title' => 'RSS Feeds',
'description' => 'Configure the RSS feeds list.',
'page callback' => 'rss_list',
'access arguments' => array('administer site configuration'),
);
$items['admin/config/content/rss_feeds/list'] = array(
'title' => 'RSS feeds list',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => 1,
);
// rss add form
$items['admin/config/content/rss_feeds/add'] = array(
'title' => 'Add rss',
'page callback' => 'drupal_get_form',
'page arguments' => array('rss_feeds_form'),
'access arguments' => array('administer site configuration'),
'type' => MENU_LOCAL_TASK,
'weight' => 2,
);
// rss edit form
$items['admin/config/content/rss_feeds/%rss/edit'] = array(
'title' => 'Edit RSS',
'page callback' => 'drupal_get_form',
'page arguments' => array('rss_feeds_form', 4),
'access arguments' => array('administer site configuration'),
'type' => MENU_CALLBACK,
);
// rss delete
$items['admin/config/content/rss_feeds/%rss/delete'] = array(
'title' => 'Delete RSS',
'page callback' => 'rss_feeds_delete',
'page arguments' => array(4),
'access arguments' => array('administer site configuration'),
'type' => MENU_CALLBACK,
);
$items['rss_feeds'] = array(
'title' => 'RSS feeds',
'page callback' => '_rss_feeds_page',
'access arguments' => array('access content'),
);
$items['rss_feeds/%rss/items'] = array(
'title' => 'RSS feed content',
'page callback' => 'rss_content',
'page arguments' => array(1),
'access callback' => TRUE,
'access arguments' => array('access content'),
'type' => MENU_CALLBACK,
);
return $items;
}
В items мы задаем URL, его заголовок (title), описание (description), функция обработки (page callback), передаваемые аргументы (page arguments), параметры доступа (access arguments), тип (type) и «вес» (weight). Хотелось бы выделить page callback — drupal_get_from. Эта функция принимает в качестве параметра форму и выводит ее. В «page arguments => array(1)», в качестве аргумента мы передаем 1-ый элемент URL (отсчет идет от 0).
Подробнее о hook_menu().
Далее мы опишем форму, через которую мы будем добавлять и редактировать наши RSS-ленты:
function rss_feeds_form($form, &$form_state, $rss = null)
{
$form['name'] = array(
'#title' => t('RSS feed name.'),
'#description' => t('Insert RSS shortcut name'),
'#type' => 'textfield',
'#default_value' => $rss ? $rss['name'] : '',
'#required' => true,
);
$form['url'] = array(
'#title' => t('RSS feed url.'),
'#description' => t('Insert RSS url'),
'#type' => 'textfield',
'#default_value' => $rss ? $rss['url'] : '',
'#required' => true,
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => $rss ? t('Save') : t('Add'),
);
if ($rss) {
$form['id'] = array(
'#type' => 'value',
'#value' => $rss['id'],
);
}
return $form;
}
Как Вы видите, если мы будем передавать в форму параметр $rss, форма будет понимать, добавляем мы новую ленту, или редактируем существующую. Функция t() (translate), позволяет локализировать модуль (об этом я расскажу чуть позже). Подробнее о hook_form().
Далее необходимо описать hook_form_validate(), который будет обрабатывать данные, введенные в форму:
function rss_feeds_form_validate($form, &$form_state)
{
$url = $form_state['values']['url'];
if (fopen($url, "r")) {
libxml_use_internal_errors(true);
$rss_feed = simplexml_load_file($url);
if (empty($rss_feed)) {
form_set_error('url', t('URL is invalid!'));
}
} else {
form_set_error('url', t('URL is invalid!'));
}
}
Сначала мы получаем данные из $form_state, а потом их обрабатываем. Если что-то не так — выкидывается стандартный form_set_error(), в котором мы указываем имя поля формы и сообщение.
Когда форма проходит валидацию, вызывается hook_form_submit():
function rss_feeds_form_submit($form, &$form_state)
{
$rss = array(
'name' => $form_state['values']['name'],
'url' => $form_state['values']['url'],
'created_at' => time(),
'updated_at' => time(),
);
// save edit data
if (isset($form_state['values']['id'])) {
$rss['id'] = $form_state['values']['id'];
drupal_write_record('rssfeeds', $rss, 'id');
drupal_set_message(t('RSS Feed saved!'));
} // add new data
else {
drupal_write_record('rssfeeds', $rss);
drupal_set_message(t('RSS Feed added!'));
}
drupal_goto('admin/config/content/rss_feeds');
}
Думаю тут все понятно. drupal_write_record() делает запись в БД, drupal_set_message() показывает системное сообщение, drupal_goto() перекидывает на заданный URL.
Для того чтобы форма принимала не просто ID ленты ($rss), а ее данные, нужно определить hool_load():
function rss_load($id)
{
$rss = db_select('rssfeeds', 'n')
->fields('n', array('id', 'name', 'url', 'created_at', 'updated_at'))
->condition('n.id', $id)
->execute()->fetchAssoc();
return $rss;
}
Теперь в качестве $rss в форму будет передаваться не число, а объект с данными.
Далее следует реализовать функцию вывода страницы, на которой мы сможем проводить редактирование нашей таблицы RSS лент - rss_list():
function rss_list()
{
$header = array(
array('data' => t('Name')),
array('data' => t('URL')),
array('data' => t('Actions'))
);
$rss = db_select('rssfeeds', 'n')
->fields('n', array('id', 'name', 'url'))
->execute()->fetchAll();
$row = array();
if ($rss) {
foreach ($rss as $rss_feed) {
$actions = array(
l(t('edit'), 'admin/config/content/rss_feeds/' . $rss_feed->id . '/edit'),
l(t('delete'), 'admin/config/content/rss_feeds/' . $rss_feed->id . '/delete'),
);
$row [] = array(
array('data' => $rss_feed->name),
array('data' => $rss_feed->url),
array('data' => implode(' | ', $actions)),
);
}
}
return theme('table', array(
'header' => $header,
'rows' => $row,
));
}
Функция l() (link) — создает ссылку. Но главная функция — theme(). С ней Вы познакомитесь отдельно, т.к. она очень обширная и имеет множество полезных параметров.
Ниже создадим функцию удаления записей rss_feeds_delete():
function rss_feeds_delete($rss)
{
$rss_deleted = db_delete('rssfeeds')
->condition('id', $rss['id'])
->execute();
drupal_set_message(t('RSS Feed deleted!'));
drupal_goto('admin/config/content/rss_feeds');
}
Без комментариев.
Для удобства в дальнейшем я добавил функцию, которая будет возвращать данные, в зависимости от параметра (блок или страница). Вот ее содержимое:
function rss_contents($display)
{
$query = db_select('rssfeeds', 'n')
->fields('n', array('id', 'name', 'url'))
->orderBy('name', 'DESC');
if ($display == 'block') {
$query->range(0, 5);
}
return $query->execute();
}
Если в качестве параметра мы укажем 'block', то нам выведет лишь 5 записей.
Далее реализуем хук вывода блока — hook_block_view():
function rss_feeds_block_view($delta = '')
{
$blocks = array();
switch ($delta) {
case 'rss_feeds':
$select = db_select('rssfeeds', 'tc');
$select->addField('tc', 'name');
$select->addField('tc', 'url');
$entries = $select->execute()->fetchAll();
$blocks['subject'] = t('List of URLs');
$blocks['content'] = theme('rssfeeds_block', array('urls' => $entries));
}
return $blocks;
}
Данный блок будет доступен в панели администрирования.
Теперь напишем функцию отображения страницы, на которой будет список лент, при нажатии на которые, будет выводиться контент (в новой вкладке):
function _rss_feeds_page()
{
drupal_set_title(t('RSS Feeds'));
$result = rss_contents('page')->fetchAll();
if (!$result) {
$page_array['rss_feeds_arguments'] = array(
'#title' => t('RSS Feeds page'),
'#markup' => t('No RSS feeds available'),
);
return $page_array;
} else {
$page_array = theme('rssfeeds_page', array('urls' => $result));
return $page_array;
}
}
… и страницу отображения контента:
function rss_content($rss)
{
$url = $rss['url'];
libxml_use_internal_errors(true);
$rss_feed = simplexml_load_file($url);
if (!empty($rss_feed)) {
drupal_set_title($rss_feed->channel->title);
$page_array = theme('rssfeeds_content', array('items' => $rss_feed));
return $page_array;
} else {
$page_array['rss_feeds_arguments'] = array(
'#title' => t('All posts from the last week'),
'#markup' => t('No posts available.'),
);
return $page_array;
}
}
Возможно Вы заметили, но страницы не будут выводиться. Я указал в функции theme() параметр 'rssfeeds_block' и 'rssfeeds_content'. Это созданные мной шаблоны. Сделаем hook_theme() для их инициализации:
function rss_feeds_theme()
{
return array(
'rssfeeds_block' => array(
'variables' => array(
'urls' => NULL
),
'template' => 'rssfeeds-block',
),
'rssfeeds_page' => array(
'variables' => array(
'urls' => NULL
),
'template' => 'rssfeeds-page',
),
'rssfeeds_content' => array(
'variables' => array(
'items' => NULL
),
'template' => 'rssfeeds-content',
)
);
}
В поле 'template' указываем название шаблона.
Теперь создаем сами файлы шаблонов. А именно: rssfeeds-block.tpl.php, rssfeeds-page.tpl.php и rssfeeds-content.tpl.php.
Содержимое первого файла:
<div id="rssfeeds-pager">
<?php foreach ($urls as $url): ?>
<span>
<a target="_blank" href="http://<?php echo $url->url; ?>"><?php echo $url->url; ?></a><br>
</span>
<?php endforeach; ?>
</div>
… второго:
<div id="rssfeeds-pager">
<?php foreach ($urls as $url): ?>
<span>
<a target="_blank" href="rss_feeds/<?php echo $url->id; ?>/items"><?php echo $url->name; ?></a><br>
</span>
<?php endforeach; ?>
</div>
… и третьего соответственно:
<?php foreach ($items->channel->item as $item): ?>
<span class="title"><a href="<?php echo $item->link; ?>"><?php echo $item->title; ?></a></span><br><br>
<?php echo $item->description; ?><br><br><?php echo $item->pubDate; ?>
<hr><br><br>
<?php endforeach; ?>
Теперь наш модуль будет функционировать. Однако, Вы наверняка заметили — к элементам определены классы. Теперь прикрепим к нашему модулю файл CSS стилей. Для этого добавим в файл .info данную строку:
stylesheets[all][] = main.css
И создаем файл main.css:
.title {
font-size: 16px; text-shadow: 0px 0px 1px black; font-weight: bold;
}
.title a {
text-decoration: none; color: #B81007;
}
.title a:hover {
text-decoration: underline;
}
Заключение
Вот собственно и получился готовый модуль. Если Вам понравилась данная статья, я напишу как добавлять js-файлы к модулям и делать файлы-локализации. Надеюсь кому-нибудь она поможет.
Спасибо за внимание!
Автор: riddya