x

Einloggen

Hast du noch keinen Account? Jetzt registrieren!

Plugins/Module für CMSMadeSimple selbst entwickeln - Tutorial

Nutzer von Content-Management-Systemen sind oft auf die im Netz angebotenen Plugins/Module angewiesen, weil sie selbst nicht wissen wie sie eine eigene Erweiterung programmieren können. Dabei ist das - wie im Falle CMSMadeSimple (CMSMS) - oft gar nicht so schwer!
Eigene Plugins/Module für CMSMS entwickeln
In diesem Tutorial erkläre ich wie man ein einfaches Modul mit Einbindung in den Administrationsbereich entwickelt, und gebe Tipps worauf man achten sollte, was einem die Entwicklung erleichtert und wo man - wenn man mag - sein fertiges Modul einreichen muss, damit es in der offizielle Erweiterungenliste von CMSMS gelangt.

Das machen wir

Zu ersten Demonstrationszwecken suchen wir uns natürlich nichts zu kompliziertes raus.
Wie wäre es mit einem einfachen Zitatemodul das folgende Funktionen besitzt:
  • Über Adminbereich Zitate (+ Autor) hinzufügen und löschen (Editierfunktion ausgelassen)
  • Zitate auf Website über Tag (z.B. {SimpleQuota} ) anzeigen lassen
  • Anzahl der jeweils anzuzeigenen Zitate über Tagparameter ( {SimpleQuota quotes="5"} ) bestimmen (andernfalls Standardeinstellung aus Adminbereich nutzen)
Das alles setzt natürlich ein bereits installiertes CMSMS vorraus. Ich arbeite mit Version 1.10.3.

Das Tutorial sollte aber bis ein paar Versionen drunter und drüber auf jeden Fall kompatibel sein.
Sollte es bei einem mit seiner CMSMS Version nicht funktionieren, bitte ein Kommentar schreiben.


Einführung

Das eine Funktion als "freies" Modul vorliegt hat natürlich viele Vorteile, vorallem jenen, dass diese, da sie ja nicht direkt in den Code des CMS eingearbeitet wurde, für viele verschiedene Projekte genutzt werden kann, ohne, dass Dateien jedes mal aufs Neue editiert werden müssen. Außerdem lässt sich dieser Code besser warten und bei Updates auf CMS-Seite wird das Plugin nicht beschädigt/gelöscht.
Durch eine Modulverwaltung können so die Module auch an einem gesammelten Platz verwaltet werden.
 
Module haben natürlich auch einen festen Platz in der Ordnerstruktur von CMSMS. Sie befinden siche alle in "/modules".
Jede Erweiterung hat einene eigenen Ordner in dem sich dann auch alle benötigten Dateien befinden. 
Modulname = Ordnername (!)

 Wenn wir uns nun zum Beispiel den Ordner des Suchplugins (Search) ansehen finden wir verschiedene Dateien vor:
  •  method.install.php, method.uninstall.php, method.upgrade.php diese sind, wie die Namen vermuten lassen für die Installation, die Deinstallation oder ein Update des Plugins zuständig. Die Installations-Datei wird ausgeführt, wenn das Modul über die Modulverwaltung installiert wird. Dort werden später dann auch die MySQL-Tabellen, Modul-Rechte, usw. angelegt.
    In der Deinstallations-Datei werden diese natürlich wieder gelöscht.

  •  function.admin_statistics_tab.php hier lässt der Name die Funktion ebenfalls vermuten. Die Datei gibt den Inhalt des Statistiken-Tabs an.
  • action.default.php der Inhalt dieser Datei wird standardmäßig bei der Einbindung des Moduls in die Website (Frontend), wenn keine andere Aktion angegeben wurde ausgeführt. Deswegen auch der Name action.default also "Standard Aktion".

  • action.defaultadmin.php wie die action.default.php nur eben für den Administrationsbereich. Hier werden auch die Tabs festgelegt.

  • Search.module.php - In dieser Datei werden Basisdaten konfiguriert und angegeben. Mit unter: Versionsnummer, Anzeigename des Plugins, welche CMS Versionen werden unterstützt, Autor, welche Parameter werden zugelassen, und und und
Die restlichen Dateien sind für uns erstmal uninteressant, aber wir haben noch 2 Ordner, die wir 100%-ig benötigen werden:
  • /lang - In diesem Ordner speichern wir alle angezeigten Texte in einer Sprachdatei ab. Dies hat den Vorteil, das eine Erweiterung unabhängig von der Sprache entwickelt und genutzt werden kann. Die Sprachdatei trägt den "Sprachennamen", für Deutsch etwa "de_DE.php" und für US-Englisch "en_US.php". Die Texte werden in einem Array gespeichert und können über $this->Lang('textname') aufgerufen werden. Die Sprachdatei der Sprache des Entwicklers wird dabei direkt in das Verzeichnis gespeichert, die anderen in das Unterverzeichnis "/ext".
  • /templates - Hier werden die Anzeigetemplates (*.tpl) abgelegt. Das hat den Vorteil, dass das Layout unabhängig vom Programmiercode vorliegt und geändert werden kann. 
    CMSMS nutz die Template-Engine "smarty". 

Fangen wir an

Nachdem das also alles geklärt hätten können wir loslegen. 
Wir erstellen einen neuen Ordner im Modul-Verzeichnis und benennen diesen nach unserem Modul, also "SimpleQuota".
In diesem erstellen wir die 2 weiteren Ordner "lang" und "templates".
Als aller erstes nehmen wir uns der "Konfigurationsdatei" unseres Moduls vor, der SimpleQuota.module.php

