Neuigkeiten:

Privates MODX und LINUX BLOG, User Registrierung ist deaktiviert! Fragen oder Tipps? Bitte per Matrix: @jolichter:tchncs.de

Hauptmenü

modxComment

Begonnen von Jo, 2019-04-14 | 15:57:00

« vorheriges - nächstes »

Jo

Einfache Kommentarkomponente
getestet mit MODX Revolution 2.7.2-pl und Bootstrap 2.3.2

Leider gibt es keine ordentliche Open Source Kommentarfunktion für MODX die DSGVO konform ist. Daher habe ich mir das mit MODX "Standard Snippets und Chunks" zusammengebaut. Das ist die Stärke von MODX: Flexibel hoch 3.

Nenne das ganze modxComment V19.04.002 und die Features sind:
+ eMail Benachrichtigung
+ eMail Syntax-Überprüfung
+ eMail Veröffentlichen ja/nein
+ eMail Maskierung
+ Spamschutz ohne nervigen Captcha (Danke an Jako!)
+ Icon Set und BBCode
+ Bewertung des Berichtes
+ Kommentare im Frontend ändern und de- bzw. aktivieren*
+ deaktivierte Kommentare endgültig löschen*
+ IP kann aus Sicherheitsgründen gespeichert werden (sollte dann einmal pro Woche via cronjob gelöscht werden)

* darf nur User mit ID1 - wenn mehr User diese Berechtigung haben sollen - einfach die Output Filter !+modx.user.id:eq=`1` durch ein !+modx.user.id:memberof=`Administrator` ersetzen (oder einer anderen Gruppe).

Vorwort:
QUIP und modxTalks werden seit sehr langer Zeit nicht mehr gepflegt und sind mir zu Fehlerhaft.
Besonders bei modxTalks finde ich das sehr Schade! Das Design ist Topp! Nur was nützt es, wenn das MODX Error Log voll läuft und es keinen Support per github gibt? Sottwell und ich hatten vergeblich versucht per github das Tool ordentlich ans laufen zu bringen  :confused:
OK, also Plan B mit FormIt & Co:

#### Vorbereitungen ####

Folgende 4 Packages werden für modxComment benötigt:
(in Klammer stehen meine Versionen)
  • FormIt (4.2.2-pl) - Form Eingabefeld
  • rowboat (1.1.0-pl) - DB lesen
  • getPage (1.2.4-pl) - Paginierung
  • StupidQuestion (0.7.2-pl) - Spamschutz


1) eine custom SQL-Tabelle für formit2db anlegen
(z.B. mit phpMyAdmin importieren)
CREATE TABLE `modx_custom_comment`(
  `id` int(10) NOT NULL auto_increment,
  `ctResource` int(10) NOT NULL default '0',
  `ctIp` varchar(50) NOT NULL default '0:0:0:0:0:0:0:0',
  `ctName` varchar(50) NOT NULL default '',
  `ctEmail` varchar(60) NOT NULL default '',
  `ctEmailshow` tinyint(1) NOT NULL default '0',
  `ctHome` varchar(50) NOT NULL default '',
  `ctVote` int(1) NOT NULL default '0',
  `ctText` text NOT NULL,
  `ctDate` timestamp NOT NULL default CURRENT_TIMESTAMP,
  `ctActive` tinyint(1) NOT NULL default '1',
PRIMARY KEY (`id`)
);
Hinweis: Der PREFIX 'modx_' ist Standard, wenn nicht muss das angepasst werden.


#### snippets erstellen ####

2) Zwei snippets db2formit und formit2db erstellen (Quelle: formit2db with autocreate schema&classes).

db2formit
<?php
# e.g.: <a href="[[~42? &resId=`[[+id]]`]]">Edit resource</a>

$prefix=$scriptProperties['prefix'];
$packageName $scriptProperties['packageName'];
$tablename $scriptProperties['tablename'];

$restrictPrefix true;

