Создание модуля под Drupal 7

в 9:42, , рубрики: cms, drupal, drupal 7, modules, web-разработка, метки: , , ,

Предисловие

Совсем недавно получил задание научиться писать модули под 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 callbackdrupal_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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js