Extension Entwicklung

Kurzbeschreibung zum Thema TYPO3 Extension Entwicklung

Teil 1 - Einführung

Eine ganz einfache Extension erstellen. Sie soll uns nur Titel, Datum, einen Text, ein Bild und einen Link ausgeben.
Sinn ist es, die grundsätzliche Arbeitsweise zu verstehen und die Arbeitsweise von Typo3 nachzuvollziehen.

Vorbereitung
Wir installieren folgende Extensions:
- kickstarter (Wizard für Basisextensions)
- extdeveval (Offline-Doku und Tools)
- cc_debug (Debug mit Optionen)

Der Anfang
ist in diesem Fall der leichteste :-)
Für unsere Planung: Wir brauchen eine Tabelle mit den Feldern
- Datum
- Text
- Bild
- Link

Die Extension soll tp_test heißen. Normalerweise besorgt man sich einen Useraccount bei typo3.org und kann dann einen Key (der Name der Extension) registrieren, um Eindeutigkeit zu gewährleisten. In unserem Fall ist das egal, da wir nicht planen, die Extension zu veröffentlichen.

Der Kickstarter

Wir rufen den Kickstarter auf, nur wo versteckt er sich?
Wir rufen den Extensionmanager auf, im oberen Menü wählen wir "make new Extension".
Bei "Enter extension key:" tragen wir unseren key ein (tp_test) und klicken auf "Update ..."

Nun kanns losgehen.
1) Wir klicken auf das + neben General Info und tragen ein paar Infos ein, dann klicken wir wieder auf Update.

2) Wir legen unsere Tabelle an (+ neben "New Database Tables")
Der Einfachheit halber tragen wir nur Namen und Titel ein und erlauben Datensätze auf normalen Seiten (Allowed on pages)
Wir scrollen nach unten und legen das erste Feld an (Titel):
- title (fieldname) = Name des DB-Feldes
- Titel (field title) = Label des Feldes im Backend
- String Input (field type) = Typ des Feldes

Nach Klick auf Update geht es zum nächsten Feld, das Datum:
- date
- Datum
- Date

Nun der Text:
- text
- Text
- Textarea with RTE

Nach Klick auf Update (running gag income!) erscheinen zusätzliche Optionen, wir wählen "Typical basic setup" (new "Bodytext" field based on CSS stylesheets)

Nun das Bild:
- image
- Bild
- Files

Nach dem Klick auf Update checken wir "Show thumbnails"

Nun der Link:
- link
- Link
- Link

Klick auf Update und wir haben die Tabelle fertig!
Wir scrollen nochmal nach oben und wählen bei "Label-field" unseren Titel aus, Klick auf Update (Macht doch Spaß, oder?).
Nun gehts zum letzten Schritt - Wir wollen die Extension ja in unsere Seite einfügen können.
Wir klicken auf das + neben Frontend Plugins, tragen unseren Titel ein -> Klick auf Update -> fertig

Wir speichern unsere Extension: 
Klick auf "View result" (Abwechslung!) => "Write to location Write" (wir können alles voreingestellt lassen).

Danach wird uns die Option "Install extension" angeboten, die wir wahrnehmen. Es wird die Tabelle angezeigt, wir übernehmen mit Update ("make updates").
Wie, wars das schon ?
Offensichtlich Wir haben eine Extension!

Na das will ich jetzt aber sehen. Wir suchen uns eine leere Seite, wechseln auf die Listenansicht und klicken auf "Neuer Datensatz"
Ganz unten wird uns "Meine TP-Items" angeboten - Klasse

Wow, nach einem Klick öffnet sich das fertige Eingabeformular:

Vor lauter Begeisterung wollen wir mal einige Datensätze anlegen. Die Ausgabe erfolgt dann in Teil 2.

 

Teil 2 - Die Extension (der eigentliche PHP - Teil)

Als erstes schauen wir uns mal die erzeugten Dateien an (im Verzeichnis typo3conf/ext/tp_test):

- doc
-- wizard_form.dat (für den kickstarter relevant)
-- wizard_form.html (für den kickstarter relevant)

- pi1 (das ist unsere Extensionklasse)
-- static
-- editorcfg.txt (Die RTE-Konfiguration)
-- class.tx_tptest_pi1.php (unsere Hauptklasse)
-- locallang.xml (die Sprachdatei unserer Hauptklasse)