$packagepath $modx->getOption('core_path') . 'components/'.$packageName.'/';
$modelpath $packagepath.'model/';
$schemapath $modelpath.'schema/';
$schemafile $schemapath.$packageName.'.mysql.schema.xml';
$manager$modx->getManager();
$generator$manager->getGenerator();
if (!
file_exists($schemafile)){

    if (!
is_dir($packagepath)) {
        
mkdir($packagepath0755);
    }
    if (!
is_dir($modelpath)) {
        
mkdir($modelpath0755);
    }
    if (!
is_dir($schemapath)) {
        
mkdir($schemapath0755);
    }
    
//Use this to create a schema from an existing database
    
$xml$generator->writeSchema($schemafile$packageName'xPDOObject'$prefix$restrictPrefix);

    
//Use this to generate classes and maps from your schema
    //NOTE: by default, only maps are overwritten; delete class files if you want to regenerate classes
    
$generator->parseSchema($schemafile$modelpath);    


$modx->addPackage($packageName,$modelpath,$prefix);
$classname $generator->getClassName($tablename);

if (isset(
$_GET['resId'])){
    if (
$dataobject=$modx->getObject($classname,array('id'=>$_GET['resId']))){
        if (!
is_object($dataobject) || !($dataobject instanceof xPDOObject)) {
            
$errorMsg='Failed to create object of type: ' $classname;
            
$hook->addError('error_message',$errorMsg);
            return 
false;
        }
        
# only if
        
if ($modx->user->isMember(array('Administrator'))){
        
# or
        # if ($modx->user->get('id') == '1'){
        
$hook->setValues($dataobject->toArray());
        }
    }
}

return 
true;

formit2db
<?php
$prefix
=$scriptProperties['prefix'];
$packageName $scriptProperties['packageName'];
$tablename $scriptProperties['tablename'];

$packagepath $modx->getOption('core_path') . 'components/'.$packageName.'/';
$modelpath $packagepath.'model/';

$modx->addPackage($packageName,$modelpath,$prefix);
$manager$modx->getManager();
$generator$manager->getGenerator();
$classname $generator->getClassName($tablename);

$dataobject=$modx->getObject($classname,array('id'=>$hook->getValue('resource_id')));

if (empty(
$dataobject)){
  
$dataobject $modx->newObject($classname);
}
else{
// kein neuen DS anlegen (also update)
}

if (!
is_object($dataobject) || !($dataobject instanceof xPDOObject)) {
  
$errorMsg='Failed to create object of type: ' $classname;
  
$hook->addError('error_message',$errorMsg);
  return 
false;
}

$allFormFields $hook->getValues();
foreach (
$allFormFields as $field=>$value){
if (
$field !== 'spam' && $field !== 'resource_id'){
  
$dataobject->set($field,$value);
}
}

$dataobject->save();
return 
true;

3) commentEntryDel
<?php
# löscht alle deaktivierte Kommentare
$table 'modx_custom_comment';
$where 'ctActive=0';

$count $modx->exec("DELETE FROM $table WHERE $where");
return 
$count' Kommentar(e) wurde(n) gelöscht!';

4) commentQty
<?php
# zähle aktivierte Einträge der aktuellen Ressource [[!commentQty?active=`1`]]
# Anzahl Platzhalter [[+ctQTY]]

$id $modx->resource->get('id');
$stmt $modx->query('SELECT * FROM `modx_custom_comment` WHERE `ctActive`='.$active.' AND `ctResource`='.$id);

  
$count=0;
  
$count $stmt->rowCount();
  
$modx->setPlaceholder('ctQTY'$count);

return 
$count;

5) emailMask
<?php
# email-Adressen vor bots durch Maskierung verbergen

$hiddenEmail '';
$length strlen($input);

for(
$i 0;$i<$length;$i++){
  
$hiddenEmail .= '&#'.ord($input[$i]).';';
}

return 
$hiddenEmail;

6) getGravatar
<?php
//Example
//[[!getGravatar? &email=`[[+gbemail:htmlent]]` &size=`40` &default=`[[++site_url]]/assets/content/images/Gravatar_40x40.jpg`]]
//or
//[[!getGravatar? &email=`[[+gbemail:htmlent]]` &size=`40` &default=``]]

$grav_url="https://www.gravatar.com/avatar/".md5(strtolower(trim($email)))."?d=".urlencode($default)."&amp;s=".$size;

return 
'<img src="'.$grav_url.'" class="img-polaroid" alt="" title="Gravatar" />';

