Ein Kommentarsystem für Kirby

Natürlich dürfen auch Kommentare in diesem Blog nicht fehlen. Da Kirby, das CMS welches wir verwenden, dies aber von Haus aus nicht kann, habe ich kurzerhand einfach selbst ein System dafür geschrieben. Wie auch du es einsetzen kannst, erklärt dieser Beitrag.

Grundprinzip

Kirby hat, im Gegensatz zu vielen anderen Content-Management-Systemen kein Kommentarsystem ab Werk eingebaut. Zwar gibt es bereits einige Plugins für Kirby, allerdings noch keine Möglichkeit, Kommentare nachzurüsten. Das größte Problem dabei dürfte sein, dass Kommentare sehr weit in die Templatestruktur eingreifen und somit nicht wirklich als Plugin realisierbar ist. Deswegen werde ich diese Anleitung auch nicht zum Beispiel bei GitHub bereitstellen, wer es nutzen möchte, sollte sich den Blogbeitrag durchlesen.

Die wichtigste Frage ist natürlich, wo die Kommentare gespeichert werden sollen. Wordpress und co. nutzen ja bekanntlich eine Datenbank, mit der das kein Problem ist. Da Kirby aber ein Flat-File-System ist, fällt diese Möglichkeit weg.

Eine weitere Option wäre es, für jeden Kommentar eine (versteckte) Unterseite anzulegen, welche die nötigen Daten enthält, und diese dann in den Artikel einzubinden. Dies ist aber zugegebenermaßen mit Kanonen auf Spatzen geschossen.

Warum nutzt man nicht einfach Disqus oder isso? Mir war wichtig, die Kommentare auch direkt im Kirby verwalten zu können. Bei Disqus kommt zudem noch hinzu, dass die Kommentare und persönlichen Daten auf Servern in den USA gespeichert werden. Beide Lösungen haben außerdem den Nachteil, erst nach Aufruf der Seite geladen zu werden, sodass Google die Kommentare schlecht indizieren kann, außerdem kann ich zum Beispiel kein neueste Kommentare-Widget in der Seitenleiste einblenden, wenn ich die Kommentare nicht bei mir direkt im System speichere.

Glücklicherweise lassen sich auch strukturierte Daten in YAML-Form für eine Seite abspeichern. Das dazu passende Panelfeld ist structure. Hier können nun für jeden Kommentar die zu speichernden Daten gesichert werden. Neben dem Kommentar selbst speichere ich noch den angegebenen Namen, das Datum, eine fortlaufende ID, Eltern-Kommentar (dazu später mehr) und ob der Kommentar freigeschaltet ist. Natürlich lassen sich auch sehr einfach weitere Felder speichern, beispielsweise eine Mailadresse. Darauf habe ich für netzleben aber bewusst verzichtet.

Vorbereitungen

Bevor das Kommentarsystem eingebaut werden kann, braucht es natürlich ein paar Vorbereitungen. Als erstes solltest du dir überlegen, wie die Kommentare auf der eigenen Seite dargestellt werden sollen. Dazu baue dir am Besten in reinem HTML eine Kommentar-Eingabe-Maske. Außerdem sollten auch einige Beispielkommentare mit eingebaut werden. In meinem System kann man auch auf Kommentare antworten, sodass ein "Kind-Kommentar" entsteht. Kommentare können also auf zwei Ebenen vorhanden sein, das solltest du beachten, wenn du dir die Kommentardarstellung überlegst.

Im nächsten Schritt solltest du für dein Artikel-Blueprint schon einmal das Feld für die Kommentare anlegen. Dazu kannst du zum Beispiel meine Vorlage nutzen:

fields:
  comments:
    label: Kommentare
    type: structure
    style: table
    fields:
      nummer:
        label: ID
        type: number
      parent:
        label: Parent
        type: number
      approved:
        label: geprüft
        type: number
      datum:
        width: 1/2
        label: Datum
        type: date
        default: today
        validate: date
      zeit:
        width: 1/2
        label: Zeit
        type: time
        default: now
        validate: time
        interval: 1
      name:
        label: Kommentator Name
        type: text
      message:
        label: Kommentar
        type: textarea