- ChangeLog (hier können wir Versionsbeschreibungen einfügen)
- ext_emconf.php (die Infos zur Extension)
- ext_icon.gif (das Extension - Icon, sichtbar im Extensionmanager)
- ext_localconf.php (Konfigurationsdatei)
- ext_tables.php (Konfiguration des TCA)
- ext_tables.sql (Die Tabellendefinitionen)
- icon_tx_tptest_items.gif (Das Icon für unsere Datensätze)
- locallang_db.xml (Die Sprachdatei für die Labels im Backend)
- README.txt (Notizen)
- tca.php (das TCA - Array unserer Extension)

Es taucht ein Begriff auf: TCA (Table Configuration Array). Eine genauere Beschreibung dieses Arrays finden wir unter [1]

Für uns ist jetzt die pi1 - Klasse entscheidend. Wir öffnen die class.tx_tptest_pi1.php und die locallang.xml im Editor.
Der Kickstarter hat uns schon einiges an Code erzeugt. Dies ist nur ein Demo, wir schauen uns das mal an!

class tx_tptest_pi1 extends tslib_pibase {
    var $prefixId      = 'tx_tptest_pi1';        // Same as class name
    var $scriptRelPath = 'pi1/class.tx_tptest_pi1.php';    // Path to this script relative to the extension dir.
    var $extKey        = 'tp_test';    // The extension key.
    var $pi_checkCHash = true;
    
    /**
     * The main method of the PlugIn
     *
     * @param    string        $content: The PlugIn content
     * @param    array        $conf: The PlugIn configuration
     * @return    The content that is displayed on the website
     */
    function main($content,$conf)    {
        $this->conf=$conf;
        $this->pi_setPiVarDefaults();
        $this->pi_loadLL();
        
    
        $content='
            <strong>This is a few paragraphs:</strong><br />
            <p>This is line 1</p>
            <p>This is line 2</p>
    
            <h3>This is a form:</h3>
            <form action="'.$this->pi_getPageLink($GLOBALS['TSFE']->id).'" method="POST">
                <input type="hidden" name="no_cache" value="1">
                <input type="text" name="'.$this->prefixId.'[input_field]" value="'.htmlspecialchars($this->piVars['input_field']).'">
                <input type="submit" name="'.$this->prefixId.'[submit_button]" value="'.htmlspecialchars($this->pi_getLL('submit_button_label')).'">
            </form>
            <br />
            <p>You can click here to '.$this->pi_linkToPage('get to this page again',$GLOBALS['TSFE']->id).'</p>
        ';
    
        return $this->pi_wrapInBaseClass($content);
    }
}
PHP Code

ie main - Funktion wird aufgerufen, wenn wir das Plugin in einer Seite haben. Das wollen wir jetzt ausprobieren. Wenn wir die Seite uns anschauen, sollten wir folgende Ausgabe sehen:

Was passiert da?
In der Funktion wird die Ausgabe in der Variablen $content gesammelt und am Schluss zurückgegeben (Kein echo oder print!).
Wir sehen laufend etwas mit pi_ - was soll das?

Unsere Hauptklasse ist eine Erweiterung der Klasse tslib_pibase. Diese befindet sich in typo3/sysext/cms/tslib/class.tslib_pibase.php
Alle Methoden dieser Klasse lassen sich also über $this->methode() aufrufen.

Was sind piVars?

Jede pi - Klasse hat ein eigenes Array für GET/POST-Variablen. Damit man sich nicht in die Quere kommt ist das Array mit dem Prefix der Klasse versehen, in unserem Beispiel:
tx_tptest_pi1[irgendwas]

Damit wir darauf leicht zugreifen können, werden diese am Anfang in ein Array verfrachtet ($this->pi_setPiVarDefaults();) und sind über $this->piVars['irgendwas'] abrufbar. Dabei ist es egal, ob es GET oder POST-Vars sind.

Das schauen wir uns mal an, wir wollen debug nutzen (cc_debug installiert?)

debug($this->piVars);
Wir fügen folgende Zeile vor dem return ein:
t3lib_div::debug($this->piVars,'unser debug');
Alternativ können wir auch ohne cc_debug ausgeben:

Wir erhalten eine Ansicht unseres piVar-Arrays. Das überprüfen wir, in dem wir einfach über die Url eine Var hinzufügen:

index.php?id=15&tx_tptest_pi1[test]=20 wobei die 15 natürlich durch die Id der aktuellen Seite zu ersetzen ist.

nun sollten wir die Variable test in unserem Array sehen.

