Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e900655b47 |
No files matched your search
@@ -1 +0,0 @@
|
||||
Framework/WEBROOT/logs
|
||||
@@ -5,7 +5,7 @@
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"workbench.colorTheme": "Dark 2026",
|
||||
"workbench.colorTheme": "Dark Modern",
|
||||
"workbench.colorCustomizations": {
|
||||
"activityBar.background": "#162df7ec",
|
||||
"activityBar.foreground": "#000000",
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
|
||||
require_once __DIR__ . '/../../src/MVC/autoloader.php';
|
||||
|
||||
use MVC\Controller\ApiController;
|
||||
|
||||
$api = (new ApiController());
|
||||
|
||||
try {
|
||||
echo ($api->get());
|
||||
} catch (\Exception $e) {
|
||||
echo ($e->getMessage());
|
||||
}
|
||||
+11
-30
@@ -8,17 +8,7 @@ use MVC\Classes\Logger;
|
||||
set_exception_handler([Logger::class, 'logException']);
|
||||
set_error_handler([Logger::class, 'logError']);
|
||||
|
||||
// 1. Determine the service
|
||||
$service = $_GET['service'] ?? null;
|
||||
|
||||
// Fallback to path-based routing if no service parameter is given
|
||||
if ($service === null) {
|
||||
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
|
||||
$service = trim($uri, '/');
|
||||
if (empty($service)) {
|
||||
$service = 'home';
|
||||
}
|
||||
}
|
||||
$service = $_GET['service'] ?? 'home';
|
||||
|
||||
try {
|
||||
$router = new \MVC\Core\Router();
|
||||
@@ -26,27 +16,18 @@ try {
|
||||
// Load routes from config
|
||||
require_once __DIR__ . '/src/config/routes.php';
|
||||
|
||||
// 2. Dispatch the service
|
||||
$routeInfo = $router->getRoute($service);
|
||||
$output = $router->dispatch($service);
|
||||
|
||||
// 3. Conditional Layout Rendering
|
||||
if ($routeInfo && !empty($routeInfo['isApi'])) {
|
||||
// API Route: Just echo the output and exit
|
||||
echo $output;
|
||||
exit;
|
||||
}
|
||||
|
||||
$layout = new \MVC\Layout\Layout('main');
|
||||
$layout->setBlock('title', 'MVC Starter');
|
||||
$layout->setBlock('header_title', '🚀 MVC Dashboard');
|
||||
|
||||
$headerActions = '<button class="btn-toggle" onclick="toggleTheme()">🌓 Theme umschalten</button>';
|
||||
if (\MVC\Classes\Auth::check()) {
|
||||
$user = \MVC\Classes\Auth::user();
|
||||
$headerActions = '<span style="margin-right: 15px; font-size: 0.9rem;">👤 ' . htmlspecialchars($user['username']) . '</span>' . '<a href="?service=logout" class="btn-toggle" style="text-decoration: none; background: rgba(255,255,255,0.2); margin-right: 10px;">🚪 Logout</a> ' . $headerActions;
|
||||
if (isset($_SESSION['user_logged_in'])) {
|
||||
$headerActions = '<span style="margin-right: 15px; font-size: 0.9rem;">👤 ' . htmlspecialchars($_SESSION['username']) . '</span>' .
|
||||
'<a href="?service=logout" class="btn-toggle" style="text-decoration: none; background: rgba(255,255,255,0.2); margin-right: 10px;">🚪 Logout</a> ' .
|
||||
$headerActions;
|
||||
} else {
|
||||
$headerActions = '<a href="?service=login" class="btn-toggle" style="text-decoration: none; background: var(--accent); color: white; margin-right: 10px;">🔑 Login</a> ' . $headerActions;
|
||||
$headerActions = '<a href="?service=login" class="btn-toggle" style="text-decoration: none; background: var(--accent); color: white; margin-right: 10px;">🔑 Login</a> ' .
|
||||
$headerActions;
|
||||
}
|
||||
$layout->setBlock('header_actions', $headerActions);
|
||||
|
||||
@@ -57,8 +38,8 @@ try {
|
||||
?>
|
||||
<nav>
|
||||
<?php foreach ($navItems as $navKey => $item):
|
||||
$isLoggedIn = \MVC\Classes\Auth::check();
|
||||
$isAdmin = \MVC\Classes\Auth::isAdmin();
|
||||
$isLoggedIn = isset($_SESSION['user_logged_in']);
|
||||
$isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
|
||||
|
||||
// Check visibility based on Router middleware
|
||||
$requiresAuth = $router->hasMiddleware($navKey, 'auth');
|
||||
@@ -70,7 +51,7 @@ try {
|
||||
<a href="?service=<?= $navKey ?>" class="<?= $service === $navKey ? 'active' : '' ?>"><?= $item['label'] ?></a>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php if (!\MVC\Classes\Auth::check()): ?>
|
||||
<?php if (!isset($_SESSION['user_logged_in'])): ?>
|
||||
<div style="margin-top: 20px; padding: 10px; font-size: 0.8rem; opacity: 0.5; border-top: 1px solid var(--border);">
|
||||
🔒 Melden Sie sich an, um alle Funktionen zu sehen.
|
||||
</div>
|
||||
@@ -79,7 +60,7 @@ try {
|
||||
<?php
|
||||
$layout->setBlock('sidebar', ob_get_clean());
|
||||
|
||||
$layout->setBlock('content', $output);
|
||||
$layout->setBlock('content', $router->dispatch($service));
|
||||
echo $layout->render();
|
||||
} catch (\Throwable $e) {
|
||||
// Log the error globally
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
<?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');
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -11,23 +11,6 @@ class Logger {
|
||||
return dirname(__DIR__, 3) . DIRECTORY_SEPARATOR . 'logs';
|
||||
}
|
||||
|
||||
private static $config = null;
|
||||
|
||||
/**
|
||||
* Lädt die Konfiguration aus der env.ini
|
||||
*/
|
||||
private static function getConfig() {
|
||||
if (self::$config === null) {
|
||||
$configFile = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'env.ini';
|
||||
if (file_exists($configFile)) {
|
||||
self::$config = parse_ini_file($configFile);
|
||||
} else {
|
||||
self::$config = [];
|
||||
}
|
||||
}
|
||||
return self::$config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Protokolliert eine Nachricht in eine Datei.
|
||||
*
|
||||
@@ -36,16 +19,6 @@ class Logger {
|
||||
* @param string $file Optional: Die Zieldatei im logs-Ordner.
|
||||
*/
|
||||
public static function log($message, $level = 'INFO', $file = 'system.log') {
|
||||
$config = self::getConfig();
|
||||
|
||||
// Überprüfe Konfiguration für spezielle Logs
|
||||
if ($file === 'access.log' && empty($config['LOG_ACCESS'])) {
|
||||
return;
|
||||
}
|
||||
if ($file === 'blocked.log' && empty($config['LOG_SECURITY'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$logDir = self::getLogDir();
|
||||
|
||||
if (!is_dir($logDir)) {
|
||||
@@ -100,4 +73,4 @@ class Logger {
|
||||
$message = "$errstr in $errfile on line $errline";
|
||||
self::log($message, $level, 'error.log');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,32 +8,17 @@ use MVC\Classes\Logger;
|
||||
use MVC\Model\Mailing;
|
||||
|
||||
class Mailer {
|
||||
private const DEFAULT_MAIL = 'bruno.eckhardt07@gmail.com';
|
||||
|
||||
private static function getConfig() {
|
||||
return parse_ini_file(__DIR__ . '/../../config/env.ini');
|
||||
}
|
||||
|
||||
private static function getDefaultRecipient(array $config): string {
|
||||
return $config['MAIL_TO'] ?? self::DEFAULT_MAIL;
|
||||
}
|
||||
|
||||
private static function getSender(array $config): string {
|
||||
return $config['MAIL_FROM'] ?? self::DEFAULT_MAIL;
|
||||
}
|
||||
|
||||
private static function resolveRecipient(?string $to, array $config): string {
|
||||
$to = trim((string) $to);
|
||||
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) {
|
||||
public static function send($subject, $message, $url = null, bool $log = true) {
|
||||
$config = self::getConfig();
|
||||
$from = self::getSender($config);
|
||||
$recipient = self::resolveRecipient($to, $config);
|
||||
$from = $config['MAIL_FROM'] ?? "bruno.eckhardt07@gmail.com";
|
||||
$to = $config['MAIL_TO'] ?? "bruno.eckhardt07@gmail.com";
|
||||
|
||||
$headers = "From: " . $from . "\r\n";
|
||||
$headers .= "Reply-To: " . $from . "\r\n";
|
||||
@@ -41,10 +26,10 @@ class Mailer {
|
||||
|
||||
// mail() unterdrückt Fehlermeldungen standardmäßig nicht,
|
||||
// wir prüfen den Rückgabewert.
|
||||
$success = @mail($recipient, $subject, $message, $headers);
|
||||
$success = @mail($to, $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");
|
||||
Logger::log("Mail dispatch failed for subject: '$subject'. Check your system's sendmail/SMTP configuration.", "ERROR", "error.log");
|
||||
}
|
||||
|
||||
if ($log) {
|
||||
@@ -60,8 +45,8 @@ class Mailer {
|
||||
public static function error($title, $error, bool $log = true) {
|
||||
$subject = "ERROR: $title";
|
||||
$config = self::getConfig();
|
||||
$from = self::getSender($config);
|
||||
$to = self::getDefaultRecipient($config);
|
||||
$from = $config['MAIL_FROM'] ?? "bruno.eckhardt07@gmail.com";
|
||||
$to = $config['MAIL_TO'] ?? "bruno.eckhardt07@gmail.com";
|
||||
|
||||
$headers = "From: " . $from . "\r\n";
|
||||
$headers .= "Reply-To: " . $from . "\r\n";
|
||||
@@ -80,34 +65,6 @@ class Mailer {
|
||||
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.
|
||||
*/
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
<?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.");
|
||||
}
|
||||
}
|
||||
@@ -18,9 +18,7 @@ class AdminController extends Controller {
|
||||
$subject = $_POST['subject'] ?? 'Test Mail';
|
||||
$body = $_POST['body'] ?? '';
|
||||
|
||||
if ($to !== '' && !filter_var($to, FILTER_VALIDATE_EMAIL)) {
|
||||
$message = ["type" => "error", "text" => "Bitte eine gültige Empfänger-Adresse angeben."];
|
||||
} elseif (Mailer::send($subject, $body, null, true, $to)) {
|
||||
if (Mailer::send($subject, $body)) {
|
||||
$message = ["type" => "success", "text" => "E-Mail erfolgreich versendet!"];
|
||||
} else {
|
||||
$message = ["type" => "error", "text" => "Fehler beim E-Mail-Versand."];
|
||||
|
||||
@@ -11,11 +11,4 @@ class ApiController extends Controller {
|
||||
'data' => 'DummyData',
|
||||
]);
|
||||
}
|
||||
|
||||
public static function become() {
|
||||
return Api::jsonResponse([
|
||||
'data' => 'Hello',
|
||||
'state' => 'none'
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,11 @@
|
||||
namespace MVC\Controller;
|
||||
|
||||
use MVC\Core\Controller;
|
||||
use MVC\Classes\Auth;
|
||||
use MVC\Model\Users;
|
||||
|
||||
class AuthController extends Controller {
|
||||
public function login() {
|
||||
if (Auth::check()) {
|
||||
if (isset($_SESSION['user_logged_in'])) {
|
||||
header('Location: ?service=home');
|
||||
exit;
|
||||
}
|
||||
@@ -16,7 +16,14 @@ class AuthController extends Controller {
|
||||
$username = $_POST['username'] ?? '';
|
||||
$password = $_POST['password'] ?? '';
|
||||
|
||||
if (Auth::login($username, $password)) {
|
||||
$user = Users::getByUsername($username);
|
||||
|
||||
if ($user && password_verify($password, $user['PASSWORD'])) {
|
||||
$_SESSION['user_logged_in'] = true;
|
||||
$_SESSION['username'] = $user['USERNAME'];
|
||||
$_SESSION['role'] = $user['ROLE'];
|
||||
$_SESSION['is_admin'] = ($user['ROLE'] === 'admin');
|
||||
|
||||
header('Location: ?service=home');
|
||||
exit;
|
||||
} else {
|
||||
@@ -28,7 +35,7 @@ class AuthController extends Controller {
|
||||
}
|
||||
|
||||
public function logout() {
|
||||
Auth::logout();
|
||||
session_destroy();
|
||||
header('Location: ?service=login');
|
||||
exit;
|
||||
}
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace MVC\Controller;
|
||||
|
||||
use MVC\Core\Controller;
|
||||
use MVC\Model\Users;
|
||||
|
||||
class UserMgmtController extends Controller {
|
||||
public function user_mgmt() {
|
||||
$action = $_GET['action'] ?? 'list';
|
||||
$message = null;
|
||||
|
||||
// 1. Handle Delete (delete())
|
||||
if ($action === 'delete' && isset($_GET['id'])) {
|
||||
if (Users::delete("ID = ?", $_GET['id'])) {
|
||||
$message = ['type' => 'success', 'text' => 'Benutzer erfolgreich gelöscht.'];
|
||||
}
|
||||
$action = 'list';
|
||||
}
|
||||
|
||||
// 2. Handle Create (insert())
|
||||
if ($action === 'create' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$data = [
|
||||
'USERNAME' => $_POST['username'],
|
||||
'PASSWORD' => password_hash($_POST['password'], PASSWORD_DEFAULT),
|
||||
'EMAIL' => $_POST['email'],
|
||||
'ROLE' => $_POST['role']
|
||||
];
|
||||
if (Users::insert($data)) {
|
||||
$message = ['type' => 'success', 'text' => 'Benutzer erstellt.'];
|
||||
$action = 'list';
|
||||
} else {
|
||||
$message = ['type' => 'error', 'text' => 'Fehler beim Erstellen.'];
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Handle Update (update())
|
||||
if ($action === 'edit' && $_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['id'])) {
|
||||
$data = [
|
||||
'USERNAME' => $_POST['username'],
|
||||
'EMAIL' => $_POST['email'],
|
||||
'ROLE' => $_POST['role']
|
||||
];
|
||||
// Nur Passwort updaten wenn ausgefüllt
|
||||
if (!empty($_POST['password'])) {
|
||||
$data['PASSWORD'] = password_hash($_POST['password'], PASSWORD_DEFAULT);
|
||||
}
|
||||
|
||||
if (Users::update($data, "ID = ?", $_POST['id'])) {
|
||||
$message = ['type' => 'success', 'text' => 'Benutzer aktualisiert.'];
|
||||
$action = 'list';
|
||||
} else {
|
||||
$message = ['type' => 'error', 'text' => 'Fehler beim Update.'];
|
||||
}
|
||||
}
|
||||
|
||||
// --- View Logic ---
|
||||
|
||||
$users = [];
|
||||
$editUser = null;
|
||||
|
||||
if ($action === 'list') {
|
||||
// Test select() & where() & all()
|
||||
if (!empty($_GET['search'])) {
|
||||
$users = Users::where("USERNAME LIKE ?", "%" . $_GET['search'] . "%");
|
||||
} else if (isset($_GET['simple'])) {
|
||||
$users = Users::select(["ID", "USERNAME", "ROLE"]);
|
||||
} else {
|
||||
$users = Users::all();
|
||||
}
|
||||
} else if ($action === 'edit' && isset($_GET['id'])) {
|
||||
// Test find()
|
||||
$editUser = Users::find($_GET['id']);
|
||||
}
|
||||
|
||||
return $this->render('user_mgmt', [
|
||||
'action' => $action,
|
||||
'users' => $users,
|
||||
'editUser' => $editUser,
|
||||
'message' => $message
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace MVC\Core;
|
||||
|
||||
class Middleware {
|
||||
/**
|
||||
* Mapping von Middleware-Namen auf Klassen.
|
||||
*/
|
||||
protected static $map = [
|
||||
'auth' => \MVC\Middleware\AuthMiddleware::class,
|
||||
'admin' => \MVC\Middleware\AdminMiddleware::class,
|
||||
'global' => \MVC\Middleware\EveryRequestMiddleware::class,
|
||||
'block' => \MVC\Middleware\BlockMiddleware::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* Führt eine Middleware anhand ihres Namens aus.
|
||||
*
|
||||
* @param string $name Der Name der Middleware (z.B. 'auth', 'admin')
|
||||
* @throws \Exception Wenn die Middleware nicht definiert ist
|
||||
*/
|
||||
public static function run(string $name) {
|
||||
if (!isset(self::$map[$name])) {
|
||||
throw new \Exception("Middleware '$name' ist nicht definiert.");
|
||||
}
|
||||
|
||||
$class = self::$map[$name];
|
||||
|
||||
if (!class_exists($class)) {
|
||||
throw new \Exception("Middleware-Klasse '$class' wurde nicht gefunden.");
|
||||
}
|
||||
|
||||
$middleware = new $class();
|
||||
|
||||
if (!$middleware instanceof MiddlewareInterface) {
|
||||
throw new \Exception("Middleware '$class' muss MiddlewareInterface implementieren.");
|
||||
}
|
||||
|
||||
$middleware->handle();
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace MVC\Core;
|
||||
|
||||
interface MiddlewareInterface {
|
||||
/**
|
||||
* Hauptmethode der Middleware.
|
||||
*/
|
||||
public function handle();
|
||||
}
|
||||
@@ -4,43 +4,24 @@ namespace MVC\Core;
|
||||
|
||||
class Router {
|
||||
protected $routes = [];
|
||||
protected $globalMiddlewares = [];
|
||||
|
||||
public function addGlobal($middleware) {
|
||||
$this->globalMiddlewares[] = $middleware;
|
||||
}
|
||||
|
||||
public function add($service, $controller, $method, $middlewares = [], $isApi = null) {
|
||||
// Auto-detect API route if $isApi is not explicitly set
|
||||
if ($isApi === null) {
|
||||
$isApi = strpos($service, 'api/') === 0;
|
||||
}
|
||||
protected $middlewares = [];
|
||||
|
||||
public function add($service, $controller, $method, $middlewares = []) {
|
||||
$this->routes[$service] = [
|
||||
'controller' => $controller,
|
||||
'method' => $method,
|
||||
'middlewares' => $middlewares,
|
||||
'isApi' => $isApi
|
||||
'middlewares' => $middlewares
|
||||
];
|
||||
}
|
||||
|
||||
public function getRoute($service) {
|
||||
return $this->routes[$service] ?? null;
|
||||
}
|
||||
|
||||
public function dispatch($service) {
|
||||
if (!isset($this->routes[$service])) {
|
||||
throw new \Exception("Service '$service' not found.");
|
||||
}
|
||||
|
||||
// 1. Run Global Middlewares
|
||||
foreach ($this->globalMiddlewares as $middleware) {
|
||||
$this->runMiddleware($middleware);
|
||||
}
|
||||
|
||||
$route = $this->routes[$service];
|
||||
|
||||
// 2. Run Route-Specific Middlewares
|
||||
// Run Middlewares
|
||||
foreach ($route['middlewares'] as $middleware) {
|
||||
$this->runMiddleware($middleware);
|
||||
}
|
||||
@@ -70,6 +51,17 @@ class Router {
|
||||
}
|
||||
|
||||
protected function runMiddleware($middleware) {
|
||||
Middleware::run($middleware);
|
||||
if ($middleware === 'admin') {
|
||||
if (!isset($_SESSION['is_admin']) || $_SESSION['is_admin'] !== true) {
|
||||
die("Access Denied: Admin rights required.");
|
||||
}
|
||||
}
|
||||
|
||||
if ($middleware === 'auth') {
|
||||
if (empty($_SESSION['user_logged_in'])) {
|
||||
header('Location: ?service=login');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,15 +24,4 @@ class View {
|
||||
}
|
||||
return "View $template not found at $file";
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendert ein Teilstück (Partial) einer View
|
||||
*/
|
||||
public function partial($template, $data = []) {
|
||||
$view = new self($this->viewPath);
|
||||
foreach ($data as $key => $value) {
|
||||
$view->assign($key, $value);
|
||||
}
|
||||
return $view->render($template);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>403 Forbidden</title>
|
||||
<style>
|
||||
body { background: white; color: black; font-family: monospace; padding: 50px; }
|
||||
h1 { border-bottom: 1px solid black; padding-bottom: 10px; }
|
||||
pre { background: #eee; padding: 15px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>403 Forbidden</h1>
|
||||
<p>Access to this resource is denied.</p>
|
||||
<hr>
|
||||
<pre>
|
||||
Status: Blocked
|
||||
Source: <?php echo htmlspecialchars($ip); ?>
|
||||
Time: <?php echo date('r'); ?>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,24 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Systemmeldung</title>
|
||||
<style>
|
||||
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #1a1a2e; color: #e94560; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; }
|
||||
.card { background: #16213e; padding: 3rem; border-radius: 20px; text-align: center; box-shadow: 0 10px 30px rgba(0,0,0,0.5); border: 1px solid #0f3460; }
|
||||
.icon { font-size: 4rem; margin-bottom: 1rem; }
|
||||
h1 { margin: 0; font-weight: 300; letter-spacing: 2px; }
|
||||
p { color: #95a5a6; margin-top: 1.5rem; }
|
||||
.footer { margin-top: 2rem; font-size: 0.8rem; opacity: 0.5; color: white; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<div class="icon">🛡️</div>
|
||||
<h1>ZUGRIFF GESPERRT</h1>
|
||||
<p>Ihre Verbindung wurde aus Sicherheitsgründen unterbrochen.</p>
|
||||
<div class="footer">ID: <?php echo htmlspecialchars($ip); ?> | <?php echo date('Y-m-d H:i:s'); ?></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,23 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Zugriff verweigert</title>
|
||||
<style>
|
||||
body { font-family: sans-serif; background: #f4f4f4; color: #333; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; }
|
||||
.container { background: white; padding: 2rem; border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); text-align: center; max-width: 400px; border-top: 5px solid #d9534f; }
|
||||
h1 { color: #d9534f; margin-top: 0; }
|
||||
p { line-height: 1.6; }
|
||||
.ip { font-weight: bold; color: #777; font-size: 0.9rem; margin-top: 1rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>403 - Zugriff verweigert</h1>
|
||||
<p>Ihre IP-Adresse wurde aufgrund von Sicherheitsrichtlinien vorübergehend blockiert.</p>
|
||||
<p>Bitte wenden Sie sich an den Administrator, falls Sie glauben, dass dies ein Fehler ist.</p>
|
||||
<div class="ip">IP: <?php echo htmlspecialchars($ip); ?></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -3,7 +3,6 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title><?= $this->getBlock('title') ?></title>
|
||||
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23007bff' viewBox='0 0 16 16'><path d='M2.114 8.063V7.9c1.005-.102 1.497-.615 1.497-1.6V4.503c0-1.094.39-1.538 1.354-1.538h.273V2h-.376C3.25 2 2.49 2.759 2.49 4.352v1.524c0 1.094-.376 1.456-1.49 1.456v1.299c1.114 0 1.49.362 1.49 1.456v1.524c0 1.593.759 2.352 2.372 2.352h.376v-.964h-.273c-.964 0-1.354-.444-1.354-1.538V9.663c0-.984-.492-1.497-1.497-1.6M13.886 7.9v.163c-1.005.103-1.497.616-1.497 1.6v1.798c0 1.094-.39 1.538-1.354 1.538h-.273v.964h.376c1.613 0 2.372-.759 2.372-2.352v-1.524c0-1.094.376-1.456 1.49-1.456V7.332c-1.114 0-1.49-.362-1.49-1.456V4.352C13.51 2.759 12.75 2 11.138 2h-.376v.964h.273c.964 0 1.354.444 1.354 1.538V6.3c0 .984.492 1.497 1.497 1.6'/></svg>">
|
||||
<style>
|
||||
:root { --bg: #ffffff; --text: #333; --sidebar: #f4f4f4; --header: #007bff; --accent: #007bff; --border: #ddd; }
|
||||
.dark-mode { --bg: #1a1a1a; --text: #f4f4f4; --sidebar: #252525; --header: #111; --accent: #3498db; --border: #333; }
|
||||
@@ -17,8 +16,6 @@
|
||||
nav a { display: block; padding: 12px; text-decoration: none; color: inherit; border-radius: 5px; margin-bottom: 5px; }
|
||||
nav a:hover { background: rgba(0,0,0,0.05); }
|
||||
nav a.active { background: rgba(0,0,0,0.1); }
|
||||
a:link { text-decoration: none; }
|
||||
a:visited { color: inherit; }
|
||||
.logo { font-size: 1.4rem; font-weight: bold; }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace MVC\Middleware;
|
||||
|
||||
use MVC\Core\MiddlewareInterface;
|
||||
|
||||
class AdminMiddleware implements MiddlewareInterface {
|
||||
public function handle() {
|
||||
if (!isset($_SESSION['is_admin']) || $_SESSION['is_admin'] !== true) {
|
||||
http_response_code(403);
|
||||
die("Zugriff verweigert: Admin-Rechte erforderlich.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace MVC\Middleware;
|
||||
|
||||
use MVC\Core\MiddlewareInterface;
|
||||
|
||||
class AuthMiddleware implements MiddlewareInterface {
|
||||
public function handle() {
|
||||
if (empty($_SESSION['user_logged_in'])) {
|
||||
header('Location: ?service=login');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace MVC\Middleware;
|
||||
|
||||
use MVC\Core\MiddlewareInterface;
|
||||
use MVC\Classes\Logger;
|
||||
use MVC\Classes\Client;
|
||||
|
||||
/**
|
||||
* Beispiel-Middleware: Blockiert bestimmte Anfragen oder IPs
|
||||
*/
|
||||
class BlockMiddleware implements MiddlewareInterface {
|
||||
public function handle() {
|
||||
$blockedIps = ['127.0.0.2'];
|
||||
$ip = Client::ip();
|
||||
if (in_array($ip, $blockedIps)) {
|
||||
Logger::log("Blocked IP attempt: $ip", 'SECURITY', 'blocked.log');
|
||||
|
||||
// Lade Konfiguration für das Template
|
||||
$configFile = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'env.ini';
|
||||
$config = file_exists($configFile) ? parse_ini_file($configFile) : [];
|
||||
$template = $config['BLOCK_PAGE_TEMPLATE'] ?? 'standard';
|
||||
|
||||
$templatePath = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'Layout' . DIRECTORY_SEPARATOR . 'Block' . DIRECTORY_SEPARATOR . $template . '.php';
|
||||
|
||||
http_response_code(403);
|
||||
if (file_exists($templatePath)) {
|
||||
include $templatePath;
|
||||
} else {
|
||||
die("Ihre IP-Adresse wurde blockiert.");
|
||||
}
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace MVC\Middleware;
|
||||
|
||||
use MVC\Core\MiddlewareInterface;
|
||||
use MVC\Classes\Logger;
|
||||
|
||||
/**
|
||||
* Beispiel-Middleware: Wird bei jedem Request ausgeführt (Global)
|
||||
*/
|
||||
class EveryRequestMiddleware implements MiddlewareInterface {
|
||||
public function handle() {
|
||||
$ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
|
||||
|
||||
// Determine service logic consistent with index.php
|
||||
$service = $_GET['service'] ?? null;
|
||||
if ($service === null) {
|
||||
$uri = parse_url($_SERVER['REQUEST_URI'] ?? '', PHP_URL_PATH);
|
||||
$service = trim($uri, '/');
|
||||
if (empty($service)) {
|
||||
$service = 'home';
|
||||
}
|
||||
}
|
||||
|
||||
Logger::log("IP: $ip - Service: $service", 'ACCESS', 'access.log');
|
||||
}
|
||||
}
|
||||
@@ -8,17 +8,13 @@
|
||||
|
||||
<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);">
|
||||
<input type="text" name="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>
|
||||
<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);"></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>
|
||||
|
||||
@@ -1,298 +1,92 @@
|
||||
<div class="docs-container">
|
||||
<h1>🏛️ MVC Framework - Die Entwickler-Bibel</h1>
|
||||
<h1>🏛️ Framework Architektur & Dokumentation</h1>
|
||||
|
||||
<div class="toc">
|
||||
<h3>📖 Inhaltsverzeichnis</h3>
|
||||
<div class="toc-grid">
|
||||
<ul>
|
||||
<li><strong>1. Kern-Architektur</strong>
|
||||
<ul>
|
||||
<li><a href="#request-lifecycle">Request Lifecycle</a></li>
|
||||
<li><a href="#directory-structure">Verzeichnisstruktur</a></li>
|
||||
<li><a href="#autoloader">Autoloader & Namespaces</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><strong>2. Routing & Middleware</strong>
|
||||
<ul>
|
||||
<li><a href="#routing-basics">Routing Grundlagen</a></li>
|
||||
<li><a href="#middleware">Middleware (Auth/Admin)</a></li>
|
||||
<li><a href="#navigation">Navigations-System</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><strong>3. Datenbank & Models</strong>
|
||||
<ul>
|
||||
<li><a href="#db-methods">Database Methods (Low-Level)</a></li>
|
||||
<li><a href="#model-core">Core\Model (Abstraktion)</a></li>
|
||||
<li><a href="#install-db">Installations-Script</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li><strong>4. UI & Layout</strong>
|
||||
<ul>
|
||||
<li><a href="#layout-engine">Layout Engine (Blöcke)</a></li>
|
||||
<li><a href="#view-system">View System</a></li>
|
||||
<li><a href="#theme-toggle">DarkMode & Themes</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><strong>5. Framework Klassen</strong>
|
||||
<ul>
|
||||
<li><a href="#class-auth">Auth (Sicherheit)</a></li>
|
||||
<li><a href="#class-form">Form (Generatoren)</a></li>
|
||||
<li><a href="#class-storage">Storage (Files)</a></li>
|
||||
<li><a href="#class-mailer">Mailer (E-Mail)</a></li>
|
||||
<li><a href="#class-logger">Logger (Debugging)</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><strong>6. Tools & API</strong>
|
||||
<ul>
|
||||
<li><a href="#cli-tools">CLI Werkzeuge (make.php)</a></li>
|
||||
<li><a href="#api-dev">API Entwicklung</a></li>
|
||||
<li><a href="#best-practices">Best Practices</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<h3>Inhalt</h3>
|
||||
<ul>
|
||||
<li><a href="#intro">1. Einführung</a></li>
|
||||
<li><a href="#files">2. Datei-Referenz (Deep Dive)</a></li>
|
||||
<li><a href="#api">3. API-System</a></li>
|
||||
<li><a href="#database">4. Datenbank & Models</a></li>
|
||||
<li><a href="#classes">5. Helper Klassen</a></li>
|
||||
<li><a href="#generators">6. CLI-Generatoren</a></li>
|
||||
<li><a href="#mailing">7. Mailing-System</a></li>
|
||||
<li><a href="#logging">8. Error-Logging</a></li>
|
||||
<li><a href="#private-services">9. Private Services</a></li>
|
||||
<li><a href="#best-practices">10. Best Practices</a></li>
|
||||
<li><a href="#model-functions">11. Model Core Funktionen</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- 1. Kern-Architektur -->
|
||||
<section id="request-lifecycle">
|
||||
<h2>1.1 Request Lifecycle</h2>
|
||||
<p>Der Weg einer Anfrage durch das Framework ist linear und vorhersehbar:</p>
|
||||
<ol>
|
||||
<li><strong>Entry:</strong> Alle Requests treffen auf <code>WEBROOT/index.php</code>.</li>
|
||||
<li><strong>Bootstrap:</strong> Session wird gestartet, der <code>autoloader</code> lädt Klassen nach Bedarf.</li>
|
||||
<li><strong>Routing:</strong> Der <code>Router</code> lädt die <code>routes.php</code> und prüft den <code>GET['service']</code> Parameter.</li>
|
||||
<li><strong>Security:</strong> Middlewares (z.B. <code>auth</code>) werden ausgeführt.</li>
|
||||
<li><strong>Logic:</strong> Der Controller führt die Zielmethode aus.</li>
|
||||
<li><strong>Rendering:</strong> Der Controller ruft <code>$this->render()</code> auf.</li>
|
||||
<li><strong>Output:</strong> Das fertige Layout wird an den Browser gesendet.</li>
|
||||
</ol>
|
||||
<section id="intro">
|
||||
<h2>1. Einführung</h2>
|
||||
<p>Dieses Framework basiert auf dem <strong>Model-View-Controller (MVC)</strong> Entwurfsmuster und nutzt einen <strong>Front Controller</strong> (index.php). Alle Anfragen werden zentral verarbeitet, was globale Middleware (wie Authentifizierung) und ein einheitliches Layout ermöglicht.</p>
|
||||
</section>
|
||||
|
||||
<section id="directory-structure">
|
||||
<h2>1.2 Detaillierte Verzeichnisstruktur</h2>
|
||||
<pre>
|
||||
├── Framework.code-workpace
|
||||
├── README.md
|
||||
├── init.php
|
||||
└── /WEBROOT
|
||||
├── /api
|
||||
| └── /v1
|
||||
├── /src
|
||||
| ├── /config
|
||||
| ├── /database
|
||||
| ├── /MVC
|
||||
| ├── /Core
|
||||
| ├── /Classes
|
||||
| ├── /Controller
|
||||
| ├── /Model
|
||||
| ├── /View
|
||||
| └── /Layout
|
||||
| └── /Templates
|
||||
├── /commands
|
||||
├── /static
|
||||
├── /storage
|
||||
└── /logs
|
||||
</pre>
|
||||
|
||||
</section>
|
||||
|
||||
<section id="autoloader">
|
||||
<h2>1.3 Autoloader & Namespaces</h2>
|
||||
<p>Das Framework nutzt einen PSR-4 kompatiblen Autoloader in <code>src/MVC/autoloader.php</code>.</p>
|
||||
<pre><code>// Automatische Auflösung:
|
||||
// MVC\Classes\Auth -> src/MVC/Classes/Auth.php
|
||||
// MVC\Core\Router -> src/MVC/Core/Router.php</code></pre>
|
||||
</section>
|
||||
|
||||
<!-- 2. Routing -->
|
||||
<section id="routing-basics">
|
||||
<h2>2.1 Routing Grundlagen</h2>
|
||||
<p>Routen werden zentral in <code>src/config/routes.php</code> definiert.</p>
|
||||
<pre><code>$router->add('home', \MVC\Controller\HomeController::class, 'index');</code></pre>
|
||||
</section>
|
||||
|
||||
<section id="middleware">
|
||||
<h2>2.2 Middleware System</h2>
|
||||
<p>Middlewares sind Klassen, die vor dem eigentlichen Controller ausgeführt werden. Sie dienen der Validierung von Requests, Authentifizierung oder Logging.</p>
|
||||
|
||||
<h3>Globale Middlewares</h3>
|
||||
<p>Diese werden bei <strong>jedem</strong> Request ausgeführt (z.B. IP-Blocker oder globales Logging):</p>
|
||||
<pre><code>$router->addGlobal('block');
|
||||
$router->addGlobal('global');</code></pre>
|
||||
|
||||
<h3>Routen-spezifische Middlewares</h3>
|
||||
<p>Diese werden nur für bestimmte Services definiert:</p>
|
||||
<pre><code>// Erfordert Login ('auth') und Admin-Status ('admin')
|
||||
$router->add('admin', Controller::class, 'index', ['auth', 'admin']);</code></pre>
|
||||
|
||||
<h3>Verfügbare Middlewares</h3>
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Klasse</th>
|
||||
<th>Funktion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>auth</code></td>
|
||||
<td><code>AuthMiddleware</code></td>
|
||||
<td>Prüft, ob der User eingeloggt ist (<code>$_SESSION['user_logged_in']</code>).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>admin</code></td>
|
||||
<td><code>AdminMiddleware</code></td>
|
||||
<td>Prüft auf Administrator-Rechte (<code>$_SESSION['is_admin']</code>).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>block</code></td>
|
||||
<td><code>BlockMiddleware</code></td>
|
||||
<td>Einfacher IP-Blocker für gesperrte Adressen.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>global</code></td>
|
||||
<td><code>EveryRequestMiddleware</code></td>
|
||||
<td>Platzhalter für Logik, die immer laufen soll.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Eigene Middleware erstellen</h3>
|
||||
<ol>
|
||||
<li>Erstelle eine Klasse in <code>src/MVC/Middleware/</code>.</li>
|
||||
<li>Implementiere das <code>MiddlewareInterface</code>.</li>
|
||||
<li>Registriere den Namen im Mapping der <code>src/MVC/Core/Middleware.php</code>.</li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<section id="navigation">
|
||||
<h2>2.3 Navigations-System</h2>
|
||||
<p>Die Sidebar wird dynamisch aus der <code>src/config/navigation.php</code> generiert. Der Router filtert Einträge basierend auf den Berechtigungen (Middleware) des Nutzers.</p>
|
||||
<pre><code>return [
|
||||
'home' => ['label' => '🏠 Home'],
|
||||
'admin_users' => ['label' => '👤 User Service'],
|
||||
];</code></pre>
|
||||
</section>
|
||||
|
||||
<!-- 3. Datenbank -->
|
||||
<section id="db-methods">
|
||||
<h2>3.1 Database Methods (Low-Level)</h2>
|
||||
<p>Sichere DB-Operationen via <code>src/utils/databaseMethods.php</code> unter Verwendung von Prepared Statements:</p>
|
||||
<section id="files">
|
||||
<h2>2. Datei-Referenz (Deep Dive)</h2>
|
||||
<h3>Framework Core</h3>
|
||||
<ul>
|
||||
<li><code>DatabaseShow($table, $cols, $where, ...$values)</code></li>
|
||||
<li><code>DatabaseInsert($table, $cols, ...$values)</code></li>
|
||||
<li><strong>Router.php:</strong> Verwaltet die Routen. Unterstützt Middlewares. Das Dispatching erfolgt über den <code>service</code> Parameter in der URL.</li>
|
||||
<li><strong>Controller.php:</strong> Basisklasse für alle Controller. Bietet <code>render()</code> für Views und <code>json()</code> für API-Responses.</li>
|
||||
<li><strong>Model.php:</strong> Abstrakte Basis für Datenbank-Entitäten. Kapselt die <code>databaseMethods.php</code> in ein objektorientiertes Interface.</li>
|
||||
<li><strong>View.php:</strong> Behandelt das Extrahieren von Daten in HTML-Templates.</li>
|
||||
</ul>
|
||||
<h3>Konfiguration</h3>
|
||||
<ul>
|
||||
<li><strong>env.ini:</strong> Speichert Datenbank-Zugangsdaten für <code>dev</code> und <code>live</code> Umgebungen.</li>
|
||||
<li><strong>autoloader.php:</strong> Registriert einen PSR-4 ähnlichen Autoloader für den <code>MVC\</code> Namespace.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section id="api">
|
||||
<h2>3. API-System</h2>
|
||||
<p>APIs liegen im Verzeichnis <code>WEBROOT/api/v1/</code>. Sie nutzen die <code>ApiController</code>-Klasse.</p>
|
||||
<pre>
|
||||
// Beispiel: WEBROOT/api/v1/get.php
|
||||
use MVC\Classes\Api;
|
||||
use MVC\Controller\ApiController;
|
||||
|
||||
Api::jsonResponse((new ApiController())->get());
|
||||
</pre>
|
||||
</section>
|
||||
|
||||
<section id="database">
|
||||
<h2>4. Datenbank & Models</h2>
|
||||
<p>Das Framework nutzt <code>mysqli</code> mit Prepared Statements für maximale Sicherheit.</p>
|
||||
|
||||
<h3>Installation (install_db.php)</h3>
|
||||
<p>Mit dem Befehl <code>php install_db.php</code> können vordefinierte Tabellen und deren Models automatisch erstellt werden. Standardmäßig sind folgende Tabellen enthalten:</p>
|
||||
<ul>
|
||||
<li><strong>Mailing:</strong> Für das E-Mail-Logging (wird vom Mailer genutzt).</li>
|
||||
<li><strong>Users:</strong> Eine Grundstruktur für Benutzer (ID, Username, Password, Email, Role, Created_at).</li>
|
||||
</ul>
|
||||
|
||||
<h3>databaseMethods.php (Die Power-Tools)</h3>
|
||||
<ul>
|
||||
<li><code>DatabaseShow($table, $columns, $where, ...$values)</code></li>
|
||||
<li><code>DatabaseInsert($table, $columns, ...$values)</code></li>
|
||||
<li><code>DatabaseUpdate($table, $set, $where, ...$values)</code></li>
|
||||
<li><code>DatabaseDelete($table, $where, ...$values)</code></li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section id="model-core">
|
||||
<h2>3.2 Core\Model (Abstraktion)</h2>
|
||||
<p>Ermöglicht objektorientierte DB-Zugriffe durch Vererbung.</p>
|
||||
<pre><code>class User extends \MVC\Core\Model {
|
||||
protected static $table = "Users";
|
||||
}
|
||||
$allUsers = User::all();</code></pre>
|
||||
</section>
|
||||
|
||||
<section id="install-db">
|
||||
<h2>3.3 Installations-Script</h2>
|
||||
<p>Das Script <code>install_db.php</code> automatisiert das gesamte DB-Setup:</p>
|
||||
<section id="classes">
|
||||
<h2>5. Helper Klassen (MVC\Classes)</h2>
|
||||
<ul>
|
||||
<li><strong>Root-Access:</strong> Verbindet sich mit Root-Rechten (siehe <code>env.ini</code>).</li>
|
||||
<li><strong>DB-Erstellung:</strong> Erstellt die Datenbank und einen dedizierten Worker-User.</li>
|
||||
<li><strong>Tabellen:</strong> Legt <code>Users</code> (mit Admin-Account) und <code>Mailing</code> an.</li>
|
||||
<li><strong>Models:</strong> Generiert automatisch die zugehörigen Model-Dateien im Quellcode.</li>
|
||||
<li><strong>Request:</strong> Zugriff auf <code>method()</code>, <code>queryParams()</code>, <code>headers()</code>.</li>
|
||||
<li><strong>Client:</strong> Ermittelt <code>ip()</code>, <code>userAgent()</code>, <code>country()</code>.</li>
|
||||
<li><strong>Cookie:</strong> Interface zum <code>set()</code>, <code>read()</code>, <code>Delete()</code> von Cookies.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<!-- 4. UI -->
|
||||
<section id="layout-engine">
|
||||
<h2>4.1 Layout Engine (Blöcke)</h2>
|
||||
<p>Das Framework nutzt ein flexibles Block-System in <code>MVC\Layout\Layout</code>:</p>
|
||||
<pre><code>$layout = new Layout('main');
|
||||
$layout->setBlock('title', 'Mein Titel');
|
||||
$layout->setBlock('content', 'HTML-Inhalt');
|
||||
echo $layout->render();</code></pre>
|
||||
</section>
|
||||
|
||||
<section id="view-system">
|
||||
<h2>4.2 View System</h2>
|
||||
<p>Die <code>MVC\Core\View</code> Klasse extrahiert Daten-Arrays direkt in PHP-Templates:</p>
|
||||
<pre><code>// Im Controller:
|
||||
return $this->render('home', ['name' => 'Max']);
|
||||
|
||||
// In src/MVC/View/home.php:
|
||||
<h1>Hallo <?= $name ?></h1></code></pre>
|
||||
</section>
|
||||
|
||||
<section id="theme-toggle">
|
||||
<h2>4.3 DarkMode & Themes</h2>
|
||||
<p>Unterstützt nahtlose Umschaltung zwischen Light- und DarkMode:</p>
|
||||
<ul>
|
||||
<li><strong>Zustand:</strong> Gespeichert in <code>$_SESSION['theme']</code>.</li>
|
||||
<li><strong>Frontend:</strong> Die Klasse <code>.dark-mode</code> am <code><html></code> Tag schaltet CSS-Variablen um.</li>
|
||||
<li><strong>Toggle:</strong> Über den Service <code>toggle_theme</code> steuerbar.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<!-- 5. Klassen -->
|
||||
<section id="class-auth">
|
||||
<h2>5.1 Auth Klasse</h2>
|
||||
<p>Zentrales Identitätsmanagement:</p>
|
||||
<pre><code>Auth::login($user, $pass); // Login-Versuch
|
||||
Auth::check(); // Eingeloggt?
|
||||
Auth::isAdmin(); // Admin-Rechte?
|
||||
Auth::user(); // Gibt User-Array zurück</code></pre>
|
||||
</section>
|
||||
|
||||
<section id="class-form">
|
||||
<h2>5.2 Form Generator</h2>
|
||||
<p>Erstellt konsistente HTML-Formulare mit Bootstrap-ähnlichem Styling:</p>
|
||||
<pre><code>echo Form::open('service_name');
|
||||
echo Form::input('email', 'E-Mail', 'email');
|
||||
echo Form::textarea('msg', 'Nachricht');
|
||||
echo Form::submit('Senden');
|
||||
echo Form::close();</code></pre>
|
||||
</section>
|
||||
|
||||
<section id="class-storage">
|
||||
<h2>5.3 Storage & File Handling</h2>
|
||||
<p>Abstraktionslayer für das Dateisystem:</p>
|
||||
<ul>
|
||||
<li><strong>Upload:</strong> <code>Storage::upload('file_input')</code> speichert sicher in <code>/storage/uploads/</code>.</li>
|
||||
<li><strong>Download:</strong> <code>Storage::download('path/to/file')</code> erzwingt Datei-Download mit korrekten Headern.</li>
|
||||
<li><strong>Listing:</strong> <code>Storage::listFiles()</code> gibt Array aller verfügbaren Dateien zurück.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section id="class-mailer">
|
||||
<h2>5.4 Mailer Klasse</h2>
|
||||
<p>Versand von System-Mails:</p>
|
||||
<ul>
|
||||
<li><strong>Log:</strong> Jede Mail wird (wenn möglich) in der DB-Tabelle <code>Mailing</code> protokolliert.</li>
|
||||
<li><strong>Fallback:</strong> Bei DB-Fehlern erfolgt automatisches Logging in <code>logs/mails.log</code>.</li>
|
||||
<li><strong>Error-Mail:</strong> Spezielle <code>error()</code> Methode für kritische Systembenachrichtigungen.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section id="class-logger">
|
||||
<h2>5.5 Logger & Debugging</h2>
|
||||
<p>Zentrales Fehler-Management:</p>
|
||||
<ul>
|
||||
<li><strong>Auto-Catch:</strong> Registriert sich als globaler Exception- und Error-Handler.</li>
|
||||
<li><strong>Tracing:</strong> Schreibt vollständige Stacktraces in <code>WEBROOT/logs/error.log</code>.</li>
|
||||
<li><strong>Manuell:</strong> <code>Logger::log('Nachricht', 'LEVEL')</code> für eigenes Logging.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<!-- 6. Tools -->
|
||||
<section id="cli-tools">
|
||||
<h2>6.1 CLI Werkzeuge (make.php)</h2>
|
||||
<p>Das Schweizer Taschenmesser für die Entwicklung:</p>
|
||||
<section id="generators">
|
||||
<h2>6. CLI-Generatoren</h2>
|
||||
<p>Das Framework bietet ein zentrales CLI-Tool zur Automatisierung von Aufgaben.</p>
|
||||
|
||||
<h3>Modul-Generator</h3>
|
||||
<p>Erstellt Controller, View, Route und Navigationspunkt in einem Schritt.</p>
|
||||
<pre>php make.php module [Name] [Optionen]</pre>
|
||||
<ul>
|
||||
<li><code>-c, --controller</code>: Nur den Controller erstellen.</li>
|
||||
<li><code>-v, --view</code>: Nur die View erstellen.</li>
|
||||
@@ -301,57 +95,129 @@ echo Form::close();</code></pre>
|
||||
<li><code>-a, --auth</code>: Erfordert Login für diesen Service.</li>
|
||||
<li><code>-l, --label "Text"</code>: Anzeigename in der Sidebar.</li>
|
||||
</ul>
|
||||
<p>How to Use:</p>
|
||||
<ul>
|
||||
<li><code>php make.php module [Name]</code>: Erstellt Controller, View, Route und Nav-Eintrag in einem Rutsch.</li>
|
||||
<li><code>php make.php model [Tabelle] [Spalten]</code>: Generiert fertige Model-Klassen inkl. Spaltendokumentation.</li>
|
||||
</ul>
|
||||
<p><strong>Beispiel:</strong> <code>php make.php module profile -l "👤 Profil" -a</code></p>
|
||||
|
||||
<h3>Model-Generator</h3>
|
||||
<p>Erstellt ein Model basierend auf einer Datenbank-Tabelle.</p>
|
||||
<pre>php make.php model [Tabellenname] [Spalten,kommagetrennt]</pre>
|
||||
<p><strong>Beispiel:</strong> <code>php make.php model Products NAME,PRICE,STOCK</code></p>
|
||||
</section>
|
||||
|
||||
<section id="api-dev">
|
||||
<h2>6.2 API Entwicklung</h2>
|
||||
<p>Standardisierte JSON-Antworten über die <code>Api</code> Klasse:</p>
|
||||
<pre><code>// In src/MVC/Classes/Api.php
|
||||
Api::jsonResponse(['data' => '...'], 200);</code></pre>
|
||||
<p>API-Files befinden sich übersichtlich in <code>WEBROOT/api/v1/</code>.</p>
|
||||
<section id="mailing">
|
||||
<h2>7. Mailing-System</h2>
|
||||
<p>Das Framework verfügt über eine integrierte <code>Mailer</code>-Klasse für den E-Mail-Versand und die Protokollierung.</p>
|
||||
<h3>Konfiguration</h3>
|
||||
<p>In der <code>env.ini</code> können Absender und Empfänger unter <code>[mail]</code> definiert werden.</p>
|
||||
<h3>Nutzung</h3>
|
||||
<pre>
|
||||
use MVC\Classes\Mailer;
|
||||
|
||||
// Einfache Mail
|
||||
Mailer::send("Betreff", "Inhalt");
|
||||
|
||||
// Error Mail (wird speziell markiert)
|
||||
Mailer::error("Fehlertitel", "Details");
|
||||
</pre>
|
||||
<h3>Logging & Fallback</h3>
|
||||
<p>Der Mailer versucht standardmäßig in die Datenbank-Tabelle <code>Mailing</code> zu loggen. Falls diese nicht existiert, erfolgt ein automatischer Fallback in Textdateien im Ordner <code>/logs/</code>.</p>
|
||||
<p><strong>Installation:</strong> Führe <code>php install_db.php</code> aus, um die Datenbank-Struktur anzulegen.</p>
|
||||
</section>
|
||||
|
||||
<section id="logging">
|
||||
<h2>8. Error-Logging</h2>
|
||||
<p>Das Framework verfügt über ein zentrales Logging-System, das in der <code>index.php</code> global registriert ist.</p>
|
||||
<h3>Automatische Erfassung</h3>
|
||||
<p>Alle unangefangenen Exceptions und PHP-Fehler (Warnings, Notices, etc.) werden automatisch in die Datei <code>/logs/error.log</code> geschrieben, inklusive Stacktrace bei Exceptions.</p>
|
||||
<h3>Manuelle Nutzung</h3>
|
||||
<pre>
|
||||
use MVC\Classes\Logger;
|
||||
|
||||
// Einfache Nachricht in system.log
|
||||
Logger::log("Etwas ist passiert");
|
||||
|
||||
// Spezifisches Level und Datei
|
||||
Logger::log("Kritischer Fehler", "ERROR", "error.log");
|
||||
</pre>
|
||||
<p>Die Log-Dateien befinden sich im Hauptverzeichnis des Projekts im Ordner <code>/logs/</code>.</p>
|
||||
</section>
|
||||
|
||||
<section id="private-services">
|
||||
<h2>9. Private Services (Admin)</h2>
|
||||
<p>Das Framework bringt 3 Schutzklassen in der <code>/config/routes.php</code> mit:</p>
|
||||
<ul>
|
||||
<li><strong>Example: </strong><code>$router->add('home', \MVC\Controller\HomeController::class, 'index');</code></li>
|
||||
<ul><li>Dieser Service ist öffentlich verfügbar.</li></ul>
|
||||
<li><strong>Example: </strong><code>$router->add('home', \MVC\Controller\HomeController::class, 'index', ['auth']);</code></li>
|
||||
<ul><li>Dieser Service ist mit Login verfügbar.</li></ul>
|
||||
<li><strong>Example: </strong><code>$router->add('home', \MVC\Controller\HomeController::class, 'index', ['auth', 'admin']);</code></li>
|
||||
<ul><li>Dieser Service ist mit Login und Admin-Rechten verfügbar.</li></ul>
|
||||
</ul>
|
||||
<ul>
|
||||
<li><strong>Mail Service (admin_mail):</strong> Ein Formular zum Testen des Mail-Versands.</li>
|
||||
<li><strong>User Service (admin_users):</strong> Ermöglicht das Erstellen neuer Benutzer direkt in die Datenbank (Passwörter werden automatisch gehasht).</li>
|
||||
</ul>
|
||||
<p>Diese Services werden über den <code>AdminController</code> gesteuert.</p>
|
||||
</section>
|
||||
|
||||
<section id="best-practices">
|
||||
<h2>6.3 Best Practices</h2>
|
||||
<ul>
|
||||
<li><strong>Security:</strong> Keine Variablen direkt in SQL-Strings (Prepared Statements nutzen!).</li>
|
||||
<li><strong>Output:</strong> Variablen in Views immer mit <code>htmlspecialchars()</code> maskieren.</li>
|
||||
<li><strong>Architecture:</strong> Controller rufen Models auf, Views zeigen nur Daten an. Keine DB-Logik in der View!</li>
|
||||
</ul>
|
||||
<h2>10. Best Practices</h2>
|
||||
<p><strong>Naming:</strong> Controller immer mit <code>Controller</code>-Suffix. Models im Singular.</p>
|
||||
<p><strong>Security:</strong> Nutze <code>['auth']</code> Middleware konsequent für alle internen Seiten.</p>
|
||||
</section>
|
||||
|
||||
<section id="model-functions">
|
||||
<h2>11. Model Core Funktionen</h2>
|
||||
<p>Die Basisklasse <code>MVC\Core\Model</code> bietet ein objektorientiertes Interface für Datenbankoperationen.</p>
|
||||
|
||||
<h3>all()</h3>
|
||||
<p>Gibt alle Datensätze der Tabelle zurück.</p>
|
||||
<pre><code>$users = Users::all();</code></pre>
|
||||
|
||||
<h3>select($columns)</h3>
|
||||
<p>Gibt spezifische Spalten zurück. Akzeptiert einen String oder ein Array.</p>
|
||||
<pre><code>// Als String
|
||||
$names = Users::select("USERNAME, EMAIL");
|
||||
|
||||
// Als Array
|
||||
$data = Users::select(["USERNAME", "ROLE"]);</code></pre>
|
||||
|
||||
<h3>find($id, $column = 'ID')</h3>
|
||||
<p>Sucht einen einzelnen Datensatz anhand einer Spalte (Standard: ID).</p>
|
||||
<pre><code>$user = Users::find(1);
|
||||
$userByMail = Users::find("admin@example.com", "EMAIL");</code></pre>
|
||||
|
||||
<h3>where($where, ...$params)</h3>
|
||||
<p>Filtert Datensätze mit Prepared Statements.</p>
|
||||
<pre><code>$admins = Users::where("ROLE = ?", "admin");
|
||||
$activeUsers = Users::where("STATUS = ? AND ROLE = ?", "active", "user");</code></pre>
|
||||
|
||||
<h3>insert($data)</h3>
|
||||
<p>Fügt einen neuen Datensatz hinzu.</p>
|
||||
<pre><code>Users::insert([
|
||||
'USERNAME' => 'new_user',
|
||||
'PASSWORD' => password_hash('secret', PASSWORD_DEFAULT),
|
||||
'ROLE' => 'user'
|
||||
]);</code></pre>
|
||||
|
||||
<h3>update($data, $where, ...$whereParams)</h3>
|
||||
<p>Aktualisiert bestehende Datensätze.</p>
|
||||
<pre><code>Users::update(['ROLE' => 'admin'], "ID = ?", 5);</code></pre>
|
||||
|
||||
<h3>delete($where, ...$params)</h3>
|
||||
<p>Löscht Datensätze.</p>
|
||||
<pre><code>Users::delete("ID = ?", 10);</code></pre>
|
||||
|
||||
<h3>query($sql, ...$params)</h3>
|
||||
<p>Führt eine native SQL-Abfrage mit Prepared Statements aus.</p>
|
||||
<pre><code>$result = Users::query("SELECT COUNT(*) as total FROM Users");</code></pre>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.docs-container { max-width: 1100px; color: var(--text); line-height: 1.6; }
|
||||
.toc { background: var(--sidebar); padding: 30px; border-radius: 12px; margin-bottom: 40px; border: 1px solid var(--border); }
|
||||
.toc h3 { margin-top: 0; border-bottom: 2px solid var(--accent); display: inline-block; padding-bottom: 5px; }
|
||||
.toc-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 30px; }
|
||||
.toc ul { list-style: none; padding-left: 0; }
|
||||
.toc ul ul { padding-left: 20px; list-style: disc; margin-top: 5px; }
|
||||
.toc a { text-decoration: none; color: var(--text); transition: color 0.2s; }
|
||||
.toc a:hover { color: var(--accent); }
|
||||
|
||||
section { margin-bottom: 60px; padding-bottom: 30px; border-bottom: 1px solid var(--border); }
|
||||
h2 { color: var(--accent); font-size: 2rem; margin-bottom: 20px; display: flex; align-items: center; gap: 10px; }
|
||||
h2::before { content: ''; width: 4px; height: 30px; background: var(--accent); border-radius: 2px; }
|
||||
h3 { margin-top: 25px; color: var(--text); opacity: 0.9; }
|
||||
|
||||
pre { background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 8px; overflow-x: auto; border: 1px solid #333; font-size: 0.9rem; position: relative; }
|
||||
pre::after { content: 'CODE'; position: absolute; top: 5px; right: 10px; font-size: 0.7rem; color: #666; font-weight: bold; }
|
||||
code { font-family: 'Consolas', monospace; }
|
||||
|
||||
p { margin-bottom: 15px; }
|
||||
ul, ol { margin-bottom: 20px; padding-left: 25px; }
|
||||
li { margin-bottom: 8px; }
|
||||
|
||||
strong { color: var(--accent); }
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.toc-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
.docs-container { max-width: 1000px; color: var(--text); }
|
||||
.toc { background: var(--sidebar); padding: 20px; border-radius: 8px; margin-bottom: 30px; border: 1px solid var(--border); }
|
||||
.toc h3 { margin-top: 0; }
|
||||
section { margin-bottom: 50px; padding-bottom: 20px; border-bottom: 1px solid var(--border); }
|
||||
h2 { color: var(--accent); font-size: 1.8rem; margin-bottom: 20px; }
|
||||
pre { background: #1e1e1e; color: #d4d4d4; padding: 15px; border-radius: 5px; overflow-x: auto; border: 1px solid #333; font-size: 0.9rem; }
|
||||
</style>
|
||||
@@ -1,76 +0,0 @@
|
||||
<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>
|
||||
@@ -1,55 +0,0 @@
|
||||
<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>
|
||||
@@ -1,42 +0,0 @@
|
||||
<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>
|
||||
@@ -1,35 +0,0 @@
|
||||
<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>
|
||||
@@ -0,0 +1,92 @@
|
||||
<h2>👥 User Management (Full CRUD Demo)</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)'?>; border: 1px solid <?= $message['type']==='success'?'#2ecc71':'#e74c3c'?>;">
|
||||
<?= $message['text'] ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div style="margin-bottom: 20px;">
|
||||
<a href="?service=user_mgmt&action=list" class="btn">Alle Benutzer</a>
|
||||
<a href="?service=user_mgmt&action=list&simple=1" class="btn">Nur Basisdaten (select())</a>
|
||||
<a href="?service=user_mgmt&action=create" class="btn btn-primary">➕ Neu erstellen (insert())</a>
|
||||
</div>
|
||||
|
||||
<?php if ($action === 'list'): ?>
|
||||
<form method="GET" style="margin-bottom: 20px; display: flex; gap: 10px;">
|
||||
<input type="hidden" name="service" value="user_mgmt">
|
||||
<input type="text" name="search" placeholder="Suche nach Username (where())..." value="<?= htmlspecialchars($_GET['search'] ?? '') ?>" style="flex: 1; padding: 10px; border-radius: 4px; border: 1px solid var(--border); background: var(--bg); color: var(--text);">
|
||||
<button type="submit" class="btn">Suchen</button>
|
||||
</form>
|
||||
|
||||
<table style="width: 100%; border-collapse: collapse;">
|
||||
<thead>
|
||||
<tr style="background: var(--sidebar); text-align: left;">
|
||||
<th style="padding: 12px; border: 1px solid var(--border);">ID</th>
|
||||
<th style="padding: 12px; border: 1px solid var(--border);">Username</th>
|
||||
<?php if (!isset($_GET['simple'])): ?>
|
||||
<th style="padding: 12px; border: 1px solid var(--border);">Email</th>
|
||||
<?php endif; ?>
|
||||
<th style="padding: 12px; border: 1px solid var(--border);">Role</th>
|
||||
<th style="padding: 12px; border: 1px solid var(--border);">Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($users as $user): ?>
|
||||
<tr>
|
||||
<td style="padding: 12px; border: 1px solid var(--border);"><?= $user['ID'] ?></td>
|
||||
<td style="padding: 12px; border: 1px solid var(--border);"><?= htmlspecialchars($user['USERNAME']) ?></td>
|
||||
<?php if (!isset($_GET['simple'])): ?>
|
||||
<td style="padding: 12px; border: 1px solid var(--border);"><?= htmlspecialchars($user['EMAIL']) ?></td>
|
||||
<?php endif; ?>
|
||||
<td style="padding: 12px; border: 1px solid var(--border);"><span style="background: var(--accent); color: white; padding: 2px 6px; border-radius: 4px; font-size: 0.8rem;"><?= $user['ROLE'] ?></span></td>
|
||||
<td style="padding: 12px; border: 1px solid var(--border);">
|
||||
<a href="?service=user_mgmt&action=edit&id=<?= $user['ID'] ?>" style="color: var(--accent); text-decoration: none;">Bearbeiten (find())</a> |
|
||||
<a href="?service=user_mgmt&action=delete&id=<?= $user['ID'] ?>" style="color: #e74c3c; text-decoration: none;" onclick="return confirm('Sicher?')">Löschen (delete())</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<?php elseif ($action === 'create' || $action === 'edit'): ?>
|
||||
<h3><?= $action === 'edit' ? '✏️ Benutzer bearbeiten' : '➕ Neuen Benutzer anlegen' ?></h3>
|
||||
<form method="POST" action="?service=user_mgmt&action=<?= $action ?>" style="background: var(--sidebar); padding: 25px; border-radius: 8px; border: 1px solid var(--border);">
|
||||
<?php if ($action === 'edit'): ?>
|
||||
<input type="hidden" name="id" value="<?= $editUser['ID'] ?>">
|
||||
<?php endif; ?>
|
||||
|
||||
<div style="margin-bottom: 15px;">
|
||||
<label style="display: block; margin-bottom: 5px;">Username</label>
|
||||
<input type="text" name="username" required value="<?= htmlspecialchars($editUser['USERNAME'] ?? '') ?>" 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;">E-Mail</label>
|
||||
<input type="email" name="email" value="<?= htmlspecialchars($editUser['EMAIL'] ?? '') ?>" 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;">Passwort <?= $action === 'edit' ? '(leer lassen zum Behalten)' : '' ?></label>
|
||||
<input type="password" name="password" <?= $action === 'create' ? '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: 20px;">
|
||||
<label style="display: block; margin-bottom: 5px;">Rolle</label>
|
||||
<select name="role" style="width: 100%; padding: 10px; border-radius: 4px; border: 1px solid var(--border); background: var(--bg); color: var(--text);">
|
||||
<option value="user" <?= ($editUser['ROLE'] ?? '') === 'user' ? 'selected' : '' ?>>User</option>
|
||||
<option value="admin" <?= ($editUser['ROLE'] ?? '') === 'admin' ? 'selected' : '' ?>>Admin</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary"><?= $action === 'edit' ? 'Speichern (update())' : 'Erstellen (insert())' ?></button>
|
||||
<a href="?service=user_mgmt&action=list" class="btn">Abbrechen</a>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
|
||||
<style>
|
||||
.btn { display: inline-block; padding: 10px 20px; text-decoration: none; border-radius: 4px; background: var(--sidebar); color: var(--text); border: 1px solid var(--border); cursor: pointer; font-size: 0.9rem; }
|
||||
.btn:hover { background: rgba(0,0,0,0.1); }
|
||||
.btn-primary { background: var(--accent); color: white; border: none; }
|
||||
.btn-primary:hover { opacity: 0.9; }
|
||||
</style>
|
||||
@@ -16,9 +16,4 @@ DB_LIVE_DATABASE = ""
|
||||
|
||||
[mail]
|
||||
MAIL_FROM = "bruno.eckhardt07@gmail.com"
|
||||
MAIL_TO = "bruno.eckhardt07@gmail.com"
|
||||
|
||||
[logging]
|
||||
LOG_ACCESS = false
|
||||
LOG_SECURITY = true
|
||||
BLOCK_PAGE_TEMPLATE = "standard"
|
||||
MAIL_TO = "bruno.eckhardt07@gmail.com"
|
||||
@@ -9,9 +9,6 @@ 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'],
|
||||
'user_mgmt' => ['label' => '👥 User Management'],
|
||||
];
|
||||
@@ -5,10 +5,6 @@
|
||||
* Format: $router->add(service_name, controller_class, method_name, options);
|
||||
*/
|
||||
|
||||
// Global Middlewares (executed on every request)
|
||||
$router->addGlobal('global'); // Beispiel: Logging
|
||||
$router->addGlobal('block'); // Beispiel: IP-Blocker
|
||||
|
||||
$router->add('home', \MVC\Controller\HomeController::class, 'index');
|
||||
$router->add('toggle_theme', \MVC\Controller\HomeController::class, 'toggle_theme');
|
||||
$router->add('docs', \MVC\Controller\DocsController::class, 'index');
|
||||
@@ -18,12 +14,4 @@ $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']);
|
||||
|
||||
// API Routes (Path-based)
|
||||
$router->add('api/v1/test', \MVC\Controller\ApiController::class, 'get');
|
||||
$router->add('api/v1/become', \MVC\Controller\ApiController::class, 'become');
|
||||
$router->add('user_mgmt', \MVC\Controller\UserMgmtController::class, 'user_mgmt', ['auth', 'admin']);
|
||||
@@ -127,7 +127,6 @@ foreach ($tables as $tableName => $info) {
|
||||
|
||||
// 3. Test-User anlegen
|
||||
echo "👤 Erstelle Test-User...\n";
|
||||
require_once __DIR__ . '/WEBROOT/src/MVC/Core/Model.php';
|
||||
require_once __DIR__ . '/WEBROOT/src/MVC/Model/Users.php';
|
||||
try {
|
||||
if (!\MVC\Model\Users::getByUsername('admin')) {
|
||||
|
||||
@@ -4,4 +4,4 @@ $host = 'localhost';
|
||||
$root = __DIR__ . '/WEBROOT';
|
||||
echo "\n🚀 Starte MVC Dev-Server auf http://$host:$port\n";
|
||||
echo "Drücke STRG+C zum Beenden.\n\n";
|
||||
passthru("php -S $host:$port -t \"$root\" \"$root/index.php\"");
|
||||
passthru("php -S $host:$port -t \"$root\"");
|
||||
@@ -1,92 +1,166 @@
|
||||
# 🏗️ Backend MVC Framework - Die Entwickler-Bibel
|
||||
# 🏗️ Backend MVC Framework - Das Handbuch
|
||||
|
||||
Dieses Framework ist eine performante, leichtgewichtige und modulare PHP-Architektur für professionelle Backends und Dashboards.
|
||||
Dieses Framework ist eine performante, leichtgewichtige und modulare PHP-Architektur, die speziell für die Entwicklung von professionellen Backends und Dashboards entwickelt wurde. Es implementiert das **Model-View-Controller (MVC)** Pattern und trennt strikt zwischen Datenlogik, Geschäftslogik und UI-Design.
|
||||
|
||||
---
|
||||
|
||||
## 🏛️ 1. Kern-Architektur & Lifecycle
|
||||
## 🏛️ 1. Architektur & Verzeichnisstruktur
|
||||
|
||||
### Der Weg einer Anfrage (Request Lifecycle)
|
||||
1. **Entry Point:** Alle Requests werden zentral über `WEBROOT/index.php` (Front Controller) verarbeitet.
|
||||
2. **Bootstrap:** Initialisierung von Session, Autoloader und globalem Error-Handling (`Logger`).
|
||||
3. **Routing:** Der `Router` prüft die Service-ID aus `GET['service']` gegen die `routes.php`.
|
||||
4. **Security:** Ausführung von Middlewares (`auth`, `admin`).
|
||||
5. **Controller:** Instanziierung des Ziel-Controllers und Ausführung der Logik.
|
||||
6. **Layout & Views:** Zusammensetzung der UI mittels Block-System und Finalisierung des HTMLs.
|
||||
Das Herzstück des Frameworks liegt in `WEBROOT/src/MVC/`. Die Struktur ist darauf ausgelegt, dass das Projekt beliebig wachsen kann, ohne unübersichtlich zu werden.
|
||||
|
||||
### Verzeichnisstruktur
|
||||
* `/WEBROOT`: Öffentlich erreichbarer Ordner (index.php, APIs, Assets).
|
||||
* `/src/MVC/Core`: Die Framework-Engine (Controller, Model, Router, View, Layout).
|
||||
* `/src/MVC/Classes`: Mächtige Helper-Klassen (Auth, Form, Storage, Mailer, Logger).
|
||||
* `/src/MVC/Controller`: Deine Business-Logik.
|
||||
* `/src/MVC/Model`: Daten-Abstraktion (CRUD).
|
||||
* `/src/MVC/View`: HTML-Templates.
|
||||
### 📂 Ordner-Struktur im Detail
|
||||
|
||||
* **`WEBROOT/index.php`**: Der zentrale Einstiegspunkt (Front Controller). Jede Anfrage läuft hier auf. Hier werden Routen registriert und das Layout zusammengesetzt.
|
||||
* **`src/MVC/Core/`**:
|
||||
* `Controller.php`: Basisklasse für alle Controller. Bietet Methoden für Rendering und JSON-Output.
|
||||
* `Model.php`: Basisklasse für Datenbankzugriffe. Implementiert sichere CRUD-Methoden via Prepared Statements.
|
||||
* `View.php`: Die Engine, die Daten in HTML-Templates extrahiert.
|
||||
* `Router.php`: Verwaltet Routen und schaltet Middlewares (Auth/Admin) vor.
|
||||
* `Layout/Layout.php`: Das Block-System für modulares UI-Design.
|
||||
* **`src/MVC/Controller/`**: Hier landen deine PHP-Klassen für die Logik. Beispiel: `UserController.php`, `LogController.php`.
|
||||
* **`src/MVC/Model/`**: Jede Klasse entspricht einer Datenbanktabelle. Hier wird definiert, *wie* Daten geholt oder gespeichert werden.
|
||||
* **`src/MVC/View/`**: Reine HTML-Dateien mit minimalem PHP-Code für die Anzeige.
|
||||
* **`src/MVC/Layout/Templates/`**: Enthält das Grundgerüst (`main.php`), in das die Views eingebettet werden.
|
||||
|
||||
---
|
||||
|
||||
## 🚦 2. Routing & Sicherheit
|
||||
## 🚦 2. Der Request-Lifecycle (Wie eine Seite entsteht)
|
||||
|
||||
### Routen definieren (`src/config/routes.php`)
|
||||
1. Ein Benutzer ruft `?service=benutzer` auf.
|
||||
2. Die `index.php` lädt den **Autoloader** und initialisiert den **Router**.
|
||||
3. Der **Router** prüft, ob eine Middleware (z.B. `auth`) für diesen Service konfiguriert ist.
|
||||
4. Wenn der Zugriff erlaubt ist, erstellt der Router eine Instanz des jeweiligen **Controllers** und ruft die Zielmethode auf.
|
||||
5. Der **Controller** nutzt ein **Model**, um Daten aus der Datenbank zu holen.
|
||||
6. Der Controller übergibt diese Daten an eine **View** (`$this->render()`).
|
||||
7. Die **index.php** nimmt das HTML der View entgegen, packt es in den `content`-Block des **Layouts** und gibt die fertige Seite aus.
|
||||
|
||||
---
|
||||
|
||||
## 🏛️ 3. Arbeiten mit dem Model-Layer (Datenbank)
|
||||
|
||||
Vermeide SQL direkt im Controller! Nutze immer Models für eine saubere Abstraktion.
|
||||
|
||||
### Beispiel: Ein eigenes Model erstellen
|
||||
```php
|
||||
$router->add('service_id', ControllerClass, 'method', ['middleware']);
|
||||
```
|
||||
namespace MVC\Model;
|
||||
use MVC\Core\Model;
|
||||
|
||||
### Middlewares
|
||||
* `auth`: Erfordert Login.
|
||||
* `admin`: Erfordert Admin-Status.
|
||||
|
||||
---
|
||||
|
||||
## 💾 3. Datenbank-Layer (The Power-Engine)
|
||||
|
||||
### databaseMethods.php (Low-Level)
|
||||
Bietet sichere Wrapper für `mysqli` Prepared Statements:
|
||||
* `DatabaseShow()`: Flexibles SELECT.
|
||||
* `DatabaseInsert()`: Einfaches Einfügen von Daten.
|
||||
* `DatabaseShowWithFK()`: Löst Fremdschlüssel automatisch mit Caching auf.
|
||||
|
||||
### Core\Model (High-Level)
|
||||
Objektorientierte Abstraktion für sauberen Code:
|
||||
```php
|
||||
class Product extends Model {
|
||||
protected static $table = "Products";
|
||||
protected static $table = "Produkte"; // Tabellenname in der DB
|
||||
|
||||
public static function getExpensiveProducts() {
|
||||
return self::where("PREIS > ?", 100); // Sicher gegen SQL-Injection!
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Verfügbare Methoden der Basisklasse:
|
||||
* `all()`: Lädt alles aus der Tabelle.
|
||||
* `find($id)`: Findet einen Datensatz per ID.
|
||||
* `where($sql, ...$params)`: Filtert mit Prepared Statements.
|
||||
* `insert($array)`: Speichert neue Daten (z.B. `['TITEL' => 'Test']`).
|
||||
* `update($array, $where, ...$params)`: Aktualisiert bestehende Daten.
|
||||
* `delete($where, ...$params)`: Löscht Datensätze sicher.
|
||||
|
||||
---
|
||||
|
||||
## 🎮 4. Controller & Formularverarbeitung
|
||||
|
||||
Controller steuern den Ablauf. Sie entscheiden, was bei `GET` und was bei `POST` passiert.
|
||||
|
||||
### Beispiel: Daten verarbeiten
|
||||
```php
|
||||
public function save() {
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// Daten via Model speichern
|
||||
User::update(['STATUS' => 'aktiv'], "ID = ?", $_POST['id']);
|
||||
|
||||
// WICHTIG: Danach immer Redirect (Post-Redirect-Get Pattern)
|
||||
header("Location: ?service=user_list");
|
||||
exit;
|
||||
}
|
||||
return $this->render('edit_view');
|
||||
}
|
||||
// Nutzung
|
||||
$all = Product::all();
|
||||
$one = Product::find(1);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 4. CLI Werkzeuge (`make.php`)
|
||||
## 🧱 5. Das modulare Block-Layout
|
||||
|
||||
Beschleunige die Entwicklung durch automatische Code-Generierung:
|
||||
Das Layout-System ermöglicht es, verschiedene Teile der Seite (Header, Sidebar, Content) unabhängig voneinander zu befüllen.
|
||||
|
||||
* **Modul generieren:**
|
||||
`php make.php module News -l "📰 News" -a`
|
||||
*(Erstellt Controller, View, Route und Nav-Eintrag)*
|
||||
### Blöcke in `index.php` befüllen:
|
||||
```php
|
||||
$layout = new \MVC\Layout\Layout('main');
|
||||
$layout->setBlock('title', 'Admin Dashboard');
|
||||
$layout->setBlock('sidebar', $sidebarHtml);
|
||||
$layout->setBlock('content', $controllerOutput);
|
||||
```
|
||||
|
||||
* **Model generieren:**
|
||||
`php make.php model Orders ID,USER_ID,TOTAL`
|
||||
*(Erstellt Model-Klasse mit Spalten-Dokumentation)*
|
||||
### Blöcke im Template (`main.php`) ausgeben:
|
||||
```html
|
||||
<aside class="sidebar">
|
||||
<?= $this->getBlock('sidebar') ?>
|
||||
</aside>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧱 5. UI & Layout System
|
||||
## 🚀 6. CLI-Werkzeuge für Entwickler
|
||||
|
||||
Das Framework nutzt ein modulares **Block-System**:
|
||||
1. **Blöcke setzen:** In der `index.php` oder im Controller via `$layout->setBlock('name', 'inhalt')`.
|
||||
2. **Blöcke ausgeben:** Im Layout-Template via `<?= $this->getBlock('name') ?>`.
|
||||
Das Framework kommt mit automatisierten Scripten, um lästige Routinearbeiten zu vermeiden.
|
||||
|
||||
### Projekt initialisieren
|
||||
Erstellt ein komplettes Skelett inkl. Dev-Server und API.
|
||||
```bash
|
||||
php init_mvc.php MeinProjekt
|
||||
```
|
||||
|
||||
### Modul (Controller + View) erstellen
|
||||
Erstellt zwei Dateien und gibt dir den Code für den Router aus.
|
||||
```bash
|
||||
php create_module.php kunden_verwaltung
|
||||
```
|
||||
|
||||
### Model erstellen
|
||||
Erstellt eine Model-Klasse mit Tabellenzuweisung und dokumentierten Spalten.
|
||||
```bash
|
||||
php create_model.php Kunden ID,NAME,EMAIL,TELEFON
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🛡️ 6. Best Practices für Entwickler
|
||||
## 🛡️ 7. Sicherheit & Best Practices
|
||||
|
||||
1. **SQL-Injection:** Nutze <u>ausschließlich</u> die Model-Methoden oder `databaseMethods.php`.
|
||||
2. **XSS-Schutz:** Benutzereingaben in Views immer mit `htmlspecialchars()` maskieren.
|
||||
3. **Logik-Trennung:** Schwere Logik gehört in Models oder Services, Controller bleiben schlank.
|
||||
4. **Debugging:** Prüfe die `logs/error.log` für detaillierte Stacktraces bei Fehlern.
|
||||
1. **XSS-Schutz**: Gib niemals Variablen direkt in der View aus. Nutze immer `<?= htmlspecialchars($var) ?>`.
|
||||
2. **SQL-Injection**: Nutze niemals Variablen direkt in einem SQL-String. Nutze die Methoden `where()`, `update()` etc. des Models. Sie verwenden intern Prepared Statements.
|
||||
3. **Namespaces**: Alle Klassen im `src/MVC` Ordner nutzen Namespaces. Achte beim Aufruf globaler Klassen (außerhalb des MVC-Ordners) auf den führenden Backslash, z.B. `\db_connect()`.
|
||||
4. **Error Handling**: Fehler werden zentral in der `index.php` gefangen. Admins erhalten detaillierte Informationen, User sehen eine sichere Fehlermeldung.
|
||||
|
||||
---
|
||||
|
||||
*Für eine detaillierte Dokumentation starte das Framework und rufe den Service `docs` auf.*
|
||||
## ⚡ 8. AJAX & API Entwicklung
|
||||
|
||||
Um Daten ohne Seiten-Reload zu laden, bietet der Controller die `json()` Methode:
|
||||
|
||||
```php
|
||||
public function getStatus() {
|
||||
$data = ['online' => true, 'time' => date('H:i')];
|
||||
return $this->json($data); // Setzt Header und beendet Script
|
||||
}
|
||||
```
|
||||
|
||||
In deinem JavaScript kannst du dies dann einfach per `fetch()` abrufen.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 9. Installation & Start
|
||||
|
||||
1. Stelle sicher, dass PHP 8.x installiert ist.
|
||||
2. Passe die `src/config/env.ini` mit deinen Datenbankdaten an.
|
||||
3. Starte den internen Server: `php server.php`.
|
||||
4. Öffne `http://localhost:8000` im Browser.
|
||||
|
||||
---
|
||||
|
||||
*Dieses Framework wurde für maximale Produktivität entwickelt. Bei Fragen schau in die integrierte Dokumentation im Backend.*
|
||||
Reference in new issue
Block a user