Использование ПО с открытым исходным кодом для создания Web-сайта для совместной работы: Часть 6. Создание специализированного модуля в Drupal

Создание специализированного модуля для уведомлений на Web-сайте, включая реализацию и примеры кода.

Элистер Льюис-Боуэн, старший инженер-программист, IBM

Элистер Льюис-БоуэнЭлистер Льюис-Боуэн (Alister Lewis-Bowen) работает старшим инженером-программистом в IBM Internet Technology Group. Он занимается Интернет и web-технологиями как сотрудник IBM UK с 1993. Элистер был переведен в США для работы над Web-сайтами спортивных событий, спонсируемых IBM, а затем как старший Web-мастер ibm.com. В настоящее время он помогает создавать семантические Web-прототипы.



Стефен Эванчик, инженер-программист, IBM

Стефен ЭванчикСтефен Эванчик (Stephen Evanchik) работает инженером-программистом в IBM Internet Technology Group. Он принимает участие во многих проектах с открытыми исходными кодами, самым известным из которых является его IBM TrackPoint-драйвер в ядре Linux. Стефен в настоящее время работает с появляющимися семантическими Web-технологиями.



Луис Вайцман, старший инженер-программист, IBM

Луис ВайцманЛуис Вайцман (Louis Weitzman) работает старшим инженером-программистом в IBM Internet Technology Group. В течение 30 лет он работал на стыке дизайна и вычислений. Помогал разрабатывать XML, фрагментированную систему управления содержимым, используемую для ibm.com, а в настоящее время участвует в обеспечении процесса проектирования новых проектов.



25.12.2006

Введение

В данной статье вы узнаете, как создать очень простой, специализированный модуль для предоставления уведомлений на Web-сайте. Эта информация должна рассматриваться не как строгий набор правил разработки, а как отправная точка для создания ваших собственных специализированных модулей. Во многих случаях мы предоставляем краткое введение в тему и ссылаемся на документацию по Drupal для получения более подробной информации.

Наша вымышленная компания International Business Council (IBC) должна отображать важные уведомления, которые автоматически публикуются, а затем удаляются с Web-сайта по истечении указанного времени. Уведомления требуют наличия нескольких новых полей данных:

  • Краткий обзор, отличающийся от краткого обзора по умолчанию (teaser) и контролируемый автором уведомления. Он позволит нам проиллюстрировать несколько функциональных возможностей Drupal.
  • Дата публикации (когда уведомление первый раз появится на Web-сайте).
  • Дата истечения срока публикации (когда уведомление будет удалено с Web-сайта).
  • Возможность просматривать уведомления группой администраторов, даже если уведомления находятся вне промежутка между датами публикации и истечения срока действия.

Домашняя страница отображает текущие уведомления в области основного содержимого, как показано на рисунке 1. На каждой странице для включения наиболее свежих уведомлений используется боковая панель. Уведомления, созданные текущим пользователем, выделяются цветным фоном.

Рисунок 1. Домашняя страница IBC
Рисунок 1. Домашняя страница IBC

Начало работы

Перед началом работы мы создадим файл, который будет содержать весь наш код, определяющий новый модуль. Затем мы модифицируем базу данных для поддержки информации, релевантной для нашего нового модуля.

Создание файла

Для начала мы создадим каталог modules/announcement в ibc_site. В этом каталоге создадим файл announcement.module, как показано на рисунке 2. Обычно, название модуля совпадает с новым типом узла, который он создает. Этот файл будет содержать весь предназначенный для уведомлений код, рассматриваемый в данной статье.

Примечание: Согласно современному передовому опыту работы с Drupal, все модули, не относящиеся к функционированию ядра, помещаются в каталог sites для соответствующего домена. Поэтому в нашем примере лучше было бы поместить каталог directory в каталог sites/drupal.development, созданный в предыдущих статьях. Хранение добавленных модулей вне основного дистрибутива упрощает обновления базового кода Drupal.

Также в Drupal 4.7 файлы .install могут использоваться для подготовки любых таблиц и данных базы данных, необходимых для вашего модуля. Вместо использования файла announcement.sql вы можете использовать php для выполнения SQL-выражений внутри файла announcement.install, расположенного в каталоге вашего модуля уведомлений. Дополнительная информация приведена по адресу http://drupal.org/node/51220.

Спасибо, Борис, за то, что Вы это отметили. Дополнительные подробности данного метода приведены в блоге Бориса.

Рисунок 2. Вид Navigator в Eclipse
Рисунок 2. Вид Navigator в Eclipse

Изменение базы данных

Все представляемые узлом данные хранятся в таблице node и (в версии 4.7) таблице node_revisions. При изменении узла его редакции сохраняются в таблице node_revisions, включая заголовок, краткий обзор (teaser) и тело. Поскольку существующие таблицы Drupal не поддерживают требования для нашего модуля, мы создали таблицу announcement для хранения относящейся к уведомлениям информации. Чтобы иметь большее влияние на краткий обзор уведомления, мы включили наш собственный краткий обзор (abstract) в новую таблицу. Сначала мы создаем таблицу, а затем будем писать функции для доступа к этой таблице при наступлении определенных событий.

В листинге 1 показана команда, создающая таблицу announcement, связанную с таблицей node через ID узла, nid. Новыми столбцами в таблице являются abstract, publish_date и expiration_date. Новая таблица в базе данных создается при помощи командной строки SQL или через браузер MySQL-запросов. Мы сохранили эту команду в файле announcement.sql каталога announcement (рисунок 2). Этот шаг документирует таблицы базы данных для этого модуля и позволяет также легко обновлять базу данных при необходимости.