$this->pi_loadLL(); => mit dieser Anweisung wird die Sprachdatei geladen. Wir schauen sie uns an, es ist ein xml-Array Wichtig ist die Sektion <languageKey index="default" type="array"> , die immer da sein muss. Default ist immer die englische Sprache, wollen wir auch deutsch anbieten, kopieren wir diese Sektion und fügen sie darunter an und ändern nach index="de"

Die Labels lassen sich in unserer pi1 einfach aufrufen mit $this->pi_getLL('labelname') - wunderbar, Mehrsprachigkeit ist also kein Thema mehr.

Zusätzlich lässt sich unsere Extension über Typoscript konnfigurieren, die Konfiguration wird in $conf übergeben und ist in der Klasse über $this->conf abrufbar.
Wir testen das und tragen in unser Setup folgende Zeilen ein:

Nach speichern wollen wir sehen, ob es in der Extension ankommt, wir fügen folgende Debug-Anweisung ein:

debug($this->conf);
Debugg Anweisung

Wir sollten folgendes sehen:

Ein globales Objekt spielt für uns eine Rolle, es ist das $GLOBALS['TSFE']-Array. Ein debug dieses Arrays sprengt den Rahmen, es sind alle Objekte für das Rendern der Seite enthalten.

Für uns sind erstmal nur 2 Sachen wichtig:
$GLOBALS['TSFE']->id das ist die id der aktuellen Seite
$GLOBALS['TSFE']->page das sind die Infos der aktuellen Seite (der komplette Datensatz aus pages mit der uid der aktuellen Seite)

Wir brauchen uns diese Infos also nicht aus der DB holen.
Wir nutzen das gleich mal und basteln uns einen Link:

$content.=$this->pi_linkToPage($GLOBALS['TSFE']->page['title'],$GLOBALS['TSFE']->id);
Einfacher Typolink

wir sollten einen Link auf die aktuelle Seite mit dem Titel der Seite als Linktext sehen.

Woher kenne ich die Methoden der pibase ?
Wir haben uns ja extdeveval installiert, im BE haben wir oben eine Leiste mit der Offline-Doku, alle Methoden der pibase sehen wir durch Klick auf pibase.

Ihr könnt ein wenig damit spielen, bevor es dann zur relevanten Ausgabe kommt (im nächsten Teil)
www.typo3.org/documentation/docum...e_api/current/


Teil 3 - wir wollen unsere Datensätze ausgeben

Also auf zur Ausgabe. Wir löschen alles was zu $content gehört und fangen an.

Wir wollen 2 Sachen ausgeben
- eine Liste aller Datensätz
- eine Detailansicht

Beides soll auf der gleichen Seite passiseren, die Unterscheidung wird über einen GET-Parameter gehen (item), der die uid des Datensatzes enthält.

Da wir in unserer Extension kein HTML erzeugen wollen, machen wir uns ein HTML-Template, das wir im root der Extension speichern

Das eingebaute Template-System ist extrem einfach. Es gibt 3 Teile
###MARKER### (einfache Marker)
... (einfache Subparts)
... (einfache Subparts für Links)

Wir sammeln unsere Marker in Arrays
$markerArray -> es werden die Marker durch den entsprechenden Inhalt ersetzt
$subpartArray -> es werden die Subparts durch den entsprechenden Inhalt ersetzt
$linkpartArray -> es werden die Links gesetzt nach dem Muster $linkpartArray[0] ... $linkpartArray[1]

zum Ersetzen haben wir eine Funktion:
$content = $this->cObj->substituteMarkerArrayCached($subpart,$markerArray,$subpartArray,$linkpartArray);

Wir wollen das Template laden:

$this->template=$this->cObj->fileResource('EXT:tp_test/template.html');
Template laden

wir sehen was schönes: EXT: ist das Synonym für den Pfad typo3conf/ext.
Für die Ansichten machen wir also eine Fallunterscheidung

if($this->piVars['item']) {
    $content = $this->detailView();
} else {
    $content = $this->listView();
}
PiVars

wir haben also die Ansichten in 2 Funktionen ausgelagert, damit es übersichtlicher bleibt.
Wir wollen uns einmal die fertige Extension anschauen

