URL dieses Artikels:

zu Ausgabe: 1.2005
Anything goes
Typo3-Extension-Programmierung
von Thomas Murphy
Typo3 wurde in den letzten Jahren für eine breite Schicht von Anwendern immer öfter zum CMS der Wahl, denn nicht nur die flexible Steuerung des Rendering-Prozesses durch die eigens entwickelte Skriptsprache TypoScript, sondern auch das umfangreiche Extension API haben Typo3 zu einem CMS gemacht, mit dem alles möglich ist.

Eine der größten Stärken des Typo3 CMS ist jener Anspruch an modularisiertem Aufbau, dem sich viele Applikationen heutzutage rühmen und doch nur selten gerecht werden. Typo3 verspricht hier nicht zuviel, denn der Entwickler Kasper Skårhøj hat in der Version 3.5, die im November 2002 veröffentlicht wurde, den Extension Manager eingeführt, der das Kernsystem besser strukturierte und viele Teile der Funktionalität in Extensions auslagerte. In der aktuellen Version 3.7 kann man nahezu jeden Teil des Systems durch eine der zahlreichen Schnittstellen (Plug-ins, Hooks, Services, XCLASS) umgestalten und erweitern. Im weiteren Verlauf des Artikels werden wir Schritt für Schritt eine solche Extension entwickeln.


Abb. 1: Typo3 (Quelle: www.typo3.org)

Ein Frontend-Plugin entwickeln
Einen guten Einstieg in die Typo3-Extension-Programmierung findet man über die Entwicklung eines Frontend-Plug-ins (abgekürzt: FE-Plug-in), ein Content-Element, das beliebigen HTML-Code im Frontend ausgeben kann und z.B. Informationen aus der Datenbank in Listen- oder Detailansicht darstellt und über dynamische Funktionen, wie Detail-Ansichten, Sortier- und Suchfunktionen verfügen kann, die über GET-/POST-Parameter gesteuert werden. Diese Content-Elemente sind Datensätze in der Tabelle tt_content, können auf beliebigen Seiten innerhalb des Typo3-Seitenbaumes (einer Abbildung der Tabelle pages) angelegt werden und sind dann mit dieser Seite assoziiert.

Der Kickstarter
Der Kickstarter ist selbst eine Extension von Typo3 [1] und kann über den Extension Manager im Modul Tools vom Administrator per Klick installiert werden. Er ist, wie der Name vermuten lässt, eine Art Wizard zur Generierung eines Frameworks, mit dem man innerhalb von nur wenigen Minuten eine große Menge an Code generieren kann, der sich nahtlos in das System integriert.

Kein Editor
An dieser Stelle sei noch erwähnt, worauf der Kickstarter bei jedem Speichern selbst hinweist: Er ist kein Editor. Wenn man eine Weile mit dem Kickstarter gearbeitet hat, entsteht schnell der Eindruck, man würde sich in einer Art IDE bewegen, weil man auch nach Speicherung der erzeugten Extension die Möglichkeit hat, wieder zurückzukehren und Änderungen an der ursprünglichen Konfiguration zu machen. Doch der Eindruck trügt und man sollte immer darauf achten, weil der Kickstarter alle vorher gemachten Änderungen bei erneuten Speichern einfach überschreibt.

Nach der Installation des Kickstarters steht im Extension Manager oben in der Select-Box eine weitere Option zur Verfügung: Make new extension. Wird diese ausgewählt, so erscheint der Kickstarter und präsentiert eine Liste von Konfigurationsmöglichkeiten. Als erstes muss ein Extension key registriert werde, ein eindeutiger Schlüssel, der im lokalen System und bei Veröffentlichung auch im offiziellen Extension Repository nicht zweimal vorhanden sein darf. Bei der Entwicklung von Extensions, die nicht veröffentlicht werden sollen, ist hier das Prefix user_ die richtige Wahl. Kommt allerdings eine Veröffentlichung in Betracht, dann sollte man dem Link unter dem Feld folgen und einen offiziellen Schlüssel auf typo3.org registrieren.Der Menüpunkt General Info beinhaltet einige grundlegende, selbsterklärende Informationen zur Extension, die der Extension Manager später in der Detailansicht der Extension darstellen wird. Nach dem Ausfüllen dieses Formulars klicken wir das Kreuz-Symbol bei Extend existing Tables. Wir wählen die Tabelle tt_content zur Erweiterung aus, sie ist in der Select-Box bereits voreingestellt.


