write.php

repair
This commit is contained in:
2025-11-03 21:33:59 +01:00
committed by GitHub
parent 195e793580
commit 83262dd020
+165 -64
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,8 +96,11 @@ 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,
@@ -100,10 +110,14 @@ function getDB() {
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,
attachment_path TEXT,
attachment_type TEXT,
attachment_size INTEGER,
FOREIGN KEY (from_user_id) REFERENCES users(id), FOREIGN KEY (from_user_id) REFERENCES users(id),
FOREIGN KEY (to_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->bindValue(':username', ADMIN_USERNAME, SQLITE3_TEXT); $stmt = $db->prepare('SELECT COUNT(*) as count FROM admins WHERE username = :username');
$result = $stmt->execute(); $stmt->bindValue(':username', ADMIN_USERNAME, SQLITE3_TEXT);
$row = $result->fetchArray(SQLITE3_ASSOC); $result = $stmt->execute();
$row = $result->fetchArray(SQLITE3_ASSOC);
if ($row['count'] == 0) { 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 = $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);
$stmt->bindValue(':password', ADMIN_PASSWORD, SQLITE3_TEXT); $stmt->bindValue(':password', ADMIN_PASSWORD, SQLITE3_TEXT);
$stmt->execute(); $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,6 +1842,14 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') {
echo "retry: " . SSE_RETRY_MS . "\n\n"; echo "retry: " . SSE_RETRY_MS . "\n\n";
flush(); flush();
$lastPingTime = time();
// ✅ ENDLOSSCHLEIFE HINZUFÜGEN!
while (true) {
if (connection_aborted()) {
break;
}
$db = getDB(); $db = getDB();
$stmt = $db->prepare(' $stmt = $db->prepare('
@@ -1882,19 +1902,31 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') {
'from_username' => $row['from_username'], 'from_username' => $row['from_username'],
'from_display_name' => $row['from_username'] . '#' . $row['from_display_id'] 'from_display_name' => $row['from_username'] . '#' . $row['from_display_id']
]; ];
$lastMessageId = max($lastMessageId, (int)$row['id']);
} }
if (!empty($messages)) { if (!empty($messages)) {
echo "data: " . json_encode(['type' => 'messages', 'messages' => $messages]) . "\n\n"; echo "data: " . json_encode(['type' => 'messages', 'messages' => $messages]) . "\n\n";
flush(); flush();
} else { }
// Ping alle 15 Sekunden
if (time() - $lastPingTime >= 15) {
echo "data: " . json_encode(['type' => 'ping']) . "\n\n"; echo "data: " . json_encode(['type' => 'ping']) . "\n\n";
flush(); flush();
$lastPingTime = time();
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,18 +3927,59 @@ 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 message = chatInputEl.value.trim(); const fileType = (file.type || '').toLowerCase();
const fileName = file.name || '';
clearAttachmentWarning();
if (attachmentFile) {
const fileType = (attachmentFile.type || '').toLowerCase();
const fileName = attachmentFile.name || '';
const isJpeg = /^image\/jpe?g$/.test(fileType) || /\.jpe?g$/i.test(fileName); const isJpeg = /^image\/jpe?g$/.test(fileType) || /\.jpe?g$/i.test(fileName);
if (!isJpeg) { if (!isJpeg) {
@@ -3909,11 +3988,37 @@ async function sendMessage() {
return; return;
} }
if (attachmentFile.size > ATTACHMENT_MAX_SIZE) { if (file.size > ATTACHMENT_MAX_SIZE) {
showAttachmentWarning('Bild ist zu groß (max. 200 KB).'); showAttachmentWarning('Bild ist zu groß (max. 200 KB).');
clearAttachmentSelection(); clearAttachmentSelection();
return; 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();
if (!message && !attachmentFile) {
return;
}
if (!state.selectedUserId) {
alert('Bitte wähle einen Chat-Partner aus');
return;
} }
const formData = new FormData(); const formData = new FormData();
@@ -3925,14 +4030,24 @@ async function sendMessage() {
formData.append('attachment', attachmentFile); formData.append('attachment', attachmentFile);
} }
try {
const response = await postFormData(formData);
const result = await response.json();
if (result.success) { if (result.success) {
chatInputEl.value = ''; chatInputEl.value = '';
chatInputEl.dispatchEvent(new Event('input')); chatInputEl.style.height = 'auto';
clearAttachmentSelection();
// Nachricht wird via SSE empfangen
} else { } else {
alert(result.error); 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,49 +4056,40 @@ 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)) {
if (data.type === 'messages' && data.messages) {
data.messages.forEach(msg => { data.messages.forEach(msg => {
const messageId = Number(msg.id); const messageId = Number(msg.id);
if (messageId > state.lastMessageId) { if (messageId > state.lastMessageId) {
state.lastMessageId = messageId; state.lastMessageId = messageId;
if (state.selectedUserId && const isRelevant = state.selectedUserId && (
((msg.from_user_id === state.selectedUserId && msg.to_user_id === state.currentUserId) || (msg.from_user_id === state.selectedUserId && msg.to_user_id === state.currentUserId) ||
(msg.from_user_id === state.currentUserId && msg.to_user_id === state.selectedUserId))) { (msg.from_user_id === state.currentUserId && msg.to_user_id === state.selectedUserId)
);
if (!state.messages.find(m => Number(m.id) === messageId)) { if (isRelevant) {
const exists = state.messages.some(m => Number(m.id) === messageId);
if (!exists) {
state.messages.push(msg); state.messages.push(msg);
renderMessages(); renderMessages();
@@ -3995,33 +4101,28 @@ function startSSE() {
} }
}); });
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; ?>