7) smilies
<?php
# Output-Filter zur Darstellung der Smilies in den Kommentaren
# Z.B.:
# [[$smilieChunk:smilies]]
# oder
# [[+ctText:nl2br:smilies]]

// Path where the smilies are stored
$basepath $modx->config['site_url'].'assets/content/images/';
// Mapping of text to imagename
$smilies = array(':biggrin:' => 'biggrin.gif',':-)' => 'smile.gif',':-(' => 'mad.gif',':eek:' => 'bigeek.gif',':-P' => 'razz.gif',':oops:' => 'upset.gif',':cry:' => 'cry.gif',':evil:' => 'sigh.gif',':dead:' => 'dead.gif',':roll:' => 'rolleyes.gif',';-)' => 'wink.gif','8-)' => 'cool.gif',':-?' => 'confused.gif',':sleep:' => 'sleep.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 ($smilies as $key => $value)
{
$smilies[$key] = "<img src='".$basepath.$value."' class='smilie' alt='$key' />";
}
return 
str_replace(array_keys($smilies), array_values($smilies), $input);

8 ) vote
<?php
# Output-Filter zur Darstellung der Vote-Icons in den Kommentaren
# Z.B.:
# [[+ctVote:vote]]

$vote '';
for (
$i 1$i <= $input$i++) {
$vote .= '<img src="assets/content/images/vote-16x18.png" class="vote" alt="Bewertung" />';
}
return 
$vote;

9) bbcode
<?php
# Output-Filter zur Darstellung von BBCode in den Kommentaren
# Z.B.: [[+ctText:bbcode]]
# erlaubt ist:
# [b]fett[/b] [i]kursiv[/i] [u]unterstrichen[/u] [center]zentriert[/center]
# [link]https://jolichter.de[/link] oder [url]jolichter.de[/url]
#
$string preg_replace("/&amp;(#[0-9]+|[a-z]+);/i""&$1;"htmlspecialchars($input));
$string preg_replace('~\[b\](.+?)\[/b\]~is''<b>\1</b>'$string);
$string preg_replace('~\[i\](.+?)\[/i\]~is''<i>\1</i>'$string);
$string preg_replace('~\[u\](.+?)\[/u\]~is''<span style="text-decoration: underline;">\1</span>'$string);
$string preg_replace('~\[center\](.+?)\[/center\]~is''<div style="text-align:center;">\1</div>'$string);
$string preg_replace('~\[link\]www.(.+?)\[/link\]~is''<a href="https://www.\1" target="_blank">www.\1</a>'$string);
$string preg_replace('~\[link\](.+?)\[/link\]~is''<a href="\1" target="_blank">\1</a>'$string);
$string preg_replace('~\[link=(.+?)\](.+?)\[/link\]~is''<a href="\1" target="_blank">\2</a>'$string);
$string preg_replace('~\[url\]www.(.+?)\[/url\]~is''<a href="https://www.\1" target="_blank">www.\1</a>'$string);
$string preg_replace('~\[url\](.+?)\[/url\]~is''<a href="\1" target="_blank">\1</a>'$string);
$string preg_replace('~\[url=(.+?)\](.+?)\[/url\]~is''<a href="\1" target="_blank">\2</a>'$string);
$string str_replace(array("[","]","`"),array("[","&#93;","&#96;"),$string);
 
return 
$string;


#### chunks erstellen ####

10) modxComment
<hr>
<div class="span10">
<div class="comment">
Die eMail Adresse wird nur auf Wunsch angezeigt und Avatar Bilder werden verschlüsselt über <a href="https://de.gravatar.com/" target="_blank">Gravatar</a> bezogen.
</div>
<p>[[!$commentForm]]</p>


[[+ctQTY:gt=`0`:then=`<h3>[[!commentQty?active=`1`]] [[+ctQTY:eq=`1`:then=`Kommentar`:else=`Kommentare`]]</h3>`:else=``]]

[[!$commentEntry]]
</div>
diesen Chunk z.B. im Seitentemplate hinter [[*content]] per TV uncached einbinden
......
......
  [[*content]]
  [[*tvComment:is=`ja`:then=`[[!$modxComment]]`:else=``]]
......
......

