From 65e028d6e41010f105d2eb066dcafbddcada3415 Mon Sep 17 00:00:00 2001 From: Lars Gebhardt-Kusche Date: Mon, 8 Dec 2025 00:14:39 +0100 Subject: [PATCH] yxyX --- public/assets/js/ui-user.js | 209 ++++++++++++++++++++++++++++++++++-- src/ApiKernel.php | 21 ++++ 2 files changed, 224 insertions(+), 6 deletions(-) diff --git a/public/assets/js/ui-user.js b/public/assets/js/ui-user.js index 0612014..6fa1d12 100644 --- a/public/assets/js/ui-user.js +++ b/public/assets/js/ui-user.js @@ -13,6 +13,8 @@ const state = { }; const pageType = document.body?.dataset?.page || 'account'; +const DEBUG_EMAIL = 'madmin@papa-kind-treff.info'; +const MAX_CONSOLE_LINES = 200; let avatarBtn; let userMenuPanel; @@ -27,12 +29,22 @@ let bridgePreview; let validateBridgeBtn; let menuInitialized = false; let menuOpen = false; +let debugButton; +let debugDialog; +let debugPhpLoaded = false; +let debugPhpLoading = false; +let debugActiveTab = 'php'; +let phpInfoContainer; +let consoleContainer; +let debugStylesInjected = false; +let consolePatched = false; +const consoleBuffer = []; export function initUserPanel() { avatarBtn = document.getElementById('btn-user'); userMenuPanel = document.getElementById('userMenuPanel'); - updateAvatar(); - updateRoleVisibility(); + ensureConsoleCapture(); + handleUserContextChange(); if (!menuInitialized && avatarBtn && userMenuPanel) { avatarBtn.addEventListener('click', toggleUserMenu); document.addEventListener('click', handleDocumentClick, true); @@ -112,6 +124,13 @@ function enforcePageAccess() { window.location.href = '/account.php'; } +function handleUserContextChange() { + updateAvatar(); + updateRoleVisibility(); + enforcePageAccess(); + refreshDebugAccess(); +} + function updateAvatar() { const target = document.getElementById('userAvatar'); if (!target) return; @@ -191,9 +210,7 @@ async function loadAccountData() { if (!res?.ok) throw new Error(res?.error || 'Profil konnte nicht geladen werden'); if (res.user) { window.__currentUser = res.user; - updateAvatar(); - updateRoleVisibility(); - enforcePageAccess(); + handleUserContextChange(); } fillProfileForm(res.user); fillSettingsForm(res.settings || {}); @@ -246,7 +263,7 @@ async function submitProfileForm(ev) { const res = await apiAction('account.profile.update', { method: 'POST', data }); if (!res?.ok) throw new Error(res?.error || 'Profil konnte nicht gespeichert werden'); window.__currentUser = res.user; - updateAvatar(); + handleUserContextChange(); toast('Profil aktualisiert', true); } catch (err) { toast(err.message || 'Fehler beim Speichern', false); @@ -591,3 +608,183 @@ function escapeHtml(str) { .replace(/"/g, '"') .replace(/'/g, '''); } + +function refreshDebugAccess() { + const allowed = (window.__currentUser?.email || '').toLowerCase() === DEBUG_EMAIL; + if (!allowed) { + debugButton?.remove(); + debugButton = null; + if (debugDialog?.open) { + debugDialog.close(); + } + return; + } + ensureDebugStyles(); + ensureConsoleCapture(); + ensureDebugDialog(); + if (!debugButton) { + debugButton = document.createElement('button'); + debugButton.id = 'debugToggleButton'; + debugButton.className = 'debug-floating-btn'; + debugButton.type = 'button'; + debugButton.textContent = 'Debug'; + debugButton.addEventListener('click', () => openDebugDialog('php')); + document.body.appendChild(debugButton); + } +} + +function ensureDebugStyles() { + if (debugStylesInjected) return; + const style = document.createElement('style'); + style.id = 'debugStyles'; + style.textContent = ` + .debug-floating-btn{position:fixed;left:16px;bottom:16px;padding:.5rem 1rem;border-radius:999px;border:none;background:#0ea5e9;color:#fff;font-weight:600;box-shadow:0 10px 25px rgba(15,23,42,.25);z-index:60;cursor:pointer} + .debug-floating-btn:hover{background:#0284c7} + dialog#debugDialog::backdrop{background:rgba(15,23,42,.45)} + #debugDialog{border:none;border-radius:1rem;padding:0;width:80vw;max-width:960px} + .debug-shell{display:flex;flex-direction:column;height:80vh} + .debug-header{display:flex;align-items:center;justify-content:space-between;padding:1rem;border-bottom:1px solid #e2e8f0} + .debug-tabs{display:flex;border-bottom:1px solid #e2e8f0} + .debug-tabs button{flex:1;padding:.75rem 1rem;border:none;background:transparent;cursor:pointer;font-weight:600} + .debug-tabs button.active{background:#e0f2fe;color:#0c4a6e} + .debug-panel{flex:1;overflow:auto;padding:1rem;background:#f8fafc} + #debugConsoleContent{font-family:monospace;font-size:.85rem;white-space:pre-wrap} + .debug-console-entry{margin-bottom:.35rem} + .debug-console-entry.log{color:#15803d} + .debug-console-entry.warn{color:#b45309} + .debug-console-entry.error{color:#b91c1c} + `; + document.head.appendChild(style); + debugStylesInjected = true; +} + +function ensureDebugDialog() { + if (debugDialog) return; + debugDialog = document.createElement('dialog'); + debugDialog.id = 'debugDialog'; + debugDialog.innerHTML = ` +
+
+ Debug Tools + +
+
+ + +
+
+
Lade Daten…
+
+ +
`; + document.body.appendChild(debugDialog); + phpInfoContainer = debugDialog.querySelector('#debugPhpContent'); + consoleContainer = debugDialog.querySelector('#debugConsoleContent'); + debugDialog.querySelector('[data-debug-close]')?.addEventListener('click', () => closeDebugDialog()); + debugDialog.addEventListener('close', () => setDebugTab('php')); + debugDialog.querySelectorAll('[data-debug-tab]').forEach(btn => { + btn.addEventListener('click', () => setDebugTab(btn.getAttribute('data-debug-tab'))); + }); +} + +function openDebugDialog(tab = 'php') { + ensureDebugDialog(); + setDebugTab(tab); + if (!debugDialog.open) debugDialog.showModal(); + if (tab === 'php') { + loadPhpInfo(); + } else { + renderConsolePanel(); + } +} + +function closeDebugDialog() { + if (debugDialog?.open) debugDialog.close(); +} + +function setDebugTab(tab) { + if (!debugDialog) return; + debugActiveTab = tab || 'php'; + debugDialog.querySelectorAll('[data-debug-tab]').forEach(btn => { + const isActive = btn.getAttribute('data-debug-tab') === debugActiveTab; + btn.classList.toggle('active', isActive); + }); + debugDialog.querySelectorAll('[data-debug-panel]').forEach(panel => { + panel.classList.toggle('hidden', panel.getAttribute('data-debug-panel') !== debugActiveTab); + }); + if (debugActiveTab === 'php') { + loadPhpInfo(); + } else { + renderConsolePanel(); + } +} + +async function loadPhpInfo() { + if (debugPhpLoaded || debugPhpLoading || !phpInfoContainer) return; + debugPhpLoading = true; + phpInfoContainer.textContent = 'Lade phpinfo…'; + try { + const res = await apiAction('debug.phpinfo', { method: 'GET' }); + if (!res?.ok) throw new Error(res?.error || 'Fehler beim Laden'); + const frame = document.createElement('iframe'); + frame.style.width = '100%'; + frame.style.height = '100%'; + frame.style.minHeight = '400px'; + frame.style.border = 'none'; + frame.srcdoc = res.html || '

Keine Daten

'; + phpInfoContainer.innerHTML = ''; + phpInfoContainer.appendChild(frame); + debugPhpLoaded = true; + } catch (err) { + phpInfoContainer.textContent = err.message || 'Fehler beim Laden'; + } finally { + debugPhpLoading = false; + } +} + +function renderConsolePanel() { + if (!consoleContainer) return; + if (!consoleBuffer.length) { + consoleContainer.textContent = 'Noch keine Konsolenmeldungen in dieser Sitzung.'; + return; + } + const lines = consoleBuffer.map(entry => { + const time = entry.time.toLocaleTimeString(); + return `
[${time}] ${escapeHtml(entry.text)}
`; + }); + consoleContainer.innerHTML = lines.join(''); +} + +function ensureConsoleCapture() { + if (consolePatched) return; + ['log', 'warn', 'error'].forEach(type => { + const original = console[type]; + console[type] = function (...args) { + appendConsoleMessage(type, args); + if (typeof original === 'function') { + original.apply(console, args); + } + }; + }); + consolePatched = true; +} + +function appendConsoleMessage(type, args) { + const text = args.map(formatConsoleArg).join(' '); + consoleBuffer.push({ type, text, time: new Date() }); + if (consoleBuffer.length > MAX_CONSOLE_LINES) consoleBuffer.shift(); + if (debugActiveTab === 'console' && debugDialog?.open) { + renderConsolePanel(); + } +} + +function formatConsoleArg(arg) { + if (typeof arg === 'string') return arg; + try { + return JSON.stringify(arg); + } catch (err) { + return String(arg); + } +} diff --git a/src/ApiKernel.php b/src/ApiKernel.php index e399100..71c3b7a 100644 --- a/src/ApiKernel.php +++ b/src/ApiKernel.php @@ -859,6 +859,9 @@ class ApiKernel case 'placeholders.schema': $this->handlePlaceholderSchema(); break; + case 'debug.phpinfo': + $this->handleDebugPhpInfo(); + break; case 'templates.test_send': $this->handleTemplateTestSend(); break; @@ -1820,6 +1823,16 @@ class ApiKernel ]); } + private function handleDebugPhpInfo(): void + { + $user = $this->authService->requireAuth(); + $this->ensureDebugUser($user); + ob_start(); + phpinfo(INFO_GENERAL | INFO_CONFIGURATION | INFO_MODULES | INFO_ENVIRONMENT); + $html = ob_get_clean() ?: ''; + $this->respond(['ok' => true, 'html' => $html]); + } + private function resolveBridgeConfig(?int $customerId): array { $fileConf = $this->conf['placeholders']['bridge'] ?? []; @@ -2146,6 +2159,14 @@ class ApiKernel } } + private function ensureDebugUser(array $user): void + { + $email = strtolower((string)($user['email'] ?? '')); + if ($email !== 'madmin@papa-kind-treff.info') { + $this->fail('Debug nicht erlaubt', null, 403); + } + } + private function defaultApiBase(): string { $base = $this->conf['base_url'] ?? '';