Листинг 1. Команда для создания новой таблицы announcement
CREATE TABLE announcement (
  nid             int(10) unsigned NOT NULL default '0',
  abstract		  varchar(255) default '',
  publish_date    integer NOT NULL default '0',
  expiration_date integer NOT NULL default '0',
  PRIMARY KEY (nid)
);

Разработка модуля

Интерфейс Drupal для модулей работает через набор функций, называемых ловушками (hooks). В данном разделе мы рассмотрим разработку нескольких ловушек для поддержки нашего специализированного модуля. Они представляют собой стартовый набор функций для обеспечения функционирования нашего модуля; мы реализовали только некоторые из ловушек Drupal.

hook_settings
Создает атрибуты для этого модуля, которые может менять администратор.
hook_help
Предоставляет документацию, появляющуюся в различных местах интерфейса.
hook_perm
Определяет категории полномочий для доступа к информации.
hook_access
Определяет права доступа для различных операций и пользователей.
hook_menu
Настраивает URL-пути и вызываемые функции при управлении доступом.
hook_link
Определяет ссылки, которые могут быть добавлены к представлениям по всему сайту.
hook_block
Определяет блок информации из этого модуля для отображения.
hook_form
Определяет виджеты интерфейса, используемые при добавлении и редактировании этого узла.
hook_validate
Проверяет вводимую пользователем информацию перед сохранением в базе данных.
hook_submit
Изменяет узел после проверки, но до обновления базы данных.
hook_load
Загружает дополнительную информацию узла из базы данных.
hook_insert
Сохраняет дополнительную информацию узла первый раз.
hook_update
Сохраняет дополнительную информацию узла, если узел уже существует.
hook_delete
Удаляет дополнительную информацию узла при его удалении.
hook_cron
Выполняет запланированные действия, определенные администратором.
hook_search
Определяет специализированный поиск по информации о данном узле.
hook_nodeapi
Действует на узлы, определенные другими модулями.
hook_node_info
Определяет имя и атрибуты типов узлов модуля.

hook_settings

Ловушка settings обеспечивает способ добавления в модуль атрибутов, которые могут управляться администратором и использоваться при отображении данного модуля. Для нашего модуля announcement мы хотим ограничить количество уведомлений, представляемых в боковой панели. Представляемые элементы уведомления будет генерировать ловушка block. Однако мы можем предоставить администратору возможность устанавливать через интерфейс администратора количество уведомлений, отображаемых в боковой панели. Эти относящиеся к модулю атрибуты доступны в интерфейсе по следующему относительному URL:

admin/settings/<module_name>

В листинге 2 показана реализация ловушки announcement_settings.

Листинг 2. Реализация ловушки announcement_settings
function announcement_settings() {
  $form = array();
  $form['announcement_block_max_list_count'] = array(
     '#type'		=> 'textfield',
     '#title'		=> t('Maximum number of block announcements'),
     '#default_value'	=> variable_get('announcement_block_max_list_count', 3),
     '#description'	=> t('The maximum number of items listed in the announcement block'),
     '#required'	=> FALSE, 
     '#weight'		=> 0
  );
  return $form; 
}

Ловушка announcement_settings, приведенная в листинге 2, определяет элемент интерфейса, использующийся администратором при указании значения данного атрибута. Индексом массива form является имя переменной, например, announcement_block_max_list_count. Компоненты массива, хранящиеся в этом индексе, определяют то, как будет формироваться интерфейс администратора.

К этому значению затем можно будет обратиться при создании боковой панели уведомлений для отображения. Используя фрагмент кода из листинга 3, мы извлекаем определенную в ловушке settings персистентную переменную при создании боковой панели.

Листинг 3. Извлечение переменной, установленной в ловушке settings
$items = variable_get('announcement_block_max_list_count', 3);

hook_help

Ловушка help обеспечивает место для размещения документации, которая будет отображаться при взаимодействии с системой администратора или пользователя. Два таких места - на странице admin/modules, где администратор может разрешить или запретить модуль. На рисунке 3 показана строка на экране администратора для разрешения этого модуля с выделенным модулем announcement.

Рисунок 3. Справочная документация на странице администратора
Рисунок 3. Справочная документация на странице администратора

Аналогично, когда пользователи добавляют новое уведомление, используя ссылку create content, они увидят описание добавляемого ими узла, как показано на рисунке 4.

Рисунок 4. Справочное описание типа узла, который может быть добавлен
Рисунок 4. Справочное описание типа узла, который может быть добавлен

В листинге 4 приведена реализация ловушки help для модуля announcement, которая генерирует эти два описания. Могут также формироваться другие справочные описания.

Листинг 4. Реализация ловушки announcement_help
function announcement_help($section) {
  switch ($section) {
    case 'admin/modules#description':
      return t('Enables the creation of announcement pages ' .
               'that are presented on the home page.');
    case 'node/add#announcement':
      return t('An Announcement. Use this page to add an announcement page.');
  }
}

hook_perm

Ловушка perm определяет права доступа, которым может быть назначена каждая роль. Для описания действий, релевантных для вашего приложения, могут использоваться произвольные строки. Здесь мы делаем различие между созданием уведомления и редактированием (или удалением) уведомления, как показано в листинге 5. Функция hook_access может использовать эти полномочия для управления доступом к содержимому.

Листинг 5. Реализация ловушки announcement_perm
function announcement_perm() {
     return array('create announcement', 'edit announcement');
}