class tx_tptest_pi1 extends tslib_pibase {
    var $prefixId      = 'tx_tptest_pi1';        // Same as class name
    var $scriptRelPath = 'pi1/class.tx_tptest_pi1.php';    // Path to this script relative to the extension dir.
    var $extKey        = 'tp_test';    // The extension key.
    var $pi_checkCHash = true;
    var $template;
    var $id;
    /**
     * The main method of the PlugIn
     *
     * @param    string        $content: The PlugIn content
     * @param    array        $conf: The PlugIn configuration
     * @return    The content that is displayed on the website
     */
    function main($content,$conf)    {
        $this->conf=$conf;
        $this->pi_setPiVarDefaults();
        $this->pi_loadLL();
        
        #ein paar Vorbelegungen
        $this->id=$GLOBALS['TSFE']->id;
        $this->template=$this->cObj->fileResource('EXT:tp_test/template.html');
        
        #welche Ansicht?
        if($this->piVars['item']) {
            $content = $this->detailView();
        } else {
            $content = $this->listView();
        }
    
        return $this->pi_wrapInBaseClass($content);
    }
    
    function listView() {
        #unser Subpart
        $subpart=$this->cObj->getSubpart($this->template,'###LISTVIEW###'); 
        #eine einzelne Reihe
        $singlerow=$this->cObj->getSubpart($subpart,'###ROW###'); 
        
        #Datensätze holen
        $res=$GLOBALS['TYPO3_DB']->exec_SELECTquery(
        '*',   #select
        'tx_tptest_items', #from
        'hidden=0 and deleted=0 and pid='.$this->id,  #where
        $groupBy='',
        $orderBy='',
        $limit='');
        
        if($res) {
            $liste='';
            while($row=$GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
                $markerArray['###LINK###']=$this->pi_linkTP($row['title'],array($this->prefixId.'[item]'=> $row['uid']));
                $liste .= $this->cObj->substituteMarkerArrayCached($singlerow,$markerArray); 
            }
            $subpartArray['###ROW###']=$liste;
        } else {
            return $this->pi_getLL('nodata');
        }
        
        
        return $this->cObj->substituteMarkerArrayCached($subpart,$markerArray,$subpartArray,array());     
        
    }
    
    function detailView() {
        #unser Subpart
        $subpart=$this->cObj->getSubpart($this->template,'###DETAILVIEW###'); 
        
        #Datensatz holen
        $record=$this->pi_getRecord('tx_tptest_items',$this->piVars['item']);
        
        #Marker füllen
        $markerArray['###TITLE###']=$record['title'];
        $markerArray['###DATUM###']=date('d.m.Y',$record['date']);
        $markerArray['###TEXT###']=$this->pi_RTEcssText($record['text']);
        $markerArray['###IMAGE###']=$this->cObj->IMAGE(array(
            'file' => 'uploads/tx_tptest/'.$record['image'],
            'file.maxW' => 140,
        ));
        $markerArray['###LINK###']=$this->cObj->typolink($record['link'],array(
            'parameter' => $record['link'],
            'extTarget' => '_blank',
        ));
        
        #backlink als einfachen Link ohne Parameter
        $markerArray['###BACKLINK###']=$this->pi_linkToPage($this->pi_getLL('back'),$this->id);
        
        return $this->cObj->substituteMarkerArrayCached($subpart,$markerArray,array(),array());     
    }
    
}
Fertiger Code

und die entsprechende locallang.xml

Erklärungen

Datenbank: Wir müssen keine Verbindung aufbauen, da diese bereits besteht. Statt normale MySQL-Anweisungen nutzen wir jedoch die TYPO3-DBwrapper-Klasse. Der einfache Grund ist, dass dies nicht nur mit MySQL, sondern auch mit dBal und anderen DB-Engines funktioniert. Die Syntax ist in der Doku der extdeveval erklärt, einfach auf DB klicken.
Als Faustregel kann man die üblichen PHP-Befehle benutzen, nach folgendem Muster:

mysql_query($sql) => $GLOBALS['TYPO3_DB']->sql_query($sql)
mysql_num_rows($res) => $GLOBALS['TYPO3_DB']->sql_num_rows($res)

usw.

cObj (Abk. für Content-Object) ist die Klasse tslib_content (typo3/sysext/cms/tslib/class.tslib_content.php).
Die Objekte und Methoden finden wir wieder bei extdeveval unter cObj.

Die Funktion $this->pi_RTEcssText wandelt einen Richtext in das entsprechende HTML um.

Soweit erstmal, ich denke, dass es nicht besonders kompliziert war - wenn Fragen auftauchen können die hier gestellt werden.

Erstmal viel Spaß beim Nachbauen und Experimentieren !

Ein grosses Danke an Steffen Kamper der mir erlaubt hat diesen Beitrag zu veröffentlichen
Der Originalbeitrag ist hier zu finden.