<?php
#-------------------------------------------------------------------------
# Module: SimpleQuota# Version: 0.1, DEIN NAME (WEBSITE || EMAIL)
#-------------------------------------------------------------------------
# CMS - CMS Made Simple is (c) 2011 by Ted Kulp (wishy@cmsmadesimple.org)
# This project's homepage is: http://www.cmsmadesimple.org
#-------------------------------------------------------------------------

class SimpleQuota extends CMSModule{

    function GetName()
    {
        return 'SimpleQuota';
    }

    function GetFriendlyName()
    {
        return $this->Lang('friendlyname');
    }
        
        function SetParameters()
        {
        $this->RegisterModulePlugin();
        $this->RestrictUnknownParams();
        $this->CreateParameter('quotes', $this->Lang('param_quotes_see_settings'), $this->Lang('param_quotes_desc'));
        $this->SetParameterType('quotes', CLEAN_INT);
    }

    function GetVersion()
    {
        return '0.1';
    }

    function GetHelp()
    {
        return $this->Lang('help');
    }

    function GetAuthor()
    {
        return 'DEIN NAME';
    }

    function GetAuthorEmail()
    {
        return 'DEINE MAILADRESSE';
    }

    function GetChangeLog()    {
        return $this->Lang('changelog');
    }

    function IsPluginModule()
    {
        return true;
    }

    function HasAdmin()
    {
        return true;
    }

    function GetAdminSection()
    {
        return 'extensions';
    }

    function GetAdminDescription()
    {
        return $this->Lang('admindescription');
    }

    function VisibleToAdminUser()
    {
                return $this->CheckPermisson('SimpleQuota Access');
    }

        function MinimumCMSVersion()
    {
        return "1.9";
    }

    function MaximumCMSVersion()
    {
        return "1.10.9";
    }

    function InstallPostMessage()
    {
        return $this->Lang('postinstall');
    }

    function UninstallPostMessage()
    {
        return $this->Lang('postuninstall');
    }

    function UninstallPreMessage()
    {
        return $this->Lang('really_uninstall');
    }    

}

?>

(nopaste)

Wir sehen, dass jedes Modul seine eigene Klasse - die den gleichen Namen wie selbiges haben muss - besitzt.

Nun erkläre ich noch die Funktion der einzelnen Funktionen:
  • GetName() - Name des Moduls für interne Verarbeitung; Muss dem Klassennamen gleich sein
  • GetFriendlyName() - Anzeigename des Moduls im Adminbereich; Wird hier aus der Sprachdatei ausgelesen
  • SetParameters() -
    $this->RegisterModulePlugin(); sorgt dafür, dass das Modul anstatt über "{cms_module module="SimpleQuota"}" auch über "{SimpleQuota}" erreichbar ist.
    $this->RestrictUnknownParams(); - Hiermit werden nicht in dieser Funktion definierte Parameter gelöscht (Sicherheitstechnisch relevant)
    $this->CreateParameter('quotes', '1', $this->Lang('param_quotes_desc')); - Parameter "quotes" wird definiert. Hier habe ich den Standardwert absichtlich auf etwas "ungültiges", nämlich einem Text (aus der Sprachdatei) gesetzt, da dieser Standardwert so oder so nicht zum Einsatz kommt, da er durch unsere Einstellungen überschrieben wird. Die Beschreibung (später im Adminpanel einsehbar) befindet sich ebenfalls in der Sprachdatei.
    $this->SetParameterType('quotes', CLEAN_INT); - Definiert den Typ des Parameterwertes, in unserem Falle muss der Wert einem Integer entsprechen. Weitere Typen wären "CLEAN_STRING" oder "CLEAN_FLOAT".
  • GetVersion() - Gibt Modulversion an
  • GetHelp() - Hilfe für Adminbereich; Ebenfalls in der Sprachdatei abgelegt
  • GetAuthor()  - Autorname
  • GetAuthorEmail() - Emailadresse des Autors
  • GetChangeLog() - Falls Modul geupdatet werden sollte können hier die Änderungen eingetragen werden; hier ausgelegt in die Sprachdatei
  • IsPluginModule()  - Gibt an ob das Modul in die Website über Tag eingefügt werden kann
  • HasAdmin() - Gibt an, ob das Modul über einen Administrationsbereich verfügt
  • GetAdminSection() - Gibt an in welchem Teil des Administationsbereiches das Modul erscheinen soll. Folgende Werte sind möglich:
    main - Im Hauptmenü-Tab
    content - Im Inhalts-Menü
    layout - Im Layout-Menü
    usergroups - Im Benutzerverwaltungs-Menü
    extensions - Im Erweiterungen-Menü
    siteadmin - Im Webseiten-Administations-Menü
    myprefs - Im "Meine Einstellungen"-Menü
    ecomm - Im E-Commerce-Menü (nur, wenn E-Commerce Suite installiert ist)
  •  GetAdminDescription() - Eine Beschreibung die auf der Modulseite im Adminbereich angezeigt wird
  • VisibleToAdminUser() - Hier werden die benötigten Rechte abgefragt (In unserem Fall das (später) extra angelegte "SimpleQuota Access")
  • MinimumCMSVersion() - Mindest CMS-Version (Mit Version 1.9 hat es eine größere Überarbeitung bezüglich der Module gegeben, daher gehen wir hier auf Nummer sicher und wählen diese)
  • MaximumCMSVersion() - Maximale CMS-Version (Mit Version 1.11 wird sich wohl auch wieder einiges ändern, daher werden wir erstmal nur bis Version 1.10.9 untersützen)
  • InstallPostMessage() - Nachricht, die bei Installation erscheint
  • UninstallPostMessage() - Nachricht, die nach Deinstallation erscheinen soll
  • UninstallPreMessage() - Nachricht, die in einem Ja-Nein-Dialogfenster erscheinen soll, wenn das Modul deinstalliert werden soll; "Möchten Sie das Modul wirklich löschen?"; Wenn kein Dialogfenster gewünscht ist: "return false;"

