# PHP/MYSQL: Nachrichten-System



## Georgler (3. September 2015)

Hi,

ich bin gerade am Arbeiten an einem Nachrichten-Modul.
Damit soll es möglich sein, mehreren Gruppen und/oder mehreren Benutzern Nachrichten zu schreiben (über die Datenbank).

Folgende Datenbanktabelle habe ich mir überlegt:
| id | empfaenger_id | switch | absender_id | betreff | nachricht | gelesen | del | zeitpunkt |

Ich habe mir gedacht man definiert in der Spalte "switch", ob es an eine Gruppe oder an einen Benutzer gesendet wird (ENUM 'user_id', 'group_id').
Wenn beispielsweise in der Switch-Spalte "user_id" steht, wird die Spalte empfaenger_id als user_id gewertet, genauso bei Gruppen.

In der Spalte "gelesen" wird protokolliert, ob der Benutzer die Nachricht gelesen hat, damit beispielsweise auf der Startseite nicht angezeigt wird, dass man neue Nachrichten habe. Doch wie kann man das am besten umsetzen? Ich dachte spontan an ein Mousover (d.h. mit der Maus über den Text der Nachricht fahren), aber wenn man dann bei jedem Mouseover eine DB-Query macht, ich weiß ja nicht.
Oder wäre es einfach besser eine Liste mit den Betreffen anzuzeigen und sobald man auf den Betreff klickt wird die DB-Query ausgeführt?

In der Spalte "del" wird dann gesagt, ob der Absender die Nachricht gelöscht haben wollte -> sie wird dem Empfänger nicht mehr angezeigt. Oder sollte man sie dann nicht einfach ganz löschen?

Soweit der Plan, doch jetzt kam mir ein grundlegendes Problem entgegen. Das funktioniert nur als 1 Absender zu 1 Empfänger, ich wollte es aber gerade mit mehreren Empfängern haben und dann sollen die Empfänger noch unterschiedlich sein können (d.h. Gruppen *und* Benutzer dürfen Empfänger sein bei der selben Nachricht).

Wie muss ich da vorgehen? Ich dachte jetzt spontan an eine zweite Tabelle die dann die Id der Nachricht (Tabelle nachrichten) mit den Benutzer-IDs und Gruppen-IDs verknüpft.
Ungefähr so:
| nachricht_id | empfaenger_id | switch |

Doch wie müsste nun eine Query zum Eintragen und zum Abfragen aussehen?
Für das Absenden bräuchte man dann ja auf jeden Fall mehrere Querys.

Beim Abfragen einen Nachricht:

```
SELECT
    n. id,
    n. absender_id,
    n.betreff,
    n.nachricht,
    n.zeitpunkt
FROM nachrichten n
LEFT JOIN nachrichten_empfaenger ne
    ON ne.nachricht_id = n.id
WHERE ((ne.empfaenger_id = ? AND ne.switch = ? ) OR (gucken ob benutzer in einer gruppe ist, die die nachricht bekommen hat)) AND n.del != ?
```
erstes Fragezeichen: aktuelle user_id
zweites Fragezeichen: "user_id"
drittes Fragezeichen: "1"

Hierbei habe ich keine Idee, wie der Gruppen-Teil aussehen kann.

Achja, meine Gruppen-Tabelle (groups):
| group_id | group_name |

Die Tabelle mit den Gruppen-Benutzer-Zuordnungen (users_groups):
| user_id | group_id |

MfG


----------



## Ap0ll0XT (4. September 2015)

Da gibt es sehr viele verschiedene Ansätze, wie man das lösen kann. Allerdings kannst du die reine Gruppenzuordnung von Nachrichten vollkommen vergessen. Denn wenn ein Nutzer die Nachricht löscht, muss die Nachricht für ihn auch verschwinden. Deswegen würde ich für die Nachrichten mit 2 Tabellen arbeiten. Mit einer Message_Data Tabelle und mit einer Message_Link Tabelle:
 Message_Data:
| msg_id | msg_subject | msg_body |

Message_Link:
| msgl_sender (Absender-ID) | msgl_addressee (Empfänger-ID) | msgl_message (ID der Nachricht) |

*So könnte der Inhalt der Tabellen aussehen
*Message_Data:
| 21 | "Hallo ihr 5" | "So kann eine Gruppennachricht gespeichert werden" |

Message_Link:
| 1 (Benutzer 1 als Absender) | 3 (Benutzer 3 als Empfänger) | 21 (ID der Nachricht)
| 1 (Benutzer 1 als Absender) | 11 (Benutzer 11 als Empfänger) | 21 (ID der Nachricht)
| 1 (Benutzer 1 als Absender) | 23 (Benutzer 23 als Empfänger) | 21 (ID der Nachricht)
| 1 (Benutzer 1 als Absender) | 51 (Benutzer 51 als Empfänger) | 21 (ID der Nachricht)
| 1 (Benutzer 1 als Absender) | 39 (Benutzer 39 als Empfänger) | 21 (ID der Nachricht)

Die Gruppenzugehörigkeit kannst du dann über die jeweilige Benutzer und Gruppentabelle erledigen. Dazu holst du dir mit einem Query die Liste mit den ID's und bastelst dann einen langen Insert für die Tebelle mit den Links. Aber das ist noch einfach. Etwas komplexer wird es, die Nachrichten-ID dort hinnein zu bekommen. Aber auch da gibt es verschiedene Anasätze. Du könntest mit Subquerries arbeiten, die Nachricht nach dem Einfügen samt ID nochmal abfragen oder mit der PDO-Methode lastinsertid versuchen, die ID in die Spalte zu schreiben.

Das ganze ist zwar etwas fummeliger. Dafür funktioniert der Spaß perfekt. Löscht nämlich ein User die Nachricht, wird nur der Eintrag aus Message_Link gelöscht. Die anderen können die Nachricht aber weiterhin lesen. Redundanz gibt es ebenfalls nicht. Beim Löschen einer Nachricht kannst du den Request dafür nutzen um zu schauen, ob die Nachrichten-ID überhaupt noch jemanden zugewiesen ist. Wenn nicht, löscht du die Nachricht aus der Nachrichten-Tabelle.


----------



## Georgler (4. September 2015)

Hi,

vielen Dank für deine Hilfe und vor allem die Tabelle msg_link!

Ich habe bis jetzt das und wollte einfach mal wissen, ob das überhaupt in Ordnung ist. Ich habe ein bisschen getestet und bisher habe ich keine Probleme festgestellt. Ein Problem wäre es, wenn wer die Nachricht angezeigt bekommt, der dafür gar nicht berechtigt ist. 

*Meine Datenbank-Tabellen:*
msg_data:
| id | betreff | nachricht | zeitpunkt |

msg_link
| user_id (-> Absender) | switch (-> entweder "user" oder "group") | to_id (->Empfänger) | msg_id (-> ID der Nachricht aus msg_data) | gelesen (noch keine Funktion) |

Anmkerung: Wenn in der Spalte switch "user" steht wird die Spalte to_id als user_id gewertet. Das gleiche auch bei group.

users
| user_id | username | realname |

groups
| group_id | group_name |

users_groups (Linking-Tabelle: gibt an welcher Benutzer in welcher Gruppe ist)
| user_id | group_id |

*Hier mein Code (bisher nur zum Abfragen und Anzeigen der Betreffe):*

```
$group_switch = 'group';
$user_switch = 'user';
$sql = $db->prepare("
	SELECT 
		md.id,
		md.betreff,
		md.zeitpunkt,
		u.realname,
		u.username
	FROM msg_data md
	LEFT JOIN msg_link ml
		ON ml.msg_id = md.id
	LEFT JOIN users_groups ug
		ON ug.group_id = ml.to_id
	LEFT JOIN users u
		ON ml.user_id = u.user_id
	WHERE (ug.user_id = ? AND switch = ?) OR (to_id = ? AND switch = ?)
	ORDER BY md.zeitpunkt DESC
	LIMIT 150
");
$sql->bind_param('isis', $user_id, $group_switch, $user_id, $user_switch);
$sql->execute();
$sql->store_result();
$sql->bind_result($msg_id, $msg_betreff, $msg_zeitpunkt, $msg_realname, $msg_username);

if ($sql->num_rows == 0) {
	echo'Keine Nachrichten verfügbar.';
} else {
	while ($sql->fetch()) {
		echo'
		<b>Nachricht-ID:</b><br>
		'.$msg_id.'<br><br>
		<b>Absender:</b><br>
		'.$msg_realname.' ('.$msg_username.')<br><br>
		<b>Nachricht-Betreff:</b><br>
		'.$msg_betreff.'<br><br>
		<b>Nachricht-Zeitpunkt:</b><br>
		'.date('d.m.Y H:i', strtotime($msg_zeitpunkt)).'
		<hr>
		';
	}
}
```

