Gästebuch mit Jot in MODx

von Thomas Jakobi am Freitag, 5. Oktober 2007 um 12:18 Uhr.

Für die Website der Offenen Zeltstadt in Bergheim Paffendorf habe ich mit dem Jot-Snippet von MODx ein Gästebuch zusammengestellt. Neben einer Smilie-Integration gibt es noch einen RSS-Stream der letzten 10 Einträge.

Voraussetzungen

Folgende Plugins/Snippets müssen installiert und funktionsfähig sein:

Jot (wird standardmäßig mit MODx installiert)

PHx (u.a. für die Datumsformatierung)

Benötigte Dokumente und Chunks

Das Gästebuch wird als neues MODx-Dokument z.B. mit dem Alias 'gaestebuch' im Seitenbaum eingefügt und enthält folgenden Code:

guestbookCall
[[Jot? &output=`0` &placeholders=`1` &subjectModerate=`Gästebuch-Eintrag` &notify=`2` &canmoderate=`Moderator` &captcha=`1` &pagination=`10` &moderated=`0` &customfields=`name,email,homepage,location,vote,publishemail` &tplForm=`guestbookForm` &tplNav=`guestbookNav` &tplComments=`guestbookEntry` &validate=`name:Bitte einen Namen eingeben!,email:Bitte eine gültige E-Mail Adresse eingeben!:email,content:Bitte einen Kommentar eingeben!`]]
[+jot.html.navigation+]
[+jot.html.form+]
[+jot.html.comments+]
[+jot.html.moderate+]
[+jot.html.navigation+]

* Bei der Übergabe der customfields ist es wichtig, dass zwischen den einzelnen Einträgen nur ein Komma steht (und diesem kein Leerzeichen folgt – Dies wird u.a. im Wiki für Jot falsch dokumentiert). Weiterhin muss noch eine Webnutzergruppe unter Sicherheit -> Web-Berechtigungen mit Namen 'Moderator' angelegt werden und ein Webnutzer unter Sicherheit -> Webnutzer dieser Gruppe hinzugefügt werden – diesem Webnutzer werden dann die Moderations-Mails zugeschickt.

Achtung: Alle Seiten, die Jot-Aufrufe enthalten, müssen mit deaktiviertem Seiten-Cache angelegt werden – Leider gibt es eine Inkompatibilität zwischen PHx und Jot, die sich bei ungecachten Jot-Aufrufen auf gecachten Seiten bemerkbar macht.

Das RSS-Stream wird auch als MODx-Dokument z.B. mit dem Alias 'gaestebuch_feed.rss' angelegt. Der Typ dieses Dokuments muss auf 'text/xml' und das Template auf '(blank)' gesetzt werden. Es enthält folgendem Code:

guestbookFeed
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>Gästebuch</title>
<link>[(site_url)]gaestebuch.html</link>
<description>Gästebuch von ...</description>
<language>de-de</language>
[[Jot? &docid=`*` &output=`0` &placeholders=`1` &customfields=`name,email,homepage,location,vote,publishemail` &tplComments=`tplGuestbookRSS` &pagination=`10`]][+jot.html.comments+]
<atom:link href="[(site_url)]gaestebuch_feed.rss" rel="self" type="application/rss+xml" />
</channel>
</rss>

* docid verweist auf die ID des Dokument mit dem Gästebuch – bitte entsprechend anpassen!

Achtung: Das Dokument mit dem RSS-Feed darf nicht mit dem TinyMCE bearbeitet werden. Also unter Seiteneinstellungen das Häkchen bei Richtext entfernen und unterhalb des Editor-Fensters beim ersten Bearbeiten das Feld 'Eingesetzter Editor' auf 'Kein' stellen.

Weiterhin werden folgende Chunks für das Gästebuch benötigt (sie basieren auf den Standard-Templates von Jot):