Die Installationsdatei

Da wir mit der Installation des Modules auch eine Datenbank-Tabelle und ein neues Userrecht anlegen müssen, werden wir uns nun um unsere Installationsdatei - der method.install.php - kümmern:
<?php
#-------------------------------------------------------------------------
# Module: SimpleQuota
# Version: 0.1, DEIN NAME (WEBSITE || EMAIL)
#-------------------------------------------------------------------------
# CMS - CMS Made Simple is (c) 2011 by Ted Kulp (wishy@cmsmadesimple.org)
# This project's homepage is: http://www.cmsmadesimple.org
#-------------------------------------------------------------------------
if (!isset($gCms)) exit;

$db = $gCms->GetDb();

$taboptarray = array('mysql' => 'ENGINE=MyISAM');

$dict = NewDataDictionary($db);

$flds1 = "
    quote_id I NOTNULL AUTOINCREMENT PRIMARY,
    quote X,
    author C(35)";

$flds2 = "standardQuotesNumber I(1)";

$sqlarray1 = $dict->CreateTableSQL( cms_db_prefix()."module_simplequota",
                   $flds1,
                    $taboptarray);
$dict->ExecuteSQLArray($sqlarray1);

$sqlarray2 = $dict->CreateTableSQL( cms_db_prefix()."module_simplequota_settings",
                   $flds2,
                    $taboptarray);
$dict->ExecuteSQLArray($sqlarray2);

$settings = 'INSERT INTO `'.cms_db_prefix().'module_simplequota_settings` SET `standardQuotesNumber` = 5';
    $insert = $db->Execute($settings);

$this->CreatePermission('SimpleQuota Access');

$this->Audit(0, $this->Lang('friendlyname'), $this->Lang('installed', $this->GetVersion()));

?>
(nopaste)

Das "if (!isset($gCms)) exit;" sorgt dafür, dass diese Datei auch wirklich nur vom CMS ausgeführt wird.
Mit "$db = $gCms->GetDb();" stellen wir eine Verbindung zur Datenbank her.

Die 2 folgenden Zeilen sind ADOdb spezifisch und interessieren uns eher weniger. 
ADOdb ist eine Datenbank-Abstraktions-Bibliothek für PHP, die es erlaubt trotz verschiedener Datenbank-Typen einheitlich auf diese, in einem Script, zugreifen zu können. So ist es theoretisch möglich, dass das eine System auf MySQL, das andere auf ACCESS-Basis, und wieder ein anderes auf PostgreSQL läuft, und das Ganze trotzdem nur ein einziges mal programmiert werden muss!

Diesem Umstand haben wir aber auch eine etwas andere Syntax - was das Ansprechen von Datenbanken angeht - als zum Beispiel bei MySQL. So werden in der Variablen $flds die Spalten für die neue Tabelle gespeichert. 
Form: SPALTENNAME TYP (und evtl. weitere Angaben (nach $otheroptions suchen) wie zum Beispiel KEY für den Primärschlüssel)

Es gibt folgende Typen:
  • C für Buchstaben/Varchars; In Klammern kann die Länge angegeben werden
  • X für Texte
  • B für BLOBs (große binäre Objekte wie Bild- oder Audiodateien)
  • D für ein Datum/Date
  • T für einen Timestamp
  • L für logische Werte (Booleans)
  • I für Integer
  • N für andere nummerische Werte (Float, Double, ...) 
Über "$sqlarray = $dict->CreateTableSQL( cms_db_prefix()."module_simplequota(_settings)",   $flds1/2,    $taboptarray); $dict->ExecuteSQLArray($sqlarray);" erstellen wir dann letztendlich unsere Tabellen für die Zitate und die Einstellung(en), wobei "cms_db_prefix()" das bei der Installation für die Tabellen angegebene Prefix steht und das fettgedruckte der eigentliche Tabellenname ist. 

Das "$settings = 'INSERT INTO `'.cms_db_prefix().'module_simplequota_settings` SET `standardQuotesNumber` = 5'; $insert = $db->Execute($settings);" sorgt dafür, dass wir schon einen Standardwert in unseren Einstellungen (also in der Tabelle für die Einstellung) haben, auf den wir später zurückgreifen können.

Mit  "$this->CreatePermission('SimpleQuota Access');" erstellen wir das Recht für unser Modul. Dieses kann später vom Administrator an andere User vergeben werden.

"$this->Audit(0, $this->Lang('friendlyname'), $this->Lang('installed', $this->GetVersion()));" setzt eine Nachricht in die Logs des Adminbereiches. 
SimpleQuota wurde erfolgreich installiert!

Die Deinstallation
Sicherheitsabfrage
Als Entwickler - total überzeugt von seiner Erweiterung - braucht man diese überflüssige Funktion natürlich nicht!
Aber hin und wieder möchte ein User das Modul auf Belieben auch wieder deinstallieren können, hier kommt unsere method.uninstall.php ins Spiel. Sie löscht Tabellen und Nutzerrechte wieder.