11) commentSmilies
<div class="smilies">
  <div class="smilieSelect"><img src="assets/content/icons/biggrin.gif" alt="biggrin" onclick="AddSmile(':biggrin:')" /></div>
  <div class="smilieSelect"><img src="assets/content/icons/smile.gif" alt=":-)" onclick="AddSmile(':-)')" /></div>
  <div class="smilieSelect"><img src="assets/content/icons/mad.gif" alt=":-(" onclick="AddSmile(':-(')" /></div>
  <div class="smilieSelect"><img src="assets/content/icons/smilies2/bigeek.gif" alt=":eek:" onclick="AddSmile(':eek:')" /></div>
  <div class="smilieSelect"><img src="assets/content/icons/razz.gif" alt=":-P" onclick="AddSmile(':-P')" /></div>
  <div class="smilieSelect"><img src="assets/content/icons/upset.gif" alt=":oops:" onclick="AddSmile(':oops:')" /></div>
  <div class="smilieSelect"><img src="assets/content/icons/cry.gif" alt=":cry:" onclick="AddSmile(':cry:')" /></div>
  <div class="smilieSelect"><img src="assets/content/icons/sigh.gif" alt=":evil:" onclick="AddSmile(':evil:')" /></div>
  <div class="smilieSelect"><img src="assets/content/icons/dead.gif" alt="dead" onclick="AddSmile(':dead:')" /></div>
  <div class="smilieSelect"><img src="assets/content/icons/rolleyes.gif" alt=":roll:" onclick="AddSmile(':roll:')" /></div>
  <div class="smilieSelect"><img src="assets/content/icons/wink.gif" alt=";-)" onclick="AddSmile(';-)')" /></div>
  <div class="smilieSelect"><img src="assets/content/icons/cool.gif" alt="8-)" onclick="AddSmile('8-)')" /></div>
  <div class="smilieSelect"><img src="assets/content/icons/confused.gif" alt=":-?" onclick="AddSmile(':-?')" /></div>
  <div class="smilieSelect"><img src="assets/content/icons/sleep.gif" alt=":sleep:" onclick="AddSmile(':sleep:')" /></div>
</div>

12) commentAddSmile
<script>
function AddSmile(x,D,F){D=document;F=D.getElementById('ctText');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>

13) commentEmail
Neuer Kommentar gesendet von [[++site_url]] <br />

<br />[[+ctName:htmlent]] ([[+ctEmail:htmlent]]) hat als Kommentar geschrieben: <br /><br />
[[+ctText:htmlent:nl2br]]

<br /><br />

Bewertung: [[+ctVote]]<br />
Ort: [[+ctHome:htmlent]]<br />
Datenschutzerklärung akzeptiert: [[+datenschutz:is=`1`:then=`Ja`:else=`Nein`]]<br />

14) commentEntry
<div class="row"><div class="span12">
<span class="label">Seite [[+page]] von [[+pageCount]]</span>
</div></div>

[[!getPage:default=`Noch kein Eintrag vorhanden`?
   &element=`Rowboat`
   &table=`modx_custom_comment`
   &sortBy=`id`
   &sortDir=`DESC`
   &where=`[[!+modx.user.id:eq=`1`:then=`{"ctResource:=":"[[*id]]","ctActive:>=":"0"}`:else=`{"ctResource:=":"[[*id]]","ctActive:=":"1"}`]]`
   &totalVar=`rowboat.total`
   &cacheResults=`0`
   &tpl=`tplCommentEntry`
   &limit=`5`
   &pageFirstTpl=`<li class="control"><a[[+classes]][[+title]] href="[[+href]]">Erste</a></li>`
   &pageLastTpl=`<li class="control"><a[[+classes]][[+title]] href="[[+href]]">Letzte</a></li>`
]]

[[!+page.nav:notempty=`
    <div class="pagination">
        <ul>
        [[!+page.nav]]
        </ul>
    </div>
`]]
<hr>

