write.php

repair
This commit is contained in:
2025-11-03 21:33:59 +01:00
committed by GitHub
parent 195e793580
commit 83262dd020
+241 -140
View File
@@ -35,6 +35,13 @@ define('MAX_MESSAGES_PER_FETCH', 200);
define('MAX_MESSAGES_PER_MINUTE', 10); define('MAX_MESSAGES_PER_MINUTE', 10);
define('MAX_MESSAGES_PER_HOUR', 100); define('MAX_MESSAGES_PER_HOUR', 100);
define('MAX_MESSAGES_PER_DAY_U18', 50); define('MAX_MESSAGES_PER_DAY_U18', 50);
define('UPLOAD_DIR', __DIR__ . '/uploads');
define('MAX_ATTACHMENT_SIZE', 200 * 1024); // 200 KB
// Upload-Verzeichnis erstellen
if (!is_dir(UPLOAD_DIR)) {
mkdir(UPLOAD_DIR, 0755, true);
}
// Admin Credentials (BITTE ÄNDERN!) // Admin Credentials (BITTE ÄNDERN!)
define('ADMIN_USERNAME', 'admin'); define('ADMIN_USERNAME', 'admin');
@@ -89,21 +96,28 @@ function getDB() {
) )
'); ');
// Messages Table // Messages Table
$db->exec(' $db->exec('
CREATE TABLE IF NOT EXISTS messages ( CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
from_user_id INTEGER NOT NULL, from_user_id INTEGER NOT NULL,
to_user_id INTEGER NOT NULL, to_user_id INTEGER NOT NULL,
message TEXT NOT NULL, message TEXT NOT NULL,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
is_read INTEGER DEFAULT 0, is_read INTEGER DEFAULT 0,
is_flagged INTEGER DEFAULT 0, is_flagged INTEGER DEFAULT 0,
flag_reason TEXT, flag_reason TEXT,
FOREIGN KEY (from_user_id) REFERENCES users(id), attachment_path TEXT,
FOREIGN KEY (to_user_id) REFERENCES users(id) attachment_type TEXT,
) attachment_size INTEGER,
'); FOREIGN KEY (from_user_id) REFERENCES users(id),
FOREIGN KEY (to_user_id) REFERENCES users(id)
)
');
// Online Status Table // Online Status Table
$db->exec(' $db->exec('
@@ -187,23 +201,21 @@ function getDB() {
'); ');
// Create default admin if not exists // Create default admin if not exists
$stmt = $db->prepare('SELECT COUNT(*) as count FROM admins WHERE username = :username'); // Admin-Account erstellen (nur einmal!)
$stmt = $db->prepare('SELECT COUNT(*) as count FROM admins WHERE username = :username');
$stmt->bindValue(':username', ADMIN_USERNAME, SQLITE3_TEXT);
$result = $stmt->execute();
$row = $result->fetchArray(SQLITE3_ASSOC);
if ($row['count'] == 0) {
$stmt = $db->prepare('INSERT INTO admins (username, password_hash) VALUES (:username, :password)');
$stmt->bindValue(':username', ADMIN_USERNAME, SQLITE3_TEXT); $stmt->bindValue(':username', ADMIN_USERNAME, SQLITE3_TEXT);
$result = $stmt->execute(); $stmt->bindValue(':password', ADMIN_PASSWORD, SQLITE3_TEXT);
$row = $result->fetchArray(SQLITE3_ASSOC); $stmt->execute();
}
if ($row['count'] == 0) {
$stmt = $db->prepare('INSERT INTO admins (username, password_hash) VALUES (:username, :password)');
$stmt->bindValue(':username', ADMIN_USERNAME, SQLITE3_TEXT);
$result = $stmt->execute();
$row = $result->fetchArray(SQLITE3_ASSOC);
if ($row['count'] == 0) {
$stmt = $db->prepare('INSERT INTO admins (username, password_hash) VALUES (:username, :password)');
$stmt->bindValue(':username', ADMIN_USERNAME, SQLITE3_TEXT);
$stmt->bindValue(':password', ADMIN_PASSWORD, SQLITE3_TEXT);
$stmt->execute();
}
// Create indexes // Create indexes
$db->exec('CREATE INDEX IF NOT EXISTS idx_messages_users ON messages(from_user_id, to_user_id)'); $db->exec('CREATE INDEX IF NOT EXISTS idx_messages_users ON messages(from_user_id, to_user_id)');
@@ -1830,7 +1842,15 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') {
echo "retry: " . SSE_RETRY_MS . "\n\n"; echo "retry: " . SSE_RETRY_MS . "\n\n";
flush(); flush();
$db = getDB(); $lastPingTime = time();
// ✅ ENDLOSSCHLEIFE HINZUFÜGEN!
while (true) {
if (connection_aborted()) {
break;
}
$db = getDB();
$stmt = $db->prepare(' $stmt = $db->prepare('
SELECT SELECT
@@ -1846,55 +1866,67 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') {
uf.user_id as from_display_id, uf.user_id as from_display_id,
uf.age_group as from_age_group, uf.age_group as from_age_group,
ut.age_group as to_age_group ut.age_group as to_age_group
FROM messages m FROM messages m
JOIN users uf ON m.from_user_id = uf.id JOIN users uf ON m.from_user_id = uf.id
JOIN users ut ON m.to_user_id = ut.id JOIN users ut ON m.to_user_id = ut.id
WHERE m.id > :last_message_id WHERE m.id > :last_message_id
AND (m.to_user_id = :current_user_id OR m.from_user_id = :current_user_id) AND (m.to_user_id = :current_user_id OR m.from_user_id = :current_user_id)
AND NOT EXISTS ( AND NOT EXISTS (
SELECT 1 FROM blocks SELECT 1 FROM blocks
WHERE (blocker_id = :current_user_id AND blocked_id = m.from_user_id) WHERE (blocker_id = :current_user_id AND blocked_id = m.from_user_id)
OR (blocker_id = m.from_user_id AND blocked_id = :current_user_id) OR (blocker_id = m.from_user_id AND blocked_id = :current_user_id)
) )
ORDER BY m.id ASC ORDER BY m.id ASC
'); ');
$stmt->bindValue(':last_message_id', $lastMessageId, SQLITE3_INTEGER); $stmt->bindValue(':last_message_id', $lastMessageId, SQLITE3_INTEGER);
$stmt->bindValue(':current_user_id', $currentUserId, SQLITE3_INTEGER); $stmt->bindValue(':current_user_id', $currentUserId, SQLITE3_INTEGER);
$result = $stmt->execute(); $result = $stmt->execute();
$messages = []; $messages = [];
while ($row = $result->fetchArray(SQLITE3_ASSOC)) { while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
$otherAgeGroup = $row['from_user_id'] === $currentUserId ? $row['to_age_group'] : $row['from_age_group']; $otherAgeGroup = $row['from_user_id'] === $currentUserId ? $row['to_age_group'] : $row['from_age_group'];
if (!canUsersChatByAge($currentAgeGroup, $otherAgeGroup)) { if (!canUsersChatByAge($currentAgeGroup, $otherAgeGroup)) {
continue; continue;
}
$messages[] = [
'id' => $row['id'],
'from_user_id' => $row['from_user_id'],
'to_user_id' => $row['to_user_id'],
'message' => $row['message'],
'timestamp' => $row['timestamp'],
'attachment_url' => $row['attachment_path'] ?: null,
'attachment_type' => $row['attachment_type'] ?: null,
'attachment_size' => $row['attachment_size'] !== null ? (int)$row['attachment_size'] : null,
'from_username' => $row['from_username'],
'from_display_name' => $row['from_username'] . '#' . $row['from_display_id']
];
$lastMessageId = max($lastMessageId, (int)$row['id']);
} }
$messages[] = [ if (!empty($messages)) {
'id' => $row['id'], echo "data: " . json_encode(['type' => 'messages', 'messages' => $messages]) . "\n\n";
'from_user_id' => $row['from_user_id'], flush();
'to_user_id' => $row['to_user_id'], }
'message' => $row['message'],
'timestamp' => $row['timestamp'],
'attachment_url' => $row['attachment_path'] ?: null,
'attachment_type' => $row['attachment_type'] ?: null,
'attachment_size' => $row['attachment_size'] !== null ? (int)$row['attachment_size'] : null,
'from_username' => $row['from_username'],
'from_display_name' => $row['from_username'] . '#' . $row['from_display_id']
];
}
if (!empty($messages)) { // Ping alle 15 Sekunden
echo "data: " . json_encode(['type' => 'messages', 'messages' => $messages]) . "\n\n"; if (time() - $lastPingTime >= 15) {
flush(); echo "data: " . json_encode(['type' => 'ping']) . "\n\n";
} else { flush();
echo "data: " . json_encode(['type' => 'ping']) . "\n\n"; $lastPingTime = time();
flush(); touchUserSession($currentUserId);
}
// Kurze Pause, um CPU zu schonen
usleep(500000); // 0.5 Sekunden
} }
exit; exit;
} }
// ═══════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════
// HTML OUTPUT // HTML OUTPUT
// ═══════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════
@@ -3803,7 +3835,6 @@ function selectUser(userId, displayName) {
renderUserList(); renderUserList();
loadMessages(userId); loadMessages(userId);
} }
async function loadMessages(userId) { async function loadMessages(userId) {
if (!userId) { if (!userId) {
return; return;
@@ -3813,12 +3844,18 @@ async function loadMessages(userId) {
updateChatState('loading', 'Nachrichten werden geladen…'); updateChatState('loading', 'Nachrichten werden geladen…');
try { try {
const response = await fetch(`?action=get_messages&user_id=${userId}`); const response = await fetch(buildUrl({ action: 'get_messages', user_id: userId }));
if (!response.ok) { if (!response.ok) {
throw new Error('NETZWERK_FEHLER'); throw new Error('NETZWERK_FEHLER');
} }
const result = await response.json(); const text = await response.text();
if (!text || text.trim() === '') {
throw new Error('Leere Antwort vom Server');
}
const result = JSON.parse(text);
if (!result.success) { if (!result.success) {
throw new Error(result.error || 'Nachrichten konnten nicht geladen werden.'); throw new Error(result.error || 'Nachrichten konnten nicht geladen werden.');
@@ -3852,6 +3889,7 @@ async function loadMessages(userId) {
} }
} }
function renderMessages() { function renderMessages() {
const container = chatMessagesEl; const container = chatMessagesEl;
@@ -3889,31 +3927,98 @@ function renderMessages() {
updateChatState(null); updateChatState(null);
} }
async function sendMessage() {
if (!chatInputEl) {
let attachmentFile = null;
const ATTACHMENT_MAX_SIZE = 200 * 1024; // 200 KB
const attachmentButton = document.getElementById('attachmentButton');
const attachmentInput = document.getElementById('attachmentInput');
const attachmentInfo = document.getElementById('attachmentInfo');
const attachmentFileName = document.getElementById('attachmentFileName');
const attachmentClearBtn = document.getElementById('attachmentClearBtn');
const attachmentWarning = document.getElementById('attachmentWarning');
function escapeAttribute(text) {
const div = document.createElement('div');
div.textContent = text ?? '';
return div.innerHTML.replace(/"/g, '"');
}
function showAttachmentWarning(message) {
if (attachmentWarning) {
attachmentWarning.textContent = message;
attachmentWarning.classList.remove('hidden');
}
}
function clearAttachmentWarning() {
if (attachmentWarning) {
attachmentWarning.textContent = '';
attachmentWarning.classList.add('hidden');
}
}
function clearAttachmentSelection() {
attachmentFile = null;
if (attachmentInput) attachmentInput.value = '';
if (attachmentInfo) attachmentInfo.classList.add('hidden');
if (attachmentFileName) attachmentFileName.textContent = '';
clearAttachmentWarning();
}
attachmentButton?.addEventListener('click', () => {
attachmentInput?.click();
});
attachmentInput?.addEventListener('change', (e) => {
const file = e.target.files?.[0];
if (!file) {
clearAttachmentSelection();
return; return;
} }
const fileType = (file.type || '').toLowerCase();
const fileName = file.name || '';
const isJpeg = /^image\/jpe?g$/.test(fileType) || /\.jpe?g$/i.test(fileName);
if (!isJpeg) {
showAttachmentWarning('Nur JPG-Bilder sind erlaubt.');
clearAttachmentSelection();
return;
}
if (file.size > ATTACHMENT_MAX_SIZE) {
showAttachmentWarning('Bild ist zu groß (max. 200 KB).');
clearAttachmentSelection();
return;
}
attachmentFile = file;
if (attachmentFileName) {
attachmentFileName.textContent = fileName;
}
if (attachmentInfo) {
attachmentInfo.classList.remove('hidden');
}
clearAttachmentWarning();
});
attachmentClearBtn?.addEventListener('click', clearAttachmentSelection);
async function sendMessage() {
if (!chatInputEl) return;
const message = chatInputEl.value.trim(); const message = chatInputEl.value.trim();
clearAttachmentWarning(); if (!message && !attachmentFile) {
return;
}
if (attachmentFile) { if (!state.selectedUserId) {
const fileType = (attachmentFile.type || '').toLowerCase(); alert('Bitte wähle einen Chat-Partner aus');
const fileName = attachmentFile.name || ''; return;
const isJpeg = /^image\/jpe?g$/.test(fileType) || /\.jpe?g$/i.test(fileName);
if (!isJpeg) {
showAttachmentWarning('Nur JPG-Bilder sind erlaubt.');
clearAttachmentSelection();
return;
}
if (attachmentFile.size > ATTACHMENT_MAX_SIZE) {
showAttachmentWarning('Bild ist zu groß (max. 200 KB).');
clearAttachmentSelection();
return;
}
} }
const formData = new FormData(); const formData = new FormData();
@@ -3925,14 +4030,24 @@ async function sendMessage() {
formData.append('attachment', attachmentFile); formData.append('attachment', attachmentFile);
} }
if (result.success) { try {
chatInputEl.value = ''; const response = await postFormData(formData);
chatInputEl.dispatchEvent(new Event('input')); const result = await response.json();
} else {
alert(result.error); if (result.success) {
chatInputEl.value = '';
chatInputEl.style.height = 'auto';
clearAttachmentSelection();
// Nachricht wird via SSE empfangen
} else {
alert(result.error || 'Nachricht konnte nicht gesendet werden');
}
} catch (error) {
alert('Verbindungsfehler beim Senden');
} }
} }
async function markAsRead(userId) { async function markAsRead(userId) {
const formData = new FormData(); const formData = new FormData();
formData.append('action', 'mark_read'); formData.append('action', 'mark_read');
@@ -3941,87 +4056,73 @@ async function markAsRead(userId) {
await postFormData(formData); await postFormData(formData);
loadUsers(); loadUsers();
} }
function startSSE() { function startSSE() {
if (state.eventSource) { if (state.eventSource) {
state.eventSource.close(); state.eventSource.close();
} }
const url = `?stream=events&last_message_id=${state.lastMessageId}&t=${Date.now()}`; const url = buildUrl({
stream: 'events',
last_message_id: state.lastMessageId
});
state.eventSource = new EventSource(url); state.eventSource = new EventSource(url);
state.eventSource.onopen = () => { state.eventSource.onopen = () => {
state.connectionErrorShown = false; state.connectionErrorShown = false;
if (!state.selectedUserId) {
return;
}
if (state.isLoadingMessages) {
return;
}
if (state.messages.length === 0) {
updateChatState('empty', 'Noch keine Nachrichten. Starte das Gespräch!');
} else {
updateChatState(null);
}
}; };
state.eventSource.onmessage = (event) => { state.eventSource.onmessage = (event) => {
state.connectionErrorShown = false; try {
const data = JSON.parse(event.data); const data = JSON.parse(event.data);
if (data.type === 'messages' && Array.isArray(data.messages)) {
data.messages.forEach(msg => {
const messageId = Number(msg.id);
if (data.type === 'messages' && data.messages) { if (messageId > state.lastMessageId) {
data.messages.forEach(msg => { state.lastMessageId = messageId;
const messageId = Number(msg.id);
if (messageId > state.lastMessageId) { const isRelevant = state.selectedUserId && (
state.lastMessageId = messageId; (msg.from_user_id === state.selectedUserId && msg.to_user_id === state.currentUserId) ||
(msg.from_user_id === state.currentUserId && msg.to_user_id === state.selectedUserId)
);
if (state.selectedUserId && if (isRelevant) {
((msg.from_user_id === state.selectedUserId && msg.to_user_id === state.currentUserId) || const exists = state.messages.some(m => Number(m.id) === messageId);
(msg.from_user_id === state.currentUserId && msg.to_user_id === state.selectedUserId))) { if (!exists) {
state.messages.push(msg);
renderMessages();
if (!state.messages.find(m => Number(m.id) === messageId)) { if (msg.to_user_id === state.currentUserId) {
state.messages.push(msg); markAsRead(msg.from_user_id);
renderMessages(); }
if (msg.to_user_id === state.currentUserId) {
markAsRead(msg.from_user_id);
} }
} }
} }
} });
});
try { loadUsers(); // Aktualisiere Nutzerliste
const data = JSON.parse(event.data);
if (data.type === 'messages') {
processIncomingMessages(Array.isArray(data.messages) ? data.messages : []);
} }
} catch (error) { } catch (error) {
console.warn('Konnte SSE-Daten nicht verarbeiten:', error); console.warn('SSE-Daten konnten nicht verarbeitet werden:', error);
} }
}; };
state.eventSource.onerror = () => { state.eventSource.onerror = () => {
if (!state.connectionErrorShown) { if (!state.connectionErrorShown) {
state.connectionErrorShown = true; state.connectionErrorShown = true;
console.warn('SSE-Verbindung unterbrochen, versuche Neuverbindung.'); console.warn('SSE-Verbindung unterbrochen');
if (state.selectedUserId && !state.isLoadingMessages) {
updateChatState('error', 'Live-Verbindung unterbrochen. Erneuter Verbindungsversuch…');
}
} }
if (state.eventSource) { if (state.eventSource) {
state.eventSource.close(); state.eventSource.close();
} }
setTimeout(startSSE, 1500); setTimeout(startSSE, 2000);
}; };
} }
function escapeHtml(text) { function escapeHtml(text) {
const div = document.createElement('div'); const div = document.createElement('div');
div.textContent = text ?? ''; div.textContent = text ?? '';
@@ -4058,7 +4159,7 @@ setInterval(async () => {
}, 10000); }, 10000);
loadUsers(); loadUsers();
startRealtime(); startSSE();
setInterval(loadUsers, 30000); setInterval(loadUsers, 30000);
<?php endif; ?> <?php endif; ?>