guestbookEntry
<a name="jc[+jot.link.id+][+comment.id+]"></a>
<div class="jot-row [+chunk.rowclass+] [+comment.published:is=`0`:then=`jot-row-up`+]">
<div class="guestbook_top">
<span class="guestbook_big">
[+comment.createdby:userinfo=`username`:ifempty=`[+comment.custom.name:ifempty=`[+jot.guestname+]`:esc+]`+]
</span> 
<span class="guestbook_small">
am [+comment.createdon:date=`%d. %B %Y, %H:%M:%S Uhr`+] [+comment.custom.location:is=``:else=` aus `+][+comment.custom.location+]
</span>
<span class="guestbook_vote">
[+comment.custom.vote:vote+]
</span>
</div>
<div class="guestbook_content">
<div class="guestbook_contact">
[+comment.custom.publishemail:eq=`1`:then=`[+comment.custom.email:is=``:else=`<a href="mailto:`+][+comment.custom.email+][+comment.custom.email:is=``:else=`"><img src="[(base_url)]manager/media/style/MODx/images/icons/email.png" width="16" height="16" alt="E-Mail" border="0" /></a>`+]`+]
[+comment.custom.homepage:is=``:else=`<a href="http://`+][+comment.custom.homepage:nohttp+][+comment.custom.homepage:is=``:else=`"><img src="[(base_url)]manager/media/style/MODx/images/icons/world.png" width="16" height="16" alt="Homepage" border="0" /></a>`+]
</div>
<div class="guestbook_mod">
[+jot.moderation.enabled:is=`1`:then=`
<a href="[+jot.link.delete:esc+][+jot.querykey.id+]=[+comment.id+]#jotmod[+jot.link.id+]" onclick="return confirm('Möchten Sie den Kommentar wirklch löschen?')" title="Kommentar löschen"><img src="[(base_url)]manager/media/style/MODx/images/icons/event3.gif" width="16" height="16" alt="Kommentar löschen" border="0" /></a>
[+comment.published:is=`0`:then=`
<a href="[+jot.link.publish:esc+][+jot.querykey.id+]=[+comment.id+]#jotmod[+jot.link.id+]" onclick="return confirm('Möchten Sie den Kommentar wirklch veröffentlichen?')" title="Kommentar veröffentlichen"><img src="[(base_url)]manager/media/style/MODx/images/icons/add.png" width="16" height="16" alt="Kommentar veröffentlichen" border="0" /></a>
`+]
[+comment.published:is=`1`:then=`
<a href="[+jot.link.unpublish:esc+][+jot.querykey.id+]=[+comment.id+]#jotmod[+jot.link.id+]" onclick="return confirm('Möchten Sie den Kommentar wirklch zurückziehen?')" title="Kommentar zurückziehen"><img src="[(base_url)]manager/media/style/MODx/images/icons/delete.png" width="16" height="16" alt="Kommentar zurückziehen" border="0" /></a>
`+]
`:strip+]
[+jot.user.canedit:is=`1`:and:if=`[+comment.createdby+]`:is=`[+jot.user.id+]`:or:if=`[+jot.moderation.enabled+]`:is=`1`:then=`
<a href="[+jot.link.edit:esc+][+jot.querykey.id+]=[+comment.id+]#jf[+jot.link.id+]" onclick="return confirm('Möchten Sie den Kommentar wirklich bearbeiten?')" title="Kommentar bearbeiten"><img src="[(base_url)]manager/media/style/MODx/images/icons/logging.gif" width="16" height="16" alt="Kommentar bearbeiten" border="0" /></a>
`:strip+]
</div>	
<div class="guestbook_extra">
[+comment.editedon:isnt=`0`:then=`
<span class="guestbook_small">Zuletzt bearbeitet: [+comment.editedon:date=`%d. %B %Y, %H:%M:%S Uhr`+] von [+comment.editedby:userinfo=`username`:ifempty=` * `+]</span>
&nbsp;`+] [+jot.moderation.enabled:is=`1`:then=`<a href="http://www.ripe.net/perl/whois?searchtext=[+comment.secip+]">[+comment.secip+]</a>`+]
</div>
[+comment.content:wordwrap:bbcode:smilies:nl2br+]
</div>
</div>
guestbookForm
<a name="jf[+jot.link.id+]"></a>
[+jot.query.action:is=``:then=`<a id="guestbook_toggle" href="[~[*id*]~]#">Eintrag hinzufügen</a><div id="guestbook_formular">`+]
<p>Benötigte Felder sind mit einem Stern (*) markiert.</p>
[+form.error:isnt=`0`:then=`
<div class="jot-err">
[+form.error:select=`
&-3=Es wurde versucht, den selben Eintrag mehrmals abzugeben. Eventuell wurde mehrmals auf Veröffentlichen-Button geklickt. 
&-2=Der Eintrag wurde abgewiesen.
&-1=Der Eintrag wurde gespeichert und wird nach einer Begutachtung durch den Betreibers veröffentlicht.
&1=Es wurde versucht, den selben Eintrag mehrmals abzugeben. Eventuell wurde mehrmals auf Veröffentlichen-Button geklickt.
&2=Der eingegebene Sicherheitscode war nicht richtig.
&3=Es kann nur ein Eintrag innerhalb von [+jot.postdelay+] Sekunden abgegeben werden.
&4=Der Eintrag wurde abgewiesen.
&5=[+form.errormsg:ifempty=`Es wurden nicht alle benötigten Felder ausgefüllt`+]
`+]
</div>
`:strip+]
[+form.confirm:isnt=`0`:then=`
<div class="jot-cfm">
[+form.confirm:select=`
&1=Der Eintrag wurde veröffentlicht.
&2=Der Eintrag wurde gespeichert und wird nach einer Begutachtung durch den Betreibers veröffentlicht.
&3=Der Eintrag wurde gespeichert.
`+]
</div>
`:strip+]
<form method="post" action="[+form.action:esc+]#jf[+jot.link.id+]" class="jot-form">
<fieldset>
[+form.moderation:is=`1`:then=`
<div class="jot-row">
<b>Verfasst am:</b> [+form.field.createdon:date=`%a %d. %B %Y um %H:%M Uhr`+]<br />
<b>Verfasst von:</b> [+form.field.createdby:userinfo=`username`:ifempty=`[+jot.guestname+]`+]<br />
<b>IP-Adresse:</b> [+form.field.secip+]<br />
<b>Veröffentlicht:</b> [+form.field.published:select=`0=No&1=Yes`+]<br />
[+form.field.publishedon:gt=`0`:then=`
<b>Veröffentlicht am:</b> [+form.field.publishedon:date=`%a %d. %B %Y um %H:%M Uhr`+]<br />
<b>Veröffentlicht von:</b> [+form.field.publishedby:userinfo=`username`:ifempty=` – `+]<br />
`+]
[+form.field.editedon:gt=`0`:then=`
<b>Bearbeitet am:</b> [+form.field.editedon:date=`%a %d. %B %Y um %H:%M Uhr`+]<br />
<b>Bearbeitet von:</b> [+form.field.editedby:userinfo=`username`:ifempty=` –`+]<br />
`+]
</div>
`:strip+]
[+form.guest:is=`1`:then=`
<label for="name">Dein Name: *</label>
<input tabindex="[+jot.seed:math=`?+1`+]" name="name" id="name" type="text" value="[+form.field.custom.name:esc+]" /><br />
<label for="email">Deine E-Mail: *</label>
<input tabindex="[+jot.seed:math=`?+2`+]" name="email" id="email" type="text" value="[+form.field.custom.email:esc+]" />
<span>anzeigen:</span>
<input tabindex="[+jot.seed:math=`?+3`+]" name="publishemail" id="publishemail" type="checkbox" value="[+form.field.custom.publishemail+]1" /><br />
`:strip+]
<label for="homepage">Deine Homepage:</label>
<input tabindex="[+jot.seed:math=`?+4`+]" name="homepage" id="homepage" type="text" value="[+form.field.custom.homepage:esc+]" /><br />
<label for="location">Dein Wohnort:</label>
<input tabindex="[+jot.seed:math=`?+5`+]" name="location" id="location" type="text" value="[+form.field.custom.location:esc+]" /><br />
<label for="vote">Deine Bewertung unserer Seite:</label>
<select tabindex="[+jot.seed:math=`?+6`+]" name="vote" id="vote" size="1"><option value="[+form.field.custom.vote:esc+]">Bitte bewerten</option><option value="5">sehr gut</option><option value="4">gut</option><option value="3">geht so</option><option value="2">schlecht</option><option value="1">sehr schlecht</option></select><br />
<label for="content">Kommentar: *</label>
<textarea tabindex="[+jot.seed:math=`?+7`+]" name="content" id="content" rows="8" cols="50">[+form.field.content:esc+]</textarea><br />
{{smiliesSelect}}<br />
[+jot.captcha:is=`1`:then=`
<label for="vericode">Sicherheitscode: *</label><div class="jot-captcha"><a href="[+jot.link.current:esc+]"><img src="[(base_url)]manager/includes/veriword.php?rand=[+jot.seed+]" width="148" height="60" alt="Sollte der Sicherheitscode unleserlich sein, kann durch einen Klick auf das Bild ein neuer Sicherheitscode erzeugt werden." /></a></div><input type="text" name="vericode" id="vericode" size="20" /><br />
`:strip+]
<input tabindex="[+jot.seed:math=`?+8`+]" name="submit" type="submit" id="submit" value="[+form.edit:is=`1`:then=`Eintrag speichern`:else=`Eintrag senden`+]" />
[+form.edit:is=`1`:then=`
<input tabindex="[+jot.seed:math=`?+9`+]" name="submit" type="submit" id="submit" value="Cancel" onclick="history.go(-1);return false;" />
`+]
 