15) commentForm
-> redirectTo Res.-Id anpassen (hier 4711)
-> Link zur Res.-Id Datenschutz anpassen (hier 666)
[[!FormIt?
  &preHooks=`StupidQuestion,db2formit`
  &hooks=`StupidQuestion,spam,email,formit2db,redirect`
  &prefix=`modx_custom_`
  &tablename=`comment`
  &packageName=`comment`
  &emailTpl=`commentEmail`
  &emailFrom=`deine-e-mail@server.tld`
  &emailTo=`deine-e-mail@server.tld`
  &redirectTo=`4711`
  &emailUseFieldForSubject=`1`
  &emailSubject=`Neuer Kommentar`
  &validationErrorMessage=`<p class="text-error">Die eingegebenen Daten waren nicht richtig: [[+errors]]</p>`
  &validate=`ctName:required:maxLength=`50`,
    ctEmail:email:required:maxLength=`60`,
    ctHome:required:maxLength=`50`,
    ctText:required:stripTags:maxLength=`1000`,
    ctVote:required,
    datenschutz:required,
    name2:blank`
]]

[[$commentAddSmile]]

<script>
     $(function(){
        function runEffect() {
    var options = {};
    $("#effect").toggle("blind", options, 500);
    };
     $('#ctEinblenden').click(function() {
     $('#ctAusblenden').slideToggle("slow");
    $(this).toggleClass("ct");
    if($(this).hasClass("ct")){
                    $(this).html("<i class='icon-comment'></i> Eintrag ausblenden");
                }else{
                    $(this).html("<i class='icon-comment icon-white'></i> Eintrag hinzufügen");
                }
            runEffect();
        return false;
    });
        });
</script>

<div class="row"><div class="span4">
<button id="ctEinblenden" class="btn btn-primary" type="button"><i class="icon-comment icon-white"></i> Eintrag hinzufügen</button>
<div id="ctAusblenden" style="display:none;"> 

<h2>Kommentar Eintrag</h2>

[[!+fi.error.error_message:notempty=`<p>[[!+fi.error.error_message]]</p>`]]
<form action="[[~[[*id]]]]" method="post" class="form">
<input type="hidden" name="name2" value="" />
<input name="resource_id" type="hidden" value="[[+fi.id]]" />
[[- IP aktiviere ich nur kurzeitig bei Bedarf, z.B. bei DDoS-Attacken um diese dann zu sperren]]
[[- !+modx.user.id:eq=`1`:then=``:else=`<input type="hidden" name="ctIp" value="[[!myIP?]]" />`]]
<input type="hidden" name="ctResource" value="[[*id]]" />

