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:
@@ -8,6 +8,7 @@ use App\Models\User;
|
||||
use App\Models\SessionManager;
|
||||
use App\Models\RememberToken;
|
||||
use App\Services\Logger;
|
||||
use App\Services\TwoFactorService;
|
||||
use App\Helpers\AvatarHelper;
|
||||
|
||||
class ProfileController extends Controller
|
||||
@@ -71,10 +72,30 @@ class ProfileController extends Controller
|
||||
// Format sessions for display (adds deviceIcon, browserInfo, timeAgo, sessionAge)
|
||||
$formattedSessions = \App\Helpers\SessionHelper::formatForDisplay($sessions);
|
||||
|
||||
// Avatar data
|
||||
$avatar = AvatarHelper::getAvatar($user, 80);
|
||||
|
||||
// 2FA status
|
||||
$twoFactorService = new TwoFactorService();
|
||||
$twoFactorPolicy = $twoFactorService->getTwoFactorPolicy();
|
||||
|
||||
$backupCodes = !empty($user['two_factor_backup_codes'])
|
||||
? json_decode($user['two_factor_backup_codes'], true)
|
||||
: [];
|
||||
|
||||
$twoFactorStatus = [
|
||||
'enabled' => !empty($user['two_factor_enabled']),
|
||||
'setup_at' => $user['two_factor_setup_at'] ?? null,
|
||||
'backup_codes_count' => is_array($backupCodes) ? count($backupCodes) : 0,
|
||||
'required' => $twoFactorService->isTwoFactorRequired($userId),
|
||||
];
|
||||
|
||||
$this->view('profile/index', [
|
||||
'user' => $user,
|
||||
'sessions' => $formattedSessions,
|
||||
'userModel' => $this->userModel,
|
||||
'avatar' => $avatar,
|
||||
'twoFactorStatus' => $twoFactorStatus,
|
||||
'twoFactorPolicy' => $twoFactorPolicy,
|
||||
'title' => 'My Profile'
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -70,6 +70,72 @@ class SettingsController extends Controller
|
||||
// Status notification triggers
|
||||
$statusTriggers = $this->settingModel->getNotificationStatusTriggers();
|
||||
|
||||
// Timezone lists for the Application tab
|
||||
$popularTimezones = [
|
||||
'UTC' => 'UTC',
|
||||
'America/New_York' => 'Eastern Time (US)',
|
||||
'America/Chicago' => 'Central Time (US)',
|
||||
'America/Denver' => 'Mountain Time (US)',
|
||||
'America/Los_Angeles' => 'Pacific Time (US)',
|
||||
'Europe/London' => 'London',
|
||||
'Europe/Paris' => 'Paris',
|
||||
'Asia/Tokyo' => 'Tokyo',
|
||||
'Australia/Sydney' => 'Sydney'
|
||||
];
|
||||
$allTimezones = timezone_identifiers_list();
|
||||
|
||||
// Determine which notification preset is selected
|
||||
$currentNotificationDays = $settings['notification_days_before'] ?? '30,15,7,3,1';
|
||||
$selectedPreset = 'custom';
|
||||
foreach ($notificationPresets as $key => $preset) {
|
||||
if ($preset['value'] === $currentNotificationDays) {
|
||||
$selectedPreset = $key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Cron path for System tab
|
||||
$cronPath = realpath(defined('PATH_ROOT') ? PATH_ROOT . 'cron/check_domains.php' : __DIR__ . '/../../cron/check_domains.php') ?: 'cron/check_domains.php';
|
||||
|
||||
// Cached update state for Updates tab
|
||||
$cachedUpdateAvailable = false;
|
||||
$cachedUpdateData = null;
|
||||
$currentVer = $appSettings['app_version'] ?? '0';
|
||||
$latestVer = $updateSettings['latest_available_version'] ?? null;
|
||||
$updateChannel = $updateSettings['update_channel'] ?? 'stable';
|
||||
$commitsBehind = (int)($updateSettings['commits_behind_count'] ?? 0);
|
||||
$installedSha = $updateSettings['installed_commit_sha'] ?? '';
|
||||
$remoteSha = $updateSettings['latest_remote_sha'] ?? '';
|
||||
if ($installedSha !== '' && $remoteSha !== '' && str_starts_with($installedSha, $remoteSha)) {
|
||||
$commitsBehind = 0;
|
||||
}
|
||||
if ($latestVer && version_compare($latestVer, $currentVer, '>')) {
|
||||
$cachedUpdateAvailable = true;
|
||||
$cachedUpdateData = [
|
||||
'available' => true,
|
||||
'type' => 'release',
|
||||
'current_version' => $currentVer,
|
||||
'latest_version' => $latestVer,
|
||||
'release_notes' => $updateSettings['latest_release_notes'] ?? '',
|
||||
'release_url' => $updateSettings['latest_release_url'] ?? '',
|
||||
'published_at' => $updateSettings['latest_release_published_at'] ?? null,
|
||||
'channel' => $updateChannel,
|
||||
];
|
||||
} elseif ($updateChannel === 'latest' && $commitsBehind > 0) {
|
||||
$cachedUpdateAvailable = true;
|
||||
$cachedUpdateData = [
|
||||
'available' => true,
|
||||
'type' => 'hotfix',
|
||||
'current_version' => $currentVer,
|
||||
'commits_behind' => $commitsBehind,
|
||||
'commit_messages' => [],
|
||||
'channel' => $updateChannel,
|
||||
];
|
||||
}
|
||||
|
||||
// Rollback availability
|
||||
$rollbackAvailable = !empty($updateSettings['update_backup_path']) && file_exists($updateSettings['update_backup_path']);
|
||||
|
||||
$this->view('settings/index', [
|
||||
'settings' => $settings,
|
||||
'appSettings' => $appSettings,
|
||||
@@ -81,6 +147,13 @@ class SettingsController extends Controller
|
||||
'notificationPresets' => $notificationPresets,
|
||||
'checkIntervalPresets' => $checkIntervalPresets,
|
||||
'statusTriggers' => $statusTriggers,
|
||||
'popularTimezones' => $popularTimezones,
|
||||
'allTimezones' => $allTimezones,
|
||||
'selectedPreset' => $selectedPreset,
|
||||
'cronPath' => $cronPath,
|
||||
'cachedUpdateAvailable' => $cachedUpdateAvailable,
|
||||
'cachedUpdateData' => $cachedUpdateData,
|
||||
'rollbackAvailable' => $rollbackAvailable,
|
||||
'title' => 'Settings'
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -49,6 +49,11 @@ class UserController extends Controller
|
||||
|
||||
// Get filtered users
|
||||
$users = $this->userModel->getFiltered($filters, $sort, strtoupper($order), $perPage, $offset);
|
||||
|
||||
foreach ($users as &$u) {
|
||||
$u['avatar'] = \App\Helpers\AvatarHelper::getAvatar($u, 40);
|
||||
}
|
||||
unset($u);
|
||||
|
||||
$this->view('users/index', [
|
||||
'users' => $users,
|
||||
@@ -240,6 +245,17 @@ class UserController extends Controller
|
||||
// Get 2FA status
|
||||
$twoFactorStatus = $this->userModel->getTwoFactorStatus($userId);
|
||||
|
||||
// Avatar for profile header
|
||||
$userAvatar = \App\Helpers\AvatarHelper::getAvatar($user, 64);
|
||||
|
||||
// Registrar distribution
|
||||
$registrarCounts = [];
|
||||
foreach ($domains as $d) {
|
||||
$reg = !empty($d['registrar']) ? $d['registrar'] : 'Unknown';
|
||||
$registrarCounts[$reg] = ($registrarCounts[$reg] ?? 0) + 1;
|
||||
}
|
||||
arsort($registrarCounts);
|
||||
|
||||
$this->view('users/show', [
|
||||
'title' => htmlspecialchars($user['full_name']) . ' - User Profile',
|
||||
'user' => $user,
|
||||
@@ -248,6 +264,8 @@ class UserController extends Controller
|
||||
'tags' => $tags,
|
||||
'groups' => $groups,
|
||||
'twoFactorStatus' => $twoFactorStatus,
|
||||
'userAvatar' => $userAvatar,
|
||||
'registrarCounts' => $registrarCounts,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user