Abb. 2: Der Extension Kickstarter

Als erste Erweiterung erzeugen wir ein eigenes Textfeld mit integriertem Rich Text Editor. Field name ist der Name des Datenbank-Feldes, der den Extension key als Prefix erhalten wird, damit sich zwei Extensions nicht aufgrund gleicher Feldnamen überschneiden. Field title wird das (englische) Label des Backend-Formulars und Field type bestimmt den Typ des erweiterten Feldes, dort wählen wir Text Area with RTE, während wir bei den anderen Feldern lediglich darauf achten müssen, aussagekräftige Namen und im Fall des Field name keine Sonderzeichen zu verwenden. Wir wählen hier in unserem Beispiel myrte als Field name und my RTE als Field title. Wenn wir das Formular aktualisieren, erhalten wir weitere Optionen zur Konfiguration des RTE-Feldes und stellen den RTE Mode auf Typical Basic setup, den Rest des Formulars lassen wir unverändert.

Zugriffsbeschränkung
Über die Checkbox Is Exclude-field, die sich im Kickstarter neben jedem neuen Datenbankfeld befindet, lässt sich eine Zugriffsbeschränkung des Feldes auf Gruppenebene aktivieren, die der Administrator später über die einer Gruppe zugeordneten Access-Lists kontrollieren kann.

Das zweite Feld wird eine Relation unseres Datensatzes mit mehreren Bildern herstellen. Wir wählen als Field type diesmal die Option Files aus. Nach Angabe des Field name (z.B. images) und dem Field title (z.B. Images) aktualisieren wir das Formular. Als Extensions wählen wir Web-Imagefiles, die Max number of files stellen wir auf 5, Size of selector box ebenfalls auf 5 und wir aktivieren die Thumbnail-Option, die später automatisch Thumbnails der ausgewählten Bilder in dem Backend-Formular erzeugen wird.Diese beiden Felder sollen die einzigen sein, um die wir die Tabelle tt_content erweitern. Was wir jetzt noch brauchen ist das eigentliche FE-Plug-in. Dazu klicken wir auf das Kreuz-Symbol bei Frontend Plug-ins. Der Titel für das Plug-in kann frei gewählt werden und wird später im Backend verwendet, wir können hier einfach Simple Plug-in eintragen. Die Select-Box unter Apply a set of extended fields enthält unsere Erweiterung der Tabelle tt_content als Option, welche wir hier auswählen. Über die nachfolgenden Radio-Buttons können wir die Art des Plug-ins wählen, das von uns gewünschte ist bereits voreingestellt (Add to Insert Plug-in list in Content Elements), deshalb wählen wir nur noch die Option Add icon to New Content Element wizard und schreiben in das Feld darunter eine kurze Beschreibung, die neben dem Icon im Content-Element Wizard erscheinen soll.


Abb. 3: Erweiterung der Tabelle tt_content



Sprachlos
Um unsere Extension mit weiteren Übersetzungen der Labels zu versehen, können wir im Extension Kickstarter einfach die letzte Option Setup languages wählen und in der Liste der verfügbaren Backend-Sprachen alle gewünschten Übersetzungen für die Extension aktivieren. Die Label-Felder werden dann in allen Formularen um die gewählten Sprachen erweitert.

An dieser Stelle kann man den Kickstarter bereits verlassen, indem man die Extension über View result im lokalen Extension-Verzeichnis /typo3conf/ext speichert. Nachdem das getan ist, kann man im Extension Manager wieder in die Liste der Available Extensions zurückkehren und sehen, dass die frisch erstellte Extension sich in die Liste eingeordnet hat.


Abb. 4: Extension Manager - 'Avalable Extensions to install'

