mailing and template and auth fixes

This commit is contained in:
Tower committed 2026-05-31 11:54:01 +02:00
1 parent 53fa7bfe6e
commit 159922af76
1 file changed
+685 -19
+685 -19
View File
@@ -39,8 +39,11 @@ $dirs = [
'WEBROOT/src/MVC/Model',
'WEBROOT/src/MVC/View',
'WEBROOT/src/MVC/View/admin',
'WEBROOT/src/MVC/View/templates',
'WEBROOT/src/MVC/Layout',
'WEBROOT/src/MVC/Layout/Templates',
'WEBROOT/storage',
'WEBROOT/storage/uploads',
'WEBROOT/logs',
];
@@ -580,6 +583,7 @@ namespace MVC\Classes;
use MVC\Classes\Client;
use MVC\Classes\Request;
use MVC\Classes\Logger;
use MVC\Model\Mailing;
class Mailer {
@@ -602,6 +606,9 @@ class Mailer {
return $to !== '' ? $to : self::getDefaultRecipient($config);
}
/**
* Sends a standard email and optionally logs it.
*/
public static function send($subject, $message, $url = null, bool $log = true, ?string $to = null) {
$config = self::getConfig();
$from = self::getSender($config);
@@ -611,7 +618,13 @@ class Mailer {
$headers .= "Reply-To: " . $from . "\r\n";
$headers .= "X-Mailer: PHP/" . phpversion();
$success = mail($recipient, $subject, $message, $headers);
// mail() unterdrückt Fehlermeldungen standardmäßig nicht,
// wir prüfen den Rückgabewert.
$success = @mail($recipient, $subject, $message, $headers);
if (!$success) {
Logger::log("Mail dispatch failed to '$recipient' for subject: '$subject'. Check your system's sendmail/SMTP configuration.", "ERROR", "error.log");
}
if ($log) {
self::log($subject, $message, $url, false);
@@ -620,17 +633,63 @@ class Mailer {
return $success;
}
/**
* Sends an error email and optionally logs it as an error.
*/
public static function error($title, $error, bool $log = true) {
$subject = "ERROR: $title";
$config = self::getConfig();
$from = self::getSender($config);
$to = self::getDefaultRecipient($config);
$headers = "From: " . $from . "\r\n" . "Reply-To: " . $from . "\r\n" . "X-Mailer: PHP/" . phpversion();
$success = mail($to, $subject, $error, $headers);
if ($log) self::log($subject, $error, null, true);
$headers = "From: " . $from . "\r\n";
$headers .= "Reply-To: " . $from . "\r\n";
$headers .= "X-Mailer: PHP/" . phpversion();
$success = @mail($to, $subject, $error, $headers);
if (!$success) {
Logger::log("Error-Mail dispatch failed: '$title'.", "ERROR", "error.log");
}
if ($log) {
self::log($subject, $error, null, true);
}
return $success;
}
/**
* Sendet eine E-Mail basierend auf einem View-Template
*/
public static function sendTemplate($subject, $template, $data = [], bool $log = true, ?string $to = null) {
$view = new \MVC\Core\View(dirname(__DIR__) . '/View');
foreach ($data as $key => $value) {
$view->assign($key, $value);
}
$htmlContent = $view->render($template);
$config = self::getConfig();
$from = self::getSender($config);
$recipient = self::resolveRecipient($to, $config);
$headers = "MIME-Version: 1.0" . "\r\n";
$headers .= "Content-type:text/html;charset=UTF-8" . "\r\n";
$headers .= "From: " . $from . "\r\n";
$headers .= "Reply-To: " . $from . "\r\n";
$success = @mail($recipient, $subject, $htmlContent, $headers);
if ($log) {
self::log($subject, "HTML Template: $template", null, false);
}
return $success;
}
/**
* Logs the email to the database or falls back to file logging.
*/
private static function log($subject, $message, $url, bool $isError) {
$url = $url ?? Request::uri();
$requestTime = Request::time();
@@ -642,20 +701,21 @@ class Mailer {
Mailing::log($subject, $message, $url, $ip, $date);
return;
}
} catch (\Exception $e) {}
} catch (\Exception $e) {
// DB-Log fehlgeschlagen
}
self::logToFile($subject, $message, $url, $ip, $date, $isError);
}
/**
* Schreibt Log-Einträge in eine Datei (Nutzt nun die Logger-Klasse für Konsistenz).
*/
private static function logToFile($subject, $message, $url, $ip, $date, bool $isError) {
$logDir = __DIR__ . '/../../../logs';
if (!is_dir($logDir)) mkdir($logDir, 0755, true);
$fileName = $isError ? 'mail_errors.log' : 'mails.log';
$logFile = $logDir . '/' . $fileName;
$logEntry = "[$date] IP: $ip | URL: $url\nSubject: $subject\nMessage: $message\n" . str_repeat("-", 50) . "\n";
file_put_contents($logFile, $logEntry, FILE_APPEND);
$logEntry = "IP: $ip | URL: $url\nSubject: $subject\nMessage: $message\n" . str_repeat("-", 30);
Logger::log($logEntry, $isError ? 'MAIL_ERROR' : 'MAIL_INFO', $fileName);
}
}
PHP;
@@ -688,6 +748,287 @@ class Server {
}
PHP;
$files['WEBROOT/src/MVC/Classes/Auth.php'] = <<<'PHP'
<?php
namespace MVC\Classes;
use MVC\Model\Users;
class Auth {
/**
* Startet die Session falls noch nicht geschehen
*/
private static function init() {
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
}
/**
* Versucht einen Benutzer einzuloggen
*/
public static function login(string $username, string $password): bool {
self::init();
$user = Users::getByUsername($username);
if ($user && password_verify($password, $user['PASSWORD'])) {
$_SESSION['user_logged_in'] = true;
$_SESSION['user_id'] = $user['ID'];
$_SESSION['username'] = $user['USERNAME'];
$_SESSION['role'] = $user['ROLE'];
$_SESSION['is_admin'] = ($user['ROLE'] === 'admin');
return true;
}
return false;
}
/**
* Loggt den aktuellen Benutzer aus
*/
public static function logout() {
self::init();
session_destroy();
}
/**
* Prüft ob ein Benutzer eingeloggt ist
*/
public static function check(): bool {
self::init();
return isset($_SESSION['user_logged_in']) && $_SESSION['user_logged_in'] === true;
}
/**
* Gibt den aktuellen Benutzer-Datensatz zurück
*/
public static function user() {
self::init();
if (!self::check()) return null;
return [
'id' => $_SESSION['user_id'],
'username' => $_SESSION['username'],
'role' => $_SESSION['role'],
'is_admin' => $_SESSION['is_admin']
];
}
/**
* Prüft ob der Benutzer eine bestimmte Rolle hat
*/
public static function hasRole(string $role): bool {
self::init();
return self::check() && $_SESSION['role'] === $role;
}
/**
* Prüft ob der Benutzer Admin ist
*/
public static function isAdmin(): bool {
return self::hasRole('admin');
}
}
PHP;
$files['WEBROOT/src/MVC/Classes/Form.php'] = <<<'PHP'
<?php
namespace MVC\Classes;
class Form {
/**
* Startet ein HTML Formular
*/
public static function open(string $service, string $method = 'POST', array $options = []): string {
$enctype = isset($options['enctype']) ? ' enctype="' . $options['enctype'] . '"' : '';
$class = isset($options['class']) ? ' class="' . $options['class'] . '"' : '';
$id = isset($options['id']) ? ' id="' . $options['id'] . '"' : '';
return "<form action=\"?service=$service\" method=\"$method\"$enctype$class$id>";
}
/**
* Schließt ein HTML Formular
*/
public static function close(): string {
return "</form>";
}
/**
* Erstellt ein Input-Feld mit Label und Styling
*/
public static function input(string $name, string $label = '', string $type = 'text', $value = '', array $options = []): string {
$placeholder = $options['placeholder'] ?? '';
$required = isset($options['required']) && $options['required'] ? 'required' : '';
$class = $options['class'] ?? 'form-control';
$html = "<div class=\"form-group\">";
if ($label) {
$html .= "<label for=\"$name\">$label</label>";
}
$html .= "<input type=\"$type\" name=\"$name\" id=\"$name\" value=\"" . htmlspecialchars($value) . "\" class=\"$class\" placeholder=\"$placeholder\" $required>";
$html .= "</div>";
return $html;
}
/**
* Erstellt eine Textarea
*/
public static function textarea(string $name, string $label = '', string $value = '', array $options = []): string {
$placeholder = $options['placeholder'] ?? '';
$rows = $options['rows'] ?? 3;
$class = $options['class'] ?? 'form-control';
$html = "<div class=\"form-group\">";
if ($label) {
$html .= "<label for=\"$name\">$label</label>";
}
$html .= "<textarea name=\"$name\" id=\"$name\" class=\"$class\" rows=\"$rows\" placeholder=\"$placeholder\">" . htmlspecialchars($value) . "</textarea>";
$html .= "</div>";
return $html;
}
/**
* Erstellt einen Submit-Button
*/
public static function submit(string $label = 'Speichern', string $class = 'btn btn-primary'): string {
return "<button type=\"submit\" class=\"$class\">$label</button>";
}
/**
* Einfache Validierung von Formulardaten
*/
public static function validate(array $data, array $rules): array {
$errors = [];
foreach ($rules as $field => $fieldRules) {
$value = $data[$field] ?? null;
$rulesArray = explode('|', $fieldRules);
foreach ($rulesArray as $rule) {
if ($rule === 'required' && empty($value)) {
$errors[$field] = "Das Feld $field ist erforderlich.";
}
if ($rule === 'email' && !filter_var($value, FILTER_VALIDATE_EMAIL)) {
$errors[$field] = "Bitte geben Sie eine gültige E-Mail Adresse an.";
}
if (strpos($rule, 'min:') === 0) {
$min = (int)substr($rule, 4);
if (strlen($value) < $min) {
$errors[$field] = "Das Feld $field muss mindestens $min Zeichen lang sein.";
}
}
}
}
return $errors;
}
}
PHP;
$files['WEBROOT/src/MVC/Classes/Storage.php'] = <<<'PHP'
<?php
namespace MVC\Classes;
class Storage {
private static function getBaseDir() {
return dirname(__DIR__, 3) . DIRECTORY_SEPARATOR . 'storage';
}
/**
* Lädt eine Datei hoch
*/
public static function upload(string $fileKey, string $subDir = 'uploads'): array {
if (!isset($_FILES[$fileKey]) || $_FILES[$fileKey]['error'] !== UPLOAD_ERR_OK) {
return ['success' => false, 'error' => 'Keine Datei oder Upload-Fehler.'];
}
$baseDir = self::getBaseDir();
$targetDir = $baseDir . DIRECTORY_SEPARATOR . $subDir;
if (!is_dir($targetDir)) {
mkdir($targetDir, 0755, true);
}
$fileName = basename($_FILES[$fileKey]['name']);
$targetPath = $targetDir . DIRECTORY_SEPARATOR . $fileName;
// Sicherheitsprüfung: existiert bereits?
if (file_exists($targetPath)) {
$fileName = time() . '_' . $fileName;
$targetPath = $targetDir . DIRECTORY_SEPARATOR . $fileName;
}
if (move_uploaded_file($_FILES[$fileKey]['tmp_name'], $targetPath)) {
return [
'success' => true,
'name' => $fileName,
'path' => $subDir . '/' . $fileName,
'size' => $_FILES[$fileKey]['size']
];
}
return ['success' => false, 'error' => 'Fehler beim Verschieben der Datei.'];
}
/**
* Listet Dateien in einem Verzeichnis auf
*/
public static function listFiles(string $subDir = 'uploads'): array {
$dir = self::getBaseDir() . DIRECTORY_SEPARATOR . $subDir;
if (!is_dir($dir)) return [];
$files = [];
foreach (scandir($dir) as $file) {
if ($file === '.' || $file === '..') continue;
$path = $dir . DIRECTORY_SEPARATOR . $file;
$files[] = [
'name' => $file,
'size' => round(filesize($path) / 1024, 2),
'date' => date('Y-m-d H:i:s', filemtime($path))
];
}
return $files;
}
/**
* Löscht eine Datei
*/
public static function delete(string $path): bool {
$fullPath = self::getBaseDir() . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $path);
if (file_exists($fullPath) && is_file($fullPath)) {
return unlink($fullPath);
}
return false;
}
/**
* Erzwingt den Download einer Datei
*/
public static function download(string $path) {
$fullPath = self::getBaseDir() . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $path);
if (file_exists($fullPath) && is_file($fullPath)) {
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . basename($fullPath) . '"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($fullPath));
readfile($fullPath);
exit;
}
die("Datei nicht gefunden.");
}
}
PHP;
// --- CORE ---
$files['WEBROOT/src/MVC/Core/Controller.php'] = <<<'PHP'
<?php
@@ -1034,15 +1375,30 @@ PHP;
$files['WEBROOT/src/MVC/View/admin/mail.php'] = <<<'HTML'
<h2>📧 E-Mail Service</h2>
<?php if (isset($message)): ?>
<div style="padding:15px;margin-bottom:20px;border-radius:5px;background:<?= $message['type']==='success'?'rgba(46,204,113,0.2)':'rgba(231,76,60,0.2)'?>;"><?= $message['text']?></div>
<div style="padding: 15px; margin-bottom: 20px; border-radius: 5px; background: <?= $message['type'] === 'success' ? 'rgba(46, 204, 113, 0.2)' : 'rgba(231, 76, 60, 0.2)' ?>; color: var(--text);">
<?= $message['text'] ?>
</div>
<?php endif; ?>
<form method="POST" style="background:var(--sidebar);padding:20px;border-radius:8px;">
<input type="email" name="to" value="<?= htmlspecialchars($_POST['to'] ?? 'bruno.eckhardt07@gmail.com') ?>" placeholder="Empfänger" required style="width:100%;margin-bottom:10px;padding:10px;background:var(--bg);color:var(--text);border:1px solid var(--border);">
<input type="text" name="subject" value="<?= htmlspecialchars($_POST['subject'] ?? '') ?>" placeholder="Betreff" required style="width:100%;margin-bottom:10px;padding:10px;background:var(--bg);color:var(--text);border:1px solid var(--border);">
<textarea name="body" placeholder="Nachricht" required style="width:100%;height:150px;margin-bottom:10px;padding:10px;background:var(--bg);color:var(--text);border:1px solid var(--border);"><?= htmlspecialchars($_POST['body'] ?? '') ?></textarea>
<button type="submit" style="background:var(--accent);color:white;padding:10px 20px;border:none;border-radius:4px;cursor:pointer;">Senden</button>
</form>
<div style="background: var(--sidebar); padding: 30px; border-radius: 8px; border: 1px solid var(--border);">
<form method="POST">
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px;">Empfänger</label>
<input type="email" name="to" value="<?= htmlspecialchars($_POST['to'] ?? 'bruno.eckhardt07@gmail.com') ?>" required style="width: 100%; padding: 10px; border-radius: 4px; border: 1px solid var(--border); background: var(--bg); color: var(--text);">
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px;">Betreff</label>
<input type="text" name="subject" value="<?= htmlspecialchars($_POST['subject'] ?? '') ?>" required style="width: 100%; padding: 10px; border-radius: 4px; border: 1px solid var(--border); background: var(--bg); color: var(--text);">
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px;">Nachricht</label>
<textarea name="body" required style="width: 100%; height: 150px; padding: 10px; border-radius: 4px; border: 1px solid var(--border); background: var(--bg); color: var(--text);"><?= htmlspecialchars($_POST['body'] ?? '') ?></textarea>
</div>
<button type="submit" style="background: var(--accent); color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer;">E-Mail senden</button>
</form>
</div>
HTML;
$files['WEBROOT/src/MVC/View/admin/users.php'] = <<<'HTML'
@@ -1199,6 +1555,87 @@ class TestController extends Controller {
}
PHP;
$files['WEBROOT/src/MVC/Controller/TemplateController.php'] = <<<'PHP'
<?php
namespace MVC\Controller;
use MVC\Core\Controller;
use MVC\Classes\Storage;
use MVC\Classes\Form;
class TemplateController extends Controller {
public function upload() {
$message = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$upload = Storage::upload('file_upload');
if ($upload['success']) {
$message = [
'type' => 'success',
'text' => 'Datei "' . $upload['name'] . '" erfolgreich hochgeladen.'
];
} else {
$message = [
'type' => 'error',
'text' => $upload['error']
];
}
}
return $this->render('templates/upload', [
'message' => $message,
'current_service' => 'template_upload'
]);
}
public function download() {
$files = Storage::listFiles();
return $this->render('templates/download', [
'files' => $files
]);
}
public function data_form() {
$errors = [];
$message = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$errors = Form::validate($_POST, [
'firstname' => 'required',
'lastname' => 'required',
'email' => 'required|email'
]);
if (empty($errors)) {
$message = ['type' => 'success', 'text' => 'Daten erfolgreich validiert und gespeichert!'];
}
}
return $this->render('templates/data_form', [
'current_service' => 'template_form',
'errors' => $errors,
'message' => $message
]);
}
public function data_table() {
return $this->render('templates/data_table');
}
public function download_file() {
$fileName = $_GET['file'] ?? null;
if ($fileName) {
// Wir hängen 'uploads/' davor, da Storage::upload standardmäßig dorthin speichert
Storage::download('uploads/' . $fileName);
}
header('Location: ?service=template_download');
exit;
}
}
PHP;
// --- API ---
$files['WEBROOT/api/v1/get.php'] = <<<'PHP'
<?php
@@ -1477,6 +1914,226 @@ $files['WEBROOT/src/MVC/View/login.php'] = <<<'HTML'
</div>
HTML;
$files['WEBROOT/src/MVC/View/templates/data_form.php'] = <<<'HTML'
<h2>📝 Daten-Formular</h2>
<?php use MVC\Classes\Form; ?>
<?php if (isset($message)): ?>
<div style="padding:15px;margin-bottom:20px;border-radius:5px;background:rgba(46,204,113,0.2);"><?= $message['text']?></div>
<?php endif; ?>
<?php if (!empty($errors)): ?>
<div style="padding:15px;margin-bottom:20px;border-radius:5px;background:rgba(231,76,60,0.2);color:#c0392b;">
<ul>
<?php foreach($errors as $error): ?>
<li><?= $error ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<div class="card">
<?= Form::open($current_service) ?>
<div class="form-row">
<div class="form-group col">
<?= Form::input('firstname', 'Vorname', 'text', $_POST['firstname'] ?? '', ['placeholder' => 'Max', 'required' => true]) ?>
</div>
<div class="form-group col">
<?= Form::input('lastname', 'Nachname', 'text', $_POST['lastname'] ?? '', ['placeholder' => 'Mustermann', 'required' => true]) ?>
</div>
</div>
<?= Form::input('email', 'E-Mail Adresse', 'email', $_POST['email'] ?? '', ['placeholder' => 'max@example.com', 'required' => true]) ?>
<div class="form-group">
<label for="category">Kategorie</label>
<select name="category" id="category" class="form-control">
<option value="support">Support</option>
<option value="sales">Vertrieb</option>
<option value="other">Sonstiges</option>
</select>
</div>
<div class="form-group">
<label>Interessen</label>
<div class="checkbox-group">
<label class="checkbox-label"><input type="checkbox" name="interests[]" value="coding"> Coding</label>
<label class="checkbox-label"><input type="checkbox" name="interests[]" value="design"> Design</label>
<label class="checkbox-label"><input type="checkbox" name="interests[]" value="marketing"> Marketing</label>
</div>
</div>
<div class="form-group">
<label>Newsletter</label>
<div class="radio-group">
<label class="radio-label"><input type="radio" name="newsletter" value="yes" checked> Ja, ich möchte den Newsletter</label>
<label class="radio-label"><input type="radio" name="newsletter" value="no"> Nein, danke</label>
</div>
</div>
<?= Form::submit('Daten speichern') ?>
<button type="reset" class="btn btn-secondary">Abbrechen</button>
<?= Form::close() ?>
</div>
<style>
.card { background: var(--sidebar); padding: 25px; border-radius: 8px; border: 1px solid var(--border); }
.form-row { display: flex; gap: 20px; margin-bottom: 20px; }
.col { flex: 1; }
.form-group { margin-bottom: 20px; }
.form-group label { display: block; margin-bottom: 8px; font-weight: 600; }
.form-control { width: 100%; padding: 12px; border: 1px solid var(--border); border-radius: 4px; background: var(--bg); color: var(--text); box-sizing: border-box; }
.checkbox-group, .radio-group { display: flex; flex-direction: column; gap: 10px; margin-top: 5px; }
.checkbox-label, .radio-label { display: flex; align-items: center; gap: 10px; cursor: pointer; font-weight: normal; }
.btn { padding: 12px 24px; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; margin-right: 10px; }
.btn-primary { background: var(--accent); color: white; }
.btn-secondary { background: #95a5a6; color: white; }
.btn:hover { opacity: 0.9; }
</style>
HTML;
$files['WEBROOT/src/MVC/View/templates/data_table.php'] = <<<'HTML'
<h2>📊 Datenübersicht</h2>
<div class="toolbar">
<div class="search-box">
<input type="text" placeholder="Suchen..." id="tableSearch" class="form-control">
</div>
<div class="actions">
<button class="btn btn-primary">+ Neuer Eintrag</button>
<button class="btn btn-secondary">Exportieren</button>
</div>
</div>
<div class="card">
<table class="data-table">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Status</th>
<th>Datum</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
<?php for($i=1; $i<=5; $i++): ?>
<tr>
<td>#<?= 1000 + $i ?></td>
<td><strong>Beispiel Datensatz <?= $i ?></strong></td>
<td><span class="badge badge-success">Aktiv</span></td>
<td><?= date('d.m.Y') ?></td>
<td>
<button class="btn-icon">✏️</button>
<button class="btn-icon">🗑️</button>
</td>
</tr>
<?php endfor; ?>
</tbody>
</table>
</div>
<style>
.toolbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; gap: 20px; }
.search-box { flex: 1; max-width: 300px; }
.form-control { width: 100%; padding: 10px; border: 1px solid var(--border); border-radius: 4px; background: var(--bg); color: var(--text); }
.btn { padding: 10px 18px; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; }
.btn-primary { background: var(--accent); color: white; }
.btn-secondary { background: var(--sidebar); border: 1px solid var(--border); color: var(--text); }
.card { background: var(--sidebar); border-radius: 8px; border: 1px solid var(--border); overflow: hidden; }
.data-table { width: 100%; border-collapse: collapse; }
.data-table th, .data-table td { text-align: left; padding: 15px; border-bottom: 1px solid var(--border); }
.data-table th { background: rgba(0,0,0,0.03); font-weight: bold; }
.badge { padding: 4px 8px; border-radius: 12px; font-size: 0.75rem; font-weight: bold; }
.badge-success { background: #2ecc71; color: white; }
.btn-icon { background: none; border: none; cursor: pointer; font-size: 1.1rem; padding: 5px; }
</style>
HTML;
$files['WEBROOT/src/MVC/View/templates/download.php'] = <<<'HTML'
<h2>📥 Datei Downloads</h2>
<div class="card">
<table class="data-table">
<thead>
<tr>
<th>Dateiname</th>
<th>Größe</th>
<th>Datum</th>
<th>Aktion</th>
</tr>
</thead>
<tbody>
<?php if (empty($files)): ?>
<tr>
<td colspan="4" style="text-align: center; padding: 20px;">Keine Dateien verfügbar.</td>
</tr>
<?php else: ?>
<?php foreach ($files as $file): ?>
<tr>
<td><strong><?= htmlspecialchars($file['name']) ?></strong></td>
<td><?= htmlspecialchars($file['size']) ?> KB</td>
<td><?= htmlspecialchars($file['date']) ?></td>
<td>
<a href="?service=download_file&file=<?= urlencode($file['name']) ?>" class="btn-download">💾 Download</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
<style>
.card { background: var(--sidebar); padding: 20px; border-radius: 8px; border: 1px solid var(--border); }
.data-table { width: 100%; border-collapse: collapse; margin-top: 10px; }
.data-table th, .data-table td { text-align: left; padding: 12px 15px; border-bottom: 1px solid var(--border); }
.data-table th { background: rgba(0,0,0,0.02); color: var(--text); font-weight: bold; }
.data-table tr:hover { background: rgba(0,0,0,0.01); }
.btn-download { display: inline-block; padding: 6px 12px; background: var(--accent); color: white; border-radius: 4px; text-decoration: none; font-size: 0.85rem; font-weight: bold; }
.btn-download:hover { opacity: 0.9; }
</style>
HTML;
$files['WEBROOT/src/MVC/View/templates/upload.php'] = <<<'HTML'
<h2>📤 Datei Upload</h2>
<?php use MVC\Classes\Form; ?>
<?php if (isset($message)): ?>
<div class="alert <?= $message['type'] === 'success' ? 'alert-success' : 'alert-error' ?>">
<?= htmlspecialchars($message['text']) ?>
</div>
<?php endif; ?>
<div class="card">
<?= Form::open($current_service, 'POST', ['enctype' => 'multipart/form-data']) ?>
<?= Form::input('file_upload', 'Datei auswählen', 'file', '', ['required' => true]) ?>
<?= Form::textarea('description', 'Beschreibung (optional)', '', ['placeholder' => 'Kurze Beschreibung der Datei...']) ?>
<?= Form::submit('Hochladen') ?>
<?= Form::close() ?>
</div>
<style>
.card { background: var(--sidebar); padding: 25px; border-radius: 8px; border: 1px solid var(--border); box-shadow: 0 2px 10px rgba(0,0,0,0.05); }
.form-group { margin-bottom: 20px; }
.form-group label { display: block; margin-bottom: 8px; font-weight: 600; color: var(--text); }
.form-control { width: 100%; padding: 12px; border: 1px solid var(--border); border-radius: 4px; background: var(--bg); color: var(--text); box-sizing: border-box; }
.form-control:focus { border-color: var(--accent); outline: none; box-shadow: 0 0 0 2px rgba(0,123,255,0.25); }
.btn { padding: 12px 24px; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; transition: opacity 0.2s; }
.btn-primary { background: var(--accent); color: white; }
.btn:hover { opacity: 0.9; }
.alert { padding: 15px; border-radius: 4px; margin-bottom: 20px; border-left: 5px solid; }
.alert-success { background: rgba(46, 204, 113, 0.1); border-color: #2ecc71; color: #27ae60; }
.alert-error { background: rgba(231, 76, 60, 0.1); border-color: #e74c3c; color: #c0392b; }
</style>
HTML;
// --- CONFIG: routes.php ---
$files['WEBROOT/src/config/routes.php'] = <<<'PHP'
<?php
@@ -1493,6 +2150,11 @@ $router->add('login', \MVC\Controller\AuthController::class, 'login');
$router->add('logout', \MVC\Controller\AuthController::class, 'logout');
$router->add('admin_mail', \MVC\Controller\AdminController::class, 'mail_service', ['auth', 'admin']);
$router->add('admin_users', \MVC\Controller\AdminController::class, 'user_service', ['auth', 'admin']);
$router->add('template_upload', \MVC\Controller\TemplateController::class, 'upload', ['auth']);
$router->add('template_download', \MVC\Controller\TemplateController::class, 'download', ['auth']);
$router->add('template_form', \MVC\Controller\TemplateController::class, 'data_form', ['auth']);
$router->add('template_table', \MVC\Controller\TemplateController::class, 'data_table', ['auth']);
$router->add('download_file', \MVC\Controller\TemplateController::class, 'download_file', ['auth']);
PHP;
// --- CONFIG: navigation.php ---
@@ -1508,6 +2170,10 @@ return [
'test2' => ['label' => '📊 Test 2'],
'admin_mail' => ['label' => '📧 Mail Service'],
'admin_users' => ['label' => '👤 User Service'],
'template_upload' => ['label' => '📤 Upload Template'],
'template_download' => ['label' => '📥 Download Template'],
'template_form' => ['label' => '📝 Form Template'],
'template_table' => ['label' => '📊 Table Template'],
'docs' => ['label' => '📚 Dokumentation'],
];
PHP;