Ролями по умолчанию в Drupal являются анонимный пользователь (anonymous user) и аутентифицированный пользователь (authenticated user). Администратор может создать дополнительные роли через интерфейс, используя путь admin/access/control. Эти роли совместно с определенными выше полномочиями формируют основу консоли управления полномочиями, показанной на рисунке 5. Этот массив флажков позволяет администратору разрешать полномочия для каждой конкретной роли в системе. Модуль announcement выделен, для того чтобы показать две строки, определенные в функции hook_perm листинга 5. Только пользователи, имеющие роль administrator или operations, могут создавать и редактировать уведомления.

Рисунок 5. Интерфейс, позволяющий администратору назначать полномочия
Рисунок 5. Интерфейс, позволяющий администратору назначать полномочия

hook_access

В каждом модуле может быть ограничен доступ к представляемым модулем данным. Ловушка access позволяет автору модуля управлять этим доступом. На практике для проверки наличия у текущего пользователя указанных прав доступа используется функция user_access. В листинге 6 в функции user_access мы обращаемся к полномочиям, определенным в функции announcement_perm.

Функции hook_access предоставляется операция (например, create, view, update или delete) и узел, являющийся объектом операции. Функция возвращает значение Boolean, указывающее на то, должен ли текущий пользователь иметь доступ к операции с указанным узлом.

В этой реализации только те пользователи, которым присвоены полномочия "create announcement", могут создавать уведомления. Пользователи, имеющие полномочия "access content" (любой аутентифицированный пользователь), могут просматривать уведомление. Операции обновления и удаления могут выполняться в том случае, если пользователь, инициировавший запрос, является автором содержимого, либо если ему явно были назначены полномочия для редактирования уведомления.

Первый зарегистрированный пользователь, созданный во время установки Drupal (пользователь с ID = 1), имеет главные (root) полномочия и может редактировать и изменять любые данные в системе.

Листинг 6. Реализация ловушки announcement_access
function announcement_access($op, $node) {
  global $user;
	
  if ($op == 'create') {
	  return user_access('create announcement');
  }
    else if ($op == 'view') {
 	 return user_access('access content');
  }
  else if ($op == 'update' ||  $op == 'delete') {
	if($user->uid == $node->uid || user_access('edit announcement')) {
	   		return true;
	}
	else {
		return false;
	}
  }
  else {
  	return false;
  }
}

Реакция Drupal на URL определяется в функции ловушки menu. Эта функция определяет функции обратного вызова для конкретных URL и элементов меню. Стандартным способом формирования URL в Drupal при обращении к конкретному узлу является помещение ID узла перед операцией. При использовании этого формата для нашего модуля должны быть указаны следующие URL:

   /announcements
   /announcements/add
   /announcements/<id>/view
   /announcements/<id>/edit
   /announcements/<id>/delete

где <id> - это ID узла, который просматривается, редактируется или удаляется. Определение нашей функции announcement_menu приведено в листинге 7.

Листинг 7. Реализация ловушки announcement_menu
function announcement_menu($may_cache) {
   $items = array();
      if ($may_cache) {
         $items[] = array('path'     => 'announcements/add',
                          'title'    => t('Add a new Announcement'), 
                          'access'   => node_access('create', 'announcement'),
                          'type'     => MENU_CALLBACK,
                          'callback arguments' => array('announcement'),
                          'callback' => 'node_add');			 
         $items[] = array('path'     => 'announcements',
                          'title'    => t('Announcements'),
                          'access'   => user_access('access content'),
                          'type'     => MENU_CALLBACK,
                          'callback' => 'announcement_all');				 
      }
      else {
         if(is_numeric(arg(1))) {			
            $node = node_load(arg(1));
            $items[] = array('path'     => 'announcements/' . arg(1),
                             'title'    => t('View an Announcement'),
                             'access'   => node_access('view', $node),
                             'type'     => MENU_CALLBACK,
                             'callback' => 'node_page');                      
            $items[] = array('path'     => 'announcements/' . arg(1) . '/view',
                             'title'    => t('View an Announcement'),
                             'access'   => node_access('view', $node),
                             'type'     => MENU_CALLBACK,
                             'callback' => 'node_page');                      
            $items[] = array('path'     => 'announcements/' . arg(1) . '/edit', 
                             'title'    => t('Edit an Announcement'),
                             'access'   => node_access('edit', $node),
                             'type'     => MENU_CALLBACK,
                             'callback' => 'node_page');           
            $items[] = array('path'     => 'announcements/' . arg(1) . '/delete', 
                             'access'   => node_access('delete', $node),
                             'type'     => MENU_CALLBACK,
                             'callback' => 'node_delete_confirm');			
		}						 
	}
  	return $items;
}

Функция возвращает массив массивов, где каждый массив содержит:

path
URL для проверки совпадения.
title
Заголовок элемента меню, который показывается при наведении курсора мыши на этот элемент.
callback
Функция, которую нужно активировать при доступе по этому URL.
type
Тип элемента меню.
access
Права доступа к этому элементу меню.

Для того чтобы повысить эффективность Web-сайта, используется кэширование. Управлять кэшированием фрагментов меню мы можем при помощи условия $may_cache. Однако при кэшировании не отображаются изменения, выполненные в процессе разработки кода. Кэшируемые элементы не должны содержать такие переменные как arg(1) в спецификации своего пути.

