20 Napi Drupal API - 1. nap: Új mezőtípus létrehozása CCK-val

zserno képe

Jelen írás egy fordított kivonat, melyet zserno készített. Az eredeti itt található:
http://www.trellon.com/content/blog/cck-creating-new-field-types

A CCK a legfontosabb rövidítés, amit ismernünk kell ha a Drupal tartalomkezeléséről beszélünk. Az eredeti rövidítés a Content Construction Kit szavakból áll össze, ami egy olyan keretrendszer, melynek segítségével egy webhely felhasználói különféle információkat küldhetnek be.

A többi Drupal kiegészítőhöz hasonlóan a CCK igazi szépsége sem csupán az általa kínált funkcionalitásban rejlik, hanem sokkal inkább a többi modul számára kínált kibővíthetőségében. A CCK API lehetővé teszi más modulok számára, hogy saját mezőtípusokat definiáljanak, melyek aztán tökéletesen illeszkednek a Drupal platformba.

A CCK sikerét jól jellemzi, hogy nagy részét már a Drupal 7-es alapcsomag is tartalmazza
A legismertebb CCK-ra épülő modulok a Filefield, Imagefield, Emfield, Link és Email, de összesen közel 300 CCK-val cimkézett Drupal 6 modul található a http://drupal.org-on. Függetlenül a felhasználási területtől, ezek mindegyike a CCK API-ját használva képes saját mezőtípusait definiálni, melyek azután bármelyik tartalomtípusban felhasználhatók.

Egy egyszerű példaként Matthias Hutterer Email modulját fogjuk megvizsgálni. Ez a modul a CCK API-t használva egy saját mezőtípust deklarál, amivel megfelelően formázott e-mail címeket tárolhatunk saját tartalomtípusainkban. Meg fogjuk vizsgálni a kulcsfontosságú függvényeket, amik az egyedi mezőtípus definiálásához szükségesek, és ahol szükséges, rövid magyarázattal szolgálunk a konfigurációs lehetőségekről.
Kezdjük a hozzávalókkal (a legfőbb hook-ok és eljárások, amiket használhatunk):

  • Telepítés:
    • content_notify
  • Mező beállítások (field settings):
    • hook_field_info
    • hook_field_settings
    • hook_field
  • Felületi elemtípus beállítások (widget settings):
    • hook_widget_info
    • hook_widget_settings
    • hook_widget
    • hook_elements
  • Sminkelés (theming):
    • hook_theme + theme_MY_ELEMENT
    • custom formatters

A lista hosszú, vágjunk is bele.

Telepítés

Első lépésként tudatnunk kell a CCK-val, hogy modulunk mikor áll használatra készen:

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

A *.install file-ban közölhetjük vele, amikor modulunk telepítése, engedélyezése, kikapcsolása vagy eltávolítása zajlik. Ezt a megfelelő hook-ban (pl. hook_install, hook_enable, stb), a megfelelő paraméterrel (install, enable, stb.) meghívott content_notify CCK eljárással tehetjük meg.

Mező beállítások

Az email.module file-ban látható, ahogy a mező formát ölt.

function email_field_info() {
  return array(
    'email' => array(
      'label' => 'Email',
//...

A fenti kóddal regisztráltuk a mezőnket, az "email" belső azonosítóval és egy "Email" címkével.
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;
  }
}

Itt az Email modul arra kéri a CCK-t, hogy kezelje ő a szükséges adatbázissal kapcsolatos teendőit. Egy külön oszlopot kér az őt használó tartalomtípusok tábláiban. Az oszlop elnevezése a majdan létrehozott mezők nevétől függően mindig "field_MEZŐNÉV_email" alakú lesz, beállításait pedig a $columns['email'] tömbben megadott jellemzők határozzák meg. Az $op változó a "database columns" értéken kívül még a "form" szöveget is tartalmazhatja, amivel egyedi űrlap készíthető a további szükséges beállításoknak (ld. a CCK Text modulját).
Végül nézzük hogyan tudjuk validálni a mezőnk tartalmát.
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;
//...

Ha a mezőnek van értéke, akkor a fenti kód a Drupal valid_email_address eljárásával ellenőrzi, hogy az valódi e-mail cím-e.

Felületi elemtípus beállítások

Miután felvázoltuk a mezőtípusunk logikai összetevőit, jöhet a felületi elemtípus (ún. widget) űrlapjának definiálása, hogy a végleges HTML űrlapelem megkaphassa a megfelelő attribútumokat. A hook_field* függvényekhez hasonlóan a hook_widget* függvényeknek is van _info és _settings változata.

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');
  }
}

Vegyük észre hogy a hook_widget_info a hook_field_info-val hasonló struktúrát használ (name és label kulcsok), azonban kiegészíti a felhasználható mezőtípusok listájával ('field types' => array('email')).
A hook_field_settings-hez hasonlóan, a hook_widget_settings-ben is lehetőség van finomítani a beállítások űrlapján (kettőjük végeredményét látjuk az "admin/content/node-type/[TARTALOMTÍPUS NEVE]/fields/[MEZŐNÉV]" útvonalon).
Az Email modul (akárcsak a Text modul) ezt arra használja, hogy a "size" mezőnek (mely a bevihető szöveg maximális hosszát határozza meg) biztosan legyen értéke.
Ahhoz, hogy az űrlapelemünk megjelenjen az űrlapon, a hook_widget-et kell használnunk:
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;
}