<input name="JotForm" type="hidden" value="[+jot.id+]" />
<input name="JotNow" type="hidden" value="[+jot.seed+]" />
<input name="parent" type="hidden" value="[+form.field.parent+]" />
</fieldset>
</form>
[+jot.query.action:is=``:then=`</div>`+]

* Das Formular für Neueinträge im Gästebuch steht in einem DIV mit der ID 'guestbook_formular'. Dieses kann mit einem Javascript zur besseren Übersichtlichkeit ausgeblendet werden.

guestbookNav
<a name="jotnav[+jot.id+]"></a>
<div class="jot-nav">
[+jot.page.current:gt=`1`:then=`
<a href="[+jot.link.navigation:esc+][+jot.querykey.navigation+]=1#jotnav[+jot.id+]">&laquo;</a> |
<a href="[+jot.link.navigation:esc+][+jot.querykey.navigation+]=[+jot.page.current:math=`?-1`+]#jotnav[+jot.id+]">&lsaquo;</a> |
`+]
Kommentare <b>[+jot.nav.start+]</b> bis <b>[+jot.nav.end+]</b> von <b>[+jot.nav.total+]</b>
[+jot.page.current:lt=`[+jot.page.total+]`:then=` |
<a href="[+jot.link.navigation:esc+][+jot.querykey.navigation+]=[+jot.page.current:math=`?+1`+]#jotnav[+jot.id+]">&rsaquo;</a> |
<a href="[+jot.link.navigation:esc+][+jot.querykey.navigation+]=[+jot.page.total+]#jotnav[+jot.id+]">&raquo;</a>
`+]
</div>
guestbookRSS
<item>
<title>[+comment.custom.name+]: [+comment.createdon:date=`%d. %B %Y, %H:%M:%S Uhr`+]</title>
<description>
<![CDATA[
[+comment.content:wordwrap:bbcode:smilies:nl2br+]
]]>
</description>
<link>[(site_url)]gaestebuch.html</link>
<author>[+comment.custom.name+]</author>
<pubDate>[+comment.createdon:date=`%a, %d %b %Y %H:%M:%S %Z`+]</pubDate>
<guid isPermaLink="false">[(site_url)]gaestebuch/[+comment.createdon:date=`%d%m%y%H%M%S`+]</guid>
</item>

