write.php
repair
This commit is contained in:
@@ -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,6 +96,9 @@ function getDB() {
|
|||||||
)
|
)
|
||||||
');
|
');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Messages Table
|
// Messages Table
|
||||||
$db->exec('
|
$db->exec('
|
||||||
CREATE TABLE IF NOT EXISTS messages (
|
CREATE TABLE IF NOT EXISTS messages (
|
||||||
@@ -100,11 +110,15 @@ 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('
|
||||||
CREATE TABLE IF NOT EXISTS online_status (
|
CREATE TABLE IF NOT EXISTS online_status (
|
||||||
@@ -187,17 +201,12 @@ function getDB() {
|
|||||||
');
|
');
|
||||||
|
|
||||||
// Create default admin if not exists
|
// Create default admin if not exists
|
||||||
|
// Admin-Account erstellen (nur einmal!)
|
||||||
$stmt = $db->prepare('SELECT COUNT(*) as count FROM admins WHERE username = :username');
|
$stmt = $db->prepare('SELECT COUNT(*) as count FROM admins WHERE username = :username');
|
||||||
$stmt->bindValue(':username', ADMIN_USERNAME, SQLITE3_TEXT);
|
$stmt->bindValue(':username', ADMIN_USERNAME, SQLITE3_TEXT);
|
||||||
$result = $stmt->execute();
|
$result = $stmt->execute();
|
||||||
$row = $result->fetchArray(SQLITE3_ASSOC);
|
$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);
|
|
||||||
$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 = $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);
|
||||||
@@ -205,6 +214,9 @@ function getDB() {
|
|||||||
$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)');
|
||||||
$db->exec('CREATE INDEX IF NOT EXISTS idx_messages_timestamp ON messages(timestamp)');
|
$db->exec('CREATE INDEX IF NOT EXISTS idx_messages_timestamp ON messages(timestamp)');
|
||||||
@@ -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; ?>
|
||||||
|
|||||||
Reference in New Issue
Block a user