Ein Private-Message - System bereitstellen: Nutzer senden sich gegenseitig Nachrichten

Der Tabellen-Sondertyp PM-Tabelle ermöglicht es, daß ein Administrator einer Datenbank seinen Nutzern eine Private-Message - Logik (PM oder Private Nachrichten / PN) bereitstellt. Dies bezeichnet die Möglichkeit, daß sich Nutzer untereinander Nachrichten zusenden können, ohne daß sie die Mailadressen ihrer Empfänger sehen. Ferner bleiben die Nachrichten immer im System.

Grundlagen für PM-Systeme

Für die Erstellung einer PM-Tabelle müssen ein Tabellenname und der Sondertyp PM-Tabelle festgelegt werden. Anschließend sind weitere Spalten hinzuzufügen (Betreff, Nachrichtentext usw.), dann wird die Tabelle gespeichert. Beim Speichern werden die folgenden Spalten hinzugefügt:
  • pm_date / Datum: Zeitpunkt der Nachrichten-Versendung
  • pm_from / Absender: Nutzer-Id des Absenders der PM
  • pm_to / Empfänger: Nutzer-Id des Empfängers der PM
  • pm_type / Typ: Integer-Wert mit einfacher Relation: 1;sender;2;recipient
  • pm_action / Aktion: Integer-Wert mit einfacher Relation: 1;send;2;save: Pflichtwert bei der Erstellung, Standardwert 'send'. Ermöglicht alternativ zunächst ein Speichern und späteres Versenden der Mail
  • pm_state / Status: Integer-Wert mit einfacher Relation: 1;new;2;read: Neue empfangene Mails werden mit 'new' ausgegeben. Der Abruf einer neuen Mail durch den Empfänger schaltet diese auf 'read'.
Die einfachste Variante einer PM-Logik würde zwei weitere Spalten - Betreff / nvarchar(50) und Nachricht / nvarchar(500) - hinzufügen. Damit umfaßt eine Meldung Betreff und Nachricht. Es ist jedoch auch denkbar, daß eine Nachricht viele Spalten mit anderen Datentypen umfaßt.

Eine neue PM entspricht einem neuen Datensatz, der über die internen Masken oder über eine passende Ausgabeseite eingegeben wird. Hier muß die pm_to - Spalte mit dem Namen eines Nutzers belegt werden. Für diese Spalte wird intern eine Relation auf die interne Nutzertabelle definiert, so daß die Nutzer-Ids gespeichert und ein Klartextname ausgewählt werden kann. Die Spalte pm_date wird mit dem aktuellen Serverdatum, pm_from mit der ID des aktuellen Nutzers belegt. Ferner wird die pm_type - Spalte mit dem Wert 1 belegt. Falls der Nutzer das sofortige Versenden der Nachricht wünscht, wird die Zeile nach dem Speichern kopiert. Der Besitzer der zweiten Zeile ist der Empfänger der Nachricht, pm_type wird auf 2 gesetzt, pm_state auf 'new'. Zum Testen kann man sich auch Nachrichten an sich selbst senden.

Man erstelle als Administrator eine PM-Tabelle mit den beiden zusätzlichen Spalten 'Betreff' und 'Mitteilung'. Anschließend erstelle man in der internen Eingabe (Menü 1: Daten) eine neue Zeile. Nach dem Speichern und dem erneuten Suchen werden zwei Zeilen ausgegeben, die sich nur im Besitzer und dem pm_type unterscheiden.

Anforderungen an die Ausgabeseiten

Die direkte Nutzung der internen Masken ist - aufgrund dieser Verdoppelung der Datensätze - zumindest für Administratoren mit globalem Leserecht nicht zu empfehlen. Denn diese können immer alle Datensätze lesen, so daß sie sich ihre eigenen Nachrichten immer erst gezielt herausfiltern müssen. Alternativ können einfache Ausgabeseiten erstellt werden, welche die Postfach-Funktionalität bereitstellen. Für diese sind drei Einheiten notwendig: Eine Eingabe für neue PMs, eine Liste der bereits versandten PMs sowie eine Liste der erhaltenen PMs. Zu ersterem genügt es, bsp. die explizite Variante einer Eingabemaske zu erstellen und die Eingabefelder pm_date, pm_from, pm_type und pm_state zu löschen. Der Nutzer erhält nur die Möglichkeit, einen Empfänger, die Aktion (Senden/Speichern) sowie die zusätzlich definierten Spalten auszufüllen.