*Edit:*
Ich bin gerade auf ein kleines Problem gestoßen von wegen Unqiue Key.
phpmyadmin hatte mich gewarnt,  dass die Tabelle msg_link keinen Unique Key hatte.
Dann habe ich folgenden Befehl ausgeführt: ALTER TABLE msg_link ADD UNIQUE KEY (user_id, to_id, switch, msg_id)

Ich dachte immer wenn man das so macht, dann ist nur die Kombination von den 4 Feldern einzigartig. Aber als ich dann einen neuen Eintrag machen wollte mit der selben user_id kam der Fehler: Duplicate Entry.

Brauche ich für die Tabelle überhaupt einen Unique Key oder habe ich etwas falsch gemacht?


----------



## Ap0ll0XT (4. September 2015)

Für diese Tabelle brauchst du eigentlich keinen Unique. Bei mir klappen Tabellen ohne Keys.



Ich verstehe auch dein ganzes Rumspielen mut der Gruppe und dem Switch nicht. Den Quatsch brauchst du nicht. Wenn eine Nachricht an eine Gruppe gehen soll, dann holst du dir die Mitglieder-ID's einer Gruppe aus der Datenbank und speicherst für "jeden" Empfänger in der Linktabelle einen Datensatz. Den Vorteil dadurch hatte ich ja schon genannt. Jeder Nutzer kann seine Nachricht unabhängig von alles anderen Empfängern löschen. Wenn du eine Nachricht einer Gruppe zuweist, kann ein einzelner die Nachricht für sich nicht löschen, ohne das du den Hinweis auf die Löschung noch anderweitig speicherst (z.B. eine weitere Tabelle). Aber das ist unnötig und erhöht nur die Anzahl der Querries. Du machst es dir also nur unnötig kompliziert.


----------



## Georgler (4. September 2015)

Ok, ich habe das mit dem switch entfernt, ist mir gar nicht aufgefallen, dass man damit die Möglichkeit des Löschens/Ausblenden wegwirft.
Ich habe jetzt noch eine Spalte in der msg_data-Tabelle hinzugefügt, in der als String gespeichert wird, an wen die Nachricht geschickt wurde. Ich wollte unbedingt, dass wenn man etwas an eine Gruppe schickt, dass dort auch steht, dass es an jene Gruppe geschickt wurde.

Noch eine kleine Frage:Ich hatte überlegt, dass wenn man in einem Textfeld einen Benutzernamen eingibt, dass einem dann automatisch aus der Datenbank entsprechende Ergebnisse zum Anklicken vorgezeigt werden. Sollte man vorher alle Benutzernamen aus der Datenbank holen sollte und in einen Array speichern? Ich weiß aber nicht nicht, ob das Geschwindigkeitseinbußen geben könnte. Andernfalls müsste man ja mit einem change-Event eine SQl-Query ausführen, aber das wäre ja auch suboptimal, wenn man immer wieder ein Zeichen eingibt und diese kleine Auswahl an Benutzern immer neu geladen wird.

So etwas kann ja das autocomplete-Ding von jQueryUI aber kann man das auch für eine Mehrfachauswahl nutzen?


----------



## Ap0ll0XT (4. September 2015)

Mit autocomplete habe ich jetzt nicht so die Erfahrung. Aber soweit ich weiß, reicht theoretisch ein Query aus. Sobald du z.B. die ersten 3 Zeichen eingibst lässt du dir per Ajax alle Einträge mit den 3 Zeichen beginnen als Array zurückliefern. Die restliche Suche übernimmt dann das Javascript. Aber du solltest das ganze sehr Behutsam gegen Missbrauch absichern und nur das nötigste ins Array speichern. Sonst wird es hässlich. Bei großen Tabellen wie bei der Googlesuche wird sehr viel mit caching und der Wahrscheinlichkeit gearbeitet. Aber bei kleineren Sachen wird das nicht nötig sein.


----------



## Georgler (4. September 2015)

Wie sollte man sich da gegen Missbrauch absichern? Ich habe einfach folgende Query:


```
SELECT username FROm users WHERE user_id != 2
```

Die letzte Bedinung soll einfach nur dafür sorgen, dass man keine Nachrichten an den "System"-Benutzer schicken kann.
Ich nutze hierfür einfach eine normale Query, kein Prepared Statement. Gibt es da Sicherheitslücken in meiner Vorgehensweise?

*Edit:* Ich bin gerade am testen, Nachrichten in die Datenbank zu schreiben und ich muss jetzt mehrere Querys auf einmal machen. Wie mache ich das am besten, geht das auch mit Prepared Statements (also um SQL injections zu verhindern)?

Ebenfalls müsste ich die automatisch generierte ID der Nachricht (in msg_data) kriegen, um dann in der zweiten Query erfolgreich zu verknüpfen.

Bisher sind das meine Queries (alle Variablen gegeben, auch wenn sie da jetzt nicht alle stehen, da die POST-Variablen bei mir erstmal durch eine Fehlerroutine durchlaufen):

```
$user_id = $_SESSION["user_id"];
$zeitpunkt = date('d.m.Y H:i');
$null = 0;

$sql = $db->prepare("INSERT INTO msg_data (betreff, nachricht, zeitpunkt, to_string) VALUES (?, ?, ?, ?)");
$sql->bind_param('ssss', $input_betreff, $input_nachricht, $zeitpunkt, $input_username);

$sql = $db->prepare("INSERT INTO msg_link (user_id, to_id, msg_id, gelesen) VALUES (?, ?, ?, ?)");
$sql->bind_param('ssss', $user_id, $to_user_id, $LASTINSERTEDID, $null);
```


----------



## Ap0ll0XT (4. September 2015)

LastInsertedId ist eine PDO Methode. So sollte es nicht funktionieren. Außerdem kannst du auch mehrere Datensätze auf einmal mit einem Insert-Query in die Datenbank schreiben. Dafür musst du nur die Werte in Klammern mit Komma getrennt in den Query reinpacken. z.B.

```
INSERT INTO users (id,name) VALUES (1,"HUHU"),(2,"Haha"),(3,"Hehe")
```
Da hilft eine foreach-Schleife unheimlich. Du musst halt nur garantieren, das der String das passende Format hat.

Dein erster Query liefert dir ja alle Benutzernamen zurück. Am sinnvollsten wäre es aber, wenn du die Liste zumindest mit den ersten beiden Buchstaben eingrenzt. Also 2 Buchstaben eingeben, Ajax-Anfrage mit Query auf die Datenbank und Array als z.B. JSON zurück ans Autocomplete. Denn gerade bei größeren Projekten mit vielen Nutzern (100.000 aufwährts) kann so ein JSON Array/Objekt schon einmal die MB-Grenze knacken und bei langsameren Verbindungen zu unangenehmen Latenzen führen.


----------



## Georgler (4. September 2015)

Aber ich muss ja in unterschiedliche Tabellen "inserten". Da funktioniert das ja nicht mit den ganzen Klammern.


----------



## DarkMo (4. September 2015)

PHP: mysql_insert_id - Manual
das gabs auch schon vor pdo's (is ja nich so, dass man ohne in der steinzeit lebt ><). ist halt deprecated oder wie sichs schimpft.


----------



## Ap0ll0XT (4. September 2015)

@DarMo: Ich erwähnte es ja nur, da er PDO nutzt 

@Georgler: Du brauchst für jede Tabelle ein Query. Zuerst speicherst du die Nachricht in der entsprechenden Tabelle. Erst dann funktioniert auch erst lastinsertedid. Dann baust du den Query, wo du die Nachricht (über lastinsertedid) in der Linktabelle den Empfängern zuordnest so wie ich es im letzten Beitrag geschrieben hatte mit den Kommata. Und dann läuft es schon problemlos.


----------



## Georgler (5. September 2015)

Muss ich die zwei Queries komplett trennen oder lassen die sich in einem Block abarbeiten? Also die erste Query wäre die, wo man die Nachricht in die Tabelle einträgt und die zweite Query wäre die, wo man die ganzen Zuordnungen in die Datenbank schreibt (mehrere Zuordnungen möglich).

Ich habe da irgendwas mit BEGIN oder COMMIT im Kopf?

Beispiel, wie ich es mir vorstelle:

```
$sql = $db->prepare("
    BEGINN;
    INSERT INTO msg_data (betreff, nachricht, ...) VALUES (?, ?, ...);
    INSERT INTO msg_link (hier dann noch eine foreach-Schleife je nachdem, an wie viele Benutzer man gesendet hat)
    COMMIT;
")
$sql->bind_param(...);
$sql->execute();
```


----------



## Ap0ll0XT (5. September 2015)

Transaktionen sammeln im Grunde nur Querries und führen sie auf einen Schlag aus. In diesem Falle funktioniert nur lastinsertedid nicht und du kannst die Zuordnung nicht machen. Du musst also in diesem speziellen Fall trennen.


----------



## bingo88 (5. September 2015)

Normalerweise verhalten sich Transaktionen schon wie "echte" Kommandos, die werden nur erst beim COMMIT persistent in die entsprechenden Tabellen geschrieben.


----------



## Georgler (5. September 2015)

Ok, ich habe es jetzt so gemacht, dass erst wenn die erste Query erfolgreich ausgeführt wurde, die zweite Query ausgeführt wird. Das klappt auch soweit.

Ich habe nur noch eine Frage bzgl. der Tabellenstruktur.

Macht es nicht Sinn den Absender der Nachricht in msg_data zu speichern? Sonst kann man ja als Autor gar nicht seine eigene Nachricht löschen und man hat sehr viele Redundanzen in der msg_link-Tabelle, weil dort überall die gleiche user_id steht, wenn man die Nachricht an mehrere Leute schickt.


----------



## Ap0ll0XT (5. September 2015)

Diese "Redundanzen" sind in diesem Fall aber nötig. Denn jede Nachricht stellt eine einzelne Einheit dar. Wenn du diese "Redundanz" entfernen wollen würdest, hast du promt wieder die selben Probleme wie am Anfang. Du müsstest die Nachrichten wieder als Gruppe in einen Datensatz stecken, was dann mal wieder darin endet, das die Nachricht nicht mehr für jeden Empfänger einzeln behandelbar ist. Bei einem Onlineshop kannst du ja auch nicht alle Bestellungen in einen Datensatz bündeln, nur weil sonst bei den Datensätzen überall die selbe ID drin steht.

Zum Thema Autor: Eine Nachricht sollte nur gelöscht werden, wenn alle Empfänger sie löschen. Wenn der Autor die Nachricht versendet hat, ist sie weg. Ist bei Post und E-Mail ja auch so


----------



## Georgler (5. September 2015)

Aber ist die Kombination von msg_id und to_id (Empfänger) nicht eindeutig?

Dann könnte man doch einfach nach dem Schema löschen, oder?: DELETE FROM msg_link WHERE msg_id = jeweiligeNachricht AND to_id = $_SESSION["user_id"]

*Dann wären die Tabellen folgende: *
msg_link
| to_id | msg_id | gelesen |

msg_data
| user_id (Absender) | msg_id | betreff | nachricht | zeitpunkt |


----------



## Ap0ll0XT (5. September 2015)

Achsooo meinste das. Ja das wäre auch eindeutig. Aber wie schon gesagt ist die Form von Redundanz gehüpft wie gesprungen. Du hast zwar bei den Links ne Spalte weniger. Aber weder Aufwand noch Datenbestand ändern sich da großartig. Und nachträglich nach dem Senden der Nachricht sollte der Autor weder löschen noch bearbeiten können.


----------



## Georgler (5. September 2015)

Wieso eigentlich nicht? Weil's der Standard ist wahrscheinlich oder? 

Ich bin jetzt gerade dabei zu implementieren, dass man mehrere Benutzer auswählen kann.

Gefunden habe ich da das jQuery UI Autocomplete (multiple, remote): https://jqueryui.com/autocomplete/#multiple-remote

Das erfüllt genau das geforderte: mehrere Auswahlmöglichkeiten, alles wird per Datenbank abgefragt und man muss mindestens 2 Zeichen eingeben.

