Switch PHP views to Twig and add 2FA/UI enhancements

Migrate many view templates from raw PHP to Twig and modernize UI/UX for 2FA and settings. Controllers updated to provide avatar data and two-factor info (ProfileController, UserController) and SettingsController now includes timezone lists, notification preset selection, cron path, cached update state and rollback availability. ErrorHandler now attempts to render error pages via a new Core\TwigService with a safe fallback to raw PHP views. TwoFactorService generation silences deprecated warnings during QR code creation. Numerous .php view files were removed and replaced with .twig equivalents (2fa setup/verify/backup-codes and many auth, dashboard, domains, errors, layout, users, tags, tld-registry, etc.), and core/TwigService was added. These changes move the app toward a Twig-based templating system, improve 2FA flows, surface avatar images in lists/profiles, and make error rendering more robust.
This commit is contained in:
Hosteroid
2026-03-03 18:21:32 +02:00
parent cd4e3e6bcc
commit 4818172bc6
73 changed files with 9948 additions and 10686 deletions

View File

@@ -1,28 +1,29 @@
<?php
$title = 'WHOIS Debug Tool';
$pageTitle = 'WHOIS Debug Tool';
$pageDescription = 'Test and debug WHOIS data extraction';
$pageIcon = 'fas fa-search';
ob_start();
?>
{% extends "layout/base.twig" %}
<?php if (empty($domain)): ?>
{% set title = 'WHOIS Debug Tool' %}
{% set pageTitle = 'WHOIS Debug Tool' %}
{% set pageDescription = 'Test and debug WHOIS data extraction' %}
{% set pageIcon = 'fas fa-search' %}
{% block content %}
{% if domain is empty %}
<!-- Search Form -->
<div class="max-w-2xl mx-auto">
<div class="bg-white rounded-lg border border-gray-200 p-6">
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 p-6">
<form method="GET" action="/debug/whois" class="space-y-4">
<div>
<label for="domain" class="block text-sm font-medium text-gray-700 mb-1.5">
<label for="domain" class="block text-sm font-medium text-gray-700 dark:text-slate-300 mb-1.5">
Domain Name
</label>
<input type="text"
id="domain"
name="domain"
class="w-full px-3 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary transition-colors text-sm"
class="w-full px-3 py-2.5 border border-gray-300 dark:border-slate-600 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary transition-colors text-sm bg-white dark:bg-slate-700 text-gray-900 dark:text-white"
placeholder="Enter domain (e.g., google.com)"
required
autofocus>
<p class="mt-1.5 text-xs text-gray-500">
<p class="mt-1.5 text-xs text-gray-500 dark:text-slate-400">
Enter a domain name without http:// or www.
</p>
</div>
@@ -36,14 +37,14 @@ ob_start();
</div>
<!-- Info Card -->
<div class="mt-4 bg-blue-50 border border-blue-200 rounded-lg p-4">
<div class="mt-4 bg-blue-50 dark:bg-blue-500/10 border border-blue-200 dark:border-blue-500/30 rounded-lg p-4">
<div class="flex items-start">
<div class="flex-shrink-0">
<i class="fas fa-info-circle text-blue-500 text-lg"></i>
</div>
<div class="ml-3">
<h3 class="text-sm font-semibold text-gray-900 mb-1">What is this tool?</h3>
<p class="text-xs text-gray-600 leading-relaxed">
<h3 class="text-sm font-semibold text-gray-900 dark:text-white mb-1">What is this tool?</h3>
<p class="text-xs text-gray-600 dark:text-slate-400 leading-relaxed">
This debug tool shows you the raw WHOIS data for any domain and how our system parses it.
Use it to troubleshoot issues with domain information extraction.
</p>
@@ -52,10 +53,10 @@ ob_start();
</div>
</div>
<?php else: ?>
{% else %}
<!-- Back Button & Copy Report -->
<div class="mb-4 flex justify-between items-center">
<a href="/debug/whois" class="inline-flex items-center px-4 py-2 border border-gray-300 text-gray-700 text-sm rounded-lg hover:bg-gray-50 transition-colors font-medium">
<a href="/debug/whois" class="inline-flex items-center px-4 py-2 border border-gray-300 dark:border-slate-600 text-gray-700 dark:text-slate-300 text-sm rounded-lg hover:bg-gray-50 dark:hover:bg-slate-700 transition-colors font-medium">
<i class="fas fa-arrow-left mr-2"></i>
Check Another Domain
</a>
@@ -66,19 +67,19 @@ ob_start();
</div>
<!-- Domain Info Card -->
<div class="bg-white rounded-lg border border-gray-200 p-5 mb-4">
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 p-5 mb-4">
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<p class="text-xs font-medium text-gray-500 uppercase tracking-wide">Domain</p>
<p class="text-sm font-semibold text-gray-900 mt-1"><?= htmlspecialchars($domain) ?></p>
<p class="text-xs font-medium text-gray-500 dark:text-slate-400 uppercase tracking-wide">Domain</p>
<p class="text-sm font-semibold text-gray-900 dark:text-white mt-1">{{ domain }}</p>
</div>
<div>
<p class="text-xs font-medium text-gray-500 uppercase tracking-wide">WHOIS Server</p>
<p class="text-sm font-semibold text-gray-900 mt-1"><?= htmlspecialchars($server) ?></p>
<p class="text-xs font-medium text-gray-500 dark:text-slate-400 uppercase tracking-wide">WHOIS Server</p>
<p class="text-sm font-semibold text-gray-900 dark:text-white mt-1">{{ server }}</p>
</div>
<div>
<p class="text-xs font-medium text-gray-500 uppercase tracking-wide">TLD</p>
<p class="text-sm font-semibold text-gray-900 mt-1"><?= htmlspecialchars($tld) ?></p>
<p class="text-xs font-medium text-gray-500 dark:text-slate-400 uppercase tracking-wide">TLD</p>
<p class="text-sm font-semibold text-gray-900 dark:text-white mt-1">{{ tld }}</p>
</div>
</div>
</div>
@@ -86,53 +87,53 @@ ob_start();
<!-- Main Content Grid -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
<!-- Parsed Data -->
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
<div class="px-5 py-3 border-b border-gray-200 bg-green-50">
<h2 class="text-sm font-semibold text-gray-900 flex items-center">
<i class="fas fa-check-circle text-green-600 mr-2 text-sm"></i>
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
<div class="px-5 py-3 border-b border-gray-200 dark:border-slate-700 bg-green-50 dark:bg-green-500/10">
<h2 class="text-sm font-semibold text-gray-900 dark:text-white flex items-center">
<i class="fas fa-check-circle text-green-600 dark:text-green-400 mr-2 text-sm"></i>
Extracted Data (What We Save)
</h2>
</div>
<div class="p-5">
<div class="space-y-3">
<div class="flex justify-between py-2 border-b border-gray-100">
<span class="text-xs font-medium text-gray-600">Domain</span>
<span class="text-xs text-gray-900 font-mono"><?= htmlspecialchars($info['domain'] ?? 'N/A') ?></span>
<div class="flex justify-between py-2 border-b border-gray-100 dark:border-slate-700">
<span class="text-xs font-medium text-gray-600 dark:text-slate-400">Domain</span>
<span class="text-xs text-gray-900 dark:text-white font-mono">{{ info.domain ?? 'N/A' }}</span>
</div>
<div class="flex justify-between py-2 border-b border-gray-100">
<span class="text-xs font-medium text-gray-600">Registrar</span>
<span class="text-xs text-gray-900 font-mono"><?= htmlspecialchars($info['registrar'] ?? 'N/A') ?></span>
<div class="flex justify-between py-2 border-b border-gray-100 dark:border-slate-700">
<span class="text-xs font-medium text-gray-600 dark:text-slate-400">Registrar</span>
<span class="text-xs text-gray-900 dark:text-white font-mono">{{ info.registrar ?? 'N/A' }}</span>
</div>
<div class="flex justify-between py-2 border-b border-gray-100">
<span class="text-xs font-medium text-gray-600">Expiration Date</span>
<span class="text-xs text-gray-900 font-mono"><?= htmlspecialchars($info['expiration_date'] ?? 'N/A') ?></span>
<div class="flex justify-between py-2 border-b border-gray-100 dark:border-slate-700">
<span class="text-xs font-medium text-gray-600 dark:text-slate-400">Expiration Date</span>
<span class="text-xs text-gray-900 dark:text-white font-mono">{{ info.expiration_date ?? 'N/A' }}</span>
</div>
<div class="flex justify-between py-2 border-b border-gray-100">
<span class="text-xs font-medium text-gray-600">Creation Date</span>
<span class="text-xs text-gray-900 font-mono"><?= htmlspecialchars($info['creation_date'] ?? 'N/A') ?></span>
<div class="flex justify-between py-2 border-b border-gray-100 dark:border-slate-700">
<span class="text-xs font-medium text-gray-600 dark:text-slate-400">Creation Date</span>
<span class="text-xs text-gray-900 dark:text-white font-mono">{{ info.creation_date ?? 'N/A' }}</span>
</div>
<div class="flex justify-between py-2 border-b border-gray-100">
<span class="text-xs font-medium text-gray-600">Updated Date</span>
<span class="text-xs text-gray-900 font-mono"><?= htmlspecialchars($info['updated_date'] ?? 'N/A') ?></span>
<div class="flex justify-between py-2 border-b border-gray-100 dark:border-slate-700">
<span class="text-xs font-medium text-gray-600 dark:text-slate-400">Updated Date</span>
<span class="text-xs text-gray-900 dark:text-white font-mono">{{ info.updated_date ?? 'N/A' }}</span>
</div>
<div class="flex justify-between py-2 border-b border-gray-100">
<span class="text-xs font-medium text-gray-600">Registrar URL</span>
<span class="text-xs text-gray-900 font-mono"><?= htmlspecialchars($info['registrar_url'] ?? 'N/A') ?></span>
<div class="flex justify-between py-2 border-b border-gray-100 dark:border-slate-700">
<span class="text-xs font-medium text-gray-600 dark:text-slate-400">Registrar URL</span>
<span class="text-xs text-gray-900 dark:text-white font-mono">{{ info.registrar_url ?? 'N/A' }}</span>
</div>
<div class="flex justify-between py-2 border-b border-gray-100">
<span class="text-xs font-medium text-gray-600">Abuse Email</span>
<span class="text-xs text-gray-900 font-mono"><?= htmlspecialchars($info['abuse_email'] ?? 'N/A') ?></span>
<div class="flex justify-between py-2 border-b border-gray-100 dark:border-slate-700">
<span class="text-xs font-medium text-gray-600 dark:text-slate-400">Abuse Email</span>
<span class="text-xs text-gray-900 dark:text-white font-mono">{{ info.abuse_email ?? 'N/A' }}</span>
</div>
<div class="py-2">
<span class="text-xs font-medium text-gray-600 block mb-2">Nameservers</span>
<span class="text-xs font-medium text-gray-600 dark:text-slate-400 block mb-2">Nameservers</span>
<div class="space-y-1">
<?php if (!empty($info['nameservers'])): ?>
<?php foreach ($info['nameservers'] as $ns): ?>
<div class="text-xs text-gray-900 font-mono bg-gray-50 px-2 py-1 rounded"><?= htmlspecialchars($ns) ?></div>
<?php endforeach; ?>
<?php else: ?>
<span class="text-xs text-gray-400">N/A</span>
<?php endif; ?>
{% if info.nameservers is not empty %}
{% for ns in info.nameservers %}
<div class="text-xs text-gray-900 dark:text-white font-mono bg-gray-50 dark:bg-slate-700 px-2 py-1 rounded">{{ ns }}</div>
{% endfor %}
{% else %}
<span class="text-xs text-gray-400 dark:text-slate-500">N/A</span>
{% endif %}
</div>
</div>
</div>
@@ -140,30 +141,30 @@ ob_start();
</div>
<!-- Key-Value Pairs -->
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
<div class="px-5 py-3 border-b border-gray-200 bg-blue-50">
<h2 class="text-sm font-semibold text-gray-900 flex items-center">
<i class="fas fa-table text-blue-600 mr-2 text-sm"></i>
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
<div class="px-5 py-3 border-b border-gray-200 dark:border-slate-700 bg-blue-50 dark:bg-blue-500/10">
<h2 class="text-sm font-semibold text-gray-900 dark:text-white flex items-center">
<i class="fas fa-table text-blue-600 dark:text-blue-400 mr-2 text-sm"></i>
All Key-Value Pairs
</h2>
</div>
<div class="overflow-y-auto" style="max-height: 500px;">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50 sticky top-0">
<table class="min-w-full divide-y divide-gray-200 dark:divide-slate-700">
<thead class="bg-gray-50 dark:bg-slate-700 sticky top-0">
<tr>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-600 uppercase">Key</th>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-600 uppercase">Value</th>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-600 dark:text-slate-400 uppercase">Key</th>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-600 dark:text-slate-400 uppercase">Value</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-100">
<?php foreach ($parsedData as $item): ?>
<?php if (!empty($item['value'])): ?>
<tr class="hover:bg-gray-50">
<td class="px-4 py-2 text-xs font-medium text-gray-700"><?= htmlspecialchars($item['key']) ?></td>
<td class="px-4 py-2 text-xs text-gray-900 font-mono"><?= htmlspecialchars($item['value']) ?></td>
<tbody class="bg-white dark:bg-slate-800 divide-y divide-gray-100 dark:divide-slate-700">
{% for item in parsedData %}
{% if item.value is not empty %}
<tr class="hover:bg-gray-50 dark:hover:bg-slate-700">
<td class="px-4 py-2 text-xs font-medium text-gray-700 dark:text-slate-300">{{ item.key }}</td>
<td class="px-4 py-2 text-xs text-gray-900 dark:text-white font-mono">{{ item.value }}</td>
</tr>
<?php endif; ?>
<?php endforeach; ?>
{% endif %}
{% endfor %}
</tbody>
</table>
</div>
@@ -171,27 +172,27 @@ ob_start();
</div>
<!-- Raw Response -->
<div class="mt-4 bg-white rounded-lg border border-gray-200 overflow-hidden">
<div class="px-5 py-3 border-b border-gray-200 bg-gray-50">
<h2 class="text-sm font-semibold text-gray-900 flex items-center">
<i class="fas fa-file-code text-gray-600 mr-2 text-sm"></i>
<div class="mt-4 bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
<div class="px-5 py-3 border-b border-gray-200 dark:border-slate-700 bg-gray-50 dark:bg-slate-700">
<h2 class="text-sm font-semibold text-gray-900 dark:text-white flex items-center">
<i class="fas fa-file-code text-gray-600 dark:text-slate-400 mr-2 text-sm"></i>
Raw WHOIS Response
</h2>
</div>
<div class="p-5">
<pre class="text-xs font-mono bg-gray-50 p-4 rounded border border-gray-200 overflow-x-auto"><?= htmlspecialchars($response) ?></pre>
<pre class="text-xs font-mono bg-gray-50 dark:bg-slate-900 p-4 rounded border border-gray-200 dark:border-slate-700 overflow-x-auto text-gray-900 dark:text-slate-300">{{ response }}</pre>
</div>
</div>
<!-- Hidden data for JS -->
<script id="debug-data" type="application/json">
{
"domain": <?= json_encode($domain) ?>,
"tld": <?= json_encode($tld) ?>,
"server": <?= json_encode($server) ?>,
"extractedData": <?= json_encode($info) ?>,
"rawResponse": <?= json_encode($response) ?>,
"parsedKeyValuePairs": <?= json_encode($parsedData) ?>
"domain": {{ domain|json_encode|raw }},
"tld": {{ tld|json_encode|raw }},
"server": {{ server|json_encode|raw }},
"extractedData": {{ info|json_encode|raw }},
"rawResponse": {{ response|json_encode|raw }},
"parsedKeyValuePairs": {{ parsedData|json_encode|raw }}
}
</script>
@@ -231,33 +232,26 @@ ob_start();
report += data.rawResponse;
report += `\n\n=== END OF REPORT ===`;
// Copy to clipboard with fallback
copyToClipboard(report, button);
}
// Robust clipboard copy function with fallback
function copyToClipboard(text, button) {
// Try modern clipboard API first
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(text).then(() => {
showCopySuccess(button);
}).catch(err => {
console.error('Modern clipboard API failed:', err);
// Fallback to legacy method
fallbackCopyTextToClipboard(text, button);
});
} else {
// Use fallback for non-HTTPS or older browsers
fallbackCopyTextToClipboard(text, button);
}
}
function fallbackCopyTextToClipboard(text, button) {
// Create a temporary textarea
const textArea = document.createElement('textarea');
textArea.value = text;
// Make it invisible but accessible
textArea.style.position = 'fixed';
textArea.style.top = '0';
textArea.style.left = '0';
@@ -308,10 +302,6 @@ ob_start();
}
</script>
<?php endif; ?>
<?php
$content = ob_get_clean();
include __DIR__ . '/../layout/base.php';
?>
{% endif %}
{% endblock %}