Drupal Module programmieren - Autotag Teil 2 (Admin-Bereich)

Drupal basiert auf einem System von sogenannten Hooks. Grob erklärt stellen Module Hooks zur Verfügung, damit sich andere Module dort einhaken können um die Geschehnisse zu beeinflussen.

Wie das genau aussieht werdet ihr gleich sehen, dann wird auch etwas klarer, was ich meine. Zuerst sei aber gesagt, dass die Module Datei eine reine PHP-Datei ist. Hier ein Beispiel wie der Rahmen einer solchen Datei aussehen kann:

<?php
// $Id$

/**
 * @file
 * Automatically inserts tags using the token module
 */

Bitte beachte, dass ich den schließenden PHP-Tag "?>" absichtlich weggelassen habe. Dies ist in PHP optional und in Drupal ist es Best-Practice diesen wegzulassen. Ansonsten findet ihr hier wieder den CVS Platzhalter (diesmal mit zwei Slashes auskommentiert, wie es in PHP üblich ist) und darunter einen Kommentar-Bereich. Der @file Token besagt, dass die nächste Zeile eine Beschreibung dessen ist, was das Modul tut. Wer ausführlich kommentieren möchte, kann als nächstes auch eine Leerzeile einfügen und dann nochmal für weitere Entwickler etwas detaillierter beschreiben, was hier passiert.

<?php
// $Id$

/**
 * @file
 * Automatically inserts tags using the token module
 * 
 * Uses token values in the taxonomy tag field to
 * automatically create and insert tags before node
 * is saved.
 */

Es macht Sinn ausführlich und detailliert zu kommentieren. Nicht nur fällt es einem selber leichter wenn man nach langer Zeit sich wieder in das Modul einarbeiten möchte, es fällt auch fremden Entwicklern leichter nachzuvollziehen, was du gemacht hast. Dazu kommt auch, dass dir bei Problemen in Hilfe-Foren viel schneller Unterstützung zukommen wird, wenn du deinen Quelltext dort zusammen mit vernünftigen Kommentaren einstellst. Es ist also schlau den Code direkt beim Erstellen zu kommentieren.

Der Menü Hook

Wie bereits anfänglich gesagt, kann man über Hooks in andere Module einhaken um die Geschehnisse dort zu beeinflussen. Der wichtigste Hook um einen Pfad für ein Modul bereitzustellen ist hook_menu(). Dieser Hook wird z.B. beim Darstellen des Administrationsbereiches genutzt. Drupal durchsucht beim Aufrufen des Admin-Bereichs alle Module in alphabetischer Reihenfolge und checkt, ob diese aktiviert sind, um dann ein Menü zu erstellen, das Zugriff auf alle Module bietet. Wenn wir unserem Modul jetzt eine Funktion schreiben mit dem Namen [modulname]_menu(), in unserem Fall also autotag_menu(), dann haken wir uns in diesen Menü-Erstellungs-Prozess ein und sorgen dafür, dass auch für unser Modul ein Menüpunkt erstellt wird.

Damit unser Hook auch etwas bewirkt, muss dieser jetzt ein Array zurückgeben mit den Pfaden des Moduls, und Angaben was passieren soll, wenn diese Pfade aufgerufen werden. Hier ein Beispiel:

/**
 * Implementation of hook_menu().
 */
function autotag_menu()
{
  $items['admin/settings/autotag'] = array(
    'title' => 'Autotag settings',
    'description' => 'Make settings for Autotag.',
    'page callback' => 'drupal_get_form', // function called when path is requested
    'page arguments' => array('autotag_admin_settings'), // form id passed to the function
    'access arguments' => array('administer site configuration'),
    'type' => MENU_NORMAL_ITEM,
    'file' => 'autotag.admin.inc' // look for a function describing this form in this file
  );
  
  return $items;
}