Dann kommt da ein Code wie dieser: $.getJSON( "search.php". Jetzt ist die Frage, wie muss der Code in dieser search.php aussehen? Habe mich mit JSON nie so richtig beschäftigt, gibt es da gute Dokumentationen?

*Edit (der funktioniert allerdings nicht):*


```
$(function() {
	        function split( val ) {
	                  return val.split( /,\s*/ );
               }
		function extractLast( term ) {
			  return split( term ).pop();
		}
	 
		$("#msg_empfaenger")
			.bind( "keydown", function( event ) {
				if ( event.keyCode === $.ui.keyCode.TAB &&
					$( this ).autocomplete( "instance" ).menu.active ) {
					event.preventDefault();
				}
			})
			.autocomplete({
				source: function( request, response ) {
					$.getJSON( "msg_neu_array.php", {
					term: extractLast( request.term )
				}, response );
			},
			search: function() {
				var term = extractLast( this.value );
				if ( term.length < 3 ) {
					return false;
				}
			},
			focus: function() {
				return false;
			},
			select: function( event, ui ) {
				var terms = split( this.value );
				terms.pop();
            			terms.push( ui.item.value );
				terms.push( "" );
				this.value = terms.join( ", " );
				return false;
			}
		});
	});
```


```
//Variablen + ErrorHandling
	if (isset($_POST["term"])) {
		$input_term = $_POST["term"];
	} else {
		exit; 
	}
	$user_id = $_SESSION["user_id"];
	$term2 = '%'.$term.'%';
	
	//SQL
	$sql = $db->prepare("SELECT username FROM users WHERE username LIKE ?");
	$sql->bind_param('s', $term2);
	if ($sql->execute()) {
		$sql->store_result();
		$sql->bind_result($username);
		while ($sql->fetch()) {
			$usernames[] = $username;
		}
		echo json_encode($usernames);
	} else {
		exit;
	}
```


----------



## Ap0ll0XT (6. September 2015)

Das mit JSON ist ganz ganz einfach. Auch wenn man mit echo in PHP nach und nach eine HTML-Konforme Website zusammenschustert, liefert es im Grunde nur Plain-Text, der auf Grund seiner Struktur und seines MIME-Types interpretiert wird. Ist der MIME ein HTML-Dokument, wird der mit echo geschriebene Text ausgegeben. Befindet sich nun aber ein HTML-Tag in diesem Text, wird er vom HTML-Parser ausgewertet und interpretiert. Auch für JSON gibt es MIME-Types. Dadurch weißt der Browser, das die Rückgabe ein Plain-Text für Javascript ist.

Du musst also in PHP dafür sorgen, das der MIME-Type passt und das die Daten in ein JSON-String umgewandelt und ausgegeben werden. Dafür gibt es in PHP die Funktionen json_encode (Array oder Objekt zu JSON-String) oder json_decode (JSON-String zu Array). Das ganze funktioniert ähnlich dem Serialisieren eines Arrays mit serialize() oder unserialize(). Ist also vollkommen einfach. Es gibt also nur 4 Dinge, die du beachten musst:
1. Prüfen, ob der Request ein AJAX-Request war!
2. Du lieferst kein HTML oder andere Daten mit dem Script aus!
3. Die Rückgabe muss JSON-Konform sein. Nutze also am besten die Funktion json_encode dafür!
4. Setze vor der Ausgabe einen passenden MIME-Type über den Header, damit der Browser weiß, was für eine Buchstabensuppe er da bekommt!

Hier einmal ein kleines JSON-Beispiel: Demo: jQuery Ajax Call to PHP Script with JSON Return â€” Jonathan Suh Labs
Da aber der Typ dort den MIME-Type vergessen hat, hier noch einmal etwas zu dem Thema: What is the correct JSON content type? - Stack Overflow

*Und zu deiner Frage zwecks Standard beim Bearbeiten und Löschen von Nachrichten:*
Es ist ein großer Unterschied, ob man nun ein öffentliches Forum oder einen nicht-öffentlichen Nachrichtendienst anbietet. Während in einem öffentliches Forum jeder anwesende dort eine Diskussion oder einen Verlauf mitverfolgen kann, so ist das in der privaten bzw. nicht-öffentlichen Kommunikation nicht der Fall. Private Kommunikation benötigt daher eine größere Vertrauengrundlage, da meist die Öffentlichkeit das geschriebene nicht sehen und deswegen auch niemand da ist, auf den man sich beziehen kann. Daher ist es wichtig, das ein Verfasser/Absender nach dem Absenden keinerlei Einfluss mehr auf den Inhalt nehmen kann. Das geschriebene Wort bleibt also so stehen, wie es abgesendet wurde und man kann sich als Empfänger genau darauf beziehen. Genau das ist auch der Grund dafür, warum viele Firmen und Unternehmen selbst intern eigene Mailserver wie Exchange, Mercury, Postfix etc. verwenden, anstatt auf Datenbankbasierte Lösungen wie so eine Nachrichtenfunktion zu setzen.

Gerade in Zeiten von Cybermobbing oder Mobbing am Arbeitsplatz wäre so eine Funktion fatal. Man könnte sich darüber beleidigen und wenn es gelesen wurde einfach vom Absender bearbeitet oder gelöscht werden. Das würde das Vertrauen in die Software, in dessen Entwickler und auch in das Projekt, das diese Software einsetzt, extrem schädigen. Private bzw. nicht-öffentliche Kommunikation sollte also fest protokollierbar bleiben. Und das bedeutet eben, das der Text, der gesendet wurde, so auch bestehen bleibt. Ein Mailserver zum Beispiel arbeitet nach festen Standards. Wird eine Email versendet, so kann nur noch ein Abministrator mit vollem Zugriff die Nachricht aus dem System entfernen. Aber nicht der Absender selber. Wenn also ein Absender der Meinung ist, durch den Inhalt einer Nachricht gegen geltendes Recht verstoßen zu müssen, so kann diese Nachricht vom Empfänger archiviert und der Betreiber des Servers damit konfrontiert werden (natürlich nur Unternehmensintern oder in Arbeitsgruppen/Projekten sinnvoll). Der Betreiber kann dann auch selbst auf den Server schauen, den Umstand bestätigen und entsprechende Konsequenzen ziehen. Ähnlich ist es auch, wenn du die Funktionen weglässt. Dadurch kommt ebenfalls nur noch ein Datenbankadmin an die Nachricht, um sie systemweit zu löschen/zu ändern (es sei denn der/die Empfänger löschen sie alle ... das wäre dann aber auch eigene Dummheit!). Um es also auf den Pubnkt zu bringen:* Wenn ein Absender seine Nachrichten nach dem senden bearbeiten oder löschen kann, dann kann er sich auch der Verantwortung/Haftung für den Inhalt entziehen.*


----------



## Georgler (6. September 2015)

Vielen Dank für die ausführliche Erklärung für JSON und den Standard zum Löschen etc. von privaten Nachrichten!

Ich wollte schon einen Menüpunkt "Gesendet" machen, bei dem man gesendete Nachrichten löschen kann, aber das gibt's jetzt nicht mehr.


----------



## Ap0ll0XT (6. September 2015)

Georgler schrieb:


> Ich wollte schon einen Menüpunkt "Gesendet" machen, bei dem man gesendete Nachrichten löschen kann, aber das gibt's jetzt nicht mehr.


Kannste doch. Du musst nur für den Absender eine eigene Verlinkung in die Link-Tabelle speichern. Und dann kann er sie auch löschen. Die Nachricht darf dabei nur nicht ganz aus dem System verschwinden. Erst wenn alle Empfänger die Nachricht gelöscht haben, darf die Nachricht komplett verschwinden 

Dazu ein kleiner Tipp: Du kannst in die Linktabelle auch eine weitere Spalte für einen Ordner einfügen. Wenn eine Nachricht (egal ob Gruppe oder einzeln) versendet wird, bekommt jeder Empfänger den Ordner INBOX und der jenige, der sie gesendet hat bekommt die Nachricht in den Ordner SENT. Damit kannst du das einfacher realisieren


----------



## JimSim3 (6. September 2015)

Ap0ll0XT schrieb:


> *Wenn ein Absender seine Nachrichten nach dem senden bearbeiten oder löschen kann, dann kann er sich auch der Verantwortung/Haftung für den Inhalt entziehen.*



Grundsätzlich richtig, aber mit Einschränkungen. Meiner Meinung nach spricht nichts dagegen eine Nachricht bearbeiten oder löschen zu können, solange der Empfänger die Nachricht noch nicht gelesen hat. Im Prinzip bietet das bspw. auch Google Mail an, wenn ich mich recht erinnere. So kann man da in den ersten paar Sekunden nach dem man die Nachricht "gesendet" hat, diese widerrufen. Bei einer Mail geht das natürlich nur in einem gewissen Zeitfenster in dem man das Absenden verzögert. Schließlich kann man bei einer Mail nicht kontrollieren wann der Empfänger die Nachricht liest. Bei einem selbstgeschrieben Nachrichtensystem in dem man erfasst wann jemand die Nachricht liest, ist es allerdings ohne Probleme möglich eine Funktion einzubauen um Nachricht zu löschen, solange sie der Empfänger noch nicht gelesen hat. Auch ist es prinzipiell unbedenklich eine Bearbeitungsfunktion einzuführen, solange für alle Beteiligten jede Bearbeitung nachvollziehbar ist. (nen bisschen so wie Facebook das momentan macht) Dazu bräuchte man dann neben der msg_id auch noch eine msg_version. Durch das Paar aus msg_id und msg_version wird die Nachricht eindeutig identifiziert. Sowohl Empfänger als auch Absender müssen jede Version der Nachricht sehen können. So kann bspw. standardmäßig die neuste Version der Nachricht angezeigt werden und nach Bedarf ältere Versionen geladen werden.

Problematischer finde ich eher die "Gelesen" Funktion. Da hätte ich als Anwender wenig Bock drauf.


----------



## Ap0ll0XT (6. September 2015)

JimSim3 schrieb:


> Grundsätzlich richtig, aber mit Einschränkungen. Meiner Meinung nach spricht nichts dagegen eine Nachricht bearbeiten oder löschen zu können, solange der Empfänger die Nachricht noch nicht gelesen hat. Im Prinzip bietet das bspw. auch Google Mail an, wenn ich mich recht erinnere. So kann man da in den ersten paar Sekunden nach dem man die Nachricht "gesendet" hat, diese widerrufen. Bei einer Mail geht das natürlich nur in einem gewissen Zeitfenster in dem man das Absenden verzögert. Schließlich kann man bei einer Mail nicht kontrollieren wann der Empfänger die Nachricht liest. Auch ist es prinzipiell unbedenklich eine Bearbeitungsfunktion einzuführen, solange für alle Beteiligten jede Bearbeitung nachvollziehbar ist. (nen bisschen so wie Facebook das momentan macht) Dazu bräuchte man dann neben der msg_id auch noch eine msg_version. Durch das Paar aus msg_id und msg_version wird die Nachricht eindeutig identifiziert. Sowohl Empfänger als auch Absender müssen jede Version der Nachricht sehen können. So kann bspw. standardmäßig die neuste Version der Nachricht angezeigt werden und nach Bedarf ältere Versionen geladen werden.


Theoretisch machbar. Praktisch hat man dafür aber in der nicht-öffentlichen Kommunikation kaum einen wirklichen Nutzen.


----------



## Georgler (6. September 2015)

Ich habe nochmal eine Frage abseits der Frage, wie es sich mit dem Löschen von Nachrichten verhält.

Ich möchte ja mit der Eingabe in ein Textfeld, Vorschläge bekommen, welche Benutzer *oder* Gruppen es gibt, je nachdem welche drei Buchstaben ich in das Textfeld eingetippt habe.

Dazu hatte ich folgende Query, welche nur für Benutzer funktionierte:

```
SELECT username FROM users WHERE username LIKE ?
```
? wäre hier: "%benutzereingabe%"

Für eine Query, welche Gruppen und Benutzer einschließt hatte ich folgende Idee:

```
SELECT u.username, g.group_name FROM users u LEFT JOIN groups g WHERE u.username LIKE ? OR g.group_name LIKE ?
```
Beide ? wären hier: "%benutzereingabe%"

Die letzte Query funktioniert nur leider nicht. Wie lässt sich das am besten realisieren?


----------



## JimSim3 (6. September 2015)

Ap0ll0XT schrieb:


> Theoretisch machbar. Praktisch hat man dafür aber in der nicht-öffentlichen Kommunikation kaum einen wirklichen Nutzen.


Bestes Beispiel: Mein Post. 

Gepostet, dann noch editiert um es besser zu erklären, bzw. Punkte zu ergänzen.  Und ich denke viele kennen es Nachrichten zu schicken und kurz darauf zu denken "Ach quatsch, hätt ich jetzt nicht senden müssen."

Aber klar, elementar ist so eine Funktion nicht. Kann aber ganz praktisch sein.


----------



## JimSim3 (6. September 2015)

Georgler schrieb:


> Ich habe nochmal eine Frage abseits der Frage, wie es sich mit dem Löschen von Nachrichten verhält.
> 
> Ich möchte ja mit der Eingabe in ein Textfeld, Vorschläge bekommen, welche Benutzer *oder* Gruppen es gibt, je nachdem welche drei Buchstaben ich in das Textfeld eingetippt habe.
> 
> ...



Kannst kein JOIN benutzen, wenn die Tabellen keinen Wert teilen... Also entweder eine Tabelle erstellen die User und Gruppen beinhaltet oder zwei Queries abfeuern.


----------



## Georgler (6. September 2015)

Alles klar, hab jetzt einfach zwei Queries hintereinander. Gibt es eigentlich eine Möglichkeit einen Unique Key über zwei Tabellen zu verteilen? Ich würde gerne das eine Gruppe nicht den Namen eines Benutzers haben kann.


----------



## JimSim3 (6. September 2015)

Georgler schrieb:


> Alles klar, hab jetzt einfach zwei Queries hintereinander. Gibt es eigentlich eine Möglichkeit einen Unique Key über zwei Tabellen zu verteilen? Ich würde gerne das eine Gruppe nicht den Namen eines Benutzers haben kann.



Schreib doch einfach sowohl user als auch gruppen in eine Tabelle. Füg "group_id" als Wert hinzu. Ist der Wert NULL handelt es sich um einen User, hat er einen anderen Wert verweist dieser auf ne Group Tabelle in der Gruppen Usern zugeordnet wird.

Also bspw. Tabelle user:

user_id | name | group_id ( + was sonst noch so in die Tabelle muss.)

und Tabelle group:
group_id | user_id

Dann hast du a) nur ein Query wenn du nach Usern / Gruppen suchst und b) kannst du so auch einfach den Namen unique machen. Alles User kriegst du dann aus der Tabelle in dem du ein SELECT machst WHERE group_id == NULL

