ertttert
Если вам понадобится создать в Drupal новый тип контента, то, следуя традиции, придется написать модуль ноды, который будет отвечать за выполнение и предоставление новых и интересных вещей, необходимых этому типу контента. Здесь сказано “следуя традиции”, т.к. последние веяния в среде Drupal позволяют создавать типы контента в интерфейсе администрирования, расширяя их возможности добровольно разработанными модулями — без написания модуля с самого начала. В настоящей главе рассматриваются оба этих решения.
Мы напишем модуль ноды, который позволит пользователям добавлять на сайт объявления о рабочих вакансиях. Нода вакансии будет содержать заголовок, тело с текстом объявления и поле для ввода названия компании. Для заголовка и тела объявления будут использованы встроенные ноды title
и body
, стандартные для всех нод Drupal. А для названия компании потребуется новое специальное поле.
Начнем с создания папки с именем job_post
в каталоге your sites/all/modules/custom
.
Файл .install
для модуля вакансий выполняет все операции по установке: определение типа ноды, создание полей, составляющих новый тип ноды, и выполнение процесса деинсталляции модуля:
<?php/** * @file * Инсталляция файла для модуля Job Post. */ /** * Реализация hook_install(). * - Добавление поля тела. * - Настройка поля тела. * - Создание поля для названия компании. */ function job_post_install() { node_types_rebuild(); $types = node_type_get_types();// Добавление поля тела в тип ноды. node_add_body_field($types['job_post']);// Загрузка определения экземпляра для тела типа контента. $body_instance =field_info_instance( 'node', 'body', 'job_post') ;// Настройка поля тела. $body_instance['type'] = 'text_summary_or_trimmed';// Сохранение изменений в экземпляре поля тела. field_update_instance($body_instance);// Создание всех полей, добавляемых в тип контента. foreach ( _job_post_installed_fields()as $field) { field_create_field($field); }// Создание всех экземпляров полей. foreach ( _job_post_installed_instances()as $instance) { $instance['entity_type'] = 'node'; $instance['bundle'] = 'job_post'; field_create_instance($instance); } }/** * Возврат структурированного массива с определением полей, * созданного данным типом контента. * В модуле вакансий имеется лишь одно дополнительное поле – название компании. * Другие поля можно добавить, определив их в данной функции как * дополнительные элементы в приведенном ниже массиве. */ function _job_post_installed_fields() { $t = get_t();return array( 'job_post_company' =>array( 'field_name' => 'job_post_company', 'label' => $t('Company posting the job listing'), // Компания, поместившая объявление 'type' => 'text',) ,) ; }/** * Возврат структурированного массива с определениями экземпляров полей, * связанных с данным типом контента. */ function _job_post_installed_instances() { $t = get_t();return array( 'job_post_company' =>array( 'field_name' => 'job_post_company','type' => 'text','label' => $t('Company posting the job listing'),'widget' =>array( 'type' => 'text_textfield', ),'display' =>array( 'example_node_list' =>array( 'label' => $t('Company posting the job listing'),'type' => 'text',), ) ,) ,) ; }/** * Реализация hook_uninstall(). */ function job_post_uninstall() {// Сбор всего контента, который может быть создан при активации этого модуля. $sql = 'SELECT nid FROM {node} n WHERE n.type = :type'; $result = db_query($sql, array(':type' => 'job_post')); $nids = array();foreach ( $resultas $row) { $nids[] = $row->nid; }// Одновременное удаление всех нод. node_delete_multiple($nids);// Перебор всех определенных в модуле полей и удаление // всех экземпляров полей, их данных и самих полей. foreach ( array_keys(_job_post_installed_fields() )as $field) { field_delete_field($field); }// Проход в цикле по всем остальным экземплярам полей, присоединенных к типу // контента job_post (таких как поле тела), и их удаление по отдельности. $instances = field_info_instances('node', 'job_post');foreach ( $instancesas $instance_name => $instance) { field_delete_instance($instance); }// Удаление типа контента. node_type_delete('job_post');// Удаление информации о полях. field_purge_batch(1000); }
Давайте также создадим файл job_post.info
и добавим его в папку job post
:
name = Job Post description = A job posting content type package = Pro Drupal Development core = 7.x files[] = job_post.install files[] = job_post.module
И, наконец, нам нужен сам файл модуля. Создайте файл по имени job_post.module
и сохраните его в папке sites/all/modules/custom/job_posting
. После завершения создания модуля его можно активировать на странице со списками модулей (Modules). Начнем с открывающего дескриптора PHP и краткого комментария.
<?php /** * @file * Модуль для типа ноды job_post. */
Теперь все готово для добавления хуков в файл job_post.module
. Сначала реализуем хук hook_node_info()
. Drupal вызывает этот хук в процессе выяснения доступных типов нод. Здесь можно добавить некоторые метаданные о нашем модуле.
/** * Реализация hook_node_info() для типа job_post. */ function job_post_node_info() {return array( 'job_post' => array( 'name' => t('Job Post'),// Объявление о рабочей вакансии 'base' => 'job_post', 'description' => t('Use this content type to post a job.'),// Тип контента для объявлений о вакансиях. 'has_title' => TRUE, 'title_label' => t('Job Title'),// Название работы 'help' => t('Enter the job title, job description,and the name of the company that posted the job'),// Введите название работы, ее описание и название компании, // поместившей объявление,/comm. ), ); }
В одном модуле могут быть определены несколько типов нод, поэтому возвращаемое значение должно быть массивом. Ниже приведен список значений метаданных, которые могут быть указаны в хуке node_info()
.
example_foo
, то при вставке ноды этого типа будет вызвана функция example_foo_insert
). Такая строка обычно, хотя и не всегда, совпадает с именем модуля. Является обязательной.Title
).base
), используется для создания URL-адреса ссылок (Добавить новый контент). Например, в качестве внутреннего имени нашего типа ноды мы используем строку job_post
(ключ возвращаемого массива), поэтому для создания нового объявления о вакансии пользователи будут направлены по адресу http://example.com/?q=node/add/job_post
. Обычно не стоит делать это значение изменяемым (locked = FALSE
). Внутреннее имя хранится в столбце type таблиц node
и node_revisions
.Для реализации хука hook_menu()
не обязательно наличие ссылки на странице (Добавить новый контент). Среда Drupal автоматически распознает новый тип контента и добавляет его на страницу http://example.com/?q=node/add
, как показано на рис. 7.2. Прямая ссылка на форму отправки ноды будет выглядеть как http://example.com/?q=node/add/job_post
. Имя и описание берутся из значений, определенных в job_post_node_info()
.
Если прямая ссылка не нужна, ее можно удалить в хуке hook_menu_alter()
. Например, следующий код удалит страницу для всех пользователей, у которых нет права на администрирование нод:
/** * Реализация of hook_menu_alter(). */ function job_post_menu_alter( &$callbacks) {// Если у пользователя нет права администрирования нод, элемент меню // job_post деактивируется: в его обратный вызов доступа заносится FALSE. if (!
user_access('administer nodes')) { $callbacks['node/add/job_post']['access callback'] = FALSE;// Необходимо сбросить аргументы доступа, иначе Drupal использует // user_access() в качестве стандартного обратного вызова доступа. unset( $callbacks['node/add/job_post']['access arguments']) ; } }
Обычно в права для типов нод, определенных в модуле, входит возможность создавать ноду данного типа, изменять созданную ноду и изменять любую ноду заданного типа. Они определяются в коде hook_permission()
как create job_pos
t, edit own job_post
, edit any job_post
и т.д. Однако их еще понадобится определить в модуле. Сейчас мы создадим их с помощью хука hook_permission()
:
/** * Реализация hook_permission(). */ function job_post_permission() {return array( 'create job post' =>array( 'title' => t('Create a job post'),// Создание объявления о вакансии 'description' => t('Create a job post'),// Создание объявления о вакансии ) , 'edit own job post' =>array( 'title' => t('Edit own job post'),// Редактирование собственного объявления о вакансии 'description' => t('Edit your own job posting'),// Редактирование собственного объявления о вакансии ) , 'edit any job post' =>array( 'title' => t('Edit any job post'),// Редактирование любого объявления о вакансии 'description' => t('Edit any job posting'),// Редактирование любого объявления о вакансии ) , 'delete own job post' =>array( 'title' => t('Delete own job post'),// Удаление собственного объявления о вакансии 'description' => t('Delete own job posting'),// Удаление собственного объявления о вакансии ) , 'delete any job post' =>array( 'title' => t('Delete any job post'),// Удаление любого объявления о вакансии 'description' => t('Delete any job posting'),// Удаление любого объявления о вакансии ) , ); }
Если теперь на странице People (Люди) перейти на вкладку Permissions (Права доступа), только что определенные права будут там, и их можно использовать для назначения ролям пользователей.
Мы определили права доступа в хуке hook_perm()
, но как они реализуются? Модули нод могут ограничить доступ к определенным в них типам нод с помощью хука hook_access()
. Суперпользователь (пользователь с идентификатором 1) может обойти все проверки доступа, поэтому в данном случае этот хук не вызывается. Если данный хук не определен для нашего типа ноды, все проверки доступа завершатся неудачей, и, значит, только суперпользователь и пользователи с правами администрирования нод могут создавать, редактировать и удалять контент этого типа.
/** * Реализация hook_access(). */
function job_access($op, $node, $account) { $is_author = $account->uid == $node->uid;switch ($op) {case 'create':// Разрешить, если у роли пользователя есть право создания объявления. if (user_access('create job', $account)) {return NODE_ACCESS_ALLOW;}case 'update':// Разрешить, если у роли пользователя есть право правки своего // объявления и пользователь является автором, либо если у роли // пользователя есть право редактирования любого объявления. if (user_access('edit own job', $account) && $is_author || user_access('edit any job', $account)) {return NODE_ACCESS_ALLOW;}case 'delete':// Разрешить, если у роли пользователя есть право удаления своего // объявления и пользователь является автором, либо если у роли // пользователя есть право удаления любого объявления. if (user_access('delete own job', $account) && $is_author || user_access('delete any job', $account)) {return NODE_ACCESS_ALLOW;} } }
Эта функция позволяет пользователям создавать ноды объявлений о вакансиях, если у их ролей есть право создания объявления о вакансии. Они могут изменять объявления, если у их ролей имеется право редактирования собственного объявления, и они являются авторами, либо у них есть право редактирования любого объявления. Пользователи с правами удаления собственных объявлений могут удалять свои объявления, а пользователи с правами удаления любых объявлений могут удалять любые ноды типа job post
.
Еще одно значение $op
, которое передается в функцию hook_node_access()
— это view
, которое позволяет управлять тем, кто может просматривать данную ноду. Однако учтите, что hook_node_access()
вызывается лишь для страниц с отображением одной ноды. Хук hook_node_access()
не предотвратит просмотр ноды в представлении тизера на странице со списком нескольких нод. Можно, конечно, поработать и с другими хуками и непосредственно манипулировать значением $node->teaser
, но это неудачный подход. Лучшим решением будет использовать хук hook_node_grants()
, о котором будет рассказано чуть ниже.
Итак, у нас имеются метаданные и права доступа, определенные для нового типа ноды. Теперь нужно создать форму ноды, чтобы пользователи могли вводить объявления о вакансиях. Для этого понадобится реализовать хук hook_form()
. В Drupal имеется стандартная форма ноды, содержащая заголовок, тело и все дополнительные поля, которые были определены. Для типа контента объявлений о вакансиях такая форма вполне годится, так что мы используем ее для отображения формы добавления/редактирования.
/** * Реализация hook_form() на основе стандартой формы. */ function job_post_form($node, $form_state) {return node_content_form($node, $form_state); }
Если вы являетесь администратором сайта и активировали данный модуль, можете теперь перейти на страницу
→ (Добавить контент→Объявление о вакансии) и просмотреть только что созданную форму (рис. 7.3).При работе с формой ноды, если это не обобщенная форма, модуль ноды выполняет проверку и сохранение всех стандартных полей, о которых известно модулю внутри формы ноды (вроде полей title
и body
), а также предоставляет вам (разработчику) хуки для проверки и сохранения всех собственных полей. Рассмотрим эти возможности.
При отправке ноды нашего типа модуль вызывается через хук hook_validate()
. Поэтому, когда пользователь отправляет форму для создания или изменения объявления о вакансии, вызов hook_validate()
найдет функцию job_post_validate()
, чтобы проверить данные, введенные в специальных полях. После получения этих данных их можно изменить с помощью form_set_value()
, а с помощью form_set_error()
можно установить код ошибки.
/** * Реализация hook_validate(). */ function job_post_validate($node) {// Принудительное применение требования: название компании // должно содержать не менее 2 символов. if (isset($node->job_post_company) && strlen($node->job_post_company['und'][0]['value']) < 2) {form_set_error( 'job_post_company', t('The company name is too short. It must be at least 2 characters.'),// Слишком короткое название компании. Должно быть не менее 2 символов. $limit_validation_errors = NULL) ; } }
Кроме того, мы уже определили минимальное количество слов для поля body field
в хуке hook_node_info()
, и Drupal выполнит эту проверку самостоятельно. Однако поле punchline
является дополнительным полем, добавленным к форме типа ноды, поэтому за его проверку (а также за загрузку и сохранение) отвечаете вы.
При сохранении новой ноды вызывается хук hook_insert()
. Здесь перед сохранением можно выполнить любую специальную обработку содержимого ноды. Данный хук вызывается только для модуля, определенного в метаданных типа ноды. Эта информация определена в ключе base хука hook_node_info()
(см. раздел “Предоставление информации о типе ноды” ранее в главе). Например, если ключ base
содержит значение job_post
, то вызывается job_post_insert()
. Если активирован модуль описания книг и создана новая нода типа book
, то функция job_post_insert()
не будет вызвана; вместо нее будет выполнен вызов book_insert()
, т.к. в модуле book.module
тип ноды определен с помощью ключа base
со значением book
.
hook_nodeapi()
для внедрения в общий процесс отправки ноды. См. раздел “Работа с нодами произвольного типа с помощью hook_node_xxxxx()
” далее в главе.Ниже приведен текст функции hook_insert()
для модуля job_post.module
. Мы будем создавать запись в таблице слежения при каждом создании новой ноды объявления о вакансии.
/** * Реализация hook_insert(). */ function job_post_insert($node) {// Запись в журнал информации об объявлении. watchdog('job post', 'A new job post titled: '.$node->title.' for company: '. $node->job_post_company['und'][0]['value']. ' was added by UID: '.$node->uid, $variables = array(), WATCHDOG_NOTICE, $link = 'node/'.$node->nid); }
Хук update()
вызывается при редактировании ноды, когда основные данные ноды уже сохранены в базе данных. В этот момент можно записать изменения в связанные таблицы базы данных. Как и hook_insert()
, этот хук вызывается только для текущего типа ноды. Например, если ключ module
для типа ноды в hook_node_info()
содержит значение job_post
, то осуществляется вызов job_post_update()
.
/** * Реализация hook_update(). */ function job_post_update($node) {// Информация о размещении объявления для наблюдения. watchdog('job post', 'A job post titled: '.$node->title.' for company: '. $node->job_post_company['und'][0]['value']. ' was updated by UID: '.$node->uid, $variables = array(), WATCHDOG_NOTICE, $link = 'node/'.$node->nid); }
Сразу после удаления ноды из базы данных Drupal оповещает об этом модули с помощью вызова hook_delete()
. Этот хук обычно используется для удаления соответствующей информации из базы данных. Она вызывается только при удалении текущего типа ноды. Если ключ base
в hook_node_info()
для данного типа ноды содержит значение job_post
, то выполняется вызов job_post_delete()
.
/** * Реализация hook_delete(). */ function job_post_delete($node) {// Запись в журнал информации об объявлении. watchdog('job post', 'A job post titled: '.$node->title.' for company: '. $node->job_post_company['und'][0]['value']. ' was deleted by UID: '.$node->uid, $variables = array(), WATCHDOG_NOTICE, $link = 'node/'.$node->nid); }
Еще один полезный хук для нашего модуля job_post
позволяет добавлять пользовательские атрибуты ноды в объект ноды при его конструировании. Нам нужно добавить в процесс загрузки ноды спонсора объявления о вакансии, чтобы он был доступен другим модулям и уровню тем. Для этого предназначен хук hook_load()
.
Этот хук вызывается сразу после создания базового объекта ноды и только для загружаемого текущего типа ноды. Если ключ module
в hook_node_info()
для заданного типа ноды содержит значение job_post, то выполняется вызов job_post_load()
. В нашем примере мы вставим атрибут ноды sponsor
и присвоим ему значение, которое впоследствии будет использоваться где-то в другом месте.
/** * Реализация hook_load(). */ function job_post_load($nodes) { // Добавление в ноду нового элемента во время загрузки // для сохранения информации о спонсоре объявления о вакансии. foreach ($nodes as $node) { $node->sponsor = "ACME Career Services, Your Source for Drupal Jobs"; } return $node; }
Теперь у нас имеется полная система для ввода и редактирования объявлений о вакансиях. Однако спонсоры будут недовольны, т.к. информация о спонсоре хоть и добавляется с помощью hook_load()
, но не выводится при просмотре объявлений. Это можно сделать с помощью хука hook_view()
:
/** * Реализация hook_view(). */ function job_post_view($node, $view_mode) {// Добавление и темизация информации о спонсоре, // чтобы она была видна при просмотре объявления. if ($view_mode == 'full') { $node->content['sponsor'] =array( '#markup' =>theme( 'sponsor',array( 'sponsor' => $node->sponsor, 'sponsor_id' => $node_nid) ) , '#weight' => 100,) ; }return $node; }
Здесь форматирование информации о спонсоре выделено в отдельную функцию темы, чтобы ее можно было легко переопределять — так мы облегчим жизнь вечно загруженным системным администраторам, которым придется использовать наш модуль и настраивать внешний вид и поведение выходных данных. Для этого создадим функцию hook_theme()
с определением того, как модуль будет применять тему к новому полю спонсора. В функции hook_theme()
мы определим переменные, связанные с полем спонсора и файлом шаблона, который будет использован для отображения информации о спонсоре в ноде.
/** * Реализация hook_theme(). */function job_post_theme() {// Определение переменных и шаблона, связанных с полем спонсора. // Это поле будет содержать имя спонсора, а значение sponsor_id // будет применяться для создания уникального идентификатора CSS. return array( 'sponsor' => array( 'variables' => array('sponsor' => NULL, 'sponsor_id' => NULL), 'template' => 'sponsor', ), ); }
И последнее, что нужно сделать в данном процессе — создать файл шаблона для отображения информации о спонсоре. В функции hook_theme()
значение sponsor присваивается атрибуту файла шаблона, поэтому необходимо создать файл sponsor.tpl.php
в каталоге нашего модуля. Ниже приведено содержимое упомянутого файла:
<?php/** * @file * Реализация стандартной темы для отображения информации о спонсоре объявления. * * Доступные переменные: * $sponsor_id — идентификатор ноды, связанный с объявлением * $sponsor — имя спонсора объявления */ ?>
Чтобы среда Drupal обратилась к нашему хуку темы, понадобится очистить кэшированный реестр тем. Кэш можно очистить с помощью модуля devel.module
или со страницы (Модули). Теперь у нас имеется полностью готовая система ввода и просмотра объявлений о вакансиях. Попробуйте ввести несколько объявлений и просмотреть их. Вы увидите объявления в простом формате, как показано на рис. 7.4 и 7.5.
Хуки, описанные выше, вызываются только на основе базового ключа из реализации модуля hook_node_info()
. Когда Drupal видит тип ноды blog
, выполняется вызов blog_load()
. Но что если нужно добавить какую-то информацию в каждую ноду, независимо от ее типа? Хуки, с которыми мы знакомы, не способны на это; поэтому необходим исключительно мощный инструмент.
Хуки node_xxxxx
дают возможность модулям реагировать на различные действия во время жизненного цикла любой ноды. Обычно они вызываются из node.module
сразу после обратного вызова для конкретного типа ноды. Ниже приведен список первичных функций-хуков node_xxxxx
.
$nodes
— ассоциативный массив загружаемых нод (ключами являются идентификаторы нод), $types
— массив загружаемых типов нод.$view_mode
определяет режим отображения ноды — т.е. полный или в виде тизера.$op
— тип выполняемой операции (например, вставка, обновление, просмотр, удаление), а $account
— учетная запись пользователя, выполняющего операцию.Порядок вызова хуков при выводе страницы ноды http://example.com/?q=node/3
показан на рис. 7.6.
Всего комментариев: 3 | ||||
| ||||