<table class="table table-hover table-condensed">

  [[!+modx.user.id:eq=`1`:then=`
    <tr><th>Aktiv:</th>
    <th>
    <input type="hidden" name="ctActive[]" value="0" />
    <input type="checkbox" name="ctActive[]" id="ctActive" value="1" [[!+fi.ctActive:FormItIsChecked=`1`]] />
    Kommentar veröffentlichen? <br /><br />
    </th></tr>
  `:else=``]]

  <tr><th><label for="ctName">Name:</label></th>
  <th><input required type="text" name="ctName" id="ctName" maxlength="50" value="[[!+fi.ctName:htmlent]]" />
  <img src="assets/content/images/text-x-vcard.png" alt="icon" width="16" height="16" />
  <span class="text-error">[[+fi.error.ctName]]</span>
  </th></tr>

  <tr><th><label for="ctHome">Wohnort:</label></th>
  <th><input required type="text" name="ctHome" id="ctHome" maxlength="50" value="[[!+fi.ctHome:htmlent]]" />
  <img src="assets/content/images/user-home.png" alt="icon" width="16" height="16" />
  <span class="text-error">[[+fi.error.ctHome]]</span>
  </th></tr>

  <tr><th><label for="ctEmail">eMail:</label></th>
  <th><input required type="email" name="ctEmail" id="ctEmail" maxlength="50" value="[[!+fi.ctEmail:htmlent]]" />
  <img src="assets/content/images/mail-message.png" alt="icon" width="16" height="16" />
  <span class="text-error">[[+fi.error.ctEmail]]</span><div class="smallfont">
  <input type="hidden" name="ctEmailshow[]" value="0" />
  <input type="checkbox" name="ctEmailshow[]" id="ctEmailshow" value="1" [[!+fi.ctEmailshow:FormItIsChecked=`1`]] /> eMail wird  veröffentlicht!<br /><br /></div>
  </th></tr>

  <tr><th><label for="ctVote">Bericht Bewertung:</label></th>
  <th>
    <select required tabindex="0" name="ctVote" id="ctVote">
    <option value="[[+fi.ctVote]]">Bitte bewerten</option>
    <option value="5">5 Sterne</option>
    <option value="4">4 Sterne</option>
    <option value="3">3 Sterne</option>
    <option value="2">2 Sterne</option>
    <option value="1">1 Sterne</option>
    </select>
  <img src="assets/content/images/vote-16x18.png" alt="vote" width="16" height="16" />
  <span class="text-error">[[+fi.error.ctVote]]</span>
  </th></tr>

  <tr><th></th>
  <th class="smallfont"><br/>Mit <a href="[[~[[*id]]]]#" data-rel="popover-top" class="btn-min btn-success" data-content="
    erlaubt ist:<br />
    [b]fett[/b]<br />
    [i]kursiv[/i]<br />
    [u]unterstrichen[/u]<br />
    [center]zentriert[/center]" data-original-title="BBCode">BBCode</a> könnt ihr euren Text strukturieren<br />
  </th></tr>

  <tr><th><label for="ctText">Kommentar:<br />
  <span class="smallfont">(max. 1000 Zeichen)</span></label>
  <img src="assets/content/images/text-x-generic.png" alt="icon" width="24" height="24" /></th>
  <th><textarea required class="span5" name="ctText" id="ctText" cols="50" rows="6">[[!+fi.ctText:htmlent]]</textarea>
  <span class="text-error">[[+fi.error.ctText]]</span>
  </th></tr>

  <tr><th></th>
  <th>[[$commentSmilies]]
  </th></tr>

  <tr><th><label for="datenschutz">Datenschutz:</label></th>
  <th><input type="hidden" name="datenschutz[]" value="0" />
  <input required type="checkbox" name="datenschutz[]" id="datenschutz" value="1" [[!+fi.datenschutz:FormItIsChecked=`1`]] /> Ich bin damit einverstanden, dass mein Kommentar veröffentlicht wird und habe die Datenschutzerklärung im <a href="[[~666]]" target="_blank">Datenschutz</a> gelesen und akzeptiert.
  <span class="text-error">[[+fi.error.datenschutz]]</span>
  </th></tr>

  <tr><th></th>
  <th>[[!+formit.stupidquestion_html]]
  </th></tr>

  <tr><th></th>
  <th>
  [[!+modx.user.id:eq=`1`:then=`
  <button type="submit" class="btn btn-large btn-success">
  <i class="icon-play icon-white"></i> Eintrag hinzufügen ([[+fi.id]])</button>
  `:else=`
  <button type="submit" class="btn btn-large btn-success">
  <i class="icon-play icon-white"></i> Eintrag hinzufügen</button>
  ` ]]
  </th></tr>

  </table>
</form>

</div>
</div></div>

redirectTo Ressource
-> Hide From Menus and Published!
<h3 style="text-align: center;">Vielen Dank für deinen Kommentar</h3>
<br />
Solltest du deinen Beitrag löschen oder korrigieren wollen, melde dich bitte per Kontaktformular und wir werden das schnellstmöglich korrigieren.
<br /><br />
<script>
    document.write('<a href="' + document.referrer + '"><div class="back">Zurück zum Bericht</div></a>');
</script>

16) tplCommentEntry
-> im Chunk die Ressource "delete Comment" durch eure Res.-Id ersetzen (hier 815)
<div class="comment_top">
    <span class="comment_big">
      [[+ctName:htmlent]]
    </span>
    <span class="comment_small">
      am [[+ctDate:strtotime:date=`%d-%m-%Y, %H:%M Uhr`]] aus [[+ctHome:htmlent]]
    </span>
    <span class="comment_vote">
          [[+ctVote:vote]]
    </span>
</div>