EDIT: Wenn du keine Lust hast mal wieder deine Datenbankstruktur zu ändern (könnt ich durchaus verstehen, ich hasse das.  ) Dann musst du vor dem Insert in die Gruppen Tabelle ein Select id FROM user WHER name = ? abfeuern. Wenn ein Ergebnis zurück kommt ist der Name schon in Verwendung. Unique über zwei Tabellen geht nicht.


----------



## Ap0ll0XT (6. September 2015)

Ich würde das Suchen nach Gruppen bzw. Usern in getrennten  Inputs/Autocompletes machen. Dann weiß man als Nutzer wenigstens, nach  was man da genau sucht. Und eine Benutzernamen <> Gruppennamen  Kollision vermeiden ist auch nicht der richtige Weg. Stelle dir mal  folgendes Szenario vor: 200.000 Benutzer sind registriert und 200.000  Gruppen sind registriert. Das würde bedeuten, das für neue Nutzer ganze  400.000 Benutzernamen nicht verwendet werden können, wobei jeder  Benutzer trotzdem einwandfrei identifizierbar bleibt.

Wenn du es  aber nicht trennen willst, dann müsstest du mit 2 Querries arbeiten.  Denn MySQL erlaubt die Abfrage mehrerer Tabellen in einem Query nur über  eine Beziehung. Diese kannst du aber natürlich mit UNION verknüpfen:

```
SELECT name FROM user WHERE name LIKE ? UNION SELECT name FROM groups WHERE name LIKE ?
```
Damit sollte es klappen. Aber ich persönlich bin nicht begeistert von der Gleichstellung Benutzername und Gruppenname. Aber das musst du selbst wissen. Du kannst im Autocomplete Gruppen ja hervorheben (andere Farbe, Fettdruck ... wie auch immer). Da halte ich es eher für unnötig, Gruppen und Benutzer Vom Namen her gleichzusetzen. Aber ist wie gesagt deine Entscheidung.



JimSim3 schrieb:


> Bestes Beispiel: Mein Post.
> 
> Gepostet, dann noch editiert um es besser zu erklären, bzw. Punkte zu ergänzen.  Und ich denke viele kennen es Nachrichten zu schicken und kurz darauf zu denken "Ach quatsch, hätt ich jetzt nicht senden müssen."
> 
> Aber klar, elementar ist so eine Funktion nicht. Kann aber ganz praktisch sein.


Sie ist nicht nur "nicht elementar", sie ist auch in der *"nicht-öffentlichen/privaten"* Kommunikation nicht gewollt. Nachrichten/Messages sollen nach dem Absenden nicht änderbar sein. Da hilft einem eine Versionskontrolle auch nicht weiter. Der Inhalt ist geändert! Stell dir doch einmal vor hier auf dem Marktplatz verkauft einer etwas für 50 €. Der Käufer willigt ein und der Verkäufer ändert dann einfach den Preis nach der Einwilligung auf 100 €. Man ermöglicht so etwas garnicht erst. Dein Googlemail-Beispiel ist da anders. Die Mail wird erst nach einer gewissen Zeit verschickt (sie ist also noch *nicht gesendet*). Aber ein PN-System trägt nach dem Absenden die Nachricht in die Datenbank ein und der Empfänger greift darauf zu. Mann kann natürlich auch hier einen Zeitpuffer einbauen. Aber man kann es auch einfach lassen und die Nachricht nach dem Absenden stehen lassen.

Dein Post ist in einem Forum (öffentliche Kommunikation). Hier kann man sich nicht nur auf den Text, sondern auch auf andere Teilnehmer, die das gesehen haben, berufen. Bei PN's, Instant Messaging, Privatchats, E-Mail, Brief und Co. geht das natürlich nicht. Und ich persönlich würde auch ein PN-System nicht nutzen wollen, wo man nachträglich die Nachrichten ändern kann. Solchen PN-Systemen, wo ich beschissen werden kann, traue ich grundsätzlich nicht


----------



## Georgler (6. September 2015)

Hi,

Ich habe mich nun auch dazu entschieden, Gruppen und Benutzernamen voneinander zu trennen. Ich habe jetzt, wenn der Nutzer eine Gruppe auswählt hinter dem Gruppennamen in einer Klammer die Anmkerung "Gruppe". Das heißt, wenn der Nutzer eine Gruppe auswählt, sieht es so aus: "Gruppenname (Gruppe)".

Ich arbeite jetzt auch schon am Backend und habe die Benutzernamen sowie Gruppennamen schon in einem Array, das bisher so aussieht:

```
Array
(
    [0] => Admins (Gruppe)
    [1] => Benutzer (Gruppe)
    [2] => testbenutzer
)
```

Jetzt bin ich nur am überlegen, wie man das Array sortieren könnte, dass es beispielsweise einen Index ["user"] und einen Index ["groups"] gibt.

Nur wie müsste ich dann die ganzen Benutzer-IDs der Benutzer in den Gruppen rauskriegen?

Ich hätte jetzt spontan an das gedacht: SELECT ug.user_id FROM users_groups ug LEFT JOIN groups g ON g.group_id = ug.group_id WHERE g.group_name IN ?
Das ? wäre dann: $arraay["groups"] (Aber geht das überhaupt?)


----------



## Ap0ll0XT (6. September 2015)

Ein passender Query könnte so aussehen:

```
SELECT ug.user_id FROM users_groups ug LEFT JOIN groups g ON g.group_id = ug.group_id WHERE g.group_name = ?
```


----------



## JimSim3 (6. September 2015)

Ap0ll0XT schrieb:


> Sie ist nicht nur "nicht elementar", sie ist auch in der *"nicht-öffentlichen/privaten"* Kommunikation nicht gewollt. Nachrichten/Messages sollen nach dem Absenden nicht änderbar sein. Da hilft einem eine Versionskontrolle auch nicht weiter. Der Inhalt ist geändert! Stell dir doch einmal vor hier auf dem Marktplatz verkauft einer etwas für 50 €. Der Käufer willigt ein und der Verkäufer ändert dann einfach den Preis nach der Einwilligung auf 100 €. Man ermöglicht so etwas garnicht erst.



Wo ist da das Problem? Man kann doch jede Änderung nachvollziehen. Der Verkäufer kann den Preis nachträglich gerne ändern, es bringt ihm nur nichts, weil man immer noch jede Version die er vorher geschrieben hat lesen kann inklusive Zeitangabe. D.h. Verkäufer stellt Angebot für 50€ um 15:00 Uhr rein. Käufer stimmt um 16:00 Uhr zu. Verkäufer akzeptiert, ändert aber den Preis um 17:00 Uhr auf 100€. Der Käufer sieht zwar standardmäßig die Version von 17:00 Uhr, ist aber nicht dumm, klickt auf "Bearbeitet" und sieht dann jegliche vorherigen Versionen, auch die von 15:00 Uhr mit dem Preis von 50€ und kann die dem Verkäufer um die Ohren hauen. Es gibt kein Nachteil in diesem System. Nur mehr comfort. 



