28 Module entwickeln
Module erlauben die Präsentation von Daten an bestimmten Layoutpositionen in Front- oder Backend. Ihre Entwicklung ist einen Hauch komplexer als die von Plugins, da sie MVC-ähnlich die Businesslogik von der Darstellung trennen.
Nach Plugins sind Module der nächste Schritt beim Kennenlernen des Joomla!-Frameworks, da sie das CMS nicht nur um unsichtbare Helfer im Hintergrund erweitern, sondern mit dem Benutzer, egal ob Websitebesucher oder Backend-Administrator, in Interaktion treten. Während Plugins lediglich mehr oder weniger komplexe Konfigurationsformulare bereitstellen, hängen Sie sich mit der Webseitenausgabe eines Moduls direkt in die Templatemechanik. Dabei sind also nicht nur Ihre PHP-Kenntnisse gefragt, sondern auch die konforme Anwendung von HTML und CSS. Wie bei allen Programmierthemen um Joomla! gilt auch hier: Wissen Sie irgendwann nicht weiter, werfen Sie einen Blick in die Dateien des Joomla!-Cores und thematisch ähnlicher Erweiterungen, um zu erfahren, wie die Entwickler einer bestimmten Herausforderung gegenübertraten.
Das in diesem Kapitel vorgestellte Modul entwickeln Sie, ähnlich wie das Plugin, in zwei Phasen. Zunächst erzeugen Sie das Grundgerüst mit einer exemplarischen Datenbankabfrage und HTML-Ausgabe, die Ihnen für spätere Modulprojekte als Basis dienen. Im zweiten Schritt wird das Modul um Features erweitert, die etwas spezieller auf die vorliegende Beispielanwendung eingehen: Anzeige einer Liste der zuletzt bearbeiteten Beiträge im Kontrollzentrum von Joomla!.
Hinweis: Dieses Kapitel geht davon aus, dass Sie sich bereits mit der Plugin-Entwicklung der vorangegangenen Seiten beschäftigt haben, und überspringt die explizite Erwähnung von Standardkonventionen, z. B. PHP-Blöcke nicht per Shorthand <? zu beginnen oder PHP-Dateien niemals mit einem ?>-Tag zu beenden.
Die für ein Modul erforderlichen Dateien lassen bereits erste Anzeichen eines Model-View-Controller-Musters durchscheinen. Falls Sie mit diesem Software-Architekturmuster noch nicht vertraut sind, blättern Sie für einen Überblick zurück zu Abschnitt 25.2.3, »Model-View-Controller-Architekturmuster verstehen«. Kurz gesagt, es geht darum, die Eingabe (Datenbank) sauber von der Ausgabe (HTML) zu trennen und durch eine übergeordnete Schicht zu steuern. Abseits der index.html-Verzeichnisschutzdateien in allen Modulunterverzeichnissen besteht das Projekt aus diesen Dateien:
-
XML-Manifest
Das XML-Manifest unterscheidet sich nur unwesentlich von seinem Plugin-Pendant. Auch diese Variante trägt den Namen der Erweiterung (hier: mod_backendmodul.xml); die Eckdaten und die Verzeichnis- und Einstellungskonfiguration sind identisch, sie markieren aber zusätzlich den Einsatz des Moduls in Front- oder Backend. -
PHP-Steuerdatei
Nach dem XML-Manifest ist der erste Anlaufpunkt von Joomla! die Datei mod_backendmodul.php. Sie agiert wie ein Controller, da sie die Programmteile aufruft, die die Daten aus der Datenbank abfragen und in ein HTML-Template packen. -
PHP-Datenbankabfrage
Die PHP-Businesslogik befindet sich aus Übersichtsgründen nicht in der Steuerdatei, sondern ausgelagert in einer separaten Datei. Da der PHP-Code für ein Modul in der Regel übersichtlich ist, genügt eine Helferdatei mit einer Helferklasse, schlicht helper.php genannt. -
PHP/HTML-Ausgabe
Für die HTML-Ausgabe der über die Helferklassen angezogenen Daten ist eine weitere Datei, default.php, vorgesehen, die im MVC-Muster dem View entspricht, also der Ansicht auf die Daten.
Das ist bereits ein erster Ansatz des MVC-Konzepts, da die helper.php-Datei für die Bereitstellung der Daten sorgt, die Datei mod_backendmodul.php als Controller fungiert und die Daten an den View weitergibt, der als default.php im /tmpl/-Verzeichnis liegt.
28.1 Einfaches Modul erzeugen
Die Dateien für die erste Version des Moduls erzeugen Sie direkt im Installationsverzeichnis Ihrer Joomla!-Entwicklungsumgebung, um sie später für die Einrichtung auf einem Live-System zu einem Erweiterungspaket zusammenzuschnüren. Alternativ laden Sie das fertige Paket über https://joomla-handbuch.com/downloads/handbuch herunter (Modul mod_backendmodul V0.1.0), installieren es sofort und nutzen die Erklärungen in diesem Abschnitt zum Studium der enthaltenen Dateien.
Ziel: Sie erzeugen ein Modul, das die zuletzt bearbeiteten Beiträge listet, mit Beitragstitel, Bearbeitungsdatum, ID des Autors und Markierungen, ob der Beitrag veröffentlicht oder versteckt ist und ob er zur Gruppe der Standardeinträge gehört, die man im Falle eines Blog-Layouts auf der Homepage einblendet. Zur besseren Orientierung, um welchen Beitrag es sich handelt, erscheint ein Tooltip mit dem Beitragstext, sobald der Administrator mit der Maus über den Beitragstitel fährt.
Vorgehen: Sie legen nacheinander vier Dateien an, das XML-Manifest mit den Eckdaten, den einfach gestrickten Controller, das Model mit der Datenbankabfrage und den View mit der HTML-Ausgabe. Besondere Aufmerksamkeit verdienen die SQL-Abfrage und Datenaufbereitung im Model und die Beitragsschleife in der Ausgabe des Views.
Öffnen Sie nun Ihren Dateimanager, wechseln Sie in Ihrer Joomla!-Installation ins Verzeichnis /administrator/modules/, und erzeugen Sie ein neues Verzeichnis /mod_backendmodul/. Hierin erstellen Sie alle auf den folgenden Seiten vorgestellten Dateien.
Hinweis: Die Verzeichnisschutzdatei »index.html« ist obligatorisch
Beachten Sie, dass auch bei der Modulprogrammierung gilt: In jedes Verzeichnis gehört aus Sicherheitsgründen eine index.html-Datei die lediglich aus den Tags <html><body></body></html> besteht, um einer falsch konfigurierten Serververzeichnis-Darstellungskonfiguration entgegenzuwirken.
28.1.1 XML-Manifest – »mod_backendmodul.xml«
Falls Sie sich bereits durch die Plugin-Programmierung arbeiteten, sind Ihnen Format und Inhalt des XML-Manifests (hier: mod_backendmodul.php) bekannt.
<?xml version="1.0" encoding="utf-8"?>
<extension version="3.0" type="module" client="administrator"
method="upgrade">
<name>Backendmodul</name>
<author>Vorname Nachname</author>
<creationDate>June 2015</creationDate>
<copyright>Copyright (C) 2015 Vorname Nachnname. All rights reserved.
</copyright>
<license>http://www.gnu.org/licenses/gpl-3.0.html</license>
<authorEmail>vorname.nachname@IhrDomainName.de</authorEmail>
<authorUrl>https://joomla-handbuch.com</authorUrl>
<version>0.1.0</version>
<description>Beschreibung</description>
<files>
<filename module="mod_backendmodul">mod_backendmodul.php</filename>
<filename>helper.php</filename>
<filename>index.html</filename>
<folder>tmpl</folder>
</files>
</extension>
Die Unterschiede zum Plugin-XML-Manifest sind marginal. Das <extension>-Tag verweist im type-Attribut auf den Typ module und gibt an, dass das Modul im Backend eingesetzt wird (client="administrator"). In Joomla! erreicht man das Modul dann über Erweiterungen • Module • Seitenleiste Filter: Administrator • Button Neu. Der <files>-Block listet schließlich alle Moduldateien inklusive index.html. Zur besseren Organisation befindet sich die HTML-Ausgabe im Unterverzeichnis /tmpl/, die Integration ist per <folder>-Tag abgebildet.
28.1.2 Steuerdatei – »mod_backendmodul.php«
Die verhältnismäßig kleine Controllerdatei dient als Schnittstelle zwischen Datenbankabfrage und HTML-Ausgabe des Modulinhalts.
<?php
defined('_JEXEC') or die;
require_once __DIR__ . '/helper.php';
$list = mod_backendmodulHelper::getList($params);
require JModuleHelper::getLayoutPath('mod_backendmodul', $params-> get('layout', 'default'));
Nach dem Notausgang, falls der Aufruf dieser PHP-Datei direkt, also außerhalb von Joomla!, erfolgt (defined('_JEXEC') or die;), inkludiert der Quelltext eine PHP-Datei, die eine ausgelagerte Hilfeklasse enthalten wird. Die Ergänzung einer helper.php-Datei ist die erste Maßnahme bei zunehmender Programmkomplexität. Sie enthält Applikationscode, Businesslogik, die in der Steuerdatei mod_backendmodul.php nichts zu suchen hat. Die magische PHP-Konstante __DIR__ verweist übrigens immer auf das Verzeichnis, in dem die Datei des aktuell ausgeführten Codes liegt, auch wenn es sich um ein Include handelt, das aus einer Datei in einem völlig anderen Pfad eingebunden wird. Demnach erwartet PHP die helper.php-Datei in dem Ordner, in dem auch mod_backendmodul.php liegt.
$list = mod_backendmodulHelper::getList($params);
Die nächste Anweisung greift auf eine Methode der Klasse zu, die Sie gleich in die helper.php-Datei einfügen. getList() nimmt als Argument die Modulparameter auf und gibt als Ergebnis eine aus der Datenbank zusammengestellte Liste in $list zurück. In diesem Beispiel sind das nach ihrem letzten Bearbeitungsdatum sortierte Beiträge.
require JModuleHelper::getLayoutPath('mod_backendmodul', $params-> get('layout', 'default'));
Die letzte Zeile konfiguriert die Ausgabe-PHP/HTML-Datei für die Darstellung der Inhalte. default ist dabei der Name des Views und entspricht später auch dem Dateinamen, der den Ausgabecode enthält.
28.1.3 Ausgelagerte Helferklasse – »helper.php«
Weiter geht es zur helper.php-Datei, die entsprechend ihrem Namen Helferklassen mit Helfermethoden aller Art aufnimmt und im aktuellen Beispiel wie das Model des MVC-Musters agiert und die Datenabfrage und -aufbereitung aus der Datenbank übernimmt.
<?php
defined('_JEXEC') or die;
abstract class mod_backendmodulHelper
{
public static function getList(&$params)
{
$db = JFactory::getDbo();
$query = $db->getQuery(true);
$query->select($db->quoteName(array('id', 'title', 'introtext',
'fulltext', 'state', 'modified', 'modified_by', 'featured')));
$query->from($db->quoteName('#__content', 'content'));
$query->order('modified DESC');
$db->setQuery($query, 0, 10);
try
{
$results = $db->loadObjectList();
}
catch (RuntimeException $e)
{
JFactory::getApplication()->enqueueMessage($e->getMessage(), 'error');
return false;
}
foreach ($results as $row => $fields)
{
$results[$row] = new stdClass;
$results[$row]->id = $fields->id;
$results[$row]->title = $fields->title;
$results[$row]->introtext = strip_tags($fields->introtext, '<br><br/>
<strong><em><b><i>');
$results[$row]->fulltext = strip_tags($fields->fulltext, '<br><br/>
<strong><em><b><i>');
if (!($results[$row]->introtext))
{
$results[$row]->summary = strlen($results[$row]->introtext) > 300 ?
substr($results[$row]->introtext, 0, 300) . " […]" : $results
[$row]->introtext;
}
else if (!($results[$row]->fulltext))
{
$results[$row]->summary = strlen($results[$row]->fulltext) > 300 ?
substr($results[$row]->fulltext, 0, 300) . " […]" : $results[$row]->
fulltext;
}
else
{
$results[$row]->summary = '[No Content]';
}
$results[$row]->modified_by = $fields->modified_by;
$results[$row]->state = $fields->state;
$results[$row]->modified = $fields->modified;
$results[$row]->featured = $fields->featured;
}
return $results;
}
}
Die einzige Methode getList() in der Klasse der Helferdatei hat eine einfache Aufgabe: Sie holt sich die zehn zuletzt bearbeiteten Beiträge aus der Beitragstabelle der Joomla!-Datenbank und gibt die etwas aufbereiteten Werte wie Beitragstitel, Einleitungstext, Bearbeitungsdatum, Live-Status etc. zurück an den Controller mod_backendmodul.php, der sie dann dem View für die Darstellung bereitstellt.
Der Code im Detail:
abstract class mod_backendmodulHelper
{
public static function getList(&$params)
{
Definition der abstrakten Klasse mod_backendmodulHelper, die über die mod_backendmodul.php aufgerufen wird. Abstrakt hat zur Folge, dass die Methoden dieser Klasse so, wie sie sind, verwendet werden können, ohne dass vorher ein Objekt aus ihr initialisiert wird. Achten Sie auf die exakte Schreibweise des Klassennamens.
Gleich darauf folgt die Definition der nach außen offenen (public) Methode getList(), die die Modulkonfiguration als Argumente aufnimmt.
$db = JFactory::getDbo();
$query = $db->getQuery(true);
Dies ist der Standardweg, die Verbindung zur Joomla!-Datenbank zu öffnen und eine Datenbankabfrage zu initialisieren. $query enthält ab sofort ein Objekt, dessen Methoden und Eigenschaften alle Abfrageeinstellungen enthalten und die Abfrage später ausführen.
$query->select($db->quoteName(array('id', 'title', 'introtext', 'fulltext',
'state', 'modified', 'modified_by', 'featured')));
$query->from($db->quoteName('#__content', 'content'));
$query->order('modified DESC');
Diese drei Zeilen erlauben eine Datenbank- und damit SQL-unabhängige Datenabfrage und entsprechen im Prinzip einer zerpflückten SQL-Abfrage à la SELECT felder FROM tabelle ORDER BY sortierreihenfolge;. Sprich: Rufe die Werte der Felder id (eindeutige Id), title (Beitragstitel), introtext (Einleitungstext), fulltext (Text nach der Weiterlesen-Markierung), state (Veröffentlicht oder Versteckt), modified (Bearbeitungsdatum), modified_by (von wem zuletzt bearbeitet?) und featured (ist das ein Standardbeitrag für die Homepage?) aus der Tabelle #__content ab. (#_ ersetzt Joomla! automatisch um das während der Installation zufällig vergebene Tabellenpräfix.) Und sortiere die Ergebnisliste absteigend nach dem modified-Datum, also zuletzt bearbeitete Beiträge an oberste Stelle.
Tipp: Bei der Erzeugung solcher Abfragen werfen Sie einfach mit phpMyAdmin einen Blick in die Datenbank von Joomla!, um die genauen Tabellen- und Feldnamen zu identifizieren.
quoteName() ist eine Standardsicherheitsmethode, um Namenskonflikte und SQL-Injektionen zu verhindern – an dieser Stelle (noch) nicht relevant, aber es ist eine gute Idee, sich die Benutzung solcher Mechanismen früh anzugewöhnen.
$db->setQuery($query, 0, 10);
Setzt die Abfrage ins Datenbankobjekt und beschränkt die zurückzugebenden Ergebnisse auf 10, angefangen beim ersten Datensatz (0). Die letzten beiden Parameter entsprechen somit dem SQL-Abfragezusatz LIMIT 0,10.
try
{
$results = $db->loadObjectList();
}
catch (RuntimeException $e)
{
JFactory::getApplication()->enqueueMessage($e->getMessage(), 'error');
return false;
}
Jetzt startet mit loadObjectList() endlich die Ausführung der Abfrage. Nicht aber, ohne diese sensible Kernfunktion in einen try-Block zu wickeln. Falls es Probleme mit der Datenbank gibt, verzweigt PHP dann in den catch-Block. Dieser gibt den in der Fehlerberichterstattung ($e) zurückgegebenen, für Menschen lesbaren Fehlertext (getMessage()) per rot umrandeter Fehlermeldung aus (siehe Abbildung 28.1). return false; stellt sicher, dass die getList()-Methode sofort endet, denn ohne Datenbank-Abfrageergebnis ist hier nichts mehr zu retten.
Wurde loadObjectList() dagegen erfolgreich ausgeführt, landen die Ergebnisse der Abfrage im Array $results, das pro Arrayelement ein Tabelleneintrags-Objekt enthält, also jeweils einen Beitrag mit all seinen Detaildaten.
Weiter geht also nur, wenn $results Daten enthält. Diese ließen sich nun sofort an den Controller bzw. View übergeben, aber hier, im Model, hat man eine gute Gelegenheit, die Rohdaten etwas aufzubereiten.
foreach ($results as $row => $fields)
{
[…]
}
Zunächst öffnet foreach eine Iteration über alle zurückgegebenen Beiträge und splittet das Array in die Durchnummerierung $row und die Tabelleneintrags-Objekte $fields auf.
Der Schleifeninhalt kopiert die einzelnen Daten aus dem $fields-Objekt in ein neues Objekt $results[$row], um sie gegebenenfalls nachzubearbeiten. Für den introtext und den fulltext geschieht das auch, denn die Darstellung des Beitragstexts in einem Tooltip stört die Anwesenheit von HTML-Tags. Über strip_tags() werden diese entfernt, mit Ausnahme von Zeilenumbrüchen und Fett- und Kursivmarkierungen. Es folgt eine Fallunterscheidung, welcher Text denn nun für den Tooltip eingesetzt wird, je nachdem, ob introtext oder fulltext Inhalte enthalten. $results[$row]->summary enthält schließlich den finalen Text.
Am Ende der Funktion stehen sämtliche Beitragsdaten im $results-Objekt, das der Steuerdatei mod_backendmodul.php ins Objekt $list übertragen wird, um gleich im HTML-Template ausgelesen zu werden.
28.1.4 HTML-Template – »/tmpl/default.php«
Das HTML-Template ist einfach zu verstehen, da es im Prinzip nur aus einer großen Schleife zur Ausgabe der Beitragsdetails besteht.
<?php
defined('_JEXEC') or die;
JHtml::_('behavior.tooltip');
?>
<div class="recentlyedited <?php echo $moduleclass_sfx; ?>">
<div class="row-striped">
<?php foreach ($list as $item) : ?>
<div class="row-fluid">
<?php
$stateClass = ($item->state == '1') ? ' icon-publish' : ' icon-
unpublish';
?>
<div class="span1 <?php echo $stateClass; ?>">
</div>
<?php
$featuredClass = ($item->featured == '1') ? ' icon-featured' :
' icon-unfeatured';
?>
<div class="span6 <?php echo $featuredClass; ?>">
<strong class="row-title">
<?php echo JHTML::tooltip($item->summary, '', '', $item->title); ?>
</strong>
</div>
<div class="span3">
<?php echo JHtml::_('date', $item->modified,
JText::_('DATE_FORMAT_LC2')); ?>
</div>
<div class="span2">
<?php echo $item->modified_by; ?>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
Unmittelbar nach dem PHP-Notausstieg fällt eine Joomla!-spezifische JHtml-Anweisung ins Auge, die einem Befehl von der Plugin-Programmierung ähnelt. Während dort die SqueezeBox-Popup-Technologie per JHtml::_('behavior.modal') integriert wurde, sorgt behavior.tooltip an dieser Stelle für die Bereitstellung der Tooltip-Anzeige. Die eigentliche Ausgabe des Tooltips erfolgt weiter unten per JHtml::tooltip($item->summary, '', '', $item->titleHtml);.
Es folgen einige Wrapper-Klassen und schließlich die große Ausgabeschleife, die das vom Controller bereitgestellte Objekt $list Beitrag für Beitrag in $item durchgeht, um die Details per $item->Eigenschaft auszugeben. Bemerkenswert sind an dieser Stelle die kleinen Fallunterscheidungen, welche CSS-Klasse und damit welches Icon für den Live- bzw. Homepage-Status eines Beitrags erscheint. Die Klassennamen in den Variablen $stateClass und $featuredClass sind Standardelemente aus den Stylesheets von Joomla!.
28.1.5 Modul installieren und testen
Nach dem Anlegen aller Moduldateien unterrichten Sie nun Joomla! von der Existenz des Moduls. Über Erweiterungen • Verwalten • Seitenleiste Überprüfen • Button Überprüfen listet Joomla! alle neuen Erweiterungen, die noch nicht eingerichtet sind (siehe Abbildung 28.2). Markieren Sie das Backend-Modul mit einem Häkchen, und klicken Sie auf den Button Installieren.
Prüfen Sie das Modul jetzt in der Administrationsoberfläche von Joomla!.
-
Zum Testen wechseln Sie zu Erweiterungen • Module • Seitenleiste Filter: Administrator – dies ist die Liste aller Module, aus denen sich das Administrations-Backend zusammensetzt (siehe Abbildung 28.3).
-
Klicken Sie auf den Modultitel, und stellen Sie im Konfigurationsformular des Moduls die Dropdown-Liste Status auf Veröffentlicht. Wählen Sie aus der Position-Liste den Eintrag Kontrollzentrum [cpanel], und speichern & Schliessen Sie die Konfiguration.
-
Wechseln Sie jetzt zu System • Kontrollzentrum, erscheint an oberster Stelle das neue Modul mit einer langen Liste von absteigend nach Bearbeitungsdatum sortierten Beiträgen (siehe Abbildung 28.4).