Wie man hier sieht, erstellen wir erstmal nur einen Eintrag im Array. Es ist natürlich möglich mehrere zu erstellen. Der Schlüssel des Eintrags ist "admin/settings/autotag" und stellt den Pfad dar, der in diesem Array Eintrag genauer beschrieben wird. Diese genauere Beschreibung wird wieder in ein Array geschrieben. In diesem Array haben wir die Schlüssel "title" zur Angabe eines Namens für den Menüpunkt und Titel der Modul-Seite, eine "description" für genau die gleichen Fälle, einen "page callback" die genau definiert, welche Funktion ausgeführt werden soll, wenn der Pfad aufgerufen wird, einen Schlüssel "page arguments" der die ID des Formulars festlegt, was nötig ist, weil wir als page callback ein Formular angefordert haben (drupal_get_form), "access arguments" um den Zugriff auf das Modul nur bestimmten Benutzerrollen zu gestatten, einen "type" und ein "file", das besagt in welcher Datei wir das Formular mit der korrekten ID finden. Diese Datei müssen wir natürlich noch erstellen. Es macht Sinn das Formular aus der MODULE-Datei herauszuholen und in eine eigene Datei zu packen, weil das Formular ja nur benötigt wird, wenn man sich im Admin-Bereich befindet und deshalb nicht bei jeder Modul-Benutzung mitverarbeitet werden muss.

Das Admin-Formular

Wir erstellen also eine Datei autotag.admin.inc (ebenfalls eine PHP-Datei) und fügen eine Funktion hinzu, die einerseits den Namen unserer Formular-ID trägt (also autotag_admin_settings) und andererseits ein Formular zurückgibt (denn diese wird ja mit drupal_get_form() aufgerufen).

Das Formular soll das Muster speichern, mit dem die automatischen Tags vergeben werden. Ein mögliches Muster wäre z.B.

[month], [language], autotag

Statt [month] wird dann der aktuelle Monat und statt [language] die ausgewählte Sprache eingefügt. Würde ich heute einen Artikel mit diesem Modul schreiben und vergessen, die Tags einzugeben, würden also die Tags

january, german, autotag

eingefügt. Hierfür benötigen wird dann auch später das Token-Modul, denn das ist in der Lage "Tokens" wie [month] in einem Text dynamisch durch andere Werte zu ersetzen. In unserem Admin-Bereich wollen wir dem Benutzer jetzt ermöglichen, selber ein Pattern einzugeben. Die Funktion könnte so aussehen:

<?php
// $Id$

/**
 * @file
 * Administration page callbacks for the autotag module.
 */
 
/**
 * Form builder. Configure autotags
 * @ingroup forms
 * @see system_settings_form().
 */

function autotag_admin_settings()
{
  $form['autotag']['autotag_pattern'] = array(
    '#type' => 'textfield',
    '#title' => t('Autotag pattern'),
    '#default_value' => variable_get('autotag_pattern', '[month], [language], autotag'),
  );
 
  return system_settings_form($form);

}

Hier wird jetzt ein Eingabefeld erzeugt, mit der Überschrift "Autotag pattern" und einem Standardwert von "[month], [language], autotag".

Was wir als erstes betrachten sollten ist die Struktur des Arrays $form. Der Schlüssel unseres Formulars ist "autotag" und der Unterbereich ist "autotag_pattern". Das zeigt sich in der verschachtelten Struktur des Arrays. Der Unterbereich kann als eine Art Formular-Abschnitt betrachtet werden. Wie dieser Abschnitt aussieht, wird (erneut in Form eines Arrays) in diesem Unterbereich gespeichert. So entsteht ein sehr komplex-verschachteltes Objekt, das aber einfachen und logischen Zugriff auf alle nötigen Informationen ermöglicht.