> Dein Googlemail-Beispiel ist da anders. Die Mail wird erst nach einer gewissen Zeit verschickt (sie ist also noch *nicht gesendet*). Aber ein PN-System trägt nach dem Absenden die Nachricht in die Datenbank ein und der Empfänger greift darauf zu. Mann kann natürlich auch hier einen Zeitpuffer einbauen. Aber man kann es auch einfach lassen und die Nachricht nach dem Absenden stehen lassen.



Du brauchst in einem selbst geschriebenen Nachrichtensystem keinen "Zeitpuffer" wenn du erfassen kannst, wann die Nachricht gelesen wird... Solange der Empfänger die Nachricht noch nicht gelesen hat, macht es für den Empfänger keinen Unterschied ob du die Nachricht nachträglich löschst oder nicht. Wobei es hier zugegebener maßen ein wenig auf den Einsatzzweck ankommt. Unter Umständen gibt es hier gesetzliche Auflagen, so dass grundsätzlich eine Kopie der Nachricht archiviert werden muss. Mit einem Zeitpuffer umgeht man das Problem natürlich, denn eine Nachricht die "nie" abgesendet wurde brauch man natürlich auch nicht archivieren... Für ein privates Forum seh ich da aber keine Bedenken.



> Dein Post ist in einem Forum (öffentliche Kommunikation). Hier kann man sich nicht nur auf den Text, sondern auch auf andere Teilnehmer, die das gesehen haben, berufen. Bei PN's, Instant Messaging, Privatchats, E-Mail, Brief und Co. geht das natürlich nicht. Und ich persönlich würde auch ein PN-System nicht nutzen wollen, wo man nachträglich die Nachrichten ändern kann. Solchen PN-Systemen, wo ich beschissen werden kann, traue ich grundsätzlich nicht



Ich versteh immer noch nicht, wo du hier beschissen werden kannst... Nochmal: Bearbeitete Nachrichten verschwinden ja nicht, sondern existieren weiterhin und sind weiterhin abrufbar. Ein System was Bearbeitung von Nachrichten zu lässt muss (oder besser darf) nicht die ursprüngliche Nachricht ersetzen, sondern erstellt praktisch eine neue Nachricht... Du benutzt kein Facebook, oder? Falls doch, schau dir doch mal an wie die das gelöst haben... Meiner Meinung nach ein guter Ansatz.


----------



## JimSim3 (6. September 2015)

Georgler schrieb:


> Hi,
> 
> Ich habe mich nun auch dazu entschieden, Gruppen und Benutzernamen voneinander zu trennen. Ich habe jetzt, wenn der Nutzer eine Gruppe auswählt hinter dem Gruppennamen in einer Klammer die Anmkerung "Gruppe". Das heißt, wenn der Nutzer eine Gruppe auswählt, sieht es so aus: "Gruppenname (Gruppe)".
> 
> ...



Moment. Bring nicht Rechte und Gruppen durcheinander. Was sollen die Gruppen bei dir genau machen? Ich dachte bei Gruppen eher an "php Programmierer" oder "Witcher 3 Fans". Benutzer und Admin sind ja erstmal bestimmte Rechte die ein User hat. Es kann dann zwar zusätzlich noch eine Gruppe Admins geben, aber ne Gruppe "Benutzer" scheint mir wenig sinnvoll... Besonders wenn man dann Nachrichten an alle Benutzer schicken kann schreit das nach SPAM....

Was dein Beispiel mit dem Array angeht... Du könntest ein $temp = explode("(", $array[$i]) machen für jeden Eintrag im Array. Gibt es ein $temp[1] ist es eine Gruppe und du schreibst trim($temp[0]) in ein neues GruppenArray. Existiert kein $temp[1] ist ein User und $temp[0] kommt in ein neues UserArray. Dann müsstest du allerdings vorher festlegen, das keine "(" im Namen vorkommen dürfen...  Alles eher suboptimal...