Tehát amikor a CCK hozzáadja a mezőnket az űrlaphoz, tudatjuk vele, hogy állítsa be a megfelelő widget típust (ez esetünkben az "email_textfield") és az alapértelmezett értéket, ha az létezik. Ha a modulunk több widget típust is használna, akkor itt a widget típus szerinti feltételes elágazással tudnánk a megfelelő attribútumokat beállítani. A legtöbb esetben azonban a fenti függvény elegendő (bővebben: nodereference.module).
Miután minden szükséges információt tudattunk a CCK-val, még a Drupal Form API-jának is el kell magyarázni, hogy miként bánjon a mezőnkkel. Ezt a hook_elements függvénnyel tehetjük meg:
function email_elements() {
  return array(
    'email_textfield' => array(
      '#input' => TRUE,
      '#columns' => array('email'),
      '#delta' => 0,
      '#process' => array('email_textfield_process'),
    ),
  );
}

Vegyük észre, hogy itt az "email" azonosító helyett az "email_textfield"-et használjuk, mert a Fom API az új widget-re kíváncsi, amit még a hook_widget_info-ban a mezőnkhöz kapcsoltunk. Továbbá azt is megmondjuk, hogy hol találja az új űrlapelem feldolgozásakor használandó kódot: '#process' => array('email_textfield_process').
Az eddig tárgyalt függvények mind az új mezőnk létrehozásával foglalkoztak: definiálták annak beállításait és létrehozták a szükséges widget-et. Egyikük sem foglalkozott azonban azzal, hogy a Drupal hogyan is fogja a widget-ünket szabályos HTML űrlapelemként megjeleníteni egy node szerkesztő form-on. Ennek leírása a (hook_elements-ben már említett) "#process" attribútumban megadott callback függvénnyel tehető meg:
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;
}

A Form API-t ismerőknek ez a kódrészlet már első ránézésre sokat elárul. A mezőnk egy szöveges beviteli mező (textfield) lesz, melyhez felhasználtuk a korábban definiált mező- és widget beállításokat ('widget' kulcs a fenti kódban), valamint a CCK-tól kapott információkat (ilyen pl. az $element['#title'], ami a "Mezők kezelése" oldalon egy új mezőcímke beküldésekor kerül beállításra).
Most hogy a CCK már tud az új mezőtípusunkról és a FAPI is tudja, hogy miként jelenítse meg azt a node szerkesztő űrlapon, nincs más hátra, mint az összegyűjtött adatok sminkelése.

Sminkelés

A hook_elements meghívásakor a Drupal feltételezi, hogy léteznek a deklarált elemekhez azok sminkfüggvényei is. Így pl. az Email modul esetében mivel az egyetlen deklarált elemünk az "email_textfield" volt, ezért létre kell hoznunk egy "theme_ email_textfield" nevű sminkfüggvényt. Habár a Drupal automatikusan összekapcsolja az űrlapelemet a hozzá tartozó sminkfüggvénnyel, szükség van a szokásos hook_theme függvény megírására is, hogy tudja: ez egy sminkelhető függvény.

function email_theme() {
  return array(
    'email_textfield' => array(
      'arguments' => array('element' => NULL),
    ),
// More theme functions declared here…
  );
}
 
function theme_email_textfield($element) {
  return $element['#children'];
}

A theme_email_textfield eljárás nem csinál semmi különöset, mindössze a Drupal Form API-ja által generált HTML értéket adja vissza (ahogy az az 'includes/form.inc'-ben látható). Érdemes tudni, hogy lehetne ennél komolyabb sminkfüggvényt is írni az element['field_name'] és $element['delta'] értékek felhasználásával (ezek az űrlapelem nevét és pozícióját tárolják).
Utolsó simításként készíthetünk a felhasználók által kezelhető ún. egyedi mező formázókat is, melyek az adott tartalomtípus "Megjelenítési beállítások" oldalán érhetők el (admin/content/node-type/[TARTALOMTÍPUS NEVE]/display).
Példa: az Email modul a hook_field_formatter_info segítségével definiálja saját mező formázóit, majd a hook_theme-ben deklarálja az azokat előállító callback függvényeket, végül megvalósítja az így deklarált theme_EGYEDI_FORMÁZÓ függvényeit, melyekben összeállítja a végső HTML kimeneteket.
---
A cikk hossza ellenére még csak a felszínét súroltuk a CCK lehetőségeinek. Az említett függvényekről bővebben a CCK csomagban található kiegészítő modulokban olvashatunk (pl. text, nodereference), melyek gazdag dokumentációt tartalmaznak.

Hozzászólások

aboros képe

kíváncsian várom a többi részt is. ezek igazi színfoltjai lesznek a drupal.hu kézikönyvnek. habár angolul rengeteg információt lehet ezekről a dolgokról elérni, az angol szövegértés, mint bottleneck :) folyamatosan jelen van a fórumon, bizonyos problémák megoldás-javaslatainak megítélésekor.

szép kerek mondat. :)
remek lett, megy a karma ;)

-
clear: both;

zserno képe

Az elismeres pedig a Trellon csapatat is eppugy megilleti. Apropo kuldom is nekik a linket a forditasrol. :)

Dzsozef képe

Végre jött egy ember - akire 2010 május 30-ig vártunk kezdők -, aki felismerte hogy ez a cikk igazi csemege kivétel nélkül minden Drupalos kezdőnek a CCK titkairól. /De talán nem csak nekik./

Nagy köszönet Zserno Neked és a Trellon Csapatnak ezért a kiváló, értékes munkáért!!!

Üdvözöllek: Dzsozef
Ez az első nap, hol a többi magyarul persze?
...csak egy kicsit vagyok telhetetlen.:)

york képe

Nagyon szep es hasznos forditas.
Most mar CCK mezot is tudok irni :D.
Varjuk a folytatast es a sok sok egyeb hasznos forditast.