Одним из базовых принципов, которому мы пытаемся следовать, является графическое размещение действий рядом с элементом, с которым они работают. Для уведомлений мы поместили ссылки на добавление, редактирование, удаление и комментирование рядом с заголовком уведомления. Естественно, эти действия должны предоставляться только тогда, когда текущий пользователь имеет соответствующие полномочия. Действия оформляются маленьким красным шрифтом, как показано на рисунке 6.

Рисунок 6. Ссылки на действия рядом с заголовком уведомления
Рисунок 6. Ссылки на действия рядом с заголовком уведомления

Ловушка link обеспечивает нас механизмом для выполнения этой задачи. Листинг 8 демонстрирует нашу реализацию данной ловушки. Она позволяет создать соответствующие ссылки, основанные на полномочиях, а затем оформить эти ссылки с соответствующей разметкой, что дает возможность использовать CSS для оформления текста маленьким красным шрифтом. Модуль comment тоже участвует в этом наборе ссылок, предоставляя действие "Add Comment".

В последовательности l(t('Edit')...) в листинге 8 мы используем две вспомогательные функции для поддержки генерирования ссылок. Функция t сначала преобразовывает строку 'Edit' в текущие региональные настройки, затем функция l форматирует эту строку в соответствующую внутреннюю ссылку Drupal. Ссылки оформляются механизмом поддержки шаблонов при помощи вызова theme('links', $links) и затем отображаются в переменную шаблона $links.

Листинг 8. Реализация ловушки announcement_link
function announcement_link($type, $node = NULL, $teaser = FALSE) {
   global $user;
   $links = array();
	
   if($type == 'node' && $node->type == 'announcement') {
      if (node_access('create', 'announcement')) {			
         $links[] = l(t('Add'), "node/add/announcement", 
                      array('title' => t('Add a new announcement'))); 
      }
      if (node_access('update', $node)) {
         $links[] = l(t('Edit'), "announcements/$node->nid/edit",
                      array('title' => t('Edit Announcement ') . $node->title)); 

         $links[] = l(t('Delete'), "announcements/$node->nid/delete",
                      array('title' => t('Delete Announcement ') . $node->title)); 
      }
   }
   return $links;
}

hook_block

Уведомления появляются в центре домашней страницы и в навигационной панели на каждой странице (см. рисунок 7). Содержимое боковой панели разрешается ловушкой block, которая позволяет модулю участвовать в формировании содержимого, которое может отображаться в любом месте экрана. Обычно представленная в боковой панели информация является кратким обзором полного содержимого. Обратите внимание на то, что выделение уведомлений для текущего пользователя выполняется как в боковой панели, так и в области главного содержимого. На рисунке 7 показана боковая панель для администратора, поэтому вы видите несколько ресурсов (Resources), которые могут использоваться после регистрации в системе. Они не отображаются для обычного пользователя.

Рисунок 7. Уведомления в боковой панели при использовании ловушки block
Рисунок 7. Уведомления в боковой панели при использовании ловушки block

После определения этой ловушки администратор может указать расположение информации блока через пользовательский интерфейс, как показано на рисунке 8. Уведомления разрешены, и для них указан вес -10 в правой боковой панели. Чем меньше число, тем выше отображается содержимое в боковой панели. Это гарантирует, что уведомления являются первым информационным блоком, отображаемым в правой боковой панели.

Рисунок 8. Интерфейс администратора для указания расположения блока
Рисунок 8. Интерфейс администратора для указания расположения блока

В листинге 9 приведена реализация нашей ловушки block для модуля announcement. Эта функция способна формировать более одного блока. Drupal использует аргумент $delta для индексации отображаемого блока, как определено в массиве $block. Первое условие, которое проверяет функция - операция list. Эта информация используется в интерфейсе администратора, показанном на рисунке 8.

Второй операцией является view, которая собирает соответствующие уведомления для показа в информационном блоке, используемом нами в правой боковой панели. Она использует параметр уведомления announcement_block_max_list_count, определенный в ловушке announcement_settings, для определения количества отображаемых уведомлений. Операция view использует файл шаблона announcement_block_list (announcement_block_list.tpl.php) для оформления уведомлений (механизм оформления уведомлений будет рассматриваться в одной из следующих статей).

Листинг 9. Реализация ловушки announcement_block
function announcement_block($op = 'list', $delta = 0, $edit = array()) {
   global $user;
   if ($op == 'list') {
      $blocks[0]['info'] = t('Recently updated announcements');	
      return $blocks;
   }
   else if ($op == 'view')	{
      $block	= array();
      $output	= '';
      switch ($delta) {
         case 0:
            $now = time();
            if (user_access('access content')) {
               $q = 'SELECT N.uid,N.nid,N.title,A.publish_date,N.status '.
			'FROM {node} N JOIN {announcement} A USING(nid) '.
			"WHERE N.type='announcement' ".
			'AND N.status = 1 '.
			'AND A.publish_date < ' . $now . ' '.
			'AND A.expiration_date > ' . $now . ' '.
			'ORDER BY A.publish_date DESC ';
               $items = variable_get('announcement_block_max_list_count', 3);
               if ($items) { $q .= "LIMIT 0,$items"; }
               $announcements = db_query($q);
               $announcement_items = array();
               while (db_num_rows($announcements) > 0 and $announcement =     
                      db_fetch_object($announcements)) {	
                  $announcement_items[] = $announcement;			
               }
         }
         $block['subject'] = t('Announcements');
         $block['content'] = theme('announcement_block_list',$announcement_items);
	  break;
      }
      return $block;
   }
}

hook_form

