isAuthenticated()) { header('Location: admin.php?route=login'); exit; } // Authenticated routes switch ($route) { case 'logout': $auth->logout(); header('Location: admin.php?route=login'); exit; case 'dashboard': case '': handleDashboard($auth, $appConfig); break; case 'content': handleContent($auth, $appConfig); break; case 'content-edit': handleContentEdit($auth, $appConfig); break; case 'content-new': handleContentNew($auth, $appConfig); break; case 'content-delete': handleContentDelete($auth, $appConfig); break; case 'config': handleConfig($auth, $appConfig); break; case 'plugins': handlePlugins($auth, $appConfig); break; case 'users': handleUsers($auth, $appConfig); break; default: header('Location: admin.php?route=dashboard'); exit; } // --- Route Handlers --- function handleLogin(AdminAuth $auth): void { $error = ''; if ($_SERVER['REQUEST_METHOD'] === 'POST') { $username = trim($_POST['username'] ?? ''); $password = $_POST['password'] ?? ''; $result = $auth->login($username, $password); if ($result['success']) { header('Location: admin.php?route=dashboard'); exit; } $error = $result['message']; } require __DIR__ . '/../admin-console/templates/login.php'; } function handleDashboard(AdminAuth $auth, array $config): void { $user = $auth->getCurrentUser(); $csrf = $auth->getCsrfToken(); // Gather stats $contentDir = $config['content_dir']; $pluginsDir = $config['plugins_dir']; $configJson = $config['config_json']; $stats = [ 'pages' => countFiles($contentDir, ['md', 'php', 'html']), 'directories' => countDirs($contentDir), 'plugins' => countDirs($pluginsDir), 'config_exists' => file_exists($configJson), 'content_size' => formatSize(dirSize($contentDir)), 'php_version' => PHP_VERSION, ]; // Load site config $siteConfig = file_exists($configJson) ? json_decode(file_get_contents($configJson), true) : []; require __DIR__ . '/../admin-console/templates/layout.php'; } function handleContent(AdminAuth $auth, array $config): void { $user = $auth->getCurrentUser(); $csrf = $auth->getCsrfToken(); $contentDir = $config['content_dir']; $subdir = $_GET['dir'] ?? ''; // Prevent path traversal $subdir = str_replace(['../', '..\\'], '', $subdir); $fullPath = rtrim($contentDir, '/') . '/' . $subdir; if (!is_dir($fullPath)) { $fullPath = $contentDir; $subdir = ''; } $items = scanContentDir($fullPath, $subdir); $route = 'content'; require __DIR__ . '/../admin-console/templates/layout.php'; } function handleContentEdit(AdminAuth $auth, array $config): void { $user = $auth->getCurrentUser(); $csrf = $auth->getCsrfToken(); $contentDir = $config['content_dir']; $file = $_GET['file'] ?? ''; $file = str_replace(['../', '..\\'], '', $file); $filePath = rtrim($contentDir, '/') . '/' . $file; $message = ''; $messageType = ''; // Validate path $realPath = realpath($filePath); $realContentDir = realpath($contentDir); if (!$realPath || !$realContentDir || strpos($realPath, $realContentDir) !== 0) { header('Location: admin.php?route=content'); exit; } if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (!$auth->verifyCsrf($_POST['csrf_token'] ?? '')) { $message = 'Ongeldige CSRF token.'; $messageType = 'danger'; } else { $content = $_POST['content'] ?? ''; file_put_contents($filePath, $content); $message = 'Bestand opgeslagen.'; $messageType = 'success'; } } $fileContent = file_get_contents($filePath); $fileName = basename($filePath); $fileExt = pathinfo($filePath, PATHINFO_EXTENSION); $route = 'content-edit'; require __DIR__ . '/../admin-console/templates/layout.php'; } function handleContentNew(AdminAuth $auth, array $config): void { $user = $auth->getCurrentUser(); $csrf = $auth->getCsrfToken(); $contentDir = $config['content_dir']; $dir = $_GET['dir'] ?? ''; $dir = str_replace(['../', '..\\'], '', $dir); $message = ''; $messageType = ''; if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (!$auth->verifyCsrf($_POST['csrf_token'] ?? '')) { $message = 'Ongeldige CSRF token.'; $messageType = 'danger'; } else { $filename = trim($_POST['filename'] ?? ''); $content = $_POST['content'] ?? ''; $type = $_POST['type'] ?? 'md'; if (empty($filename)) { $message = 'Bestandsnaam is verplicht.'; $messageType = 'danger'; } else { // Sanitize filename $filename = preg_replace('/[^a-zA-Z0-9._-]/', '-', $filename); if (!preg_match('/\.(md|php|html)$/', $filename)) { $filename .= '.' . $type; } $targetDir = rtrim($contentDir, '/') . '/' . $dir; $filePath = $targetDir . '/' . $filename; if (file_exists($filePath)) { $message = 'Bestand bestaat al.'; $messageType = 'danger'; } else { if (!is_dir($targetDir)) { mkdir($targetDir, 0755, true); } file_put_contents($filePath, $content); header('Location: admin.php?route=content&dir=' . urlencode($dir)); exit; } } } } $route = 'content-new'; require __DIR__ . '/../admin-console/templates/layout.php'; } function handleContentDelete(AdminAuth $auth, array $config): void { $contentDir = $config['content_dir']; $file = $_GET['file'] ?? ''; $file = str_replace(['../', '..\\'], '', $file); $filePath = rtrim($contentDir, '/') . '/' . $file; $realPath = realpath($filePath); $realContentDir = realpath($contentDir); if ($_SERVER['REQUEST_METHOD'] === 'POST' && $auth->verifyCsrf($_POST['csrf_token'] ?? '') && $realPath && $realContentDir && strpos($realPath, $realContentDir) === 0 ) { if (is_file($filePath)) { unlink($filePath); } } $dir = dirname($file); header('Location: admin.php?route=content&dir=' . urlencode($dir === '.' ? '' : $dir)); exit; } function handleConfig(AdminAuth $auth, array $config): void { $user = $auth->getCurrentUser(); $csrf = $auth->getCsrfToken(); $configJson = $config['config_json']; $message = ''; $messageType = ''; if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (!$auth->verifyCsrf($_POST['csrf_token'] ?? '')) { $message = 'Ongeldige CSRF token.'; $messageType = 'danger'; } else { $jsonContent = $_POST['config_content'] ?? ''; $parsed = json_decode($jsonContent, true); if ($parsed === null && json_last_error() !== JSON_ERROR_NONE) { $message = 'Ongeldige JSON: ' . json_last_error_msg(); $messageType = 'danger'; } else { file_put_contents($configJson, json_encode($parsed, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); $message = 'Configuratie opgeslagen.'; $messageType = 'success'; $jsonContent = null; // reload from file } } } $siteConfig = file_exists($configJson) ? file_get_contents($configJson) : '{}'; if (isset($jsonContent)) { $siteConfig = $jsonContent; } $route = 'config'; require __DIR__ . '/../admin-console/templates/layout.php'; } function handlePlugins(AdminAuth $auth, array $config): void { $user = $auth->getCurrentUser(); $csrf = $auth->getCsrfToken(); $pluginsDir = $config['plugins_dir']; $plugins = []; if (is_dir($pluginsDir)) { foreach (scandir($pluginsDir) as $item) { if ($item[0] === '.') continue; $pluginPath = $pluginsDir . '/' . $item; if (!is_dir($pluginPath)) continue; $hasConfig = file_exists($pluginPath . '/config.json'); $pluginConfig = $hasConfig ? json_decode(file_get_contents($pluginPath . '/config.json'), true) : []; $hasMainFile = file_exists($pluginPath . '/' . $item . '.php'); $hasReadme = file_exists($pluginPath . '/README.md'); $plugins[] = [ 'name' => $item, 'path' => $pluginPath, 'enabled' => $pluginConfig['enabled'] ?? true, 'config' => $pluginConfig, 'has_config' => $hasConfig, 'has_main' => $hasMainFile, 'has_readme' => $hasReadme, ]; } } $route = 'plugins'; require __DIR__ . '/../admin-console/templates/layout.php'; } function handleUsers(AdminAuth $auth, array $config): void { $user = $auth->getCurrentUser(); $csrf = $auth->getCsrfToken(); $message = ''; $messageType = ''; if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (!$auth->verifyCsrf($_POST['csrf_token'] ?? '')) { $message = 'Ongeldige CSRF token.'; $messageType = 'danger'; } else { $action = $_POST['action'] ?? ''; if ($action === 'add') { $result = $auth->addUser( trim($_POST['username'] ?? ''), $_POST['password'] ?? '', $_POST['role'] ?? 'admin' ); $message = $result['message']; $messageType = $result['success'] ? 'success' : 'danger'; } elseif ($action === 'delete') { $result = $auth->deleteUser($_POST['delete_username'] ?? ''); $message = $result['message']; $messageType = $result['success'] ? 'success' : 'danger'; } elseif ($action === 'change_password') { $result = $auth->changePassword( $_POST['pw_username'] ?? '', $_POST['new_password'] ?? '' ); $message = $result['message']; $messageType = $result['success'] ? 'success' : 'danger'; } } } $users = $auth->getUsers(); $route = 'users'; require __DIR__ . '/../admin-console/templates/layout.php'; } // --- Helper functions --- function countFiles(string $dir, array $extensions): int { $count = 0; if (!is_dir($dir)) return 0; $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS)); foreach ($iterator as $file) { if ($file->isFile() && in_array($file->getExtension(), $extensions)) { $count++; } } return $count; } function countDirs(string $dir): int { if (!is_dir($dir)) return 0; $count = 0; foreach (scandir($dir) as $item) { if ($item[0] !== '.' && is_dir($dir . '/' . $item)) $count++; } return $count; } function dirSize(string $dir): int { $size = 0; if (!is_dir($dir)) return 0; $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS)); foreach ($iterator as $file) { if ($file->isFile()) $size += $file->getSize(); } return $size; } function formatSize(int $bytes): string { $units = ['B', 'KB', 'MB', 'GB']; $i = 0; while ($bytes >= 1024 && $i < count($units) - 1) { $bytes /= 1024; $i++; } return round($bytes, 1) . ' ' . $units[$i]; } function scanContentDir(string $fullPath, string $subdir): array { $items = []; if (!is_dir($fullPath)) return $items; foreach (scandir($fullPath) as $item) { if ($item[0] === '.') continue; $itemPath = $fullPath . '/' . $item; $relativePath = $subdir ? $subdir . '/' . $item : $item; $items[] = [ 'name' => $item, 'path' => $relativePath, 'is_dir' => is_dir($itemPath), 'size' => is_file($itemPath) ? formatSize(filesize($itemPath)) : '', 'modified' => date('d-m-Y H:i', filemtime($itemPath)), 'extension' => is_file($itemPath) ? pathinfo($item, PATHINFO_EXTENSION) : '', ]; } // Sort: directories first, then files alphabetically usort($items, function ($a, $b) { if ($a['is_dir'] !== $b['is_dir']) return $b['is_dir'] - $a['is_dir']; return strcasecmp($a['name'], $b['name']); }); return $items; }