Grischa am :
Woah.. Franz Steiniger hat mich gerade auf diese Seite aufmerksam gemacht. Alle Achtung! Sollte in einem S9Y Wiki verlinkt werden..
Was bringt die Rohform, wenn man nicht weiß, wie man etwas damit anfangen kann? Ich will hier zeigen, wie Serendipity-Plugins funktionieren und ein paar der typischen Probleme ansprechen. Gedacht ist das als verfrühtes kleines Weihnachtsgeschenk, für die langen und ruhigen Weihnachtsfeiertage ;)
Ein Wort zu PHP: Davor sollte man keine Angst haben. Es mag in manchen Kreisen als Skriptkiddie-Sprache gelten, warum auch immer. Wahrscheinlich spielt da rein, dass es nicht die schönste aller Sprachen ist, mit Semikolon-Zwang, der Mischung aus funktionaler und objektorientierter Programmierung und $-Variablenbezeichnung. Aber es tut seinen Job, und wer etwas Bash und eine beliebige objektorientierte Sprache kennt wird sich zurechtfinden. Bash mit Objekten beschreibt PHP ganz gut.
Diese Anleitung ist als Ergänzung/Erklärung zur umfassenden Auflistung der Plugin-API auf s9y.org zu verstehen.
Ein Plugin kann nur dort ansetzen, wo das Mutterprogramm es zulässt. Bei Serendipity sind dafür eine Vielzahl von Eintrittspunkten definiert, die Event Hooks (oder einfach Events). An bestimmten Punkten im Programmablauf, z.B. jedes Mal wenn im Adminbereich ein Kommentar gelistet wird, wird solch ein Event geworfen. Ein Plugin kann das Event fangen, die geworfenen Daten manipulieren und zurückgeben.
Das bedeutet aber nicht, dass keine echte eigene Funktionalität möglich ist. Ein Plugin kann problemlos eigene Seiten anlegen und auf denen machen, was es will.
Es muss noch unterschieden werden zwischen den Seitenleistenpugins (serendipity_plugin) und den Event-Plugins (serendipity_event). Letztere sind die "echten" Plugins mit voller Funktionalität. Die Seitenleistenplugins sind nur dazu da, Informationen in der Seitenleiste auszugeben und nicht dafür, alle möglichen Events zu fangen. Der grundsätzliche Aufbau ist aber gleich.
Ein Plugin ist ein PHP-Objekt, das von serendipity_event erbt. In den folgenden Abschnitten wird dann die Funktionalität eingebaut.
Die Funktion introspect(&$propbag) definiert die ganzen grundlegenden Eigenschaften des Plugins, inklusive den Konfigurationsoptionen und den genutzten Events. Was hier nicht angelegt wird, kann später nicht genutzt werden.
function introspect(&$propbag) { global $serendipity; $propbag->add('name', PLUGIN_EVENT_PLUGINNAME_NAME); $propbag->add('description', PLUGIN_EVENT_PLUGINNAME_DESC); $propbag->add('author', 'Your Name'); $propbag->add('version', '0.1'); $propbag->add('requirements', array( 'serendipity' => '0.8')); $propbag->add('event_hooks', array('frontend_display' => true)); $propbag->add('configuration', array('directblock')); }
Mittels $propbag->add() werden die verschiedenen Eigenschaften des Plugins definiert. Man sieht die ganzen grundlegenden Beschreibungen wie $propbag->add('name', 'PLUGIN_EVENT_PLUGINNAME_NAME'), wovon es natürlich noch einige weitere gibt. Wichtig und an sich selbsterklärend sind die Voraussetzungen zur Installation des Plugins.
Am allerwichtigsten ist jedoch das Hinzufügen der Events, die später genutzt werden sollen. $propbag->add('event_hooks', array('frontend_display' => true)) fügt den Event frontend_display hinzu, das Plugin soll also immer dann etwas tun, wenn eine normale Blogseite (wie dieser Artikel) aufgerufen wird.
Doch zuerst zur Konfiguration: $propbag->add('configuration', array('directblock')) fügt die Option 'directblock' hinzu, jedoch ohne anzugeben, was das für eine Art von Option ist. Das folgt im nächsten Abschnitt.
Alle angelegten Optionen werden in der Funktion introspect_config_item($name, &$propbag) ausgewertet und mit Funktionalität versehen.
function introspect_config_item($name, &$propbag) { global $serendipity; switch($name) { case 'directblock': $propbag->add('type', 'boolean'); $propbag->add('name', PLUGIN_EVENT_PLUGIN_DIRECTBLOCK); $propbag->add('description', PLUGIN_EVENT_PLUGIN_DIRECTBLOCK_DESC); $propbag->add('default', false); break; default: return false; } return true; }
Mit der switch/case-Anweisung werden alle Optionen durchgegangen. Hier gibt es nur eine, directblock, also wird nur diese gefunden. Ihr wird ein Typ vergeben, der Typ Boolean, ein Name, Beschreibung und ein Standardwert. Je nach Typ wird in der grafischen Konfiguration ein anderes Objekt daraus, das hier wird ein Radiobutton mit "Nein" und "Ja" zur Auswahl.
Im folgenden Verlauf des Programms können die Optionen dann jederzeit mit $this->get_config('option', 'default') abgefragt werden, aber meistens wird es so sein, dass sie direkt bei der Abfrage der Events gebraucht werden - dort wird die Funktionalität spezifiziert.
Um die Events kümmert sich die Funktion event_hook($event, &$bag, &$eventData, $addData = null). Ein Beispiel:
function event_hook($event, &$bag, &$eventData, $addData = null) { global $serendipity; $hooks = &$bag->get ( 'event_hooks' ); if (isset ($hooks[$event])) { switch ($event) { case 'external_plugin' : switch ($eventData) { case 'learncomment': //do something break; } return true; break; default : return false; break; } } else { return false; } }
Zuerst wird geguckt, ob überhaupt Events vorhanden sind. Wenn dem so ist und das Event 'external_plugin' (eines der wichtigsten, da freien Events, dazu später mehr) eintritt und dann dort der mitgegebene String '$eventData' 'learncomment' lautet, dann macht das Plugin irgendwas.
Statt 'external_plugin' hätte man auch das Event von oben, 'frontent_display', fangen können. An diesem sieht man etwas besser, wie $event_Data verwendet wird:
case 'frontend_display': foreach ($this->markup_elements as $temp) { if (serendipity_db_bool($this->get_config($temp['name'], true)) { $element = $temp['element']; $eventData[$element] = $this->_s9y_markup($eventData[$element]); } }
Das ist ein Auszug aus dem Code des s9ymarkup-Plugins, das standardmäßig in jedem Serendipity-Blog aktiviert ist. Wichtig ist hier nur, wie die Ausgabe angefasst wird. $eventData[$element] steht z.B. für 'body', verweist also auf den Blogtext eines Eintrags. Der wird manipuliert und dann wieder $eventData[$element] zugewiesen, also überschrieben. Und schon ist das Prinzip einer Auszeichnungssprache umgesetzt, das Plugin hat die eigentlichen Daten von Serendipity verändert.
Natürlich gibt es mehr als nur diese beiden Events.
Alle Texte, de ausgegeben werden sollen, können natürlich nicht einfach so ins Plugin geschrieben werden. Schließlich soll Serendipity nicht nur auf Englisch verfügbar sein. Daher die Nutzung der Variablen wie PLUGIN_EVENT_PLUGINNAME_NAME. Diese werden aus den Sprachdateien wie lang_de.inc.php eingebunden:
// Probe for a language include with constants. Still include defines later on, if some constants were missing $probelang = dirname(__FILE__) . '/' . $serendipity['charset'] . 'lang_' . $serendipity['lang'] . '.inc.php'; if (file_exists($probelang)) { include $probelang; } include dirname(__FILE__) . '/lang_en.inc.php';
In diesen Dateien kann dann der eigentliche Wert angegeben werden:
@define('PLUGIN_EVENT_PLUGINNAME_NAME', 'Spamschutz (Bayes)');
Viele Plugins haben Dateien, die sie im Laufe ihres Ablaufs benötigen. Das kann die Grafik sein, die neben einem Button angezeigt werden soll. Da wir nicht wissen, wo unser Plugin liegt, wissen wir vorher nicht, wie dieses Bild eingebunden werden kann. Daher fragen wir uns am besten selbst:
$serendipity['baseURL'] . 'index.php?/plugin/spamblock_bayes.spam.png'
Mit diesem Code lösen wir unser eigenes Event aus, das uns dann das Bild zurückgeben kann. Schlüssel hierzu ist das 'external_plugin'-Event:
switch ($event) { case 'external_plugin' : switch ($eventData) { case 'spamblock_bayes.spam.png': header('Content-Type: image/png'); echo file_get_contents(dirname(__FILE__). '/img/spamblock_bayes.spam.png'); break; } }
So kann zuverlässig das Bild ausgegeben werden. Mit dem gleichen Prinzip kann natürlich auch etwas anderes ausgegeben werden, beliebiges - dieses Event ist die Grundlage alle Ajax-Abfragen. Hiermit redet das Javacript, um Daten zu übermitteln oder zu bekommen.
Diese Methode hat jedoch einen Nachteil: Sie ist teuer, da wir ganz Serendipity nur dazu nutzen, eine mickrige Datei auszugeben. Und an sich wird sie nur selten gebraucht, die meisten Plugins liegen ja doch einfach unter /plugins/. Daher sollten Plugins eine 'path'-Option anbieten, die dann so genutzt wird:
$path = $this->get_config('path', ''); if (!empty($path) && $path != 'default' && $path != 'none' && $path != 'empty') { $path_defined = true; $imgpath = $path . 'img/'; } else { $path_defined = false; $imgpath = $serendipity['baseURL'] . 'index.php?/plugin/'; }
Statt dann blind auf den Event-Hook zu verweisen, nutzen wir den vorgegeben Pfad:
<img src="'. $imgpath . 'spamblock_bayes.spam.png" />
Es sind insbesondere Javascript-Frameworks wie jQuery, auf die andere Plugins aufbauen, wobei natürlich auch oft ein Event- mit einem Seitenleistenplugin gekoppelt wird. jQuery soll natürlich nicht von jedem Plugin, das dies benötigt, selbst eingebunden werden. Es gibt ein jQuery-Plugin, das dann in introspect(&$propbag) als Abhängigkeit eingetragen werden sollte:
$this->dependencies = array('serendipity_event_jquery' => 'remove');
Will man von JavaScript aus mit dem Server reden, bastelt man eine Ajax-Anfrage und redet mit 'external_plugin'. Dies sendet z.B. eine id (eines Kommentars) an das Plugin:
function spam(id) { if (window.XMLHttpRequest) { // Mozilla, Safari, Opera, IE7 httpRequest = new XMLHttpRequest(); } else if (window.ActiveXObject) { // IE6, IE5 httpRequest = new ActiveXObject("Microsoft.XMLHTTP"); } httpRequest.onreadystatechange = setMessage; lastID = id; httpRequest.open('POST', '<?php echo $serendipity ['baseURL'] . 'index.php?/plugin/learncomment'; ?>', true); httpRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=<?php echo LANG_CHARSET; ?>'); httpRequest.send('id='+id+'&category=spam'); // Start request setLoadIndicator(id); }
Um dann die id zu holen greift man auf die üblichen PHP-Mittel zurück:
switch ($event) { case 'external_plugin' : switch ($eventData) { case 'learncomment': if (!serendipity_checkPermission('adminComments')) { break; } $category = $_REQUEST ['category']; $id = $_REQUEST ['id']; ....
Falls etwas dem Javascript zurückgegeben wird, kann das mit einem echo ausgegeben werden und in setMessage() gefangen werden:
function setMessage() { if (httpRequest.readyState == 4 && httpRequest.status == 200) { var response = httpRequest.responseText; } }
Natürlich gibt es noch viel mehr zu zeigen und zu erklären. Genauso kann es Dinge geben, die hier falsch oder nicht optimal gezeigt wurden oder Dinge, die einfach fehlen. Dann freue ich mich über einen Hinweis.
Generell gilt der Ratschlag von s9y.org: Wenn etwas unklar ist, einfach mal in ein anderes Plugin schauen und gucken, wie es dort gemacht wurde.
Woah.. Franz Steiniger hat mich gerade auf diese Seite aufmerksam gemacht. Alle Achtung! Sollte in einem S9Y Wiki verlinkt werden..
Danke. Als Autor selbst weiß man ja nie, ob solche Erklärungen wirklich verständlich sind. Von mir aus könnte man die Reihe auch komplett in das Wiki packen.
Nur ein Blog am : Serendipity Plugins selbst programmieren
Vorschau anzeigen
onli blogging am : Eigene Seiten in Serendipitys Adminbereich
Vorschau anzeigen
onli blogging am : Eigene Pluginseiten in Serendipity
Vorschau anzeigen
onli blogging am : 15 Jahre Serendipity als Entwickler - ein Rückblick und ein Ausblick
Vorschau anzeigen