Ловушка form вызывается для генерирования пользовательского интерфейса, использующегося при добавлении или редактировании узла. Эта ловушка возвращает массив массивов для каждого фрагмента содержимого, которое должно редактироваться пользователем. Узел announcement редактируется через интерфейс, показанный на рисунке 9. Мы предоставляем поля ввода для заголовка, дат публикации и истечения срока действия, краткого обзора и тела. Ниже каждого элемента формы предоставляется краткое описание, указывающее пользователю, что представляет собой информация, и как она будет использоваться.

Рисунок 9. Форма редактирования узла announcement
Рисунок 9. Форма редактирования узла announcement

Для генерирования элемента формы редактирования краткого обзора уведомления мы создаем массив, как показано в листинге 10.

Листинг 10. Формирование текстовой области для редактирования краткого обзора уведомления
$form['abstract'] = array(
   '#type' => 'textarea',
   '#title' => t('Abstract'),
   '#default_value' => $node->abstract,
   '#rows' => 3,
   '#description' => t('Short summary of the full announcement'),
   '#required' => TRUE,
   '#weight' => 9 
);

Индексом (abstract) массива является имя элемента. Доступ к значению осуществляется из структуры данных node: $node->abstract. Детали этого элемента:

type
Типом виджета является textarea (оказывает влияние на другие доступные атрибуты).
title
Отображаемый заголовок, преобразованный функцией "t".
default_value
Значение, используемое при первоначальном отображении виджета, например, текущее значение.
rows
Строки, отображаемые в textarea.
description
Текст, отображаемый ниже виджета в интерфейсе.
required
Необходимо данное поле ввода или нет.
weight
Влияет на сортировку виджетов: чем меньше значение, тем выше в интерфейсе отображается виджет.

Для группирования дат публикации и истечения срока publication определена как fieldset. Полная функция announcement_form, формирующая интерфейс для редактирования узла announcement, приведена в листинге 11. Первые два оператора if устанавливают приемлемые значения по умолчанию (если их нет) для дат публикации и истечения срока действия. Мы используем элементы #prefix и #suffix в массиве publication для вставки дополнительных HTML-тегов, позволяющих простое CSS-оформление поля выбора даты. Строковый индекс массива $form используется для последующего разыменования конкретного значения из узла.

Листинг 11. Полная функция announcement_form
function announcement_form(&$node) {
   if ($node->expiration_date == NULL) {
      $node->expiration_date = time() + (365 * 86400);
   }
   if ($node->publish_date == NULL) {
      $node->publish_date = time();
   }
   $form['title'] = array('#type' => 'textfield',
      '#title'         => t('Title'),
      '#default_value' => $node->title,
      '#description'   => t('Title of the announcement'),
      '#required'      => TRUE, 
      '#weight'        => 1
   );
   $form['publication'] = array('#type'=> 'fieldset',
      '#collapsible'   => FALSE,
      '#title'         => t('Publication dates'),
      '#weight'        => 5 
   ); 
   $form['publication']['publish_date'] = array(
      '#prefix'        => '<div class="date_widget">',
      '#suffix'        => '</div>',
      '#type'          => 'date',
      '#title'         => t('Publication date'),
      '#default_value' => _announcement_unixtime2drupaldate($node->publish_date)
   );
   $form['publication']['expiration_date'] = array(
      '#prefix'        => '<div class="date_widget">',
      '#suffix'        => '</div>',
      '#type'          => 'date',
      '#title'         => t('Expiration date'),
      '#default_value' => _announcement_unixtime2drupaldate($node->expiration_date)
   );
   $form['abstract'] = array('#type' => 'textarea',
      '#title'         => t('Abstract'),
      '#default_value' => $node->abstract,
      '#rows'          => 3,
      '#description'   => t('Short summary of the full announcement'),
      '#required'      => TRUE,
      '#weight'        => 9 
   );
   $form['body'] = array('#type' => 'textarea',
      '#title'         => t('Body'),
      '#default_value' => $node->body,
      '#description'   => t('Full content for the announcement which ' .
		      'is shown with the abstract on the details page'),
      '#required'      => TRUE, 
      '#weight'        => 10 
   );
   return $form;
}

Одним из существенныхих изменений в Drupal 4.7 по сравнению с 4.6 является реализация ловушки form. На Web-сайте Drupal размещено большое количество соответствующей документации, включая:

hook_validate

В конце процесса редактирования перед сохранением узла активизируется ловушка validate. Она может использоваться для проверки корректности данных перед их записью в базу данных. Для уведомлений мы используем эту ловушку, чтобы гарантировать, что дата начала меньше даты конца. Интерактивная реализация, использующая язык сценариев на стороне клиента, не позволила бы такому произойти, согласовав эти два поля друг с другом. Если вы хотите выполнить дополнительные изменения в узле перед его сохранением, используйте ловушку submit.

В листинге 12 приведена реализация ловушки validate. Сначала мы преобразовываем даты в целые числа, которые можем сравнить. Если существует проблема, которую должен решить пользователь, используется функция form_set_error, которая применяет механизм обработки ошибок Drupal. Первый аргумент этой функции, publish_date в листинге 12, ссылается на имя массива form, используемого в ловушке form (листинг 11), и будет выделять этот элемент в интерфейсе.

Листинг 12. Ловушка announcement_validate
function announcement_validate($node) {
   if ($node) {
      $publish_date    = 
          _announcement_drupaldate2unixtime($node->publish_date);
      $expiration_date = 
          _announcement_drupaldate2unixtime($node->expiration_date);
      if ($publish_date >= $expiration_date) {
         form_set_error('publish_date', 
        t('The publish date of an announcement must be before its expiration date.'));
      }
   }
}

