Version stable 01

This commit is contained in:
Flatlogic Bot 2026-02-16 22:09:05 +00:00
parent c0b4015a24
commit e387e07cc6
5 changed files with 256 additions and 74 deletions

View File

@ -1,28 +1,42 @@
<?php
// Request log
file_put_contents('requests.log', date('Y-m-d H:i:s') . " - api_v1_user.php - " . $_SERVER['REQUEST_METHOD'] . " - POST: " . json_encode($_POST) . "\n", FILE_APPEND);
require_once 'auth/session.php';
header('Content-Type: application/json');
// Detailed log
$log = [
'date' => date('Y-m-d H:i:s'),
'method' => $_SERVER['REQUEST_METHOD'],
'post' => $_POST,
'session' => $_SESSION
];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$user = getCurrentUser();
if (!$user) {
$log['error'] = 'Unauthorized';
file_put_contents('requests.log', json_encode($log) . "\n", FILE_APPEND);
echo json_encode(['success' => false, 'error' => 'Unauthorized']);
exit;
}
$log['user_id'] = $user['id'];
$username = !empty($_POST['username']) ? $_POST['username'] : $user['username'];
$avatar_url = isset($_POST['avatar_url']) ? $_POST['avatar_url'] : $user['avatar_url'];
$dnd_mode = isset($_POST['dnd_mode']) ? (int)$_POST['dnd_mode'] : (int)($user['dnd_mode'] ?? 0);
$sound_notifications = isset($_POST['sound_notifications']) ? (int)$_POST['sound_notifications'] : (int)($user['sound_notifications'] ?? 0);
$theme = !empty($_POST['theme']) ? $_POST['theme'] : (!empty($user['theme']) ? $user['theme'] : 'dark');
$dnd_mode = isset($_POST['dnd_mode']) ? (int)$_POST['dnd_mode'] : 0;
$sound_notifications = isset($_POST['sound_notifications']) ? (int)$_POST['sound_notifications'] : 0;
$theme = !empty($_POST['theme']) ? $_POST['theme'] : $user['theme'];
try {
$stmt = db()->prepare("UPDATE users SET username = ?, avatar_url = ?, dnd_mode = ?, sound_notifications = ?, theme = ? WHERE id = ?");
$stmt->execute([$username, $avatar_url, $dnd_mode, $sound_notifications, $theme, $user['id']]);
$success = $stmt->execute([$username, $avatar_url, $dnd_mode, $sound_notifications, $theme, $user['id']]);
$log['db_success'] = $success;
file_put_contents('requests.log', json_encode($log) . "\n", FILE_APPEND);
echo json_encode(['success' => true]);
} catch (Exception $e) {
$log['db_error'] = $e->getMessage();
file_put_contents('requests.log', json_encode($log) . "\n", FILE_APPEND);
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}
exit;

View File