<div [[+_alt:is=`0`:then=`class="comment_content"`:else=`class="comment_content_b"`]]>
        <span class="ctEmail">
        [[+ctEmailshow:eq=`1`:then=`<a href="[[emailMask? &input=`mailto:[[+ctEmail:htmlent]]`]]"><i class="icon-envelope" title="eMail"></i></a>`:else=``]]
        [[!+modx.user.id:eq=`1`:then=`<br />[[+ctEmail:htmlent:emailMask]]`:else=``]]
        </span>
        <p class="gbavatar">[[!getGravatar? &email=`[[+ctEmail:htmlent]]` &size=`40` &default=``]]</p>
        [[+ctText:htmlent:bbcode:nl2br:smilies]]
        [[!+modx.user.id:eq=`1`:then=`
        <br /><a href="[[~[[*id]]? &resId=`[[+id]]`]] ">Eintrag ändern</a><br />
         [[+ctActive:eq=`1`:then=``:else=`<font color=#990000>Kommentar ist deaktiviert || <a href="[[~815]]">alle deaktivierte löschen!</a></font>`]]
        `:else=``]]
</div>

Ressource "delete Comment"
-> im Chunk die Ressource "delete all disabled comments" durch eure Res.-Id ersetzen (hier 4711)
-> Hide From Menus and not Published!
<h2 style="text-align: center;">Warnung!</h2>
<p style="text-align: center;">Hier werden alle deaktivierte Kommentare gelöscht!</p>
[[!+modx.user.id:memberof=`Administrator`:then=`
<div class="back"><a href="[[~4711]]"><font color=#990000>Achtung! Jetzt diese Kommentare löschen?</font></a></div>
`:else=`Sorry, du bist kein Admin!`]]
<div class="back"><a href="[[~[[++site_start]]]]">Abrechen!</a></div>

[[!getPage:default=`Es ist kein deaktivierter Eintrag vorhanden!`?
  &element=`Rowboat`
  &table=`modx_custom_comment`
  &sortBy=`id`
  &sortDir=`DESC`
  &where=`[[!+modx.user.id:memberof=`Administrator`:then=`{"ctActive":"0"}`:else=`{"ctActive":"9"}`]]`
  &totalVar=`rowboat.total`
  &cacheResults=`0`
  &tpl=`tplCommentEntryDel`
  &limit=`10`
  &pageFirstTpl=`<li class="control"><a[[+classes]][[+title]] href="[[+href]]">Erste</a></li>`
  &pageLastTpl=`<li class="control"><a[[+classes]][[+title]] href="[[+href]]">Letzte</a></li>`
]]

[[!+page.nav:notempty=`
    <div class="pagination">
        <ul>
        [[!+page.nav]]
        </ul>
    </div>
`]]

Ressource "delete all disabled comments"
-> Hide From Menus and not Published!
[[!+modx.user.id:memberof=`Administrator`:then=`[[!commentEntryDel]]`:else=`keine Berechtigung!`]]
<div class="back"><a href="[[~[[++site_start]]]]">Zur Startseite</a></div>

17) CSS Zeilen für das Seitentemplate (Beispiel)
/* Comments */
#ctText, #ctName, #ctHome, #ctEmail {padding: 2px; clear: right}
#ctEinblenden, #ctAusblenden {text-align: center; font-size: 14px; font-weight: bold; margin: 0; padding: 2px 5px 2px 5px}
#ctAusblenden label {margin-right: 50px}
.smilie {vertical-align: sub}
.smilies {margin: 5px 2px 40px 0px}
.smilieSelect {float: left; margin-right: 5px}
.comment_top {background-color: #123; height: 26px; color: #fff; margin-right: 10px; padding: 2px 5px 2px 5px}
.comment_big {float: left; font-size: 16px; font-weight: bold; margin-right: 10px; padding-top: 4px}
.comment_small {float: left; font-size: 10px; font-weight: bold; padding: 6px 0 0 0}
.ctAvatar {float:left; padding-right: 5px; margin-left: -60px}
.comment_content {min-height: 80px; margin: 0 10px 20px 0; padding: 5px 30px 0 40px; clear: right; background: #fff;  border-width: 1px; border-style: solid; border-color: #123}
.comment_content_b {min-height: 80px; margin: 0 10px 20px 0; padding: 5px 30px 0 40px; clear: right; background: #f4f4f4;  border-width: 1px; border-style: solid; border-color: #123}
.comment_content .ctEmail, .comment_content_b .ctEmail {float: right; margin: 0 -20px 0 20px; color: green; font-size: 10px}
.comment_vote  {float: right; padding: 3px 5px 3px 0; width: 100px}
.comment_vote .vote {float: right; padding: 0px 1px 0; width: 16px; height: 16px}