hook_submit

Если узел прошел фазу проверки корректности, вызывается ловушка submit, где мы можем выполнить дополнительные изменения в узле перед реальным обновлением базы данных. В листинге 13 ловушка submit обновляет даты публикации и истечения срока действия в узле, а затем изменяет состояние узла на основе текущей даты. Если текущая дата находится в интервале между датами публикации и истечения срока действия, состояние устанавливается в 1, в противном случае - в 0.

Листинг 13. Ловушка announcement_submit
function announcement_submit(&$node) {
   $node->publish_date    = 
      _announcement_drupaldate2unixtime($node->publish_date);
   $node->expiration_date = 
      _announcement_drupaldate2unixtime($node->expiration_date);
	
   $now = time();
   if ($now >= $node->publish_date && 
       $now < $node->expiration_date) {
      $node->status = 1;
   }
   else {
      $node->status = 0;
   }
}

Ловушки базы данных

Drupal активизирует функцию hook при различных событиях, возникающих в среде в процессе взаимодействия с базой данных. К важным событиям относятся загрузка, вставка, обновление и удаление. Здесь размещена дополнительная информация по этим событиям ловушек.

Мы рассмотрим MySQL и уровень абстракции базы данных более детально в следующей статье.

Drupal имеет уровень абстракции данных, используемый по всему коду. Функции, начинающиеся с db_, используются в ловушках базы данных для доступа к этому уровню абстракции.

hook_load

Ловушка load автоматически вызывается при загрузке из базы данных узла типа announcement. Эта функция позволяет специализированному модулю загружать любое дополнительное содержимое из базы данных для завершения его определения. Возвращаемым из функции значением является массив дополнительного содержимого, объединяемого в структуру данных node. В нашем случае мы должны загружать три элемента данных из новой таблицы: краткий обзор, дату публикации и дату истечения срока действия. В листинге 14 приведен код для загрузки этой дополнительной информации для нашего узла announcement.

Листинг 14. Ловушка announcement_load для загрузки узла типа announcement
function announcement_load(&$node) {
   $additions = db_fetch_object(db_query('SELECT * FROM {announcement} ' .
                                         'WHERE nid = %d', $node->nid));
   return $additions;
}

hook_insert

При создании узла announcement на Web-сайте автоматически активизируется ловушка insert (листинг 15). Эта ловушка обеспечивает для нового модуля возможность сохранять любую дополнительную информацию в базе данных при первом создании узла. Передаваемый в функцию объект node содержит все данные из входных форм. Даты публикации и истечения срока действия возвращаются в виде массива с месяцем, днем и годом. Они преобразуются локально функцией _announcement_drupaldate2unixtime. По соглашению все локальные функции модуля предваряются символом подчеркивания ("_") и именем модуля, например _announcement. Затем мы вызываем уровень абстракции базы данных для вставки новой строки в таблицу announcement. $node->nid - это первичный ключ, который связывает таблицу announcement с таблицей node.

Листинг 15. Ловушка announcement_insert и вспомогательная функция для добавления уведомления в базу данных
function _announcement_drupaldate2unixtime($drupal_date) {
	$year  = $drupal_date["year"];
	$month = $drupal_date["month"];
	$day   = $drupal_date["day"];
	return mktime(0,0,0, (int)$month, (int)$day, (int)$year);
}

function announcement_insert($node) {
 $publish_date    = _announcement_drupaldate2unixtime($node->publish_date);
 $expiration_date = _announcement_drupaldate2unixtime($node->expiration_date);
 db_query("INSERT INTO {announcement} (nid, abstract, publish_date, expiration_date) ".
          "VALUES (%d, '%s', '%d', '%d')", 
          $node->nid, $node->abstract, $publish_date, $expiration_date);
}

hook_update

Если информация уже существует в базе данных, и пользователь редактирует ее, вызывается ловушка update (листинг 16). Она аналогична ловушке insert, но в базу данных выдается команда UPDATE.

Листинг 16. Ловушка announcement_update для изменения существующего узла announcement
function announcement_update($node) {
   $publish_date    = _announcement_drupaldate2unixtime($node->publish_date);
   $expiration_date = _announcement_drupaldate2unixtime($node->expiration_date);

   db_query("UPDATE {announcement} SET abstract='%s', publish_date = '%s', " .
            "expiration_date = '%s' WHERE nid = %d", 
            $node->abstract, $publish_date, $expiration_date, $node->nid);
}

hook_delete

Наконец, при удалении пользователем узла announcement вызывается ловушка delete (показанная в листинге 17). Это предоставляет модулю возможность удалить все дополнительное содержимое из других таблиц базы данных. Здесь мы удаляем одну строку в таблице announcement, связанную с узлом через поле nid.

Листинг 17. Ловушка announcement_delete для удаления узла announcement
function announcement_delete($node) {
   db_query('DELETE FROM {announcement} WHERE nid = %d', $node->nid);
}

hook_cron

Ловушка cron позволяет модулям планировать задания для выполнения через регулярные интервалы времени. Интервалы могут быть установлены администратором сайта через задание cron, выполняющее запрос HTTP GET на http://<sitename.com>/cron.php. Это активизирует определенную ловушку cron для всех модулей. Состояние задания cron можно увидеть через интерфейсе администратора, выбрав Administer > Settings (например, /admin/settings) в разделе cron jobs.