Wir definieren also die Einträge '#type' als 'textfield', '#title' als 'Autotag pattern' und fügen '#default_value' den Wert der Variable "autotag_pattern" hinzu. Der Nummern-Operator vor dem Schlüssel zeigt an, dass es sich hier um eine Formular-Eigenschaft handelt. Mit Verwendung der Funktion variable_get() stellen wir sicher, dass unsere Variable nicht nur lokal zur Laufzeit, sondern in der Drupal Variablen-Tabelle gespeichert wird. variable_get() erwartet als ersten Parameter den Namen der Variable und als zweiten Parameter einen Standardwert, der genutzt wird, falls die Variable nicht existiert. In diesem Fall wird die Variable mit dem Standardwert angelegt und sofort zurückgegeben.

Mit Hilfe der Funktion "system_settings_form()" wird das soeben erstellte Formular direkt mit den Systemeinstellungen formatiert und ausgegeben. Wir erhalten also automatisch einen Speichern und einen Löschen Knopf und die Abarbeitung des Formulars passiert ebenfalls automatisch.

Was wir jetzt noch machen können ist, dem Benutzer eine Liste der verfügbaren Tokens anzuzeigen, damit dieser auch weiß, was er alles für sein Muster verwenden kann. Dafür erweitern wir das Formular um zwei weitere Felder:

if (module_exists('token')) {
$form['autotag']['token_help'] = array(
  '#title' => t('Replacement patterns'),
  '#type' => 'fieldset',
  '#collapsible' => TRUE,
  '#collapsed' => TRUE,
  '#description' => t('Prefer raw-text replacements for text to avoid problems with HTML entities!'),
);
 
$form['autotag']['token_help']['help'] = array(
  '#value' => theme('token_help', 'node'),
);
}

Als erstes fügen wir ganz normal einen weiteren Formular-Abschnitt ein, den wir "token_help" nennen. Dieser Abschnitt bekommt die Überschrift "Replacement patterns", ist vom Typ "fieldset" (also eine Gruppierung von weiteren Feldern), soll ein- und ausklappbar sein und standardmäßig eingeklappt sein.

Als nächstes fügen wir diesem Fieldset einen Bereich "help" zu. Das funktioniert so, dass wir das zuzuordnende Feld dem Fieldset in der Array-Struktur unterordnen

$form['autotag']['token_help']['help']

Dieser Bereich enthält einfach nur einen Wert. Wir verwenden theme('token_help', 'node'), weil das in der Dokumentation des Token-Moduls so empfohlen wird, wenn man eine Tabelle der möglichen Tokens ausgegeben haben möchte. Die theme()-Funktion wird natürlich von Drupal und nicht vom Token-Modul bereitgestellt und kann daher auch in anderen fällen verwendet werden. Dazu aber mehr an anderer Stelle.

Unsere fertige autotag.admin.inc sieht also so aus:

<?php
// $Id$

/**
 * @file
 * Administration page callbacks for the autotag module.
 */
 
/**
 * Form builder. Configure autotags
 * @ingroup forms
 * @see system_settings_form().
 */

function autotag_admin_settings()
{
  $form['autotag']['autotag_pattern'] = array(
    '#type' => 'textfield',
    '#title' => t('Autotag pattern'),
    '#default_value' => variable_get('autotag_pattern', '[month], [language], autotag'),
  );
 
  if (module_exists('token')) {
    $form['autotag']['token_help'] = array(
      '#title' => t('Replacement patterns'),
      '#type' => 'fieldset',
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
      '#description' => t('Prefer raw-text replacements for text to avoid problems with HTML entities!'),
    );
 
    $form['autotag']['token_help']['help'] = array(
       '#value' => theme('token_help', 'node'),
    );
  }
 
  return system_settings_form($form);

}

Wenn ihr jetzt das Modul aktiviert, werdet ihr den entsprechenden Eintrag im Administrationsmenü unter Einstellungen sehen und bearbeiten können. Im nächsten Teil beschäftigen wir uns damit, wie wir das Modul dann wirklich dazu kriegen dieses Muster zu verwenden, um neue Beiträge automatisch mit Tags zu versehen.