Die Extension lässt sich jetzt ebenso einfach wie alle anderen per Klick installieren, wobei der Extension Manager darauf hinweist, dass die Datenbank aktualisiert werden muss und um welche Felder es sich dabei genau handelt. Außerdem werden zwei Verzeichnisse angelegt, die für den Bild-Upload nötig sind. Nach den Aktualisierungen kann das neue Content-Element auf Seiten angelegt werden. Es erscheint nun im Content-Element Wizard und enthält bereits die neuen, voll funktionsfähigen Felder my RTE und Images. Im Frontend werden nach dem Speichern des Content-Elements ein paar Zeilen Dummy-Inhalt angezeigt, die der Kickstarter erzeugt hat. Werfen Sie nun einen Blick auf die erstellten Dateien (Tabelle 1).
Tabelle 1Man kann die Extension nun aus dem Verzeichnis /typo3conf/ext/user_simpleext herunterladen und offline bearbeiten, sollte man gerade keinen Editor zur Hand haben oder nur kleine Änderungen vornehmen wollen, kann man über die Detailansicht der Extension (auf den Titel klicken) auch den internen Editor benutzen, der über die obere Select-Box unter Edit Files zu erreichen ist.Achten Sie von nun an darauf, nach allen Änderungen den Cache von Typo3 über das Backend zu löschen, damit Ihre Änderungen sichtbar werden.

Formulare bearbeiten
Die erste Datei, die wir editieren werden, ist die ext_tables.php, die Informationen zur Generierung und Validierung der Formulare und zur Eingabe und Bearbeitung der Datensätze enthält. Der Array $tempColumns enthält z.B. Angaben zu unseren Erweiterungen der Tabelle tt_content, die hier mit dem Extension key geprefixt werden, damit sie global eindeutig sind. Im Array config finden sich die Konfigurationseinstellungen zu dem Feld, wir können bei user_simpleext_myrte z.B. die Größe des Textfeldes bestimmen (Listing 1).

Listing 1

"user_simpleext_myrte" => Array (		"exclude" => 1,
"label" => "LLL:EXT:user_simpleext/locallang_db.php:tt_content.user_simpleext_myrte",
"config" => Array (
"type" => "text",
"cols" => "30",
"rows" => "5",
"wizards" => Array(
"_PADDING" => 2,
"RTE" => Array(
"notNewRecords" => 1,
"RTEonly" => 1,
"type" => "script",
"title" => "Full screen Rich Text Editing|[...]",
"icon" => "wizard_rte2.gif",
"script" => "wizard_rte.php",
),
),
)
),

Wir werden nun unser Backend-Formular etwas anpassen, indem wir den Rich Text Editor um ein paar Funktionen verringern, um den Redakteuren nur bestimmte Formatierungen zu gestatten. Wir finden weiter unten in der Datei eine Zuweisung von zusätzlichen Werten in den Array $TCA (Table Configuration Array). Wir haben hier die subtypes_excludelist und die subtypes_addlist, die beide extensionspezifische Änderungen an der Standardkonfiguration der Formulare vornehmen. Wenn wir jetzt z.B. in der subtypes_excludelist den Wert hidden einfügen, die Extension speichern und den cache löschen werden wir feststellen, dass in unserem Plug-in unter General Options die Checkbox Hide verschwunden ist, die vorher aktiviert war.In der subtypes_addlist sehen wir unsere beiden Felder mit weiteren Optionen. Was sofort ins Auge fällt ist die Liste der RTE-Optionen, die mit [cut|copy|paste... beginnt und auf ...|chMode] endet. Das sind die Funktionen, mit denen der RTE konfiguriert ist. Durch Eingabe von * kann man dort alle Optionen anschalten oder die Liste wie hier

$TCA["tt_content"]["types"]["list"]["subtypes_addlist"][$_EXTKEY."_pi1"]=
"user_simpleext_myrte;;;richtext[cut|copy|paste|bold|italic|underline]:
rte_transform[mode=ts_css|imgpath=uploads/tx_usersimpleext/rte/];1-1-1, user_simpleext_images";

mit einer persönlichen Auswahl von Optionen zusammenstellen. Weitere Möglichkeiten zur sehr flexiblen Konfiguration des RTE finden sie unter [2] und [3].

Der Plugin-Code
Die Extension erzeugt immer noch die Dummy-Ausgaben des Kickstarters. Das kann in der Datei pi1/class.user_simpleext_pi1.php geändert werden. Diese Datei enthält die Klasse, die initialisiert wird, wenn das Content-Element gerendert wird. Die Funktion main() wird aufgerufen übernimmt hier die Ausgabe.In main() wird die Variable $content mit Inhalt gefüllt und an die Funktion $this->pi_wrapInBaseClass() übergeben, die aus der Klasse tslib_pibase (class.tslib_pibase.php) stammt. Das ist die Klasse, die durch FE-Plug-ins erweitert wird und zahlreiche Funktionen enthält, die zur Arbeit mit FE-Plug-ins unerlässlich sind.Die pi_wrapInBaseClass() tut nichts anderes, als unseren HTML-Code in einen eigenen <div>-Tag zu wrappen. Wir sind also an dieser Stelle in der Lage, beliebigen Output zu erzeugen, doch muss man vorsichtig sein, hier nicht alle Vorteile, die einem das Framework bietet, wieder zunichte zu machen. Es folgen einige Punkte, die man bei der Programmierung beachten sollte.