<?php
#-------------------------------------------------------------------------
# Module: SimpleQuota
# Version: 0.1, DEIN NAME (WEBSITE || EMAIL)
#-------------------------------------------------------------------------
# CMS - CMS Made Simple is (c) 2011 by Ted Kulp (wishy@cmsmadesimple.org)
# This project's homepage is: http://www.cmsmadesimple.org
#-------------------------------------------------------------------------
if (!isset($gCms)) exit;

$db = $gCms->GetDb();

$taboptarray = array('mysql' => 'ENGINE=MyISAM');
$dict = NewDataDictionary($db);

$sqlarray1 = $dict->DropTableSQL(cms_db_prefix()."module_simplequota");
$dict->ExecuteSQLArray($sqlarray1);

$sqlarray2 = $dict->DropTableSQL(cms_db_prefix()."module_simplequota_settings");
$dict->ExecuteSQLArray($sqlarray2);

$this->RemovePermission('SimpleQuota Access');

$this->Audit(0, $this->Lang('friendlyname'), $this->Lang('uninstalled'));?>
(nopaste)

Mithilfe von "$sqlarray = $dict->DropTableSQL(cms_db_prefix()."module_simplequota"); $dict->ExecuteSQLArray($sqlarray);" wird unsere Tabelle wieder gelöscht.
"$this->RemovePermission('SimpleQuota Access');" löscht das Recht "SimpleQuota Access" wieder.
Außerdem schreiben wir wieder einen Eintrag ins Log.

Die Sprachdatei

Jetzt wollen wir uns noch an unsere Sprachdatei wagen. Diese werden wir im Laufe des Tutorials noch erweitern.
Bis jetzt hat sie folgenden Inhalt:
<?php
$lang['friendlyname'] = 'SimpleQuota';
$lang['admindescription'] = 'SimpleQuota ist ein einfaches Modul, dass die Ausgabe von vorher eingetragenen Zitaten auf der Website ermöglicht. Es diente ursprünglich als Demozweck eines Tutorials auf PC.DE (http://www.pc.de/Klener).';
$lang['help'] = 'Uns ist nicht mehr zu helfen.';
$lang['changelog'] = '-';
$lang['postinstall'] = 'SimpleQuota wurde erfolgreich installiert. Viel Spaß!';
$lang['installed'] = 'SimpleQuota Version %s wurde installiert.';
$lang['postuninstall'] = 'SimpleQuota wurde erfolgreich deinstalliert.';
$lang['uninstalled'] = 'SimpleQuota deinstalliert.';
$lang['really_uninstall'] = 'Möchten Sie SimpleQuota und alle eingetragenen Zitate wirklich löschen?';
 /* Params */
$lang['param_quotes_desc'] = 'Gibt an wie viele Zitate angezeigt werden sollen.';
?>
(nopaste)

Hierzu gibt es eigentlich nicht viel mehr zu sagen, als ich bereits gesagt habe. 
Die Texte werden in einem Array ($lang) gespeichert und sind über $this->Lang('NAME'); abrufbar.
Interessant ist noch "$lang['installed'] = 'SimpleQuota Version %s wurde installiert.';": %s wird später durch die Versionsnummer des Moduls ersetzt. Dies geschieht über den Aufruf $this->Lang('installed', GetVersion()); wobei GetVersion() eine beliebige Variable sein kann. Denkbar wäre auch das ersetzen durch einen Username o.Ä.

Endlich - Der Adminbereich

Jetzt möchten wir endlich zum sichtbaren Teil des Ganzen kommen: Dem Adminbereich. 
Wir möchten eine Übersichtsseite über die Zitate mit der Möglichkeit weitere Hinzuzufügen oder Zitate zu löschen. 
Ein einem weiteren Tab soll die Standardeinstellung der Anzahl der anzuzeigenden Zitate bearbeitet werden können.
Tabs von SimpleQuota
Für die Tab-Darstellung benötigt die Datei action.defaultadmin.php folgenden Inhalt:
<?php
if (!isset($gCms)) exit;

if (!$this->CheckPermission('SimpleQuota Access')) {
  return $this->DisplayErrorPage($id, $params, $returnid,$this->Lang('accessdenied'));
}

if (!empty($params['active_tab'])) {
    $tab = $params['active_tab'];
} else {
  $tab = '';
}

echo $this->StartTabHeaders();
    echo $this->SetTabHeader('quotes_overview',$this->Lang('tab_title_overview'), ('quotes_overview' == $tab)?true:false);
    echo $this->SetTabHeader('settings', $this->Lang('tab_title_settings'), ('settings' == $tab)?true:false);
echo $this->EndTabHeaders();

echo $this->StartTabContent();
    echo $this->StartTab('quotes_overview');
        include(dirname(__FILE__).'/function.admin_tab_quotes_overview.php');
    echo $this->EndTab();
    echo $this->StartTab('settings');
        include(dirname(__FILE__).'/function.admin_tab_settings.php');
    echo $this->EndTab();
echo $this->EndTabContent();

?>
(nopaste)
 