Wenn du im Panel nun einen Artikel aufrufst, kannst du direkt sehen, dass nun Kommentare machbar sind. Natürlich ist das Feld aber noch leer - ich empfehle dir, einige Dummy-Kommentare anzulegen, damit du für die Entwicklung auch passende Daten hast.

Auf der Artikelseite

Du hast dir ja bereits (hoffentlich) deinen HTML-Code für die Kommentardarstellung überlegt und idealerweise diesem auch schon etwas Styling per CSS verpasst. Nun geht es daran, die Kommentare auch darstellen zu können. Da ich deine Struktur nicht kenne, poste ich einfach mal meine, die relevanten Teile musst du dir dann entsprechend rauskopieren. Wenn du Bootstrap einsetzt hast du Glück, dann dürftest du relativ problemlos alles übernehmen können.

Zuerst die Kommentareingabemaske. Hier frage ich bewusst nur nach einem Namen und dem Kommentar selbst, um Kommentare abgeben zu können ist es einfach nicht notwendig, seine Mail mit anzugeben, also habe ich das gleich ganz weg gelassen. Das ist jetzt natürlich nur ein einfaches Formular, du könntest zum Beispiel einen Markdown-Editor wie SimpleMDE mit integrieren, da das Textfeld auch Markdown speichern kann.

<div id="kommentar-hinterlassen" class="media-blog">
    <div class="row">
        <div class="col-sm-12">
            <h3 class="title-md hr-left mb-40">Hinterlasse einen Kommentar</h3>
            <form>
                <input type="hidden" id="parent" name="parent" value="0">
                <p id="antworten-auf"></p>
                <?php if ($alert): ?>
                    <div class="alert">
                        <ul>
                            <?php foreach ($alert as $message): ?>
                                <li><?php echo html($message) ?></li>
                            <?php endforeach ?>
                        </ul>
                    </div>
                <?php endif ?>
                <?php if (get('kommentar') != null): ?>
                    <div class="alert">
                        <ul>
                            <li><?php if (get('kommentar') == 'moderation') echo 'Dein Kommentar muss erst freigegeben werden, bevor er angezeigt wird.'; ?></li>
                        </ul>
                    </div>
                <?php endif ?>
                <label for="name">Name <abbr title="benötigt">*</abbr></label>
                <div class="row">
                    <div class="col-md-7 col-md-offset-0">
                        <input type="text" id="name" name="name" class="form-control">
                    </div>
                </div>

                <label for="message">Kommentar <abbr title="benötigt">*</abbr></label>
                <div class="row">
                    <div class="col-md-11 col-md-offset-0">
                        <textarea id="message" name="message" class="form-control" rows="8" required>
                   </div>
                </div>

                <p>
                    <input class="btn btn-primary" type="submit" name="submit" value="Abschicken">
                </p>

            </form>

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

Eventuell ist dir hier ein verstecktes Eingabefeld aufgefallen: Wenn jemand auf "hierauf antworten" bei einem bestehenden Kommentar klickt, wird dieses Feld per JavaScript automatisch gefüllt, sodass im Formular die ID des beantworteten Kommentars mitgeliefert wird. Das dazu nötige Schnipselchen JavaScript möchte ich dir natürlich auch nicht vorenthalten.

function antworten(nummer, name) {
    $('#antworten-auf').text("Antwort an "+name);
    $('#parent').val(nummer);
}

Anschließend auch die Ausgabe der bisher vorhandenen Kommentare. Natürlich wird dieser Block nur angezeigt, wenn auch wirklich Kommentare vorhanden sind, ebenfalls werden nur vorher freigeschaltete Kommentare dargestellt.