Für die Smilies werden folgende Chunks benötigt:

smiliesSelect

guestbookSmilies
<div class="smilies">
<div class="smilieSelect"><img src="assets/images/smilies/icon_biggrin.gif" alt=":grin:" onclick="AddSmile(':grin:')" /></div>
<div class="smilieSelect"><img src="assets/images/smilies/icon_smile.gif" alt=":-)" onclick="AddSmile(':-)')" /></div>
<div class="smilieSelect"><img src="assets/images/smilies/icon_sad.gif" alt=":-(" onclick="AddSmile(':-(')" /></div>
<div class="smilieSelect"><img src="assets/images/smilies/icon_eek.gif" alt=":eek:" onclick="AddSmile(':eek:')" /></div>
<div class="smilieSelect"><img src="assets/images/smilies/icon_razz.gif" alt=":-P" onclick="AddSmile(':-P')" /></div>
<div class="smilieSelect"><img src="assets/images/smilies/icon_redface.gif" alt=":oops:" onclick="AddSmile(':oops:')" /></div>
<div class="smilieSelect"><img src="assets/images/smilies/icon_cry.gif" alt=":cry:" onclick="AddSmile(':cry:')" /></div>
<div class="smilieSelect"><img src="assets/images/smilies/icon_evil.gif" alt=":evil:" onclick="AddSmile(':evil:')" /></div>
<div class="smilieSelect"><img src="assets/images/smilies/icon_twisted.gif" alt=":twisted:" onclick="AddSmile(':twisted:')" /></div>
<div class="smilieSelect"><img src="assets/images/smilies/icon_rolleyes.gif" alt=":roll:" onclick="AddSmile(':roll:')" /></div>
<div class="smilieSelect"><img src="assets/images/smilies/icon_wink.gif" alt=";-)" onclick="AddSmile(';-)')" /></div>
<div class="smilieSelect"><img src="assets/images/smilies/icon_lol.gif" alt=":lol:" onclick="AddSmile(':lol:')" /></div>
<div class="smilieSelect"><img src="assets/images/smilies/icon_cool.gif" alt="8-)" onclick="AddSmile('8-)')" /></div>
<div class="smilieSelect"><img src="assets/images/smilies/icon_confused.gif" alt=":-?" onclick="AddSmile(':-?')" /></div>
<div class="smilieSelect"><img src="assets/images/smilies/icon_mad.gif" alt="" onclick="AddSmile(':upset:')" /></div>
</div>