"if (!$this->CheckPermission('SimpleQuota Access'))" prüft ob die benötigten Berechtigungen vorhanden sind. Andernfalls wird eine Fehlerseite mit dem Text "accessdenied" ausgegeben.
Diesen und die Titel der Tabs habe ich der Sprachdatei hinzugefügt:
[...]
$lang['really_uninstall'] = [...]
$lang['accessdenied'] = 'Zugriff verweigert. Hast du die nötigen Nutzerrechte für SimpleQuota?';
/* Tabs */
$lang['tab_title_overview'] = 'Zitate-Übersicht';
$lang['tab_title_settings'] = 'Einstellungen'; 
/* Params */
[...]  
Außerdem prüfen wir ob ein Parameter mit dem Namen "active_tab" übergeben worden ist. Mit diesem Parameter ist es möglich einen anderen Anfangstab (also hier zum Beispiel den Einstellungstab) bei einem direkten Link auszuwählen.

Parameter (egal ob über GET oder POST) werden in CMSMS über das "$params"-Array abgerufen. Die normalen Wege, also $_GET und $_POST funktionieren nur bedingt, da CMSMS eigene Zeichenketten an die Parameter einfügt, die es über das eigene Parameter-Array automatisch wieder rausfiltert.

"echo $this->StartTabHeaders();" sorgt dafür, dass wir ab jetzt unsere Tab-Titel angeben können. Man muss sich einfach Vorstellen, dass hiermit ein Div und eine Liste aufgerufen wird (zum Beispiel ein div und eine Liste).
Dann werden über "echo $this->SetTabHeader()" die verschiedenen Tab-Titel aufgelistet (Listenpunkt "TITEL").
Mit "EndTabHeaders()" wird die Tab-Anweisung wieder geschlossen (Beispielsweise das schließen einer Liste und eines Divs).

Das selbe Spielchen erwartet uns zwischen  echo "$this->StartTabContent();" und echo "$this->EndTabContent();", nur eben mit dem Inhalt der Tabs.
"echo $this->StartTab('TAB-ID');"  und  "echo $this->EndTab();" umschließen den Inhalt des Tabs. In unserem Falle wird eine weitere Datei über include() eingebunden.

Die Zitate-Übersicht

Da wir in unserer action.defaultadmin.php als Inhalt unseres Zitate-Übersicht-Tabs die Datei function.admin_tab_quotes_overview.php (was für ein scheußlicher Name) angegeben haben möchten wir diese nun auch anlegen.
Diese Datei soll uns später eine Übersicht über die eingetragenen Zitate liefern. Außerdem möchten wir in dieser Datei das Erstellen und Löschen von Zitaten händeln.

Doch eins nach dem anderen: Kümmern wir uns erst einmal um die Ausgabe der einzelnen Zitate:

<?php
if (!isset($gCms)) exit;
$db = $gCms->GetDb();

$quotesRes = $db->Execute('SELECT * FROM `'.cms_db_prefix().'module_simplequota` ORDER BY `quote_id`');

if($quoteRes !== false) {
    $c = 0;
    while($row = $quotesRes->FetchRow()) {
        switch($c) {
                case 0: $trclass = 'row1'; $c++;
                     break;
                case 1: $trclass = 'row2'; $c--;
                     break;
                }
        $rows .= ''.$row['quote_id'].''.$row['quote'].''.$row['author'].''.$this->CreateLink($id, 'defaultadmin', $returnid, $this->Lang('delete'), array('delQuote' => 'true', 'quote_id' => $row['quote_id']), $this->Lang('really_delete')).'';
    }
}
/* Add Quote Form */
$form = $this->CreateFormStart($id, 'defaultadmin', $returnid);
$form .= $this->Lang('form_quote').' '.$this->CreateInputText($id, 'quote', '', '50');
$form .= $this->Lang('form_author').' '.$this->CreateInputText($id, 'author', '', '10');
$form .= $this->CreateInputSubmit($id, 'new_quote', $this->Lang('form_submit_quote'));
$form .= $this->CreateFormEnd();

$this->smarty->assign('createForm', $form);
$this->smarty->assign('SQ', $this);$this->smarty->assign('rows', $rows);
echo $this->ProcessTemplate('quotes_overview.tpl');
?>
(nopaste)

In Zeile 5 führen wir über $db->Execute eine "normale" Datenbankabfrage aus. Diese soll uns alle eingetragenen Zitate - genordnet nach unserer Zitate-ID "quote_id" - in einer Variablen speichern.
Nachdem wir mithilfe von if() überprüft haben, ob es auch nich zu Fehlern gekommen ist, deklarieren wir erst die Zählervariable "$c" (diese benötigen wir um später eine bessere Übersicht in unserer Liste zu erhalten) und durchlaufen dann jede Datenreihe mit "while()". Durch "$row = $quotesRes->FetchRow()" wird pro Durchlauf jeweils eine Datenreihe in das Array $row gespeichert, über welche dann innerhalb der Schleife zugegriffen werden kann.

Über "switch()" und unserer Zählervariablen definieren wir die jeweilige CSS-Klasse für die aktuelle Tabellenreihe ().

Die komplette Tabellenreihe(n) speichern wir in der Variablen "$rows". Durch das ".=" werden die weiteren Reihen der Variable angehängt, sodass die alten Einträge bei Mehrfachdurchläufen nicht überschrieben werden. 

Mit "$this->CreateLink()" (Zeile 16) erstellen wir einen internen Link, der wieder auf die gleiche Seite im Administrationsbereich verweist, allerdings noch 2 Parameter übergibt: "deleteQuote" und "quote_id", um das Zitat zu löschen. Die genaue Funktion lässt sich gut in den Docs nachlesen.

