Создаем новые типы полей CCK

Перевел первую статью цикла "20 API за 20 дней". Называется "Using CCK to Create New Field Types", автор - Майкл Хаггерти. В статье на примере модуля Email рассказывается, как добавить, сохранить в БД, оформить и темизировать свое поле CCK.

Перевод

CCK - самая важная аббревиатура, которую вам нужно знать на тему управления контентом в Друпале. Она расшифровывается как Content Construction Kit (конструктор контента - прим. переводчика). Это фреймворк, позволяющий людям вводить самую разную информацию на свои веб-сайты.

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

Среди самых известных модулей, использующих эту возможность, можно назвать Filefield, Imagefield, Emfield, Link и Email, но вообще есть почти 300 модулей для Друпала 6, отмеченных тегом CCK. Независимо от масштабов приложения, все эти модули используют CCK API для определения уникальных типов полей, которые становятся доступны для использования во всех типах нод Друпала.

Для примера, мы пройдемся по модулю Email Маттиаса Хаттерера. Этот модуль использует API для создания уникального поля CCK, содержащего правильно отформатированный email-адрес. Мы определим ключевые функции, которые необходимо реализовать в модуле для задания собственного типа поля и по ходу дела разъясним конфигурационные параметры.

Начнем со списка ингредиентов (наших хуков):

  • Чтобы дать CCK понять, что мы пришли (инсталляция):
    • content_notify
  • Чтобы рассказать CCK о себе (настройки поля):
    • hook_field_info
    • hook_field_settings
    • hook_field
  • Чтобы рассказать CCK и Друпалу, как мы будем себя вести при создании форм редактирования ноды (настройки виджета):
    • hook_widget_info
    • hook_widget_settings
    • hook_widget
    • hook_elements
  • Чтобы рассказать Друпалу, как мы будем выглядеть при просмотре ноды (темизация):
    • hook_theme + theme_MY_ELEMENT
    • дополнительные средства форматирования

Длинный получился списочек, так что давайте разбираться.

Инсталляция

Для начала, нужно дать знать CCK, что наш модуль доступен для использования:

function email_install() {
  drupal_load('module', 'content');
  content_notify('install', 'email');
}

В файле *.install нашего модуля, мы сообщаем CCK, когда модуль инсталлируется, включается, выключается и деинсталлируется. Для этого мы вызываем соответствующий случаю хук (hook_install, hook_enable, и т.д.) и исполняем функцию 'content_notify' с подходящим параметром ('install', 'enable' и т.д.) внутри этого хука.

Настройки поля

В файле email.module мы видим как наше поле обретает форму:

function email_field_info() {
  return array(
    'email' => array(
    'label' => 'Email',
  // функция завершается...

Теперь наше поле зарегистрировано с внутренним именем 'email' и административной меткой 'Email'.

function email_field_settings($op, $field) {
  switch ($op) {
    case 'database columns':
      $columns['email'] = array('type' => 'varchar', 'length' => 255, 'not null' => FALSE, 'sortable' => TRUE);
      return $columns;
  }
}

Здесь модуль Email просит CCK, чтобы тот управлял хранением значений поля в базе данных. Модулю Email нужно, чтобы в таблице базы данных типа содержимого был создан специальный столбец, если данный тип использует поле Email. Модуль хочет, чтобы столбец назывался 'field_ИМЯ_ПОЛЯ_email' и имел специальные настройки. В дополнение к определению столбцов при $op='database columns', при $op='form' можно сгенерировать специальную форму настроек (для примера смотри CCK-модуль 'text').

Наконец, проделаем некоторую валидацию значений, получаемых нашим полем:

function email_field($op, &$node, $field, &$items, $teaser, $page) {
  switch ($op) {
    case 'validate':
      if (is_array($items)) {
        foreach ($items as $delta => $item) {
          if ($item['email'] != '' && !valid_email_address(trim($item['email']))) {
            form_set_error($field['field_name'],t('"%mail" is not a valid email address',array('%mail' => $item['email'])));
          }
        }
      }
      break;
    //функция завершается...

Если будет введено значение, этот код проверит валидность email-адреса вызовом функции Друпала valid_email_address().

Настройки виджета

Как только мы описали логические элементы нашего типа поля как было показано выше, нам нужно определить форму-виджет для поля, так чтобы конечный элемент HTML-формы мог получить правильные атрибуты.
Так же как и функции hook_field* выше, функции hook_widget представлены в вариантах _info и _settings:

function email_widget_info() {
  return array(
    'email_textfield' => array(
      'label' => t('Text field'),
      'field types' => array('email'),
      'multiple values' => CONTENT_HANDLE_CORE,
      'callbacks' => array(
        'default value' => CONTENT_CALLBACK_DEFAULT,
      ),
    ),
  );
}
...
function email_widget_settings($op, $widget) {
  switch ($op) {
    case 'form':
      $size = (isset($widget['size']) && is_numeric($widget['size'])) ? $widget['size'] : 60;
      $form['size'] = array(
        '#type' => 'textfield',
        '#title' => t('Size of textfield'),
        '#default_value' => $size,
        '#element_validate' => array('_email_widget_settings_size_validate'),
        '#required' => TRUE,
      );
    return $form;

    case 'save':
    return array('size');
  }
}

Заметьте, что hook_widget_info копирует hook_field_info (имя и метку), но также сообщает CCK, к каким типам полей применяется этот виджет ('field types' => array('email')).

Так же как в hook_field_settings, в hook_widget_settings мы получаем возможность расширить форму настроек (в форме по адресу 'admin/content/node-type/[ИМЯ ТИПА СОДЕРЖИМОГО]/fields/[ИМЯ ПОЛЯ]' мы увидим совокупность результатов обоих функций). Модуль Email производит те же расширения, что и модуль CCK Text; он проверяет, установлен ли размер текстового поля, и если нет - устанавливает размер по умолчанию, равный 60.

Чтобы присоединить наш элемент формы к самой форме, мы вызываем hook_widget:

function email_widget(&$form, &$form_state, $field, $items, $delta = 0) {
  $element = array(
    '#type' => $field['widget']['type'],
    '#default_value' => isset($items[$delta]) ? $items[$delta] : '',
  );
  return $element;
}

Итак, когда CCK прикрепляет элемент формы к форме, мы просим его установить правильный тип (мы определили только один в функции hook_widget_info, 'email_textfield') и использовать заданное нами значение по умолчанию, если оно доступно. Если наш модуль описывает несколько виджетов, мы можем использовать здесь условные операторы, чтобы добавить атрибуты в зависимости от типа виджета, но в большинстве случаев приведенной выше функции достаточно (для примера настроек нескольких виджетов смотри модуль NodeReference).

Наконец, после того как мы рассказали CCK все что можем про наше новое поле, нужно рассказать о нем и Form API Друпала. Для этого мы вызываем hook_elements:

function email_elements() {
  return array(
    'email_textfield' => array(
      '#input' => TRUE,
      '#columns' => array('email'),
      '#delta' => 0,
      '#process' => array('email_textfield_process'),
    ),
  );
}

Заметьте что мы указываем как элемент не 'email', а 'email_textfield'; FAPI интересует наш новый виджет, который мы логически увязали с полем ранее, в hook_widget_info. Далее мы рассказываем FAPI, где обрабатывать этот новый элемент: ‘#process’ => array(‘email_textfield_process’).

Все функции до этого момента имели дело с установкой нашего нового поля - задавали его настройки и описывали новый виджет. Ни одна из них не связана с тем как Друпал рендерит наш виджет в стандартный элемент HTML-формы в форме редактирования ноды. Это происходит в функции, заданной в атрибуте #process как мы только что видели в hook_elements:

function email_textfield_process($element, $edit, $form_state, $form) {
  $field = $form['#field_info'][$element['#field_name']];
  $field_key = $element['#columns'][0];
  $delta = $element['#delta'];
  $element[$field_key] = array(
    '#type' => 'textfield',
    '#title' => $element['#title'],
    '#description' => content_filter_xss($field['widget']['description']),
    '#required' => $element['#required'],
    '#maxlength' => 255,
    '#size' => !empty($field['widget']['size']) ? $field['widget']['size'] : 60,
    '#attributes' => array('class' => 'text', 'dir' => 'ltr'),
    '#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : NULL,
  );
  return $element;
}

Для тех, кто разбирается в Form API, этот код должен быть гораздо привычнее. Наше поле будет типа 'textfield', но усовершенствованное всеми настройками поля и виджета, которые мы определили выше (где видите надпись ['widget']), а также некоторыми значениями из ядра CCK ($element['#title'] устанавливается модулем CCK, когда вы задаете значение "Label" новому полю на странице управления полями).

Мы уже рассказали СCK про элемент нашего поля и его виджет, рассказали FAPI, как генерировать этот элемент на странице редактирования ноды, но как данные, которые мы соберем, будут отрендерены при просмотре ноды? Идем в атаку на темизацию.

Темизация

Когда мы вызываем hook_elements, Друпал автоматически ожидает, что функции темизации будут называться по именам объявляемых элементов. Так, в модуле Email у нас есть 'email_textfield' - один их объявленных нами элементов - так что нам нужно добавить функцию theme_email_textfield. Даже несмотря на то, что Друпал автоматически связывает элемент формы и функцию темизации, нам все равно нужно объявить функцию темизации в hook_theme как обычно:

function email_theme() {
  return array(
    'email_textfield' => array(
      'arguments' => array('element' => NULL),
    ),
    // здесь объявляются другие темизирующие функции...
  );
}

function theme_email_textfield($element) {
  return $element['#children'];
}

Функция theme_email_textfield просто возвращает дефолтный HTML, сгенерированный в includes/form.inc Друпала. Так или иначе, существуют дополнительные значения в $element['field_name'] и $element['delta'] (имя и позиция элемента соответственно), которые тоже можно использовать для темизации.

В качестве последнего украшения темизации CCK, вы можете предоставить больше опций пользователю созданием дополнительных средств форматирования, которые будут доступны на странице настроек отображения для каждого типа содержимого (admin/content/node-type/[имя типа содержимого]/display). Модуль Email вызывает hook_formatter_info для регистрации средств форматирования, объявляет их в hook_theme, затем создает функции theme_СРЕДСТВО_ФОРМАТИРОВАНИЯ для определения окончательного вывода HTML. Чтобы использовать одно из этих средств форматирования, пользователь может зайти на страницу настроек отображения и выбрать его из выпадающего списка.

----------------------------

Несмотря на длину заметки, мы рассмотрели процесс только поверхностно. Более детальную информацию об использованных здесь функциях, ищите в модулях, включенных в состав CCK, таких как 'text' и 'nodereference', они содержат детальные комментарии к коду каждой функции. И будьте уверены, что читали все заметки про API компании Trellon, так как в них мы исследуем фундаментальные инструменты расширения функционала Друпала.

Ch
Аватар пользователя Ch

Видел уже несколько таких статей и везде почему то при описании hook_elements ни слова не говорится для чего нужны ключи #columns и #delta.

graker
Аватар пользователя graker

Ничего не могу поделать - перевод :)

Ярик (гость)
Аватар пользователя Ярик

фразу "И будьте уверены, что читали все заметки..."

может, стоило перевести как
"И удостоверитесь, что прочитали все заметки..."?

graker
Аватар пользователя graker

Не, и так, и так - неправильно, в оригинале он вообще о будущем говорит, типа "Не забудьте прочесть остальные заметки [когда они появятся]".
Я уже не помню, почему так перевел :)

Отправить комментарий

CAPTCHA
Пройдите, пожалуйста, проверку. Она нужна, чтобы отличать людей от спам-роботов. А если не хотите проходить эту проверку регулярно — зарегистрируйтесь.
Image CAPTCHA
Введите цифры, изображенные на картинке (без пробелов).