* Passende Smilie-Grafiken unter 'assets/images/smilies' nicht vergessen. Weiterführende Links zu entsprechenden Webseiten gibt es auf dieser Seite.

addSmile

guestbookAddSmile
<script type="text/javascript">
function AddSmile(x,D,F){D=document;F=D.getElementById('content');D.selection?(F.focus(),D.selection.createRange().text=x):(F.selectionStart||F.selectionStart===0)?F.value=F.value.substring(0,F.selectionStart)+x+F.value.substring(F.selectionEnd,F.value.length):F.value+=x}
</script>

* Dieses Chunk muss im <head>-Abschnitt des Gästebuch-Dokuments erscheinen (z.B. direkt im Template oder mit diesem Snippet).

Achtung: Das Javascript setzt den Smilie-Text in ein textarea mit der ID 'content' ein. Auf der Seite darf es deshalb kein anderes Element mit dieser ID geben.

Für die Darstellung der Smilies in den Gästebucheinträgen muss noch ein passender Modifier für PHx mit dem Namen 'smilies.phx.php' im Ordner '/assets/plugins/phx/modifiers' angelegt werden. Dieser enthält folgenden PHP-Code (basiert auf dem Smilies-Modifier aus dem MODx-PHx-Wiki):

smilies.phx.php
<?php
// Path where the smilies are stored
$basepath = $modx->config['site_url'] . 'assets/images/smilies/';
// Mapping of text to imagename
$smiles = array(':D' => 'icon_biggrin.gif', ':-D' => 'icon_biggrin.gif', ':grin:' => 'icon_biggrin.gif', ':)' => 'icon_smile.gif', ':-)' => 'icon_smile.gif', ':(' => 'icon_sad.gif', ':-(' => 'icon_sad.gif', ':eek:' => 'icon_eek.gif', ':P' => 'icon_razz.gif', ':-P' => 'icon_razz.gif', ':-p' => 'icon_razz.gif', ':p' => 'icon_razz.gif', ':oops:' => 'icon_redface.gif', ':cry:' => 'icon_cry.gif', ':evil:' => 'icon_evil.gif', ':twisted:' => 'icon_twisted.gif', ':roll:' => 'icon_rolleyes.gif', ';)' => 'icon_wink.gif', ';-)' => 'icon_wink.gif', ':wink:' => 'icon_wink.gif', ':lol:' => 'icon_lol.gif', '8)' => 'icon_cool.gif', '8-)' => 'icon_cool.gif', ':?' => 'icon_confused.gif', ':-?' => 'icon_confused.gif', ':upset:' => 'icon_mad.gif');
// Convert imagenames to html tags here, just to keep the structure above 
// easy to maintain, especially when I want to change the generated HTML.
foreach ($smiles as $key => $value) {
    $smiles[$key] = "<img src='" . $basepath . $value . "' class='smilie' alt='$key' />";
}
return str_replace(array_keys($smiles), array_values($smiles), $output);

Außerdem muss zum Darstellen der Vote-Ergebnisse ein weiterer Modifier für PHx an der gleichen Stelle mit dem Namen vote.phx.php angelegt werden. Dieser enthält folgenden PHP-Code:

vote.phx.php
<?php
$vote = '';
for ($i = 1; $i <= $output; $i++) {
    $vote .= '<img src="assets/images/smilies/vote.png" class="vote" alt="Vote" />';
}
return $vote;

* Die passende Grafik unter 'assets/images/smilies' nicht vergessen.

Zum Aufhübschen können noch ein paar CSS-Zeilen im Seitentemplate definiert werden. Folgende Klassen bieten sich dafür an:

guestbookCSS
.guestbook_top { height: 1.6em; padding: 0.3em 0.5em 0 }
.guestbook_big { float: left; font-size: 1.2em; font-weight: bold; margin-right: 0.5em }
.guestbook_small { float: left; font-size: 0.8em; font-weight: bold }
.guestbook_contact { float: right; margin-left: 0.5em }
.guestbook_content { padding: 0.5em; clear: right }
.guestbook_mod { float: right; margin-left: 0.5em }
.guestbook_extra { float: right; padding-bottom: 1em }
.guestbook_vote { float: right }
.smilie { vertical-align: sub }
.smilies { margin: 0.5em 0 0.5em 9.5em }
.smilieSelect { float: left; margin-right: 0.25em }
.vote { float: left; margin-right: 0.25em }

Viel Erfolg!