CMSMS bietet ebenfalls Methoden um Formulare zu bauen. Diese habe ich in den Zeilen 20-24 genutzt um ein Formular zu "öffnen", um 2 Textfelder und einen Submit-Button einzubinden sowie das Formular wieder zu schließen. 
Auch hier empfehle ich wärmstens die Docs!

In den Zeilen 26-28 weisen wir über "$this->smarty->assign('SMARTYTAG', 'VARIABLE//TEXT//...')" unserem Smarty-Template (dazu kommen wir gleich) verschiedene Variablen, die dann über das angegebene Smartytag verfügbar sind, zu.
"$this->smarty->assign('rows', $rows);" weist beispielsweise den Inhalt der Variablen $rows dem Tag {$rows} in Smarty zu. Dieser lässt sich dann im Template nutzen.

Zuletzt "führen" wir über "$this->ProcessTemplate()" noch das zugehörige Template (quotes_overview.tpl) aus. Das Template muss sich im /templates-Ordner des Moduls befinden.

Meine Template-Datei sieht wie folgt aus: nopaste

"{$createLink}" repräsentiert hier zum Beispiel den Link, über welchen man später neue Zitate hinzufügen können soll.
 Da wir davor unser "$this" als Tag übergeben haben können wir auch weiterhin auf die Sprach-Methode "Lang()" zugreifen. Praktisch!
 
Meine Sprachdatei habe ich durch folgende Texte erweitert:
[...]
$lang['really_delete'] = 'Möchten Sie das Zitat wirklich löschen?';
$lang['delete'] = 'Löschen';
/* Forms */
$lang['form_quote'] = 'Zitat:';
$lang['form_author'] = 'Autor:';
$lang['form_submit_quote'] = 'Zitat hinzufügen';
/* Tables */
$lang['th_quoteID'] = 'ZitateID';
$lang['th_quote'] = 'Zitat';
$lang['th_author'] = 'Autor';
$lang['th_actions'] = 'Aktionen';
/* Tabs */
[...]

Wenn wir nun noch über phpmyadmin ein paar Zitate in unsere Tabelle einfügen sollten wir diese schön aufgelistet bekommen!
Das funktioniert schonmal!
Zitate-Liste

Das Hinzufügen von Zitaten
Zitat hinzufügen

Jetzt möchte man natürlich Zitate direkt über das eingebaute Formular eintragen lassen können.
Dies haben wir gesagt möchten wir ebenfalls in der Übersichts-Datei realisieren.

Zu diesem Zweck fügen wir eine weitere Variable für die spätere "Berichterstattung" und ein If-Konstrukt hinter unserer Datenbank-Verbindung ($db = ...) ein, in welchem dann letzendlich auch einer neuer Datensatz in die Zitate-Tabelle eingefügt wird:
$db = $gCms->GetDb();
$message = '';

if(isset($params['new_quote'])) {
    if(empty($params['quote'])) {
        $message = $this->Lang('error_no_quote');
    } else {
        $quote = $params['quote'];
        $author = $params['author'];
                
                $sql = 'INSERT INTO `'.cms_db_prefix().'module_simplequota` SET `quote` = ?, `author` = ?';
            $insert = $db->Execute($sql, array($params['quote'], $params['author']));
            
                        if($insert !== false) {
                $message = $this->Lang('success_add_quote');
            } else {
                $message = $this->Lang('error_database');
            }
    }
}

Außerdem habe ich die Nachrichten-Variable "$message" an Smarty übergeben:
$this->smarty->assign('createForm', $form);
$this->smarty->assign('SQ', $this);
$this->smarty->assign('rows', $rows);
$this->smarty->assign('message', $message);
(gesamte Datei in nopaste)

Wir prüfen also, ob der Submit-Button getätigt wurde. Falls dies der Fall ist wird geprüft, ob das Zitat-Feld nicht leergelassen wurde, denn das bringt ja nichts. Der Autor muss nicht zwingend angegeben werden, den weiß man ja auch nicht immer.
Unser SQL-Query (der Ausdruck ist hier vielleicht nicht ganz korrekt) speichern wir in der Variablen $sql. Die Besonderheit hier ist, dass wir die Set-Werte für die beiden Felder erstmal mit einem Fragezeichen ("?") ersetzen, um diese später über die Execute-Funktion zu ersetzen. Dies hat den Vorteil, dass unsere Execute-Funktion SQL-Injections und Co. automatisch durch gewisse "Mechanismen" vorbeugt.

Je nachdem ob dieser Vorgang erfolgreich war (das wird er wahrscheinlich in 99% aller Fälle sein), wird eine positive oder negative Nachricht (natürlich über die Sprachdatei bezogen) in der Nachrichten-Variable gespeichert. Das wars auch schon.

Hier noch meine angepasste de_DE.php
[...]
$lang['delete'] = 'Löschen';
$lang['success_add_quote'] = 'Das Zitat wurde erfolgreich hinzugefügt';
/* Forms */
$lang['form_quote'] = 'Zitat:';
$lang['form_author'] = 'Autor:';
$lang['form_submit_quote'] = 'Zitat hinzufügen';
/* Tables */
$lang['th_quoteID'] = 'ZitateID';
$lang['th_quote'] = 'Zitat';
$lang['th_author'] = 'Autor';
$lang['th_actions'] = 'Aktionen';
/* Tabs */
$lang['tab_title_overview'] = 'Zitate-Übersicht';
$lang['tab_title_settings'] = 'Einstellungen';
/* Params */
$lang['param_quotes_desc'] = 'Gibt an wie viele Zitate angezeigt werden sollen.';
/* Errors */
$lang['error_no_quote'] = 'Es wurde kein Zitat angegeben';
$lang['error_database'] = 'Es ist ein Fehler mit der Datenbank aufgetreten. Bitte kontaktieren den Administrator, sollte diese Nachricht weiterhin erscheinen.';
?>
(nopaste)
 