Модуль announcement проверяет даты публикации и истечения срока действия для определения необходимости визуализации уведомления. Однако уведомление все еще может отображаться через стандартный механизм узлов (/node/id/view), если состояние узла не установлено в 0. Мы используем ловушку cron для установки флага состояния всех узлов announcement, срок действия которых истек. В листинге 18 приведена функция announcement_cron, которая сначала запрашивает в базе данных все уведомления с истекшим сроком действия, а затем устанавливает состояние этих узлов в значение 0.

Листинг 18. Реализация ловушки announcement_cron
function announcement_cron() {
   $queryResult = db_query("UPDATE {node} AS n INNER JOIN {announcement} AS a " .
       "ON n.nid = a.nid SET n.status = 0 WHERE n.type='announcement' " .
       "AND n.status = 1 AND a.expiration_date < %d", time());
}

Ловушка search позволяет модулю расширить поисковую страницу возможностью выполнять поиск по ключевым словам в узлах, созданных модулем. Прежде всего, на странице administer > modules необходимо разрешить модуль search. Это позволит нам включить блок search в заголовок на странице administer > block. При использовании ловушки search на странице search визуализируется еще одна закладка для простого поиска. Эта поисковая форма затем используется для поиска ключевых слов в узлах того типа, который создается вашим модулем.

Модуль search использует задание cron для создания таблицы индексов данных, найденных в узле, что позволяет Drupal обеспечить полнотекстовый поиск по содержимому узла.

Мы хотим, чтобы для модуля announcements поисковый механизм выбирал новое поле abstract, определенное в таблице announcements. Мы также хотим изменить действие при поиске по умолчанию таким образом, чтобы показывалось поле abstract вместо содержимого, но не хотим использовать отдельную закладку для его представления. К счастью, существует альтернатива ловушке search, соответствующая нашим требованиям.

hook_nodeapi

Чтобы гарантировать поисковой форме возможности искать ключевые слова в поле abstract таблицы announcements, мы реализовали функцию ловушки nodeapi. Это позволяет нам включить данное поле в обновление индексов. В листинге 19 приведена наша реализация ловушки nodeapi.

Листинг 19. Реализация ловушки announcement_nodeapi
function announcement_nodeapi(&$node, $op) {
   switch ($op) {
      case 'update index':
         if ($node->type == 'announcement') {
            $text = ''; 
            $q = db_query('SELECT a.abstract FROM node n LEFT JOIN announcement a ' .
                          'ON n.nid = a.nid WHERE n.nid = %d', $node->nid);
		if ($r = db_fetch_object($q)) {
		   $text = $r->abstract;
		}
		return $text;
		}
	}
}

В этой функции мы проверяем операцию обновления индекса, указывающую, что Drupal находится в состоянии сбора дополнительных данных перед индексированием их в базе данных. Если индексируемый узел имеет тип announcement, из таблицы announcement извлекается и возвращается связанное с ним значение поля abstract.

Теперь, когда Drupal может проиндексировать поле abstract, мы должны отобразить эту информацию, если она совпадает с искомым ключевым словом, на странице результатов поиска. Это делается путем переопределения функции theme_search_item в модуле search с использованием функции phptemplate_search_item, как показано в листинге 20. Поскольку эта функция может считаться глобальным изменением темы, мы помещаем ее в файл template.php, расположенный в каталоге нашей темы.

Листинг 20. Функция phptemplate_search_item
function phptemplate_search_item($item, $type) {
   return _phptemplate_callback('search_item', 
      array('node' => $item), 'search_item-' . strtolower($item['type']));
}

В этой функции мы используем функцию _phptemplate_callback для связи оформления элемента search с предложенным файлом шаблона. Почти так же, как механизм phptemplate позволяет использовать файлы шаблонов node.tpl.php и node-<node-type>.tpl.php для настройки способа визуализации узла, мы используем эту функцию для разрешения использовать шаблоны search_item.tpl.php и search_item-<node-type>.tpl.php, как показано в листинге 21.

Теперь у нас есть шаблон для внешнего вида элемента search по умолчанию, который, в сущности, является слегка измененной версией оригинально оформленного элемента search, найденного в функции theme_search_item в файле search.module. Этот полезный технический прием может быть применен для любого объекта.

Листинг 21. Шаблон search_item.tpl.php
<dt class="title search_item">
  <a href="<?php print check_url($node['link']); ?>"><?php print 
     check_plain($node['title']) ?></a>
</dt>
<?php
$info = array();
if ($node['type']) $info[] = strtolower($node['type']);
if ($node['user']) $info[] = $node['user'];
if ($node['date']) $info[] = format_date($node['date'],'small');
if (is_array($node['extra'])) $info = array_merge($info, $node['extra']);
?>
<dd class="search_item">
	<p><?php print $node['snippet']; ?></p>
	<p class="search-info"><?php print implode(' - ', $info); ?></p>
</dd>

Используя шаблон search_item-announcement.tpl.php, мы можем оформить внешний вид элемента search для узла типа announcement и заменить фрагмент по умолчанию нашим собственным полем abstract. В листинге 22 обратите внимание на использование нами функции search_excerpt для выделения ключевых слов в поле abstract.

Листинг 22. Шаблон search_item-announcement.tpl.php
<dt class="title search_item_announcement">
	<a href="<?php print check_url($node['link']); ?>">
	<?php print check_plain($node['title']) ?></a>