Ich würde dann eher versuchen Gruppen und User mehr zu trennen. Entweder du hast zwei Input-Felder "Gruppen" und "User" bei einer neuen Nachricht, oder du erstellst für jeden Empfänger dynamisch ein neues Input-Feld, wie bei einer E-Mail. (D.h. wenn du einen Namen eingegeben hast, der auch gefunden wurde im Ajax Result, erstellst du dynamisch ein neues Input-Feld für den nächsten Empfänger. Nach jeder Eingabe lässt du den Namen mit dem Ajax Result gegen deine bekannten Gruppen und User laufen, identifizierst ob es ein User oder eine Gruppe ist und verpasst dem Input Feld dann per javascript das passende name attribut (bspw. name="group[]") Allerdings brauchst du dann natürlich zwei Ajax Requests, einen um die User zu holen, einen um die Gruppen zu holen.

Dabei muss man aber natürlich aufpassen. Ich kenne aus Foren auch "Geheime" Gruppen. Die Fliegen bei so einem Ajax Request natürlich schnell auf... Dann müsstest du bei der Suche und Auslieferung der Gruppen immer noch berücksichtigen welche Rechte der User eigentlich hat und ob er die Gruppe überhaupt sehen darf... etc.

Wie du siehst, Gruppen können die Angelegenheit schon etwas komplizierter machen.


----------



## Georgler (6. September 2015)

Hi,

ich habe die Rechte nicht an Gruppen gebunden. Das wird mit einer Extra-Spalte in der User-Tabelle geregelt, die ist dann entweder "user" oder "admin". Zu der Spam-Problematik: Die Benutzer-Gruppe besteht derzeit nur zu Testzwecken 

*Ich bin bisher soweit:*

Der User gibt beispielsweise folgenden String (-> Empfänger) an die PHP-Datei: "Admins (Gruppe), Benutzer (Gruppe), testbenutzer"

So wird der String dann in der PHP-Datei verarbeitet:

```
//Variablen + Überprüfung dieser
if (isset($_POST["ajax_empfaenger"], $_POST["ajax_betreff"], $_POST["ajax_nachricht"])) {
	$input_betreff = $_POST["ajax_betreff"];
	$input_names = $_POST["ajax_empfaenger"];
	$input_nachricht = $_POST["ajax_nachricht"];
} else {
	echo 'Fehler: Es wurden keine Daten übertragen.';
	exit; 
}
if (empty($input_betreff) OR empty($input_names) OR empty($input_nachricht)) {		
	echo 'Fehler: Nicht alle Felder ausgefüllt.';
	exit; 
}

//Letztes Komma des Strings wird entfernt
$input_names2 = rtrim(trim($input_names), ',');

//String zu Array
$input_names3 = explode(', ', $input_names2);

//Array wird neu sortiert (wenn ein Name/Wert das Wort "(Gruppe)" enthält, wird der Wert zum Index ["gruppe"] gegeben, andernfalls kommt der Wert in den ["user"]-Index)
foreach ($input_names3 as $wert) {
	if (strpos($wert,'(Gruppe)') !== false) {
		$input_names4["groups"][] = str_replace(' (Gruppe)', '', $wert);
	} else {
		$input_names4["user"][] = $wert;
	}
}

//Der "groups"-Index des Arrays wird in einen String konvertiert
$input_names5_groups = implode(', ', $input_names4["groups"]);

//SQL: Gruppen zu User-IDs
$sql_g2u = $db->prepare("SELECT ug.user_id FROM users_groups ug LEFT JOIN groups g ON g.group_id = ug.group_id WHERE g.group_name IN (?)");
$sql_g2u->bind_param('s', $input_names5_groups);
if ($sql_g2u->execute()) {
	$sql_g2u->store_result();
	$sql_g2u->bind_result($user_id);
	while ($sql_g2u->fetch()) {
		echo $user_id.', ';
	}
} else {
	echo 'Fehler: Es konnten keine Benutzer aus den Gruppen bestimmt werden.';
	exit;
}
```

Nur werden mir hier keine Benutzer-IDs ausgegeben. Es liegt aber am SQL-Teil, denn die wenn ich mir die Variable $input_names5_groups ausgeben lasse, bekomme ich: "Admins, Benutzer"

Wo liegt hier der Fehler?


----------



## JimSim3 (6. September 2015)

Ups, peinlich. Hab gar nicht an strpos und str_replace gedacht... Haste schön gelöst. 

Das Problem ist, das SQL "Admins, Benutzer" als ein String betrachtet. Und den gibt es nunmal nicht. Du müsstest für jede Variable ein eigenes Fragezeichen einbauen. In deinem Beispiel wäre das also "... IN (?,?)" mit bind_param("Admins", "Benutzer").

Mit call_user_func_array() kannst du ein Array an das bind_param übergeben. Die "?" im IN musst du dynamisch erzeugen und an die länge deines Arrays anpassen.


----------



## Georgler (6. September 2015)

Hi,

ich setze mich nun gerade daran die Verlinkungen in die Tabelle msg_link einzutragen.

*Das ist mein Code:*
PS: Vor dem Eintragen der Verlinkungen, wurde bereits die Nachricht an sich in msg_data eingetragen (daher kommt auch $sql_data->insert_id)

```
$sql_data_lastinsertid = $sql_data->insert_id;
		
$placeholders_empfaenger_ids = implode(', ', array_fill(0, count($empfaenger_ids2), "(?, ?, ?)"));

$sql_link_query = "INSERT INTO msg_link (to_id, msg_id, gelesen) VALUES ($placeholders_empfaenger_ids)";
$sql_link = $db->prepare($sql_link_query);
$type3 = str_repeat('iis', count($empfaenger_ids2));
foreach ($empfaenger_ids2 as $to_id) {
	$values3[] = $to_id;
	$values3[] = $sql_data_lastinsertid;
	$values3[] = $null;
}
foreach ($values3 as $key3 => $value3) {
	$value_references3[$key3] = &$values3[$key3];
}
call_user_func_array('mysqli_stmt_bind_param', array_merge(array($sql_link, $type3), $value_references3));
if ($sql_link->execute()) {
	echo '<div id="ajax_response_css"><span class="green">Nachricht wurde erfolgreich an '.htmlentities($input_names2, ENT_QUOTES, 'UTF-8').' verschickt.</span></div>';
	exit;
} else {
	echo '<div id="ajax_response_css"><span class="red">Fehler: Nachricht konnte nicht verschickt werden. [Fehlercode: SQL-01a]</span></div>';
	exit;
}
```

Dieser Code gibt mir allerdings folgende Fehlermeldung:
Warning: mysqli_stmt_bind_param() expects parameter 1 to be mysqli_stmt, boolean given 
Fatal error: Call to a member function execute() on a non-object


----------



## JimSim3 (6. September 2015)

'mysqli_stmt_bind_param' ersetzen mit array($sql_link, 'bind_param') wenn ich mich recht erinnere....


----------



## Georgler (6. September 2015)

Dann bekomme ich folgende Fehlermeldung: Parse error: syntax error, unexpected T_OBJECT_OPERATOR, expecting ')

Diese Codeschnipsel funktionieren mit diesem System:

```
$sql_n2u_query = "SELECT user_id FROM users WHERE username IN ($placeholders_users)";
$stmt2 = $db->prepare($sql_n2u_query);
$type2 = str_repeat('s', count($input_names4["users"]));
foreach ($input_names4["users"] as $user_name) {
	$values2[] = $user_name;
}
foreach ($values2 as $key2 => $value2) {
	$value_references2[$key2] = &$values2[$key2];
}
call_user_func_array('mysqli_stmt_bind_param', array_merge(array($stmt2, $type2), $value_references2));
if ($stmt2->execute()) {
	$stmt2->store_result();
	$stmt2->bind_result($user_id2);
	while ($stmt2->fetch()) {
		$empfaenger_ids[] = $user_id2;
	}
} else {
	echo 'Fehler: Datenbankabfrage fehlgeschlagen.';
    exit;
}
```

Zum ursprünglichen Code habe ich mal ein paar Variablen ausgeben lassen:

*$empfaenger_ids2 (Array):*

```
Array
(
    [0] => 3
    [1] => 5
)
```

*$sql_link_query (Wichtig hierbei: Ich habe bei den Empfängern die Admin-Gruppe (1 Mitglied) + textbenutzer (anderes Mitglied) ausgewählt):*
INSERT INTO msg_link (to_id, msg_id, gelesen) VALUES ((?, ?, ?), (?, ?, ?))

*$values_references3 (Array):*

```
Array
(
    [0] => 3
    [1] => 28
    [2] => 0
    [3] => 5
    [4] => 28
    [5] => 0
)
```


----------



## JimSim3 (6. September 2015)

Habs nochmal editiert... Versuchs mal ohne $this-> also einfach nur array($sql_link, 'bind_param')

Hm... Andererseits... Du benutzt doch PDO oder? Kann man da nicht einfach im execute die Values übergeben? Ich merk gerade, meine letzten PHP Projekte sind doch schon ne Zeitlang her...  Ap0ll0XT  kann dir vermutlich besser helfen als ich.


----------



## Georgler (6. September 2015)

Hm, das klappt auch nicht.

Also ich benutze bei fast allen Sachen MySQLI und dann halt objektorientiert.  Ich habe bisher nur bei einem Projekt PDO benutzt.

Könnte aber hierfür gut PDO benutzen, die Connect-Datei dazu habe ich ja.

Ich habe nur bisher fast nie was mit PDO gemacht, deshalb wüsste ich nicht, wie man so eine "dynamische" Query machen könnte.
Die Anzahl der Übergebenen Variablen variiert ja, je nachdem wie viele Benutzer/Gruppen als Empfänger ausgegeben wurden.


----------



## JimSim3 (6. September 2015)

Ach... Da fehlt ne Klammer am Ende...


----------



## Georgler (6. September 2015)

Habe jetzt folgende Zeile:

```
call_user_func_array((array($sql_link, 'bind_param')), array_merge(array($sql_link, $type3), $value_references3));
```

und bekomme folgende Fehlermeldung:
Warning: call_user_func_array() expects parameter 1 to be a valid callback, first array member is not a valid class name or object
Fatal error: Call to a member function execute() on a non-object


----------



## JimSim3 (6. September 2015)

Hm, ne. Bin das nochmal durchgegangen und hab das mal mit nem alten Projekt von mir verglichen. Zumindest den Fehler kann ich mir nicht erklären... (Das mit der Klammer war mal wieder nen BrainFart von mir... Nochmal nachgezählt, stimmte doch...)  Die Version sollte eigentlich funktionieren:

call_user_func_array(array($sql_link, 'bind_param'), array_merge(array($sql_link, $type3), $value_references3));

Welcher fehler kam da? Auch der valid callback error?


----------



## Georgler (6. September 2015)

Warning: call_user_func_array() expects parameter 1 to be a valid callback, first array member is not a valid class name or object 
Fatal error: Call to a member function execute() on a non-object


----------



## JimSim3 (6. September 2015)

Kann der Fehler vorher liegen? Schonmal überprüft was beim prepare rauskommt?


----------



## Georgler (6. September 2015)

Ne, wie kann man das denn überprüfen? Aber wie gesagt, die vorherigen Queries (die genau diese Passage auch nutzen) funktionieren.


----------



## JimSim3 (6. September 2015)

Einfach nach dem prepare ausgeben lassen...
var_dump($sql_link)

Ansonsten sollte man generell überprüfen ob da was falsch läuft nachdem man das prepare ausgeführt hat bspw. mit
if(!$sql_link) {
  //error handling.
}


----------



## Georgler (6. September 2015)

Ausgegebener Text mit vardump:
bool(false)
Warning: call_user_func_array() expects parameter 1 to be a valid callback, first array member is not a valid class name or object
Fatal error: Call to a member function execute() on a non-object

Mein Code:

```
$sql_data = $db->prepare("INSERT INTO msg_data (betreff, nachricht, to_string, user_id) VALUES (?, ?, ?, ?)");
	$sql_data->bind_param('sssi', $input_betreff, $input_nachricht3, $input_names2, $user_id);
	if ($sql_data->execute()) {
		$sql_data_lastinsertid = $sql_data->insert_id;
		
		$placeholders_empfaenger_ids = implode(', ', array_fill(0, count($empfaenger_ids2), "(?, ?, ?)"));
		
		$sql_link_query = "INSERT INTO msg_link (to_id, msg_id, gelesen) VALUES ($placeholders_empfaenger_ids)";
		$sql_link = $db->prepare($sql_link_query);
		var_dump($sql_link);
		$type3 = str_repeat('iis', count($empfaenger_ids2));
		foreach ($empfaenger_ids2 as $to_id) {
			$values3[] = $to_id;
			$values3[] = $sql_data_lastinsertid;
			$values3[] = $null;
		}
		foreach ($values3 as $key3 => $value3) {
			$value_references3[$key3] = &$values3[$key3];
		}
		call_user_func_array(array($sql_link, 'bind_param'), array_merge(array($sql_link, $type3), $value_references3));
		if ($sql_link->execute()) {
			echo '<div id="ajax_response_css"><span class="green">Nachricht wurde erfolgreich an '.htmlentities($input_names2, ENT_QUOTES, 'UTF-8').' verschickt.</span></div>';
			exit;
		} else {
			echo '<div id="ajax_response_css"><span class="red">Fehler: Nachricht konnte nicht verschickt werden. [Fehlercode: SQL-01a]</span></div>';
			exit;
		}
	} else {
		echo '<div id="ajax_response_css"><span class="red">Fehler: Nachricht konnte nicht gespeichert und nicht verschickt werden. [Fehlercode: SQL-01a]</span></div>';
		exit;
	}
```


----------



## JimSim3 (6. September 2015)

Aha. Also wirft das prepared nen Fehler...

Lass dir mal $sql_link_query ausgeben und guck dir an wo da der Fehler liegen könnte...


----------



## Georgler (6. September 2015)

Da bekomm ich folgende Ausgabe (die Fehler sind natürlich weiterhin die gleichen):
string(75) "INSERT INTO msg_link (to_id, msg_id, gelesen) VALUES ((?, ?, ?), (?, ?, ?))"

Sieht eigentlich richtig aus, denn ich habe eine Gruppe ausgewählt (mit einem Mitglied) und dann noch einen Benutzer. Ergibt zwei Benutzer insgesamt und für jeden Benutzer wird in msg_link ja eine Row angelegt mit jeweils 3 Spalten.
Das einzige, was mich verwirrt, ist das strin(75) davor. Kommt das von var_dump?


----------



## Ap0ll0XT (6. September 2015)

Die einzelnen Datensätze werden durch Kommata getrennt. Die komplett umschließende Klammer ist nicht korrekt.


----------



## Georgler (6. September 2015)

Oh, vielen Dank! 

Jetzt bekomme ich folgenden Fehler:
Warning: Parameter 2 to mysqli_stmt::bind_param() expected to be a reference, value given

*Edit:*

Wenn ich mir mal das Array "value_references3" ausgeben lasse, bekomme ich folgendes.

```
Array
(
    [0] => 3
    [1] => 40
    [2] => 0
    [3] => 29
    [4] => 40
    [5] => 0
)
```

Das macht beim zweiten Hingucken schon Sinn, denn in jedem 3er-Abschnitt ist die erste Zahl die Benutzer-ID (Empfänger), dann die Msg-ID und dann der Wert, ob die Nachricht gelesen wurde (beim Senden 0).

Aber wird das auch richtig interpretiert, wahrscheinlich nicht, sonst würde es ja funktionieren .

*Edit 2:*
Es funktioniert nun wieder. Folgenen Code musste ich verwenden:

```
call_user_func_array('mysqli_stmt_bind_param', array_merge(array($sql_link, $type3), $value_references3));
```


----------



## Ap0ll0XT (6. September 2015)

Klar funktioniert es jetzt. Du nutzt jetzt auch wieder den prozeduralen Stil. Beim objektorientierten Stil hättest du bei array_merge einfach nur das $sql_link wegnehmen müssen. Dann wäre das auch gegangen.

So zum Beispiel:

```
call_user_func_array(array($sql_link, 'bind_param'), array_merge(array($type3), $value_references3));
```

bzw. in sauber:

```
array_unshift($value_references3,$type3);
call_user_func_array(array($sql_link, 'bind_param'), $value_references3);
```


----------



## Georgler (6. September 2015)

Stimmt, so wärs auch gegangen.

Ich habe nochmal eine Frage .

Ich habe am Anfang der PHP-Datei, bei der der Inhalt einer Nachricht ausgegeben wird, eine Blockade eingebaut, sodass man da nichts angezeigt bekommt, falls die Nachricht nicht für einen bestimmt war (= nicht Empfänger war). Nun baue ich gerade den Gesendet-Bereich und daher muss ich diese Blockade nun umbauen.

Vorher wurde das so geprüft:

```
$sql_sec = $db->prepare("SELECT msg_id FROM msg_link WHERE msg_id = ? AND to_id = ?");
$sql_sec->bind_param('ii', $input_id, $user_id);
```

Jetzt muss ich ja noch die Autor-ID berücksichtigen und habe dann folgendes getestet:

```
$sql_sec = $db->prepare("SELECT ml.msg_id FROM msg_link ml LEFT JOIN msg_data md ON md.id = ml.msg_id WHERE (ml.msg_id = ? AND ml.to_id = ?) OR (md.user_id = ?)");
$sql_sec->bind_param('iii', $input_id, $user_id, $user_id);
```

Zweiteres gibt mir nur leider keine Rows zurück. Woran kann das liegen?


----------



## Ap0ll0XT (6. September 2015)

Lass mich raten. Die Nachricht, die du abprüfst ist zwar vom Autor, aber nicht an den abzurufenden Benutzer adressiert? Das ist vollkommen normal. Denn du hast dort eine Join-Anweisung, bei der die msg_data Tabelle eingefügt wird, sobald es für md.id und ml.msg_id eine Übereinstimmung gibt. Blöd nur, wenn der Nutzer Author und kein Empfänger ist. Denn dann fehlt diese Übereinstimmung und es wird keine Zeile zurückgeliefert. Versuche es mal mit Dual-Select:

```
SELECT ml.msg_id, md.id FROM msg_link ml, msg_data md WHERE (ml.msg_id = ? AND ml.to_id = ?) OR (md.user_id = ?)
```
Das kann ich jetzt nicht testen und kann dir auch nicht sagen, ob das funktioniert. Aber mit einer JOIN-Anweisung wird es definitiv nichts. Denn wenn die Bedingung für das Join nicht erfüllt wird, dann kann md.user_id auch nicht geprüft werden. Zur Not nutzt du 2 Querries dafür oder eben UNION.


----------



## JimSim3 (7. September 2015)

Ap0ll0XT schrieb:


> Lass mich raten. Die Nachricht, die du abprüfst ist zwar vom Autor, aber nicht an den abzurufenden Benutzer adressiert? Das ist vollkommen normal. Denn du hast dort eine Join-Anweisung, bei der die msg_data Tabelle eingefügt wird, sobald es für md.id und ml.msg_id eine Übereinstimmung gibt. Blöd nur, wenn der Nutzer Author und kein Empfänger ist. Denn dann fehlt diese Übereinstimmung und es wird keine Zeile zurückgeliefert. Versuche es mal mit Dual-Select:
> 
> ```
> SELECT ml.msg_id, md.id FROM msg_link ml, msg_data md WHERE (ml.msg_id = ? AND ml.to_id = ?) OR (md.user_id = ?)
> ...



Dual Select ist eigentlich nichts anderes als ein impliziter Join und nichts anderes macht die Datenbank intern... Also wird es wohl 2 Queries brauchen.


----------



## Ap0ll0XT (7. September 2015)

Hm ich hatte immer im Hinterkopf, das ein Join-Query erst dann liefert, wenn die Abhängigkeitsbedingung erfüllt wird. Aber wenn dem so ist, dann 2 Querries.


----------



## Georgler (7. September 2015)

Wie stark beeinflussen Queries eigentlich so die Performance? Ich scheue mich immer davor mehrere zu benutzen, weil mir ich mir das schlecht für die Geschwindigkeit einer Website vorstelle.

Edit: Bisher funktioniert die Variante mit Union übrigens.


----------



## Ap0ll0XT (7. September 2015)

Das kommt ganz auf du Datenbank und dessen Füllstand an. Wenn ich mir ansehe, wie viele Querries Wordpress oder Joomla da manchmal raushauen (oder auch PHPBB), dan biste noch bestens dabei. Am wichtigsten für die Performance sind die Querries auf stark frequentierten Seiten. Die Nachrichtenfunktion zum Beispiel wird weniger stark frequentiert als eine Startseite wie hier bei PCGH, wo unzählige News, Artikel oder Videos aus der Datenbank geholt werden. Man darf es nicht zu streng sehen. Versuche mit so wenig Querries wie nötig zu arbeiten. Aber mach dir auch kein Kopf, wenn du für ein Skript ein oder 2 Querries mehr brauchst.


----------



## Georgler (7. September 2015)

Ist dieses $sql->close(); eigentlich immer notwendig oder kann man das weglassen?


----------