Zitate löschen

Wenn man ein Zitat aus der Datenbank löschen möchte, so muss natürlich nicht das ganze Modul neuinstalliert werden.
Wir fügen unserer function.admin_tab_quotes_overview.php ein weiteres If-Konstrukt hinter das eben eingefügte hinzu, dass für das Löschen eines Zitates (über die ID) zuständig ist. Die Links dafür haben wir ja schon vorhin gesetzt.
if($params['delQuote'] == 'true' && !empty($params['quote_id']) && is_numeric($params['quote_id'])) {
    $sql = 'DELETE FROM `'.cms_db_prefix().'module_simplequota` WHERE `quote_id` = ?';
        $delete = $db->Execute($sql, array($params['quote_id']));
        if($delete !== false) {
             $message = $this->Lang('success_delete_quote');
        } else {
           $message = $this->Lang('error_database');
        }
}
(gesamte Datei in nopaste)

Hier kommt ja zum Glück nicht mehr so viel neues auf uns zu.
Eine Abfrage ob der Parameter "delQuote" auf "true" gesetzt ist und ob die "quote_id" nicht leer und ob  sie numerisch ist.
Wenn all dies zutrifft löschen wir den Datensatz nach der einzigartigen quote_id über $db->Execute(); 
Hier wäre es theoretisch nicht nötig den Parameter extra nochmal durch die Sicherheitsschleuse zu führen, da man mit einer Nummer meines wissens nach keine SQL-Injection durchführen kann. Aber doppelt gemoppelt hält bekanntlich besser.

Die Sprachdatei habe ich erneut durch eine Zeile erweitert. Diese habe ich direkt unter den "success_add_quote"-Text eingefügt:
"$lang['success_delete_quote'] = 'Das Zitat wurde erfolgreich gelöscht';"

Der Einstellungen-Tab

Bringen wir ihn schnell hinter uns! In diesem möchten wir ja auch nur eine einzige Standardeinstellung vornehmen können.
Erstellen wir also die Datei "function.admin_tab_settings.php".
<?php
if (!isset($gCms)) exit;
$db = $gCms->GetDb();

if(isset($params['change_settings']) && !empty($params['standardQuotes']) && is_numeric($params['standardQuotes'])) {
    $sql = 'UPDATE `'.cms_db_prefix().'module_simplequota_settings` SET `standardQuotesNumber` = ?';
        $update = $db->Execute($sql, array($params['standardQuotes']));
}

$standardSQL = 'SELECT `standardQuotesNumber` FROM `'.cms_db_prefix().'module_simplequota_settings` LIMIT 1';
    $getStandard = $db->Execute($standardSQL)->FetchRow();

$form = $this->CreateFormStart($id, 'defaultadmin', $returnid, 'post', '', false, '', array('active_tab' => 'settings'));
$form .= $this->Lang('form_standard_quotes_number').' '.$this->CreateInputText($id, 'standardQuotes', $getStandard['standardQuotesNumber'], '5');
$form .= $this->CreateInputSubmit($id, 'change_settings', $this->Lang('form_submit_change_settings'));
$form .= $this->CreateFormEnd();

$this->smarty->assign('form', $form);
echo $this->ProcessTemplate('settings.tpl');?>
(nopaste)

Hier gibt es glaube ich wenig Erklärungsbedarf. Die einzige Neuheit ist, dass wir mit unserem Formular einen weiteren Parameter übergeben. Und zwar den "active_tab"-Parameter, dass wir beim Neuladen der Seite auch wieder im Einstellungs-Tab landen. Wir erinnern uns, das haben wir am Anfang gelernt. 

Die Template-Datei "settings.tpl" sieht dementsprechend unspektakulär aus:
{$form}

Der Moment der Wahrheit - Das Modul einbindbar machen

Nachdem wir nun diesen weiten Weg hinter uns haben fehlt uns nur noch eins: Das Modul ist noch nicht einbindbar!
Dies geht glücklicherweise recht simpel. Öffnen wir eine neue Datei: action.default.php 
Diese Datei wird standardmäßig beim Einbinden des Moduls aufgerufen, insofern keine andere Aktion/action angegeben wurde.

Das Modul muss hierfür also eine bestimmte Anzahl an Zitaten aus der Datenbank auslesen, und diese dann ausgeben. Easy!
<?php
if (!isset($gCms)) exit;
$db = $gCms->GetDb();

if(is_numeric($params['quotes'])) {
    $quotesNumber = $params['quotes'];
} else {
    $getStandardQuotesNumber = 'SELECT `standardQuotesNumber` FROM `'.cms_db_prefix().'module_simplequota_settings`';
         $res = $db->Execute($getStandardQuotesNumber)->FetchRow();
    $quotesNumber = $res['standardQuotesNumber'];
}

$quotesSQL = 'SELECT * FROM `'.cms_db_prefix().'module_simplequota` ORDER BY rand() LIMIT '.$quotesNumber.'';
    $getQuotes = $db->Execute($quotesSQL);