Für die Listen von versendeten und empfangenen Nachrichten werden am besten zwei Views deklariert. Diese enthalten die Spalten pm_date, pm_to bzw. pm_from sowie die benutzerdefinierten Spalten. Der View für gesendete Nachrichten enthält zusätzlich 'pm_action', der View für die empfangenen Nachrichten zusätzlich 'pm_state'. Pro View wird ein sd:rs -Element deklariert und diesem zwei sd:ro-value -Elemente zugeordnet:

  • Liste der versendeten Nachrichten:
    • <sd:ro-value sd:for='Name des sd:rs-Elements' sd:col='pm_type' sd:std-value='1'/>
    • <sd:ro-value sd:for='Name des sd:rs-Elements' sd:col='pm_from' sd:std-value='USER_ID'/>
  • Liste der erhaltenen Nachrichten:
    • <sd:ro-value sd:for='Name des sd:rs-Elements' sd:col='pm_type' sd:std-value='2'/>
    • <sd:ro-value sd:for='Name des sd:rs-Elements' sd:col='pm_to' sd:std-value='USER_ID'/>
Damit werden die entsprechenden Spalten vor dem Ausführen einer Suche mit der ID des aktuellen Nutzers bzw. dem Wert für versendete bzw. empfangene Nachrichten belegt. Die Suche gibt höchstens alle diesen Nutzer betreffenden Nachrichten zurück und ermöglicht weitere Sucheinschränkungen durch den Nutzer.

Ein kleines Beispiel: Eingabe und beide Ausgaben auf einer Seite

Das folgende Beispiel basiert auf der Tabelle 'pm_table' mit den beiden benutzerdefinierten Spalten Betreff (nvarchar(50)) und Message (nvarchar(500)). Es nutzt zwei sd:rs -Elemente mit Namen 'sample-gesendet' und 'sample-empfangen'. Diesen liegen zwei Views 'pm_gesendet' und 'pm_empfangen' zugrunde. Die Views enthalten die Spalten Betreff, pm_date und pm_to / pm_action bzw. pm_from / pm_state. Die ID-Spalte wird immer hinzugefügt. Die sd:result-steps -Elemente sind die Standardelemente und unterscheiden sich nur durch den Bezug auf die sd:rs -Elemente.
<sd:ro-value sd:for="sample-gesendet" sd:col="pm_type" sd:std-value="1" />
<sd:ro-value sd:for="sample-gesendet" sd:col="pm_from" sd:std-value="USER_ID" />

<sd:ro-value sd:for="sample-empfangen" sd:col="pm_type" sd:std-value="2" />
<sd:ro-value sd:for="sample-empfangen" sd:col="pm_to" sd:std-value="USER_ID" />