<?php if ($page->comments()->toStructure()->count() > 0): ?>
   <div class="media-blog">
       <div class="row">
           <div class="col-sm-12">
               <h3 class="title-md hr-left mb-40">Kommentare</h3>

               <?php foreach ($page->comments()->toStructure() as $comment):
                   if ($comment->parent() != "0") continue;
                   $datum = strtotime($comment->datum() . ' ' . $comment->zeit());
                   if ($comment->approved() == "0") continue;
                   ?>
                   <?php if ($comment->nummer() != "1") echo '<hr>'; ?>
                   <div class="media" id="<?php echo "kommentar" . $comment->nummer(); ?>">

                       <div class="media-body">
                           <h4 class="media-heading"><?php echo htmlspecialchars($comment->name()) ?></h4>
                           <ul class="list-unstyled list-inline post-date-author">
                               <li>
                                   <a href="#<?php echo "kommentar" . $comment->nummer(); ?>"><?php echo strftime("%e. %B %G, %H:%M", $datum); ?></a>
                               </li>
                           </ul>
                           <?php echo markdown(htmlspecialchars($comment->message())); ?>
                           <a class="display-block" href="#kommentar-hinterlassen" onclick="antworten(<?= $comment->nummer() ?>, '<?= $comment->name() ?>');"><small><i class="fa fa-reply mr-8"></i>Hierauf antworten</small>

                               <?php foreach($page->comments()->toStructure() as $childcomment):
                                   if ($childcomment->parent()->int() != $comment->nummer()->int()) continue;
                                   $datum = strtotime($childcomment->datum() . ' ' . $childcomment->zeit());
                                   if ($childcomment->approved() == "0") continue; ?>
                                   <div class="media" id="<?php echo "kommentar" . $childcomment->nummer(); ?>">

                                       <div class="media-body" style="padding-left: 50px;">
                                           <h4 class="media-heading"><?php echo htmlspecialchars($childcomment->name()) ?></h4>
                                           <ul class="list-unstyled list-inline post-date-author">
                                               <li>
                                                   <a href="#<?php echo "kommentar" . $childcomment->nummer(); ?>"><?php echo strftime("%e. %B %G, %H:%M", $datum); ?></a>
                                               </li>
                                           </ul>
                                           <?php echo markdown(htmlspecialchars($childcomment->message())); ?>
                                           <a class="display-block" href="#kommentar-hinterlassen" onclick="antworten(<?= $comment->nummer() ?>, '<?= $childcomment->name() ?>');"><small><i class="fa fa-reply mr-8"></i>Hierauf antworten</small>
                                       </div>
                                   </div>

                               <?php endforeach; ?>

                       </div>
                   </div>

               <?php endforeach; ?>

           </div>
       </div>
   </div>
<?php  endif; ?>

Kommentare absenden

Die schönste Eingabemaske nützt nichts, wenn die Daten nicht auch im System gespeichert werden. Hier kommen Kirbys Controller zum Einsatz, welche die entsprechende Logik enthalten. Beim Aufruf der Seite wird automatisch geschaut, ob Parameter (also entsprechende Daten) übergeben wurden. Ist dies der Fall, springt der Controller ein und verarbeitet entsprechend. Wenn nicht bzw. die Verarbeitung abgeschlossen ist, wird dann das normale Template befüllt und ausgegeben. Das heißt, dass das Formular die Daten an die eigene Seite absenden muss, was aber im obrigen Beispiel schon der Fall ist. Fehlt also noch der Controller (zu platzieren unter site\controllers\artikel.php, wenn dein Artikel-Template ebenfalls artikel.php heißt), der die Eingaben dann im Structure-Field abspeichert:

<?php
return function ($site, $pages, $page) {
    $alert = null;
    if (get('submit')) {

        $data = array(
            'name' => filter_var(get('name'), FILTER_SANITIZE_STRING),
            'message' => filter_var(get('message'), FILTER_SANITIZE_STRING),
            'datum' => date('Y-m-d'),
            'zeit' => date('H:i'),
            'approved' => 0,
            'parent' => filter_var(get('parent'), FILTER_SANITIZE_STRING),
            'nummer' => $page->comments()->toStructure()->count() + 1
        );
        $rules = array(
            'name' => array('required'),
            'message' => array('required', 'min' => 1, 'max' => 4096),
        );
        $messages = array(
            'name' => 'Please enter a valid name',
            'message' => 'Please enter a text between 1 and 1024 characters'
        );

        function sonderzeichen($string)
        {
            $search = array("�", "�", "�", "�", "�", "�", "�", "�");
            $replace = array("Ae", "Oe", "Ue", "ae", "oe", "ue", "ss", "");
            return str_replace($search, $replace, $string);
        }

        // some of the data is invalid
        if ($invalid = invalid($data, $rules, $messages)) {
            $alert = $invalid;
        } else {

            try {
                $comments = $page->comments()->yaml();
                $comments[] = $data;

                page()->update(array(
                    'comments' => yaml::encode($comments),
                ));
                go($page->uid() . "?kommentar=moderation");

            } catch (Exception $e) {
                echo $e->getMessage();
            }

        }
    }
    return compact('alert');
};