Datenbank-Abstraktion
Typo3 besitzt eine eigene Abstraktions-Ebene für den Zugriff auf Datenbanken. Um zu anderen Architekturen kompatibel zu sein, sollte man daher statt der gewohnten Query-Funktionen, von den Typo3-eigenen exec_*-Funktionen Gebrauch machen, z.B. exec_SELECTquery() oder exec_INSERTquery(). Mehr Informationen dazu findet man unter [4].

Links
Innerhalb des Outputs der Extension sollte man unbedingt auf die verschiedenen Linkfunktionen aus der Klasse pi_base zurückgreifen. Zwei davon befinden sich in der Ausgabe des Plug-ins, das vom Kickstarter erzeugt wurde: $this->pi_getPageLink() wird eine Seiten-ID als Parameter übergeben und liefert eine URL zu der Seite zurück, $this->pi_linkToPage() benötigt zwei Parameter, der erste ist ein String und der zweite eine Seiten-ID, die Funktion liefert dann einen kompletten <a>-Tag zurück, der um den String gewrapped ist.

Mehrsprachigkeit
Immer wenn man Textinhalt erzeugt, sollte man darauf achten, dabei auf die Funtkion $this->pi_getLL() zurückzugreifen und die Sprachtoken aus der Datei pi1/locallang.php zu nutzen, d.h., statt eine Ausgabe zu erzeugen wie '<h1>Überschrift<h1>' die Alternative '<h1>.'$this->pi_getLL('headline')'.</h1>' zu wählen und den Token 'headline' in der Datei pi1/locallang.php einzufügen. Das ermöglicht eine vergleichsweise einfache Übersetzung der Extension in andere Sprachen.

Konfiguration & Templates
Das Erscheinungsbild des FE-Plug-ins sollte so gut es geht außerhalb des Extension-Sourcecodes konfigurierbar sein und deshalb innerhalb der Klasse eines FE-Plug-ins viel mit der Variable $this->conf gearbeitet werden, dem Array, der die TypoScript-Konfiguration des Plug-ins enthält. Im Listing 2 befinden sich ein paar Zeilen TypoScript, die im Setup des Seiten-Templates vorhanden sein müssen, um uns z.B. den Zugriff auf die Variablen $this->conf['test'] und $this->conf['hello.']['world'] (man beachte den Punkt nach hello) zu geben.Die Verwendung von Templates ist ebenfalls zu empfehlen, denn es erleichtert die Anpassung eines Plug-ins sehr. Typo3 verfügt über eine eigene Template-Engine, die mit Token (hier: Marks) und so genannten Subparts umgehen kann. Auch Smarty ist bereits als Extension vorhanden und kann zum Erzeugen der Ausgabe benutzt werden. Man sollte auf jeden Fall darauf achten, in seinen Templates ausschließlich mit validem XHTML Transitional zu arbeiten (Typo3 erzeugt XHTML-kompatiblen Code) und die Ausgabe mit CSS zu formatieren.

Listing 2

plugin.user_simpleext_pi1 {
test = Hello World!
hello.world = 1

images = IMAGE
images.file.width = 100

images{
imageLinkWrap = 1
imageLinkWrap {
bodyTag = <body style=background-color:#000000;>
wrap = <a href="javascript:close();">|</a>
width = 500
height = 500
JSwindow = 1
JSwindow.newWindow = 1
JSwindow.expand = 20,20
enable = 1
}
}
}