<div class="sd-text-container"><h3>Postfach</h3></div>
<div class="sd-db-container">
  <sd:form sd:name="myForm">
    <div class="sd-input-part">
      <div class="sd-input">
        <sd:input-table sd:name="sample-input"
          sd:table="pm_table" sd:rel-format="auto">
          <table>
            <tr>
              <td>
                <sd:label sd:col="Betreff" />
              </td>
              <td>
                <sd:input-text sd:col="Betreff" />
              </td>
            </tr>
            <tr>
              <td>
                <sd:label sd:col="Message" />
              </td>
              <td>
                <sd:input-text sd:col="Message" />
              </td>
            </tr>
            <tr>
              <td>
                <sd:label sd:col="pm_to" />
              </td>
              <td>
                <sd:input-text sd:col="pm_to" />
              </td>
            </tr>
            <tr>
              <td>
                <sd:label sd:col="pm_action" />
              </td>
              <td>
                <sd:input-text sd:col="pm_action" />
              </td>
            </tr>
          </table>
        </sd:input-table>
      </div>
      <p />
      <div class="sd-button-list">
        <span style="padding:3px">
          <sd:button sd:name="myButton" sd:source="sample-input"
            sd:result="sample-gesendet">
            <sd:label-search>Suche</sd:label-search>
            <sd:label-save>Speichern</sd:label-save>
          </sd:button>
        </span>
        <span style="padding:3px">
          <sd:button sd:name="reset">
            <sd:label-reset>Reset</sd:label-reset>
          </sd:button>
        </span>
        <span style="padding:3px">
          <sd:switch-link sd:for="sample-input">
            <sd:label-new>Neue Nachricht</sd:label-new>
            <sd:label-del>Löschen</sd:label-del>
          </sd:switch-link>
        </span>
      </div>
      <p />
      <sd:err-message />
    </div>
    <div class="sd-output-part">
      <div class="sd-output">
        <h3>Gesendete Nachrichten</h3>
        <table border="">
          <tr>
            <sd:col-set sd:for="sample-gesendet">
              <sd:normal>
                <th style="text-align:left">
                  <sd:col-name /> <sd:sort />
                </th>
              </sd:normal>
            </sd:col-set>
          </tr>
          <sd:rs sd:name="sample-gesendet" sd:edit="sample-input"
            sd:source-type="view" sd:sc="pm_tableId"
            sd:so="2" sd:source-name="pm_gesendet">
            <sd:normal>
              <tr>
                <sd:cell-set>
                  <sd:normal>
                    <td>
                      <sd:cell-value
                        sd:datetime-format="dd.MM.yyyy HH:mm:ss" />
                    </td>
                  </sd:normal>
                </sd:cell-set>
                <td>
                  <sd:del-link>Löschen</sd:del-link>
                </td>
              </tr>
            </sd:normal>
          </sd:rs>
        </table>
      </div>
      <p />
      <div class="sd-result-steps">
        <sd:result-steps sd:for="sample-gesendet"
          sd:page-number-style="relative" sd:name="result-steps">
          <table cellpadding="2" cellspacing="1"
            class="sd-step-buttons" style="border-bottom:3px solid #ececcc;">
            <tr>
              <sd:begin>
                <td>
                  <sd:result-link>|&lt;</sd:result-link>
                </td>
              </sd:begin>
              <sd:previous>
                <td>
                  <sd:result-link>&lt;</sd:result-link>
                </td>
              </sd:previous>
              <sd:next>
                <td>
                  <sd:result-link>&gt;</sd:result-link>
                </td>
              </sd:next>
              <sd:end>
                <td>
                  <sd:result-link>&gt;|</sd:result-link>
                </td>
              </sd:end>
              <td> <sd:row-number /> / <sd:rows-count /> </td>
              <td style="width:200px;" />
              <sd:page-list>
                <sd:current>
                  <td class="sd-current" style="width:20px;text-align:center">
                    <sd:result-link>
                      <sd:page-number />
                    </sd:result-link>
                  </td>
                </sd:current>
                <sd:other>
                  <td style="width:20px;background-color:#efefef; text-align:center">
                    <sd:result-link>
                      <sd:page-number />
                    </sd:result-link>
                  </td>
               </sd:other>
              </sd:page-list>
            </tr>
          </table>
        </sd:result-steps>
      </div>
      <p />
    </div>
     <div class="sd-output-part">
      <div class="sd-output">
        <h3>Empfangene Nachrichten</h3>
        <table border="">
          <tr>
            <sd:col-set sd:for="sample-empfangen">
              <sd:normal>
                <th style="text-align:left">
                  <sd:col-name /> <sd:sort />
                </th>
              </sd:normal>
            </sd:col-set>
          </tr>
          <sd:rs sd:name="sample-empfangen" sd:edit="sample-input"
            sd:source-type="view" sd:sc="pm_tableId"
            sd:so="2" sd:source-name="pm_empfangen">
            <sd:normal>
              <tr>
                <sd:cell-set>
                  <sd:normal>
                    <td>
                      <sd:cell-value sd:datetime-format="dd.MM.yyyy HH:mm:ss" />
                    </td>
                  </sd:normal>
                </sd:cell-set>
                <td>
                  <sd:del-link>Löschen</sd:del-link>
                </td>
              </tr>
            </sd:normal>
          </sd:rs>
         </table>
        </div>
      <p />
      <div class="sd-result-steps">
        <sd:result-steps sd:for="sample-empfangen"
          sd:page-number-style="relative" sd:name="result-steps-empfangen">
          <table cellpadding="2" cellspacing="1" class="sd-step-buttons"
            style="border-bottom:3px solid #ececcc;">
            <tr>
              <sd:begin>
                <td>
                  <sd:result-link>|&lt;</sd:result-link>
                </td>
              </sd:begin>
              <sd:previous>
                <td>
                  <sd:result-link>&lt;</sd:result-link>
                </td>
              </sd:previous>
              <sd:next>
                <td>
                  <sd:result-link>&gt;</sd:result-link>
                </td>
              </sd:next>
              <sd:end>
                <td>
                  <sd:result-link>&gt;|</sd:result-link>
                </td>
              </sd:end>
              <td> <sd:row-number /> / <sd:rows-count /> </td>
              <td style="width:200px;" />
              <sd:page-list>
                <sd:current>
                  <td class="sd-current" style="width:20px;text-align:center">
                    <sd:result-link>
                      <sd:page-number />
                    </sd:result-link>
                  </td>
                </sd:current>
                <sd:other>
                  <td style="width:20px;background-color:#efefef; text-align:center">
                    <sd:result-link>
                      <sd:page-number />
                    </sd:result-link>
                  </td>
                </sd:other>
              </sd:page-list>
            </tr>
          </table>
        </sd:result-steps>
      </div>
      <p />
    </div>
  </sd:form>