</dt>
<?php
$info = array();
if ($node['type']) $info[] = strtolower($node['type']);
if ($node['user']) $info[] = $node['user'];
if ($node['date']) $info[] = format_date($node['date'],'small');
if (is_array($node['extra'])) $info = array_merge($info, $node['extra']);
?>
<dd class="search_item_announcement">
	<p><?php print ($node['node']->abstract ? '<p>'. 
	search_excerpt(search_get_keys(),$node['node']->abstract) . 
	'</p>' : $node['snippet']); ?></p>
	<p class="search-info"><?php print implode(' - ', $info); ?></p>
</dd>
Рисунок 10. Страница результатов поиска с выделенными элементами поиска
Рисунок 10. Страница результатов поиска с выделенными элементами поиска

hook_node_info

В листинге 23 приведена функция, позволяющая модулям node определять несколько специализированных типов узлов (ловушка node_info). Мы используем эту функцию для указания Drupal определить тип узла announcement. Существуют два значения, требующиеся Drupal для связывания с типом узла: читабельное имя типа узла, используемое в пользовательском интерфейсе, и база - префикс, используемый для функций, связанных с этим типом узла. Если бы мы хотели добавить еще один тип узла, то могли бы добавить еще один элемент массива.

Листинг 23. Announcement_node_info для определения имени и атрибутов типов узлов модуля
function announcement_node_info() {
 return array('announcement' => array('name' => 'Announcement', 
                                      'base' => 'announcement'));
}

Оформление внешнего вида выводимой модулем информации

Файлы модуля предоставляют несколько функций theme, которые используются для отображения информации модуля в различных контекстах. Для большинства целей полезно представлять себе эти контексты следующим образом:

  • Подробная схема, где представляется вся информация для данного узла.
  • Компактное или суммарное представление, где показываются только основные части для выдачи краткой информации.
  • Блок, представляющий еще меньшую форму информации узла, которая обычно встраивается в левую или правую боковую панель.

Для модуля announcements мы использовали функцию theme для каждого из этих контекстов:

  • Страница announcement использовала функцию theme_announcement для создания подробной схемы.
  • Фронтальная или домашняя страница сайта использовала функцию theme_announcement_compact для создания списка уведомлений.
  • Блок announcement в правой боковой панели на всех страницах использовал функцию theme_announcement_block_list.

Как говорилось в пятой части данной серии статей ("Начало работы с Drupal"), функции theme могут использоваться для создания внешнего вида вывода модуля announcement по умолчанию, независимо от выбранного механизма поддержки тем.

Поскольку в нашем случае используется механизм phptemplate, мы переопределяем эти функции theme по умолчанию (показанные в листинге 24), используя функции phptemplate_announcement, phptemplate_announcement_compact и phptemplate_announcenemt_block_list. Мы считаем полезным отделить определение структуры и стиля от логики модуля, поэтому используем функцию _phptemplate_callback для связывания файла шаблона с каждым контекстом.

Листинг 24. Функции theme по умолчанию и функции механизма PHP Template, которые переопределяют их
function theme_announcement($announcement) {
	// Поместите здесь вашу тему по умолчанию для деталей уведомления 
	return '';
}

function theme_announcement_compact($announcement) {
	// Поместите здесь вашу тему по умолчанию для краткого обзора уведомления
	return '';
}

function theme_announcement_block_list($announcement_list) {
	// Поместите здесь вашу тему по умолчанию для блока announcement 
	return '';
}

function phptemplate_announcement($announcement) {
  return _theme_phptemplate_announcement($announcement, 'announcement');
}

function phptemplate_announcement_compact($announcement) {
  return _theme_phptemplate_announcement($announcement, 'announcement_compact');
}

function phptemplate_announcement_block_list($announcement_list) {
	global $user;
  return _phptemplate_callback('announcement_block_list', 
                                    array('announcements' => $announcement_list, 
                                    'user' => $user));	
}

function _theme_phptemplate_announcement($announcement, $announcement_template) {
   $expired = FALSE;
   if ($announcement->expiration_date < time()) {
      $expired = TRUE;		
   }
   $variables = array(
      'title'     => $announcement->title,
      'body'      => $announcement->body,
      'links'     => $announcement->links ? 
                           theme('links', $announcement->links) : '',
      'abstract'  => $announcement->abstract,
      'published' => format_date($announcement->publish_date,'custom','j M, Y'),
      'expires'   => format_date($announcement->expiration_date,'custom','j M, Y'),
      'expired'  => $expired,
      'node'      => $announcement
   );
   return _phptemplate_callback($announcement_template, $variables);	
}

Обратите внимание на использование вспомогательной функции _theme_phptemplate_announcement, которая обеспечивает общий способ подготовки переменных, переданных в выбранный файл шаблона.

Мы рассмотрели простой пример оформления внешнего вида для нашего специализированного модуля announcement. В одной из следующих статей мы рассмотрим стилевое оформление узлов более подробно.


Резюме

В данной статье вы узнали о реализации простого специализированного модуля (модуля announcement) для Web-сайта. Этот модуль предоставляет уведомления, которые автоматически появляются и исчезают с Web-сайта в зависимости от даты публикации и даты истечения срока публикации. Уведомления появляются в основной области домашней страницы и в боковых панелях на всех других страницах. Многие из основных функций (или ловушек) используются для обеспечения функционирования модуля.

В следующих статьях данной серии вы узнаете более подробно о стилевом оформлении нашего вымышленного web-сайта, а также об интерфейсе к SQL и уровню абстракции базы данных.

Ресурсы

Научиться

Получить продукты и технологии

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Open source
ArticleID=185519
ArticleTitle=Использование ПО с открытым исходным кодом для создания Web-сайта для совместной работы: Часть 6. Создание специализированного модуля в Drupal
publish-date=12252006