Die Ausgabe
Um die Ausgabe unseres Plug-ins zu verändern, können wir die Zuweisung der Variable $content auskommentieren oder löschen und gegen unseren eigenen Content austauschen. Dazu müssen wir noch die Inhalte unseres Content-Elements aus der Tabelle tt_content holen, dazu benötigen wir $this->cObj->currentRecord, dort ist ein String in der Form [tablename]:[uid] enthalten. Wir schicken diesen String durch ein explode() und benutzen die Return-Werte für die getRecord()-Funktion der Klasse pi_base, die uns einen assoziativen Array mit den Werten unseres Datensatzes zurückliefert (Listing 3).Die Pfade der im Formular hochgeladenen Bilder befinden sich durch Kommazeichen separiert in einem BLOB und können von uns ebenfalls durch ein explode() extrahiert werden. Damit die Bilder nicht alle in Originalgröße von dem Redakteur in der Site platziert werden, entwickeln wir noch eine Funktion getImage(), die uns über die cObj-Funktion IMAGE einen <img>-Tag rendert, dem über das TypoScript aus Listing 2 zu magischen Fähigkeiten verholfen wird: Die IMAGE-Funktion kann durch die übergebene $imageConfig dazu gebracht werden, dem Bild eine Maximal-Breite bzw. -Höhe zuzuweisen und außerdem automatisch ein Klick-Pop-up mit dem Bild in einer größeren Variante zu erzeugen. (Um eine Default-Einstellung für das TypoScript der Extension zu haben, kopieren wir das Listing 2 in eine neue Datei ins Extension-Verzeichnis, die wir ext_typoscript_setup.txt nennen.) Nun können wir die Werte wie in Listing 3 beschrieben ausgeben und verzichten an dieser Stelle auf die Ausgabe über ein Template.

Listing 3

class user_simpleext_pi1 extends tslib_pibase {
var $prefixId = "user_simpleext_pi1";
var $scriptRelPath = "pi1/class.user_simpleext_pi1.php";
var $extKey = "user_simpleext";

function main($content,$conf) {
$this->conf=$conf;
$this->pi_setPiVarDefaults();
$this->pi_loadLL();

$contentUID = explode(":",$this->cObj->currentRecord);
$data = $this->pi_getRecord($contentUID[0],$contentUID[1]);
$content = '<p>'.$data['user_simpleext_myrte'].'</p>';

foreach(explode(",",$data['user_simpleext_images']) as $k => $i){
$content.= $this->getImage($i);
}
return $this->pi_wrapInBaseClass($content);
}

function getImage($imageName){
$imageConfig = $this->conf['images.'];
$imageConfig['file'] = 'uploads/tx_usersimpleext/'.$imageName;
return $this->cObj->IMAGE($imageConfig);
}
}

Coding Guidelines
Das Typo3-Projekt verfügt bereits über eine große Anzahl frei verfügbarer Extensions, die einer großen Entwicklergemeinde zu verdanken sind. Es wurde bereits vor etwa zwei Jahren ein Dokument [4] veröffentlicht, welches Richtlinien zur Strukturierung und Dokumentation des Sourcecodes in Typo3-Extensions vorgibt. Bevor man also seine eigene Extension in das öffentliche Extension Repository stellt, sollte man sich vergewissern, dass man sich so gut es geht an diese Richtlinien gehalten hat, auch im Hinblick auf die Bewertung durch das Team Extension Review, welches sich zum Ziel gesetzt hat, alle Extensions einer Qualitätsprüfung zu unterziehen und in Kategorien von No cigar bis Cohiba! einzuordnen. Extensions, die sich nicht an die Coding Guidelines halten, werden niedrigere Wertungen erhalten. Auf diese Weise wird eine Qualitätssicherung im Repository geschaffen, die allen Entwicklern und Anwendern zugute kommt.Weiterführende Informationen können außerdem in [5] [6] und [7] bezogen werden. Wer Typo3 bis jetzt noch gar nicht getestet hat, kann mit der auf Knoppix basierenden Live-CD von [8] mal hineinschauen.
Thomas Murphy ist selbstständiger Softwareentwickler bei seinem Unternehmen the panem group in Berlin. Er ist erreichbar unter murphy@thepanemgroup.com.

Links
[1] typo3.org/extensions/repository/search/kickstarter/
[2] typo3.org/documentation/document-library/doc_core_api/
[3] www.mcuniverse.com/Customizing_RTE.842.0.html
[4] typo3.org/documentation/document-library/doc_core_cgl/
[5] typo3.org/documentation/document-library/Matrix/
[6] wiki.typo3.org
[7] typo3.org/documentation/mailing-lists/
[8] typo3.punkt.de/typo3live.html

© 2004 Software & Support Verlag GmbH. Vervielfältigung nur mit Genehmigung des Verlags. Fragen?