</div>
Im Beispiel werden die Spaltenüberschriften und die Zellen dynamisch ausgegeben. Für die Spaltenüberschriften leistet dies das sd:col-set -, für die Zellen das sd:cell-set -Element. Das innere sd:cell-value -Element ist durch ein Attribut zur Formatierung der Datumsangaben ergänzt. Ferner wurde eine zusätzliche Spalte zum Löschen der Nachrichten hinzugefügt.

Den beiden Ausgaben wird über die sd:sc /sd:so - Attribute mit den Werten 'pm_tableId' / 2 die ID-Spalte absteigend als Standardsortierung vorgegeben. Alternativ könnte die Datumsspalte genutzt werden. Der Unterschied liegt darin, daß der Wert der ID-Spalte beim ersten Speichern, der Wert der Datumsspalte erst beim Versenden festgelegt wird.

Beiden sd:rs -Elementen ist das 'sample-input' - Element über das sd:edit-Attribut zugeordnet. Damit kann dieselbe Maske zum Lesen beider Views genutzt werden. Durch den Button wird festgelegt, daß sich die Suche nur auf das 'sample-gesendet'-Element auswirkt. Den meisten Code erzeugt das hier eigentlich irrelevante sd:result-steps -Element, das nur der Vollständigkeit halber mit übernommen wurde.

Hinweis zur Verwendung

Erstellen Sie zunächst eine Tabelle mit den beiden Spalten 'Betreff' (nvarchar(50)) und 'Message' (nvarchar(500)) sowie die beiden Views. Anschließend erzeugen Sie eine neue Ausgabeseite (leer oder per Vorauswahl) und löschen Sie den gesamten body-Inhalt. Kopieren Sie den obigen Code in den Abschnitt zwischen <body> und </body>.

Konfiguration der Rechte

Jede versendete Nachricht erzeugt zwei Datensätze - für den Absender und für den Empfänger. Erteilt man der gewünschten Nutzergruppe das Recht, Zeilen zur zugrundeliegenden Tabelle hinzuzufügen und diese zu editieren / zu löschen, so können Nutzer ohne globales Leserecht PMs versenden und empfangene PMs lesen. Nutzer mit globalem Leserecht / Administratoren können immer alle PMs lesen und bearbeiten. Um auch für diese Ausgabeseiten übersichtlich zu gestalten, sollten die Elemente
<sd:ro-value sd:for="sample-gesendet" sd:col="pm_from" sd:std-value="USER_ID" />

<sd:ro-value sd:for="sample-empfangen" sd:col="pm_to" sd:std-value="USER_ID" />
in die Ausgabeseite eingefügt werden. Dies schränkt die zurückgegebenen Zeilen auf die selbst versandten bzw. empfangenen Datensätze ein. Zur Verwaltung und - falls notwendig - Kontrolle der PMs können Administratoren die internen Menüs nutzen. Hier sind für Nutzer mit globalem Leserecht alle PMs einsehbar. Andere Nutzer können nur ihre eigenen Nachrichten lesen.

Die Bereitstellung einer Private-Message-Logik bietet leider ein gewisses Störpotential. Falls sich Nutzer anonym anmelden können, ist dies nutzbar, um anderen Mitgliedern unerwünschte Werbung zuzusenden. Deshalb gibt es im Menü 13: Nutzer und Gruppen die Möglichkeit, einzelnen Nutzern den Zugriff auf PMs zu untersagen. Sie können zwar ihre verschickten und erhaltenen PMs noch lesen. Jedes Neuerstellen, Ändern einschließlich save -> send und Löschen von PMs ist jedoch nicht mehr möglich.


Kontaktformular:

Schreiben Sie mir und wir bauen gemeinsam Ihre neue Web-Datenbank!

Die Erläuterungen zum Datenschutz habe ich gelesen und stimme diesen zu.

© 2003-2018 Jürgen Auer, Berlin.