Hier kann auch zum Beispiel konfiguriert werden, ob die Kommentare moderiert werden sollen, indem der default-Wert von approved auf 1 oder 0 gesetzt wird. Nur Kommentare, die auch eine 1 haben, werden dann unter dem Artikel angezeigt.

Abschluss

Das Kommentarsystem ist nun prinzipiell funktionsfähig. Kommentare können angezeigt werden, ebenso lassen sich neue Kommentare speichern. Aber vielleicht ist es dir aufgefallen: Kommentare müssen moderiert werden, dazu wäre natürlich ein Hinweis per Mail ganz sinnvoll. Wie genau das geht und wie man die Kommentare einfach moderieren kann, folgt im zweiten Teil. Bis dahin aufgekommene Fragen beantworte ich natürlich gerne in den Kommentaren. :-)

Facebook Twitter Google+ WhatsApp

von Florian Schmidt

Gründer von Netzleben. Studiert IT System Engineering am Hasso-Plattner-Institut in Potsdam. Kauft viel zu viel Technik online ein. Liebt Kekse.

Hinterlasse einen Kommentar

Kommentare

||| tomas jay

Hallo Florian,

super Tutorial! Ich werde das gleich mal selbst ausprobieren. Mir waren bisher die Nachteile extern eingebundener Kommentarsysteme gar nicht bewusst. Und natürlich ist es schöner, wenn alles aus einem Guss ist. Und weil ich dein Engagement sehr schätze, gehen ab heute alle Amazon-Bestellungen unseres Unternehmens über den Affiliate-Link raus. Wir bestellen dort zwar nicht oft, aber Kleinvieh macht auch Mist.

Viele Grüße,
Thomas

Hierauf antworten

schmidtflo

Vielen Dank für dein Kompliment und deine Unterstützung!

Hierauf antworten

Julian

Hi Florian,
ich bin gerade dabei, deinen Code auf meine Kirby-Instanz zu portieren. Dabei ist mir aufgefallen, dass die Kommentare momentan nicht für Unterseiten (domain.tld/blog/artikel) fähig sind. Braucht aber nur ne kleine Modifikation im Controller:

go($page->uid() . &#34;?kommentar=moderation&#34;);

muss zu

go($page->uri() . &#34;?kommentar=moderation&#34;);

geändert werden. Ansonsten funktioniert alles perfekt, danke dafür!

Viele Grüße,
Julian

Hierauf antworten

Hendrik

Moin,
vielen Dank für die Infos.
Leider scheitere ich aktuell an der Umsetzung. Füge ich den ersten PHP-Teil in meine site/templates/article.php, dann wird diese nur bis einschließlich zum Textfeld ausgeführt/angezeigt. Die Schaltfläche kommt nicht mehr. Und: Wo muss der JavaScript-Schnipsel hin?
Danke und Grüße

Hierauf antworten

schmidtflo

Hey Hendrik,

also, die Eingabemaske für Kommentare ist sichtbar, der Abschicken-Button aber nicht? Dann stimmt an deinem HTML irgendwas nicht, da dazwischen ja kein PHP kommt, bei dem es scheitern könnte.

Der JS-Code muss natürlich so eingebaut werden, dass er an der Stelle verfügbar ist. Da ich deine Dateistruktur nicht kenne, müsstest du aber eigentlich auch selbst wissen, wo dein JS eingebunden wird.

LG
Florian

Hierauf antworten