@ -121,9 +121,6 @@ document.addEventListener('DOMContentLoaded', () => {
div.onclick = (e) => {
if (e.target.closest('.emote-actions')) return;
navigator.clipboard.writeText(emote.code);
const originalBg = div.style.backgroundColor;
div.style.backgroundColor = 'var(--blurple)';
setTimeout(() => div.style.backgroundColor = originalBg, 200);
};
div.querySelector('.delete-emote').onclick = async (e) => {
@ -163,9 +160,6 @@ document.addEventListener('DOMContentLoaded', () => {
div.textContent = emoji;
div.onclick = () => {
navigator.clipboard.writeText(emoji);
const originalBg = div.style.backgroundColor;
div.style.backgroundColor = 'var(--blurple)';
setTimeout(() => div.style.backgroundColor = originalBg, 200);
};
grid.appendChild(div);
});
@ -179,10 +173,131 @@ document.addEventListener('DOMContentLoaded', () => {
btn.style.backgroundColor = idx === 0 ? 'var(--separator)' : 'transparent';
btn.innerHTML = `<i class="fas ${cat === 'Custom' ? 'fa-star' : 'fa-smile'} opacity-75"></i> ${cat}`;
btn.onclick = () => {
const sidebar_btns = picker.querySelectorAll('.emoji-sidebar button');
sidebar_btns.forEach(b => b.style.backgroundColor = 'transparent');
btn.onclick = async () => {
sidebar.querySelectorAll('button').forEach(b => {
b.classList.remove('active');
b.style.backgroundColor = 'transparent';
});
btn.classList.add('active');
btn.style.backgroundColor = 'var(--separator)';
await renderGrid(cat);
};
sidebar.appendChild(btn);
});
searchInput.oninput = async () => {
const term = searchInput.value.trim();
if (term) {
sidebar.querySelectorAll('button').forEach(b => {
b.classList.remove('active');
b.style.backgroundColor = 'transparent';
});
await renderGrid(null, term);
} else {
const activeBtn = sidebar.querySelector('button.active');
const activeCat = activeBtn ? activeBtn.innerText.trim() : 'Custom';
await renderGrid(activeCat);
}
};
if (uploadInput) {
uploadInput.onchange = async () => {
const file = uploadInput.files[0];
if (!file) return;
const fd = new FormData();
fd.append('emote', file);
const res = await (await fetch('api/emotes.php?action=upload', { method: 'POST', body: fd })).json();
if (res.success) renderGrid('Custom');
else alert(res.error || "Upload failed");
};
}
await renderGrid('Custom');
}
const UniversalEmojiPicker = {
currentPicker: null,
show: async function(anchor, callback, options = {}) {
this.hide();
const picker = document.createElement('div');
picker.className = 'emoji-picker-container rounded shadow-lg p-0 d-flex flex-column';
picker.style.position = 'fixed';
picker.style.zIndex = '10000';
picker.style.width = options.width || '400px';
picker.style.height = options.height || '450px';
picker.style.backgroundColor = '#2b2d31';
picker.style.border = '1px solid #1e1f22';
picker.style.display = 'flex';
picker.style.flexDirection = 'column';
const mainLayout = document.createElement('div');
mainLayout.className = 'd-flex flex-grow-1 overflow-hidden';
const tabs = document.createElement('div');
tabs.className = 'emoji-sidebar d-flex flex-column p-2 border-end border-secondary custom-scrollbar';
tabs.style.width = '60px';
tabs.style.overflowY = 'auto';
tabs.style.backgroundColor = '#1e1f22';
const contentArea = document.createElement('div');
contentArea.className = 'd-flex flex-column flex-grow-1';
const searchContainer = document.createElement('div');
searchContainer.className = 'p-2 border-bottom border-secondary';
const searchInput = document.createElement('input');
searchInput.type = 'text';
searchInput.placeholder = 'Search emojis...';
searchInput.className = 'form-control form-control-sm bg-dark border-secondary text-white';
searchContainer.appendChild(searchInput);
const grid = document.createElement('div');
grid.className = 'emoji-grid flex-grow-1 p-2 overflow-auto custom-scrollbar';
grid.style.display = 'grid';
grid.style.gridTemplateColumns = 'repeat(auto-fill, minmax(36px, 1fr))';
grid.style.gap = '4px';
const categories = ['Custom', ...Object.keys(EMOJI_CATEGORIES)];
const renderGrid = async (category, searchTerm = '') => {
grid.innerHTML = '';
if (category === 'Custom' && !searchTerm) {
const emotes = await window.loadCustomEmotes();
emotes.forEach(emote => {
const div = document.createElement('div');
div.className = 'emoji-item rounded p-1 text-center';
div.style.cursor = 'pointer';
div.innerHTML = `<img src="${emote.path}" style="width: 24px; height: 24px; object-fit: contain;" title="${emote.code}">`;
div.onclick = () => {
callback(emote.code);
if (!options.keepOpen) this.hide();
};
grid.appendChild(div);
});
} else {
const list = searchTerm ? ALL_EMOJIS.filter(e => e.includes(searchTerm)) : EMOJI_CATEGORIES[category];
(list || []).forEach(emoji => {
const div = document.createElement('div');
div.className = 'emoji-item rounded p-1 text-center';
div.style.cursor = 'pointer';
div.style.fontSize = '20px';
div.textContent = emoji;
div.onclick = () => {
callback(emoji);
if (!options.keepOpen) this.hide();
};
grid.appendChild(div);
});
}
};
categories.forEach(cat => {
const btn = document.createElement('button');
btn.className = 'btn btn-link text-white text-decoration-none p-2 mb-1 opacity-75';
btn.title = cat;
btn.innerHTML = categoryIcons[cat] || '😀';
btn.onclick = async () => {
tabs.querySelectorAll('button').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
await renderGrid(cat);
};
tabs.appendChild(btn);
@ -191,21 +306,20 @@ document.addEventListener('DOMContentLoaded', () => {
searchInput.oninput = async () => {
const term = searchInput.value.trim();
if (term) {
tabs.querySelectorAll('button').forEach(b => {
b.classList.remove('active');
b.style.backgroundColor = 'transparent';
});
tabs.querySelectorAll('button').forEach(b => b.classList.remove('active'));
await renderGrid(null, term);
} else {
const activeBtn = tabs.querySelector('button.active');
const activeCat = activeBtn ? activeBtn.title : 'Custom';
await renderGrid(activeCat);
await renderGrid(activeBtn ? activeBtn.title : 'Custom');
}
};
picker.appendChild(tabs);
picker.appendChild(searchContainer);
picker.appendChild(grid);
mainLayout.appendChild(tabs);
contentArea.appendChild(searchContainer);
contentArea.appendChild(grid);
mainLayout.appendChild(contentArea);
picker.appendChild(mainLayout);
document.body.appendChild(picker);
this.currentPicker = picker;
@ -213,24 +327,14 @@ document.addEventListener('DOMContentLoaded', () => {
const rect = anchor.getBoundingClientRect();
let top = rect.top - picker.offsetHeight - 10;
if (top < 0) top = rect.bottom + 10;
let left = rect.left;
if (left + picker.offsetWidth > window.innerWidth) left = window.innerWidth - picker.offsetWidth - 20;
if (left < 10) left = 10;
// Ensure it doesn't go off screen at the bottom
if (top + picker.offsetHeight > window.innerHeight) {
top = window.innerHeight - picker.offsetHeight - 10;
}
// Ensure it doesn't go off screen at the top
if (top < 0) top = 10;
picker.style.top = `${top}px`;
picker.style.left = `${left}px`;
await renderGrid('Custom');
// Handle outside click
const outsideClick = (e) => {
if (!picker.contains(e.target) && e.target !== anchor && !anchor.contains(e.target)) {
this.hide();
@ -239,7 +343,6 @@ document.addEventListener('DOMContentLoaded', () => {
};
setTimeout(() => document.addEventListener('click', outsideClick), 10);
},
hide() {
if (this.currentPicker) {
this.currentPicker.remove();
@ -329,6 +432,13 @@ document.addEventListener('DOMContentLoaded', () => {
window.loadCustomEmotes();
const emotesTabBtn = document.getElementById('emotes-tab-btn');
if (emotesTabBtn) {
emotesTabBtn.addEventListener('click', () => {
setupSettingsEmotes();
});
}
// Scroll to bottom
scrollToBottom(true);
@ -2177,6 +2287,8 @@ document.addEventListener('DOMContentLoaded', () => {
});
// User Settings - Avatar Search
// User Settings - Save logic removed and moved to index.php for reliability
const avatarSearchBtn = document.getElementById('search-avatar-btn');
const avatarSearchQuery = document.getElementById('avatar-search-query');
const avatarResults = document.getElementById('avatar-results');
@ -2227,44 +2339,7 @@ document.addEventListener('DOMContentLoaded', () => {
});
}
// User Settings - Save
const saveSettingsBtn = document.getElementById('save-settings-btn');
saveSettingsBtn?.addEventListener('click', async () => {
const form = document.getElementById('user-settings-form');
if (!form) return;
const formData = new FormData(form);
// Ensure switches are correctly sent as 1/0
const dndMode = document.getElementById('dnd-switch')?.checked ? '1' : '0';
const soundNotifications = document.getElementById('sound-switch')?.checked ? '1' : '0';
formData.set('dnd_mode', dndMode);
formData.set('sound_notifications', soundNotifications);
// Explicitly get theme to ensure it's captured
const themeInput = form.querySelector('input[name="theme"]:checked');
const theme = themeInput ? themeInput.value : 'dark';
formData.set('theme', theme);
// Visual feedback
document.body.setAttribute('data-theme', theme);
try {
const resp = await fetch('api_v1_user.php?v=' + Date.now(), {
method: 'POST',
body: formData
});
const result = await resp.json();
if (result.success) {
window.location.href = window.location.pathname + window.location.search;
} else {
alert(result.error || 'Failed to save settings');
}
} catch (e) {
console.error('Error saving settings:', e);
alert('Failed to save settings. Please check your connection.');
}
});
// User Settings - Save handled in index.php
function escapeHTML(str) {
const div = document.createElement('div');

View File

@ -1059,12 +1059,62 @@ $emote_html = '<img src="' . htmlspecialchars($ce['path']) . '" alt="' . htmlspe
<div class="modal-footer">
<button type="button" class="btn btn-link text-white text-decoration-none" data-bs-dismiss="modal">Cancel</button>
<button type="button" id="save-settings-btn" class="btn btn-primary" style="background-color: var(--blurple); border: none; padding: 10px 24px;">Save Changes</button>
<button type="button" onclick="handleSaveUserSettings(this)" class="btn btn-primary" style="background-color: var(--blurple); border: none; padding: 10px 24px;">Save Changes</button>
</div>
</div>
</div>
</div>
<script>
async function handleSaveUserSettings(btn) {
const originalContent = btn.innerHTML;
const form = document.getElementById('user-settings-form');
if (!form) return;
if (!form.reportValidity()) return;
btn.disabled = true;
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span> Saving...';
const formData = new FormData(form);
// Ensure switches are correctly sent as 1/0
const dndMode = document.getElementById('dnd-switch')?.checked ? '1' : '0';
const soundNotifications = document.getElementById('sound-switch')?.checked ? '1' : '0';
formData.set('dnd_mode', dndMode);
formData.set('sound_notifications', soundNotifications);
// Explicitly get theme to ensure it's captured
const themeInput = form.querySelector('input[name="theme"]:checked');
const theme = themeInput ? themeInput.value : 'dark';
formData.set('theme', theme);
try {
console.log('Sending save request...');
const resp = await fetch('api_v1_user.php?v=' + Date.now(), {
method: 'POST',
body: formData
});
const result = await resp.json();
console.log('Response received:', result);
if (result.success) {
btn.innerHTML = '<i class="fa-solid fa-check me-2"></i> Saved!';
setTimeout(() => window.location.reload(), 500);
} else {
alert('Error: ' + (result.error || 'Unknown error'));
btn.disabled = false;
btn.innerHTML = originalContent;
}
} catch (e) {
console.error('Save error:', e);
alert('Connection error. Please try again.');
btn.disabled = false;
btn.innerHTML = originalContent;
}
}
</script>
<!-- Server Settings Modal -->
<!-- Server Settings Modal -->
<div class="modal fade" id="serverSettingsModal" tabindex="-1">

View File

@ -303,3 +303,39 @@
2026-02-16 20:29:40 - GET /index.php?server_id=1&channel_id=6 - POST: []
2026-02-16 20:35:04 - GET /?fl_project=38443 - POST: []
2026-02-16 20:49:01 - GET /index.php?server_id=1&channel_id=6 - POST: []
2026-02-16 21:20:57 - GET /?fl_project=38443 - POST: []
2026-02-16 21:30:31 - GET /index.php?server_id=1&channel_id=6 - POST: []
2026-02-16 21:30:38 - GET /index.php?server_id=1&channel_id=6 - POST: []
2026-02-16 21:31:19 - GET /?fl_project=38443 - POST: []
2026-02-16 21:33:59 - GET /?fl_project=38443 - POST: []
2026-02-16 21:35:15 - GET /?fl_project=38443 - POST: []
2026-02-16 21:46:25 - GET /index.php?server_id=1&channel_id=6 - POST: []
2026-02-16 21:46:33 - GET /index.php?server_id=1&channel_id=6 - POST: []
2026-02-16 21:48:54 - api_v1_user.php - - POST: {"username":"testuser","theme":"light"}
2026-02-16 21:48:58 - api_v1_user.php - POST - POST: {"username":"testuser","theme":"light"}
2026-02-16 21:53:22 - GET /?fl_project=38443 - POST: []
2026-02-16 21:56:55 - GET /index.php?server_id=1&channel_id=6 - POST: []
2026-02-16 21:57:11 - GET /index.php?server_id=1&channel_id=6 - POST: []
2026-02-16 21:57:15 - GET /index.php?server_id=1&channel_id=6 - POST: []
2026-02-16 21:57:23 - GET /index.php?server_id=1&channel_id=6 - POST: []
{"date":"2026-02-16 22:01:01","method":"POST","post":{"username":"DebugUser","theme":"light"},"session":{"user_id":1},"user_id":1,"db_success":true}
2026-02-16 22:01:28 - GET /?fl_project=38443 - POST: []
2026-02-16 22:01:51 - GET /index.php?server_id=1&channel_id=6 - POST: []
2026-02-16 22:01:56 - GET /index.php?server_id=1&channel_id=6 - POST: []
2026-02-16 22:02:03 - GET /index.php?server_id=1&channel_id=6 - POST: []
{"date":"2026-02-16 22:02:07","method":"POST","post":{"avatar_url":"","username":"swefpifh","dnd_mode":"1","sound_notifications":"1","theme":"dark"},"session":{"user_id":2,"username":"swefpifh"},"user_id":2,"db_success":true}
2026-02-16 22:02:08 - GET /index.php?server_id=1&channel_id=6 - POST: []
2026-02-16 22:02:09 - GET /index.php?server_id=1&channel_id=6 - POST: []
2026-02-16 22:02:28 - GET /index.php?server_id=1&channel_id=11 - POST: []
2026-02-16 22:02:30 - GET /index.php?server_id=1&channel_id=17 - POST: []
2026-02-16 22:02:33 - GET /index.php?server_id=1&channel_id=11 - POST: []
2026-02-16 22:02:52 - GET /index.php?server_id=1&channel_id=17 - POST: []
2026-02-16 22:03:17 - GET /index.php?server_id=1&channel_id=17 - POST: []
2026-02-16 22:03:19 - GET /index.php?server_id=1&channel_id=17 - POST: []
2026-02-16 22:08:26 - GET /?fl_project=38443 - POST: []
2026-02-16 22:08:27 - GET /index.php?server_id=1&channel_id=17 - POST: []
{"date":"2026-02-16 22:08:44","method":"POST","post":{"avatar_url":"","username":"swefpifh","dnd_mode":"1","sound_notifications":"1","theme":"light"},"session":{"user_id":2,"username":"swefpifh"},"user_id":2,"db_success":true}
2026-02-16 22:08:45 - GET /index.php?server_id=1&channel_id=17 - POST: []
2026-02-16 22:08:46 - GET /index.php?server_id=1&channel_id=17 - POST: []
{"date":"2026-02-16 22:08:52","method":"POST","post":{"avatar_url":"","username":"swefpifh","dnd_mode":"1","sound_notifications":"1","theme":"dark"},"session":{"user_id":2,"username":"swefpifh"},"user_id":2,"db_success":true}
2026-02-16 22:08:52 - GET /index.php?server_id=1&channel_id=17 - POST: []

7
test_save.php Normal file
View File

@ -0,0 +1,7 @@
<?php
require_once 'auth/session.php';
$_SERVER['REQUEST_METHOD'] = 'POST';
$_SESSION['user_id'] = 1; // Assuming user 1 exists
$_POST['username'] = 'testuser';
$_POST['theme'] = 'light';
require_once 'api_v1_user.php';