$c = 0;
while($row = $getQuotes->FetchRow()) {
    $quotes[$c]['quote'] = $row['quote'];
    $quotes[$c]['author'] = $row['author'];
    $c++;
$this->smarty->assign('quotesArray', $quotes);
echo $this->ProcessTemplate('display.tpl');
(nopaste)

Sollte der im/vom Modul übergebene Parameter für die Anzahl der Zitate ("quotes") keiner Nummer entsprechen (wie er das standardmäßig auch dank dem fehlerhaften Standardwert auch nicht tut), so wird aus der Datenbank der in den Einstellungen eingetragene Wert ausgelesen. Sollte ein korrekter Wert über den Tag, also zum Beispiel "{SimpleQuota quotes="1"}", übergeben worden sein, so wird dieser gewählt.

Mit "ORDER BY rand()" und "LIMIT" werden zufällig so viele Zitate wie angegeben ausgelesen.
Die Zitate werden dann gebündelt in einem Array ($quotesArray) an das Template weitergegeben:

{foreach from=$quotesArray item=array}
    "{$array.quote}" {if $array.author != ""} - {$array.author} {/if} 
{/foreach}
(nopaste)

Smarty bietet zum Glück auch Funktionen wie foreach mit sich. Damit können wir unser Zitate-Array ($quotesArray) durchlaufen lassen, und so wieder an de einzelnen Zitate und deren Autoren gelangen.
Sollte das Autoren-Feld leer sein, so wird dies mittels if ermittelt und es wird kein Bindestrich (+ Autor) ausgegeben.

SimpleQuota über Tag als Inhalt einfügen

Wow, Du hast es geschafft! Jetzt hast du dein erstes eigenes CMSMS-Modul entwickelt! Fühl dich von mir auf die Schulter geklopft. ;-)
Ich hoffe man konnte einiges mitnehmen, denn auch für mich war es nicht immer ein Zuckerschlecken alles zu erklären.

Ich musste das Tutorial ein paar mal komplett überholen, deswegen könnte es unter Umständen möglich sein, dass eine Ungereimhheit auftaucht. In diesem Falle bitte einfach als Kommentar oder Nachricht bescheid geben, da wäre ich sehr dankbar.

Feedback ist natürlich auch erwünscht! 

Komplettes Modul herunterladen: Zum Download
5300 Mal gelesen
+5
10. Apr 2012, 04:52

Kommentare

(5)
RSS
avatar
+1
v x
emexy 10. Apr 2012, 18:58

Tolles Tutorial!

CMSms gefällt mir sehr gut, aufgrund mangelhafter Dokumentation hinsichtlich Modul/Plugin — Entwicklung aus meiner Seite nie produktiv im Einsatz gewesen. Vielen Dank für dein How To! Gerne mehr. :-)

emexy

avatar
+1
v x
Klener 13. Apr 2012, 22:10

Freut mich, dass dir das Tutorial gefällt, hat seine Zeit gebraucht :)

CMSms find' ich auch super, der Einstieg war für mich vor geraumer Zeit eigentlich ganz angenehm und ich bin schnell zu den Ergebnissen gekommen, die ich wollte. Darum geht's schließlich!

avatar
+1
v x
charwatp 4. Mai 2012, 14:33

Hallo und vielen Dank für das tolle Tutorial. Endlich mal jemand, der klar und verständlich in die Modul-Entwicklung einführt… Danke!
Eine kurze Frage habe ich aber trotzdem: nachdem ich auf Basis Deines Beispieles ein eignes Modul geschrieben habe schlägt dessen Installation fehl, leider ohne das ich nachvollziehen kann warum. Hast Du eine Idee, wie ich das am besten Debuggen kann? nach dem drücken auf "installieren" erfolgt ein Reload der Seite aber das Modul ist weiterhin nicht installiert. Im Logfile des CMS wird trotzdem eine Erfolgsmeldung eingetragen.
Vielen Dank für jeden Tipp, Peter

avatar
0
v x
Klener 4. Mai 2012, 17:56

Hi charwatp,
vielen Dank für das Lob!

So auf die Schnelle und ohne Code vor Augen wird das natürlich recht schwer zu sagen, wo genau der Fehler liegt, was ich mir aber vorstellen kann ist, dass in irgendeinem Abschnitt deines Codes das Script abgebrochen wird, was du aber wegen unterdrückter Fehlermeldungen nicht sehen kannst.

Mühsam aber funktioniert ganz gut: Nach und nach die Bestandteile deiner method.install.php durchtestest, d.h. Du fängst mit zuerst nur einem kleinen Teil (zum Beipsiel dem "NewDataDictionary()" an, und versuchst zu installieren. Sollte das klappen, machst du immer weiter, bis es einmal nicht klappt (zwischendurch natürlich immer wieder deinstallieren).

Eventuelle hast du in der MODULNAME.module.php einen Fehler eingebaut.

Falls das nicht helfen sollte kannst du wie gesagt gerne den Code der beiden Dateien posten (bzw. mir per Nachricht schicken).

avatar
0
v x
Klener 27. Jul 2012, 00:11

Gibt seit geraumer Zeit übrigens auch ein offizielles Supportforum zu diesem Tutorial (+ Plugin) unter http://forum.janex-media.de/. Würden uns über ein paar Fragen freuen! ;-)


Kommentieren

Fett Kursiv Unterstrichen Durchgestrichen   Link Zitieren Code
Ich bin mit den Nutzungsbedingungen einverstanden.
Zukünftige Kommentare zu diesem Beitrag abonnieren (abbestellbar).
 
Bitte klicke jetzt auf den Bestätigungslink in deiner E-Mail.