Compare commits

..
35 Commits
Author SHA1 Message Date
Bruno Eckhardt 97acc9cb84 fixes 2026-06-22 12:57:53 +02:00
Bruno Eckhardt b23a501511 Ensure project 'config' directory is created and dev server uses 'public' as docroot; fix dirs array to include config\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> 2026-06-22 11:50:11 +02:00
Bruno Eckhardt e6f40c7e01 Restructure init.php to modern layout: public/, src/, config/, resources/, storage/; update paths and directories list; keep generation consistent.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> 2026-06-22 11:46:19 +02:00
Bruno Eckhardt 56dfb79c02 Apply heredoc/nowdoc fixes to init.php: normalize openers, unindent closers and entries so script parses and generates files (install_db.php).\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> 2026-06-22 11:42:27 +02:00
Tower e48534c69d add custom block templates and logging 2026-06-02 18:28:06 +02:00
Tower a48e99b454 custom block templates 2026-06-02 18:27:50 +02:00
Tower 5c4f0a161a no logs in the git 2026-06-02 15:42:16 +02:00
Tower 1b235ded34 deleted 2026-06-02 15:40:50 +02:00
Tower a6920922ca middleware added 2026-06-02 15:39:31 +02:00
Tower d9b943f9ea new theme 2026-06-02 15:39:13 +02:00
Tower 9943a8349f new middleware with docs 2026-06-02 15:39:03 +02:00
bruno deacdadb5c new init 2026-06-02 14:31:58 +02:00
bruno 3bd3214e48 routing changes 2026-06-02 14:31:20 +02:00
Tower 7df0f15d5c new docs 2026-05-31 12:23:34 +02:00
Tower 6a2f7d5470 all new fixes 2026-05-31 11:54:29 +02:00
Tower 159922af76 mailing and template and auth fixes 2026-05-31 11:54:01 +02:00
Tower 53fa7bfe6e edit: default mail or other 2026-05-31 10:54:14 +02:00
Tower fb358323ba hot fixes 2026-05-31 08:44:07 +02:00
Bruno Eckhardt dad1eb91de new favicon 2026-05-26 12:21:05 +02:00
Bruno Eckhardt 578ea31b1c just form changes and css 2026-05-26 12:11:19 +02:00
Bruno Eckhardt f8bee32fac add log to default 2026-05-26 12:10:51 +02:00
Bruno Eckhardt b32f4f9323 new database method and docs 2026-05-26 11:32:13 +02:00
Bruno Eckhardt 5e65d328c9 auth only in routes.php 2026-05-26 10:50:44 +02:00
Bruno Eckhardt d0e6d921ed new docs 2026-05-26 10:41:51 +02:00
Bruno Eckhardt 964a3e44fc auto build admin user when install_db.php and better auth service bzw admin control 2026-05-26 10:25:17 +02:00
Bruno Eckhardt 40b602a79a now can install Database 2026-05-26 10:10:33 +02:00
Bruno Eckhardt 4ea2e014cf in testing new make.php 2026-05-26 10:06:40 +02:00
Tower db5e9b0bd9 fixes 2026-05-25 16:47:21 +02:00
Tower 00fee2ee08 extract navitems and routes to other files, add it to the docs and new file creator 2026-05-25 16:37:36 +02:00
Tower 07dc6686ba fixes 2026-05-23 22:15:39 +02:00
Tower 2c6ea99af2 framework installed 2026-05-23 18:23:47 +02:00
Bruno Eckhardt 655db43616 add docs 2026-05-22 14:52:31 +02:00
Bruno Eckhardt ec6be1da80 add login 2026-05-22 14:28:04 +02:00
Bruno Eckhardt e1c2ef46c5 sehr gutes init 2026-05-22 12:49:04 +02:00
Bruno Eckhardt 7b332543a2 fixes 2026-05-22 08:17:09 +02:00
79 changed files with 4828 additions and 1753 deletions

No files matched your search

+1
View File
@@ -0,0 +1 @@
Framework/WEBROOT/logs
@@ -5,9 +5,9 @@
}
],
"settings": {
"workbench.colorTheme": "Default Dark Modern",
"workbench.colorTheme": "Dark 2026",
"workbench.colorCustomizations": {
"activityBar.background": "#ff000075",
"activityBar.background": "#162df7ec",
"activityBar.foreground": "#000000",
"breadcrumb.foreground": "#FFFFFF",
},
@@ -29,7 +29,7 @@
"editor.minimap.showSlider": "always",
"files.autoSave": "off",
"files.autoSaveDelay": 3000,
"window.title": "BACKEND",
"window.title": "MVC",
"workbench.editor.wrapTabs": true,
},
"extensions": {
File renamed without changes.
+93
View File
@@ -0,0 +1,93 @@
<?php
session_start();
require_once __DIR__ . '/src/MVC/autoloader.php';
use MVC\Classes\Logger;
// Global Error & Exception Handlers
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';
}
}
try {
$router = new \MVC\Core\Router();
// 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;
} else {
$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);
// Sidebar Block
ob_start();
// Load navigation from config
$navItems = require __DIR__ . '/src/config/navigation.php';
?>
<nav>
<?php foreach ($navItems as $navKey => $item):
$isLoggedIn = \MVC\Classes\Auth::check();
$isAdmin = \MVC\Classes\Auth::isAdmin();
// Check visibility based on Router middleware
$requiresAuth = $router->hasMiddleware($navKey, 'auth');
$requiresAdmin = $router->hasMiddleware($navKey, 'admin');
if ($requiresAuth && !$isLoggedIn) continue;
if ($requiresAdmin && !$isAdmin) continue;
?>
<a href="?service=<?= $navKey ?>" class="<?= $service === $navKey ? 'active' : '' ?>"><?= $item['label'] ?></a>
<?php endforeach; ?>
<?php if (!\MVC\Classes\Auth::check()): ?>
<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>
<?php endif; ?>
</nav>
<?php
$layout->setBlock('sidebar', ob_get_clean());
$layout->setBlock('content', $output);
echo $layout->render();
} catch (\Throwable $e) {
// Log the error globally
Logger::logException($e);
http_response_code(500);
echo "<h1>Error</h1><p>" . htmlspecialchars($e->getMessage()) . "</p>";
if (ini_get('display_errors')) {
echo "<pre>" . htmlspecialchars($e->getTraceAsString()) . "</pre>";
}
}
@@ -0,0 +1,82 @@
<?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');
}
}
@@ -0,0 +1,94 @@
<?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;
}
}
@@ -0,0 +1,103 @@
<?php
namespace MVC\Classes;
class Logger {
/**
* Ermittelt den absoluten Pfad zum Log-Verzeichnis.
* Das Verzeichnis liegt in WEBROOT/logs
*/
private static function getLogDir() {
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.
*
* @param string $message Die zu protokollierende Nachricht.
* @param string $level Das Log-Level (z.B. ERROR, INFO, DEBUG).
* @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)) {
if (!mkdir($logDir, 0777, true)) {
// Wenn das Verzeichnis nicht erstellt werden kann,
// können wir leider nirgendwohin loggen.
return;
}
}
$logFile = $logDir . DIRECTORY_SEPARATOR . $file;
$timestamp = date('Y-m-d H:i:s');
$formattedMessage = "[$timestamp] [$level] $message\n";
// Nutze FILE_APPEND und LOCK_EX für sicheres Schreiben
file_put_contents($logFile, $formattedMessage, FILE_APPEND | LOCK_EX);
}
/**
* Spezielles Logging für Exceptions/Errors inkl. Stacktrace.
*/
public static function logException(\Throwable $e) {
$message = sprintf(
"Uncaught Exception: '%s' with message '%s' in %s:%d\nStack trace:\n%s",
get_class($e),
$e->getMessage(),
$e->getFile(),
$e->getLine(),
$e->getTraceAsString()
);
self::log($message, 'CRITICAL', 'error.log');
}
/**
* Spezielles Logging für PHP-Fehler (aus set_error_handler).
*/
public static function logError($errno, $errstr, $errfile, $errline) {
$levels = [
E_ERROR => 'ERROR',
E_WARNING => 'WARNING',
E_PARSE => 'PARSE_ERROR',
E_NOTICE => 'NOTICE',
E_USER_ERROR => 'USER_ERROR',
E_USER_WARNING => 'USER_WARNING',
E_USER_NOTICE => 'USER_NOTICE',
E_DEPRECATED => 'DEPRECATED',
E_USER_DEPRECATED => 'USER_DEPRECATED'
];
$level = $levels[$errno] ?? 'UNKNOWN_ERROR';
$message = "$errstr in $errfile on line $errline";
self::log($message, $level, 'error.log');
}
}
@@ -0,0 +1,141 @@
<?php
namespace MVC\Classes;
use MVC\Classes\Client;
use MVC\Classes\Request;
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) {
$config = self::getConfig();
$from = self::getSender($config);
$recipient = self::resolveRecipient($to, $config);
$headers = "From: " . $from . "\r\n";
$headers .= "Reply-To: " . $from . "\r\n";
$headers .= "X-Mailer: PHP/" . phpversion();
// mail() unterdrückt Fehlermeldungen standardmäßig nicht,
// wir prüfen den Rückgabewert.
$success = @mail($recipient, $subject, $message, $headers);
if (!$success) {
Logger::log("Mail dispatch failed to '$recipient' for subject: '$subject'. Check your system's sendmail/SMTP configuration.", "ERROR", "error.log");
}
if ($log) {
self::log($subject, $message, $url, false);
}
return $success;
}
/**
* Sends an error email and optionally logs it as an error.
*/
public static function error($title, $error, bool $log = true) {
$subject = "ERROR: $title";
$config = self::getConfig();
$from = self::getSender($config);
$to = self::getDefaultRecipient($config);
$headers = "From: " . $from . "\r\n";
$headers .= "Reply-To: " . $from . "\r\n";
$headers .= "X-Mailer: PHP/" . phpversion();
$success = @mail($to, $subject, $error, $headers);
if (!$success) {
Logger::log("Error-Mail dispatch failed: '$title'.", "ERROR", "error.log");
}
if ($log) {
self::log($subject, $error, null, true);
}
return $success;
}
/**
* Sendet eine E-Mail basierend auf einem View-Template
*/
public static function sendTemplate($subject, $template, $data = [], bool $log = true, ?string $to = null) {
$view = new \MVC\Core\View(dirname(__DIR__) . '/View');
foreach ($data as $key => $value) {
$view->assign($key, $value);
}
$htmlContent = $view->render($template);
$config = self::getConfig();
$from = self::getSender($config);
$recipient = self::resolveRecipient($to, $config);
$headers = "MIME-Version: 1.0" . "\r\n";
$headers .= "Content-type:text/html;charset=UTF-8" . "\r\n";
$headers .= "From: " . $from . "\r\n";
$headers .= "Reply-To: " . $from . "\r\n";
$success = @mail($recipient, $subject, $htmlContent, $headers);
if ($log) {
self::log($subject, "HTML Template: $template", null, false);
}
return $success;
}
/**
* Logs the email to the database or falls back to file logging.
*/
private static function log($subject, $message, $url, bool $isError) {
$url = $url ?? Request::uri();
$requestTime = Request::time();
$date = ($requestTime !== 'UNKNOWN') ? date('Y-m-d H:i:s', $requestTime) : date('Y-m-d H:i:s');
$ip = Client::ip();
try {
if (class_exists('MVC\Model\Mailing')) {
Mailing::log($subject, $message, $url, $ip, $date);
return;
}
} catch (\Exception $e) {
// DB-Log fehlgeschlagen
}
self::logToFile($subject, $message, $url, $ip, $date, $isError);
}
/**
* Schreibt Log-Einträge in eine Datei (Nutzt nun die Logger-Klasse für Konsistenz).
*/
private static function logToFile($subject, $message, $url, $ip, $date, bool $isError) {
$fileName = $isError ? 'mail_errors.log' : 'mails.log';
$logEntry = "IP: $ip | URL: $url\nSubject: $subject\nMessage: $message\n" . str_repeat("-", 30);
Logger::log($logEntry, $isError ? 'MAIL_ERROR' : 'MAIL_INFO', $fileName);
}
}
@@ -0,0 +1,96 @@
<?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.");
}
}
@@ -0,0 +1,66 @@
<?php
namespace MVC\Controller;
use MVC\Core\Controller;
use MVC\Classes\Mailer;
use MVC\Model\Users;
class AdminController extends Controller {
/**
* Service zum Senden einer Test-Mail
*/
public function mail_service() {
$message = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$to = $_POST['to'] ?? '';
$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)) {
$message = ["type" => "success", "text" => "E-Mail erfolgreich versendet!"];
} else {
$message = ["type" => "error", "text" => "Fehler beim E-Mail-Versand."];
}
}
return $this->render('admin/mail', ['message' => $message]);
}
/**
* Service zum Erstellen eines neuen Benutzers
*/
public function user_service() {
$message = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
$email = $_POST['email'] ?? '';
$role = $_POST['role'] ?? 'user';
if (!empty($username) && !empty($password)) {
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
$success = Users::insert([
'USERNAME' => $username,
'PASSWORD' => $hashedPassword,
'EMAIL' => $email,
'ROLE' => $role
]);
if ($success) {
$message = ["type" => "success", "text" => "Benutzer '$username' erfolgreich erstellt!"];
} else {
$message = ["type" => "error", "text" => "Fehler beim Erstellen des Benutzers (evtl. Name schon vergeben)."];
}
} else {
$message = ["type" => "error", "text" => "Bitte Benutzername und Passwort angeben."];
}
}
return $this->render('admin/users', ['message' => $message]);
}
}
@@ -0,0 +1,21 @@
<?php
namespace MVC\Controller;
use MVC\Core\Controller;
use MVC\Classes\Api;
class ApiController extends Controller {
public static function get(){
return Api::jsonResponse([
'data' => 'DummyData',
]);
}
public static function become() {
return Api::jsonResponse([
'data' => 'Hello',
'state' => 'none'
]);
}
}
@@ -3,10 +3,11 @@
namespace MVC\Controller;
use MVC\Core\Controller;
use MVC\Classes\Auth;
class AuthController extends Controller {
public function login() {
if (isset($_SESSION['user_logged_in'])) {
if (Auth::check()) {
header('Location: ?service=home');
exit;
}
@@ -15,15 +16,7 @@ class AuthController extends Controller {
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
// Hardcoded logins
$users = [
'admin' => 'password123',
'user' => 'user123'
];
if (isset($users[$username]) && $users[$username] === $password) {
$_SESSION['user_logged_in'] = true;
$_SESSION['username'] = $username;
if (Auth::login($username, $password)) {
header('Location: ?service=home');
exit;
} else {
@@ -35,7 +28,7 @@ class AuthController extends Controller {
}
public function logout() {
session_destroy();
Auth::logout();
header('Location: ?service=login');
exit;
}
@@ -10,4 +10,4 @@ class DocsController extends Controller {
'title' => '📚 System-Dokumentation'
]);
}
}
}
@@ -1,6 +1,10 @@
<?php
namespace MVC\Controller;
use MVC\Core\Controller;
use MVC\Classes\Client;
use MVC\Classes\Cookie;
use MVC\Classes\Request;
use MVC\Classes\Server;
class HomeController extends Controller {
public function index() {
@@ -0,0 +1,78 @@
<?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,31 @@
<?php
namespace MVC\Controller;
use MVC\Core\Controller;
use MVC\Classes\Client;
use MVC\Classes\Cookie;
use MVC\Classes\Request;
use MVC\Classes\Server;
class TestController extends Controller {
public function service1() {
return $this->render(
'test1',
[
'msg' => 'Service 1 ist bereit!'
]
);
}
public function service2() {
return $this->render(
'test2',
[
'data' =>
[
'Layout' => 'Header + Sidebar',
'Theme' => 'Auto-Toggle'
]
]
);
}
}
@@ -0,0 +1,41 @@
<?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();
}
}
@@ -0,0 +1,10 @@
<?php
namespace MVC\Core;
interface MiddlewareInterface {
/**
* Hauptmethode der Middleware.
*/
public function handle();
}
@@ -20,6 +20,13 @@ abstract class Model {
return \DatabaseShow(static::$table, "*");
}
public static function select($columns = "*") {
if (is_array($columns)) {
$columns = implode(", ", $columns);
}
return \DatabaseShow(static::$table, $columns);
}
public static function find($id, $column = 'ID') {
$result = \DatabaseShow(static::$table, "*", "$column = ?", $id);
return $result[0] ?? null;
@@ -4,24 +4,43 @@ namespace MVC\Core;
class Router {
protected $routes = [];
protected $middlewares = [];
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;
}
public function add($service, $controller, $method, $middlewares = []) {
$this->routes[$service] = [
'controller' => $controller,
'method' => $method,
'middlewares' => $middlewares
'middlewares' => $middlewares,
'isApi' => $isApi
];
}
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];
// Run Middlewares
// 2. Run Route-Specific Middlewares
foreach ($route['middlewares'] as $middleware) {
$this->runMiddleware($middleware);
}
@@ -42,18 +61,15 @@ class Router {
return $controller->$method();
}
public function getRouteMiddleware($service) {
return $this->routes[$service]['middlewares'] ?? [];
}
public function hasMiddleware($service, $middleware) {
return in_array($middleware, $this->getRouteMiddleware($service));
}
protected function runMiddleware($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;
}
}
Middleware::run($middleware);
}
}
@@ -24,4 +24,15 @@ 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);
}
}
@@ -0,0 +1,22 @@
<!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>
@@ -0,0 +1,24 @@
<!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>
@@ -0,0 +1,23 @@
<!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>
@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html lang="de" class="<?= ($_SESSION['theme'] ?? 'light') === 'dark' ? 'dark-mode' : '' ?>">
<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; }
body { font-family: 'Segoe UI', sans-serif; margin: 0; background: var(--bg); color: var(--text); transition: 0.3s; height: 100vh; overflow: hidden; display: flex; flex-direction: column; }
.main-header { height: 60px; background: var(--header); color: white; display: flex; align-items: center; justify-content: space-between; padding: 0 20px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); z-index: 10; }
.main-container { flex: 1; display: flex; overflow: hidden; }
.sidebar { width: 260px; background: var(--sidebar); padding: 20px; border-right: 1px solid var(--border); overflow-y: auto; }
.content { flex: 1; padding: 40px; overflow-y: auto; }
.btn-toggle { padding: 8px 15px; border: 1px solid rgba(255,255,255,0.3); border-radius: 5px; cursor: pointer; background: transparent; color: white; font-size: 0.9rem; }
.btn-toggle:hover { background: rgba(255,255,255,0.1); }
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>
<body>
<header class="main-header">
<div class="logo"><?= $this->getBlock('header_title') ?></div>
<div class="header-actions">
<?= $this->getBlock('header_actions') ?>
</div>
</header>
<div class="main-container">
<div class="sidebar">
<?= $this->getBlock('sidebar') ?>
</div>
<div class="content">
<?= $this->getBlock('content') ?>
</div>
</div>
<script>
function toggleTheme() { fetch('?service=toggle_theme').then(() => location.reload()); }
</script>
</body>
</html>
@@ -0,0 +1,14 @@
<?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.");
}
}
}
@@ -0,0 +1,14 @@
<?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;
}
}
}
@@ -0,0 +1,35 @@
<?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;
}
}
}
@@ -0,0 +1,27 @@
<?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');
}
}
@@ -0,0 +1,26 @@
<?php
namespace MVC\Model;
use MVC\Core\Model;
/**
* Model für die Tabelle 'Mailing'
* Verfügbare Spalten: 'DATUM', 'SUBJECT', 'MESSAGE', 'URL', 'CLIENT_IP'
*/
class Mailing extends Model {
protected static $table = "Mailing";
/**
* Erstellt einen neuen Log-Eintrag für eine versendete E-Mail.
*/
public static function log($subject, $message, $url, $ip, $date) {
return self::insert([
'DATUM' => $date,
'SUBJECT' => $subject,
'MESSAGE' => $message,
'URL' => $url,
'CLIENT_IP' => $ip
]);
}
}
+17
View File
@@ -0,0 +1,17 @@
<?php
namespace MVC\Model;
use MVC\Core\Model;
class Users extends Model {
protected static $table = "Users";
/**
* Findet einen Benutzer nach seinem Namen.
*/
public static function getByUsername($username) {
$result = self::where("USERNAME = ?", $username);
return $result[0] ?? null;
}
}
@@ -0,0 +1,25 @@
<h2>📧 E-Mail Service</h2>
<?php if (isset($message)): ?>
<div style="padding: 15px; margin-bottom: 20px; border-radius: 5px; background: <?= $message['type'] === 'success' ? 'rgba(46, 204, 113, 0.2)' : 'rgba(231, 76, 60, 0.2)' ?>; color: var(--text);">
<?= $message['text'] ?>
</div>
<?php endif; ?>
<div style="background: var(--sidebar); padding: 30px; border-radius: 8px; border: 1px solid var(--border);">
<form method="POST">
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px;">Empfänger</label>
<input type="email" name="to" value="<?= htmlspecialchars($_POST['to'] ?? 'bruno.eckhardt07@gmail.com') ?>" required style="width: 100%; padding: 10px; border-radius: 4px; border: 1px solid var(--border); background: var(--bg); color: var(--text);">
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px;">Betreff</label>
<input type="text" name="subject" value="<?= htmlspecialchars($_POST['subject'] ?? '') ?>" required style="width: 100%; padding: 10px; border-radius: 4px; border: 1px solid var(--border); background: var(--bg); color: var(--text);">
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px;">Nachricht</label>
<textarea name="body" required style="width: 100%; height: 150px; padding: 10px; border-radius: 4px; border: 1px solid var(--border); background: var(--bg); color: var(--text);"><?= htmlspecialchars($_POST['body'] ?? '') ?></textarea>
</div>
<button type="submit" style="background: var(--accent); color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer;">E-Mail senden</button>
</form>
</div>
@@ -0,0 +1,33 @@
<h2>👤 Benutzerverwaltung</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)' ?>; color: var(--text);">
<?= $message['text'] ?>
</div>
<?php endif; ?>
<div style="background: var(--sidebar); padding: 30px; border-radius: 8px; border: 1px solid var(--border);">
<h3>Neuen Benutzer anlegen</h3>
<form method="POST">
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px;">Benutzername</label>
<input type="text" name="username" 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;">Passwort</label>
<input type="password" name="password" 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;">E-Mail</label>
<input type="email" name="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;">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">User</option>
<option value="admin">Admin</option>
</select>
</div>
<button type="submit" style="background: var(--accent); color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer;">Benutzer erstellen</button>
</form>
</div>
+357
View File
@@ -0,0 +1,357 @@
<div class="docs-container">
<h1>🏛️ MVC Framework - Die Entwickler-Bibel</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>
</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>
<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>
<ul>
<li><code>DatabaseShow($table, $cols, $where, ...$values)</code></li>
<li><code>DatabaseInsert($table, $cols, ...$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>
<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>
</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:
&lt;h1&gt;Hallo &lt;?= $name ?&gt;&lt;/h1&gt;</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>&lt;html&gt;</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>
<ul>
<li><code>-c, --controller</code>: Nur den Controller erstellen.</li>
<li><code>-v, --view</code>: Nur die View erstellen.</li>
<li><code>-r, --route</code>: Nur die Route registrieren.</li>
<li><code>-n, --nav</code>: Nur den Navigationspunkt hinzufügen.</li>
<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>
</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>
<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>
</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; }
}
</style>
+1
View File
@@ -0,0 +1 @@
<h1>Dashboard</h1><p>Willkommen in deinem neuen MVC-System mit integriertem Header.</p>
+25
View File
@@ -0,0 +1,25 @@
<style>
.login-container { max-width: 400px; margin: 50px auto; padding: 30px; border: 1px solid var(--border); border-radius: 8px; background: var(--sidebar); box-shadow: 0 4px 15px rgba(0,0,0,0.1); }
.login-container h2 { text-align: center; margin-top: 0; color: var(--text); }
.login-container input { width: 100%; padding: 12px; margin: 10px 0; border: 1px solid var(--border); border-radius: 4px; box-sizing: border-box; background: var(--bg); color: var(--text); }
.login-container button { width: 100%; padding: 12px; background: var(--header); color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 1rem; font-weight: bold; }
.login-container button:hover { opacity: 0.9; }
.error-msg { color: #e74c3c; background: rgba(231, 76, 60, 0.1); padding: 10px; border-radius: 4px; margin-bottom: 15px; text-align: center; font-size: 0.9rem; }
</style>
<div class="login-container">
<h2>🔑 Login</h2>
<?php if (isset($error)): ?>
<div class="error-msg"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<form method="POST">
<div>
<label>Benutzername</label>
<input type="text" name="username" required autofocus placeholder="Admin / User">
</div>
<div>
<label>Passwort</label>
<input type="password" name="password" required placeholder="********">
</div>
<button type="submit">Anmelden</button>
</form>
</div>
@@ -0,0 +1,76 @@
<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>
@@ -0,0 +1,55 @@
<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>
@@ -0,0 +1,42 @@
<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>
@@ -0,0 +1,35 @@
<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>
+1
View File
@@ -0,0 +1 @@
<h1>Service 1</h1><p><?= $msg ?></p>
+1
View File
@@ -0,0 +1 @@
<h1>System Check</h1><ul><?php foreach($data as $k => $v): ?><li><b><?= $k ?>:</b> <?= $v ?></li><?php endforeach; ?></ul>
+24
View File
@@ -0,0 +1,24 @@
[database]
APP_ENV = "dev"
DB_ROOT_USERNAME = "root"
DB_ROOT_PASSWORD = "bBHofurUJWKzPE8Z"
DB_DEV_HOST = "blackpi.ipv64.net"
DB_DEV_USERNAME = "worker"
DB_DEV_PASSWORD = "bBHofurUJWKzPE8Z"
DB_DEV_DATABASE = "framework"
DB_LIVE_HOST = ""
DB_LIVE_USERNAME = ""
DB_LIVE_PASSWORD = ""
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"
@@ -0,0 +1,17 @@
<?php
/**
* Define your sidebar navigation items here.
* Format: 'service_name' => ['label' => 'Label']
*/
return [
'home' => ['label' => '🏠 Home'],
'test1' => ['label' => '🛠️ Test 1'],
'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'],
];
+29
View File
@@ -0,0 +1,29 @@
<?php
/**
* Define your routes here.
* 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');
$router->add('test1', \MVC\Controller\TestController::class, 'service1', ['auth']);
$router->add('test2', \MVC\Controller\TestController::class, 'service2', ['auth']);
$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');
@@ -14,6 +14,25 @@ function db_connect() {
return $conn;
} catch (Exception $e) {
return null;
}
}
function db_connect_root() {
$config = parse_ini_file(__DIR__ . '/../config/env.ini');
$env = strtoupper($config['APP_ENV']);
try {
$conn = new mysqli(
$config["DB_{$env}_HOST"],
$config["DB_ROOT_USERNAME"],
$config["DB_ROOT_PASSWORD"]
);
$conn->set_charset("utf8mb4");
return $conn;
} catch (Exception $e) {
return null;
}
+148
View File
@@ -0,0 +1,148 @@
<?php
/**
* Database & Models Installer
* Nutzung: php install_db.php
*/
if (php_sapi_name() !== 'cli') {
die("Dieses Script muss über die CLI ausgeführt werden.\n");
}
require_once __DIR__ . '/WEBROOT/src/database/databaseConnect.php';
require_once __DIR__ . '/WEBROOT/src/utils/databaseMethods.php';
$config = parse_ini_file(__DIR__ . '/WEBROOT/src/config/env.ini');
$env = strtoupper($config['APP_ENV']);
$dbName = $config["DB_{$env}_DATABASE"];
$workerUser = $config["DB_{$env}_USERNAME"];
$workerPass = $config["DB_{$env}_PASSWORD"];
echo "🚀 Starte Datenbank-Setup als Root...\n";
$rootConn = db_connect_root();
if (!$rootConn) {
die("❌ Fehler: Verbindung als Root fehlgeschlagen. Bitte prüfe DB_ROOT credentials in env.ini\n");
}
// 1. Datenbank erstellen
echo "📁 Erstelle Datenbank '$dbName'...\n";
$rootConn->query("CREATE DATABASE IF NOT EXISTS `$dbName` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
// 2. Worker User erstellen und berechtigen (intern & extern)
echo "👤 Erstelle Worker-User '$workerUser'...\n";
$rootConn->query("CREATE USER IF NOT EXISTS '$workerUser'@'localhost' IDENTIFIED BY '$workerPass'");
$rootConn->query("CREATE USER IF NOT EXISTS '$workerUser'@'%' IDENTIFIED BY '$workerPass'");
echo "🔐 Vergebe Berechtigungen...\n";
$rootConn->query("GRANT ALL PRIVILEGES ON `$dbName`.* TO '$workerUser'@'localhost'");
$rootConn->query("GRANT ALL PRIVILEGES ON `$dbName`.* TO '$workerUser'@'%'");
$rootConn->query("FLUSH PRIVILEGES");
$rootConn->close();
echo "🚀 Starte Datenbank-Tabellen-Installation...\n";
// Definitionen der Tabellen und Models
$tables = [
'Mailing' => [
'columns' => "
`ID` INT AUTO_INCREMENT PRIMARY KEY,
`DATUM` DATETIME NOT NULL,
`SUBJECT` VARCHAR(255),
`MESSAGE` TEXT,
`URL` VARCHAR(255),
`CLIENT_IP` VARCHAR(45)
",
'model' => <<<PHP
<?php
namespace MVC\Model;
use MVC\Core\Model;
class Mailing extends Model {
protected static \$table = "Mailing";
public static function log(\$subject, \$message, \$url, \$ip, \$date) {
return self::insert([
'DATUM' => \$date,
'SUBJECT' => \$subject,
'MESSAGE' => \$message,
'URL' => \$url,
'CLIENT_IP' => \$ip
]);
}
}
PHP
],
'Users' => [
'columns' => "
`ID` INT AUTO_INCREMENT PRIMARY KEY,
`USERNAME` VARCHAR(50) UNIQUE NOT NULL,
`PASSWORD` VARCHAR(255) NOT NULL,
`EMAIL` VARCHAR(100),
`ROLE` VARCHAR(20) DEFAULT 'user',
`CREATED_AT` DATETIME DEFAULT CURRENT_TIMESTAMP
",
'model' => <<<PHP
<?php
namespace MVC\Model;
use MVC\Core\Model;
class Users extends Model {
protected static \$table = "Users";
/**
* Findet einen Benutzer nach seinem Namen.
*/
public static function getByUsername(\$username) {
\$result = self::where("USERNAME = ?", \$username);
return \$result[0] ?? null;
}
}
PHP
],
];
foreach ($tables as $tableName => $info) {
echo "--- Tabelle: $tableName ---\n";
try {
if (DatabaseCreateTable($tableName, $info['columns'])) {
echo "✅ Struktur bereit.\n";
$modelPath = __DIR__ . "/WEBROOT/src/MVC/Model/{$tableName}.php";
if (!file_exists($modelPath)) {
if (!is_dir(dirname($modelPath))) mkdir(dirname($modelPath), 0755, true);
file_put_contents($modelPath, $info['model']);
echo "✅ Model erstellt: $modelPath\n";
} else {
echo "ℹ️ Model existiert bereits.\n";
}
}
} catch (Exception $e) {
echo "❌ Fehler bei $tableName: " . $e->getMessage() . "\n";
}
}
// 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')) {
\MVC\Model\Users::insert([
'USERNAME' => 'admin',
'PASSWORD' => password_hash('admin123', PASSWORD_DEFAULT),
'EMAIL' => 'admin@example.com',
'ROLE' => 'admin'
]);
echo "✅ Test-User 'admin' mit Passwort 'admin123' erstellt.\n";
} else {
echo "ℹ️ Test-User 'admin' existiert bereits.\n";
}
} catch (Exception $e) {
echo "❌ Fehler beim Erstellen des Test-Users: " . $e->getMessage() . "\n";
}
echo "\n✨ Installation abgeschlossen.\n";
+270
View File
@@ -0,0 +1,270 @@
<?php
/**
* MVC Unified CLI Tool
* Nutzung: php make.php <command> [args]
*/
if (php_sapi_name() !== 'cli') {
die("Dieses Script muss über die CLI ausgeführt werden.\n");
}
$command = $argv[1] ?? null;
if (!$command || in_array($command, ['help', '-h', '--help'])) {
showHelp();
exit;
}
switch ($command) {
case 'model':
createModel(array_slice($argv, 2));
break;
case 'module':
createModule(array_slice($argv, 2));
break;
default:
echo "❌ Unbekannter Befehl: $command\n";
showHelp();
exit(1);
}
function showHelp() {
echo "MVC CLI Tool\n";
echo "-----------\n";
echo "Nutzung: php make.php <befehl> [optionen]\n\n";
echo "Befehle:\n";
echo " model Erstellt ein neues Model basierend auf einer Datenbanktabelle\n";
echo " module Erstellt ein neues Modul (Controller, View, Route, Navigation)\n\n";
echo "Details zu model:\n";
echo " php make.php model <TabellenName> <Spalte1,Spalte2,...>\n\n";
echo "Details zu module:\n";
echo " php make.php module <Name> [optionen]\n";
echo " Optionen:\n";
echo " -c, --controller Nur Controller erstellen\n";
echo " -v, --view Nur View erstellen\n";
echo " -r, --route Nur Route hinzufügen\n";
echo " -n, --nav Nur Navigationspunkt hinzufügen\n";
echo " -a, --auth Route/Nav erfordert Login\n";
echo " -l, --label Label für Navigationspunkt\n";
}
function createModel($args) {
$tableName = $args[0] ?? null;
$columnsInput = $args[1] ?? null;
// Interaktive Abfrage, falls Argumente fehlen
if (!$tableName) {
echo "Tabellenname eingeben (z.B. Benutzer): ";
$handle = fopen("php://stdin", "r");
$tableName = trim(fgets($handle));
fclose($handle);
}
if (!$columnsInput) {
echo "Spalten kommagetrennt eingeben (z.B. NAME,EMAIL,ROLLE): ";
$handle = fopen("php://stdin", "r");
$columnsInput = trim(fgets($handle));
fclose($handle);
}
if (!$tableName) die("❌ Fehler: Kein Tabellenname angegeben.\n");
// Namen formatieren
$className = str_replace(' ', '', ucwords(str_replace('_', ' ', strtolower($tableName))));
$modelFile = __DIR__ . "/WEBROOT/src/MVC/Model/{$className}.php";
// Spalten aufbereiten
$columns = array_map('trim', explode(',', $columnsInput));
$columnList = implode("', '", $columns);
// Model-Inhalt generieren
$content = <<<PHPMODEL
<?php
namespace MVC\Model;
use MVC\Core\Model;
/**
* Model für die Tabelle '{$tableName}'
* Verfügbare Spalten: '{$columnList}'
*/
class {$className} extends Model {
protected static \$table = "{$tableName}";
// Hier kannst du spezifische Methoden für dieses Model hinzufügen
/**
* Beispiel: Suche nach einer spezifischen Spalte
*/
public static function getByFirstColumn(\$value) {
return self::where("{$columns[0]} = ?", \$value);
}
}
PHPMODEL;
// Datei schreiben
if (!is_dir(dirname($modelFile))) {
mkdir(dirname($modelFile), 0755, true);
}
if (!file_exists($modelFile)) {
file_put_contents($modelFile, $content);
echo "✅ Model erstellt: WEBROOT/src/MVC/Model/{$className}.php\n";
echo "--- INFO ---\n";
echo "Tabelle: {$tableName}\n";
echo "Klasse: MVC\\Model\\{$className}\n";
echo "Spalten: " . implode(', ', $columns) . "\n";
} else {
echo "⚠️ Model {$className}.php existiert bereits.\n";
}
}
function createModule($args) {
// Da wir args als Slice übergeben, müssen wir sie manuell parsen
$name = null;
$manualFlags = [];
$label = null;
for ($i = 0; $i < count($args); $i++) {
$arg = $args[$i];
if ($arg === '-l' || $arg === '--label') {
$label = $args[++$i] ?? null;
} else if (strpos($arg, '-') === 0) {
$flag = ltrim($arg, '-');
$manualFlags[] = $flag;
} else if ($name === null) {
$name = $arg;
}
}
if (!$name) {
echo "❌ Fehler: Kein Modulname angegeben.\n";
return;
}
$hasFlag = function($short, $long) use ($manualFlags) {
return in_array($short, $manualFlags) || in_array($long, $manualFlags);
};
$doController = $hasFlag('c', 'controller');
$doView = $hasFlag('v', 'view');
$doRoute = $hasFlag('r', 'route');
$doNav = $hasFlag('n', 'nav');
$isAuth = $hasFlag('a', 'auth');
// Wenn gar keine spezifischen Flags gesetzt wurden, mache alles
if (!($doController || $doView || $doRoute || $doNav)) {
$doController = $doView = $doRoute = $doNav = true;
}
// Namen formatieren
$key = strtolower(str_replace(' ', '_', $name));
$className = str_replace(' ', '', ucwords(str_replace('_', ' ', $key)));
$label = $label ?: ucwords(str_replace('_', ' ', $key));
$controllerFile = __DIR__ . "/WEBROOT/src/MVC/Controller/{$className}Controller.php";
$viewFile = __DIR__ . "/WEBROOT/src/MVC/View/{$key}.php";
$routesFile = __DIR__ . "/WEBROOT/src/config/routes.php";
$navFile = __DIR__ . "/WEBROOT/src/config/navigation.php";
// 1. Controller erstellen
if ($doController) {
$controllerContent = <<<PHPCTRL
<?php
namespace MVC\Controller;
use MVC\Core\Controller;
use MVC\Classes\Client;
use MVC\Classes\Cookie;
use MVC\Classes\Request;
use MVC\Classes\Server;
// use MVC\Model\YourModel; // Importiere dein Model
class {$className}Controller extends Controller {
public function {$key}() {
// Nutze Models für den Datenbankzugriff
// \$data = YourModel::all();
return \$this->render('{$key}', [
'title' => '{$className} Modul',
'message' => 'Willkommen in deinem neuen Modul {$className}!'
]);
}
}
PHPCTRL;
if (!file_exists($controllerFile)) {
file_put_contents($controllerFile, $controllerContent);
echo "✅ Controller erstellt: WEBROOT/src/MVC/Controller/{$className}Controller.php\n";
} else {
echo "⚠️ Controller existiert bereits.\n";
}
}
// 2. View erstellen
if ($doView) {
$viewContent = <<<HTML
<h2><?= htmlspecialchars(\$title) ?></h2>
<div class="card">
<p><?= htmlspecialchars(\$message) ?></p>
<p>Hier kannst du nun dein Projekt aufbauen.</p>
</div>
<style>
.card {
background: var(--card-bg);
padding: 20px;
border-radius: 8px;
border: 1px solid var(--card-border);
}
</style>
HTML;
if (!file_exists($viewFile)) {
file_put_contents($viewFile, $viewContent);
echo "✅ View erstellt: WEBROOT/src/MVC/View/{$key}.php\n";
} else {
echo "⚠️ View existiert bereits.\n";
}
}
// 3. Route hinzufügen
if ($doRoute) {
if (file_exists($routesFile)) {
$routesContent = file_get_contents($routesFile);
$routeLine = "\$router->add('{$key}', \\MVC\\Controller\\{$className}Controller::class, '{$key}'" . ($isAuth ? ", ['auth']" : "") . ");\n";
if (strpos($routesContent, "'{$key}'") === false) {
file_put_contents($routesFile, $routesContent . $routeLine);
echo "✅ Route hinzugefügt: {$routesFile}\n";
} else {
echo "⚠️ Route für '{$key}' existiert bereits in routes.php\n";
}
} else {
echo "❌ Fehler: routes.php nicht gefunden!\n";
}
}
// 4. Navigation hinzufügen
if ($doNav) {
if (file_exists($navFile)) {
$navContent = file_get_contents($navFile);
$navItem = " '{$key}' => ['label' => '{$label}'],\n";
if (strpos($navContent, "'{$key}'") === false) {
// Wir suchen nach dem Ende des Arrays ];
$newNavContent = preg_replace('/(\];\s*)$/', $navItem . '$1', trim($navContent));
file_put_contents($navFile, $newNavContent . "\n");
echo "✅ Navigation hinzugefügt: {$navFile}\n";
} else {
echo "⚠️ Navigation für '{$key}' existiert bereits in navigation.php\n";
}
} else {
echo "❌ Fehler: navigation.php nicht gefunden!\n";
}
}
echo "\nFertig!\n";
}
+1 -1
View File
@@ -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\"");
passthru("php -S $host:$port -t \"$root\" \"$root/index.php\"");
-15
View File
@@ -1,15 +0,0 @@
<?php
use MVC\Classes\Api;
header('Content-Type: application/json');
require_once __DIR__ . '/../../src/MVC/autoloader.php';
use MVC\Controller\ApiController;
try {
Api::jsonResponse((new ApiController())->get());
} catch (\Exception $e) {
Api::jsonResponse($e->getMessage(), 404);
}
-65
View File
@@ -1,65 +0,0 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);
session_start();
require_once __DIR__ . '/src/MVC/autoloader.php';
$service = $_GET['service'] ?? 'home';
try {
$router = new \MVC\Core\Router();
$router->add('home', \MVC\Controller\HomeController::class, 'index');
$router->add('toggle_theme', \MVC\Controller\HomeController::class, 'toggle_theme');
$router->add('abgas', \MVC\Controller\AbgasController::class, 'abgas');
$router->add('vorlauf', \MVC\Controller\VorlaufController::class, 'vorlauf', ['auth']);
$router->add('puffer', \MVC\Controller\PufferController::class, 'puffer', ['auth']);
$router->add('aussentemp', \MVC\Controller\AussentempController::class, 'aussentemp', ['auth']);
$router->add('docs', \MVC\Controller\DocsController::class, 'index');
$router->add('login', \MVC\Controller\AuthController::class, 'login');
$router->add('logout', \MVC\Controller\AuthController::class, 'logout');
$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 (isset($_SESSION['user_logged_in'])) {
$headerActions = '<span style="margin-right: 15px;">👤 ' . htmlspecialchars($_SESSION['username']) . '</span>' .
'<a href="?service=logout" class="btn-toggle" style="text-decoration: none; background: rgba(255,255,255,0.2);">🚪 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;
}
$layout->setBlock('header_actions', $headerActions);
// Sidebar Block
ob_start();
$navItems = [
'home' => ['label' => '🏠 Home', 'auth' => false],
'abgas' => ['label' => '🔥 Abgas', 'auth' => false],
'vorlauf' => ['label' => '🏃‍♂️‍➡️ Vorlauf', 'auth' => true],
'puffer' => ['label' => '♨️ Puffer', 'auth' => true],
'aussentemp' => ['label' => '🌡️ Außentemperatur', 'auth' => true],
'docs' => ['label' => '📚 Dokumentation', 'auth' => false],
];
?>
<nav>
<?php foreach ($navItems as $navKey => $item):
if ($item['auth'] && !isset($_SESSION['user_logged_in'])) continue;
?>
<a href="?service=<?= $navKey ?>" class="<?= $service === $navKey ? 'active' : '' ?>"><?= $item['label'] ?></a>
<?php endforeach; ?>
<?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>
<?php endif; ?>
</nav>
<?php
$layout->setBlock('sidebar', ob_get_clean());
$layout->setBlock('content', $router->dispatch($service));
echo $layout->render();
} catch (\Throwable $e) {
http_response_code(500); echo "<h1>Error</h1><p>" . $e->getMessage() . "</p><pre>" . $e->getTraceAsString() . "</pre>";
};
@@ -1,46 +0,0 @@
<?php
namespace MVC\Controller;
use MVC\Core\Controller;
use MVC\Model\Messungen;
class AbgasController extends Controller {
public function abgas() {
$day = $_GET['day'] ?? date('Y-m-d');
$datum = \DateTime::createFromFormat('Y-m-d', $day);
if (!$datum) {
$day = date('Y-m-d');
}
$array = Messungen::getAbgasByDay($day);
$labels = [];
$abgas = [];
foreach ($array as $row) {
$labels[] = date('H:i:s', strtotime($row['ts']));
$abgas[] = (float)$row['abgas'];
}
$prevDay = date('Y-m-d', strtotime($day . ' -1 day'));
$nextDay = date('Y-m-d', strtotime($day . ' +1 day'));
return $this->render('abgas', [
'title' => 'Abgas Modul',
'xtitle' => 'Zeit',
'ytitle' => 'Temperatur',
'array' => $array,
'labels' => $labels,
'abgas' => $abgas,
'day' => $day,
'prevDay' => $prevDay,
'nextDay' => $nextDay,
]);
}
}
@@ -1,18 +0,0 @@
<?php
namespace MVC\Controller;
use MVC\Core\Controller;
use MVC\Model\Messungen;
use MVC\Classes\Api;
class ApiController extends Controller {
public function get() {
$day = "2026-05-22";
$abgas = Messungen::getAbgasByDay($day);
return Api::jsonResponse([
'abgas' => $abgas,
]);
}
}
@@ -1,46 +0,0 @@
<?php
namespace MVC\Controller;
use MVC\Core\Controller;
use MVC\Model\Messungen;
class AussentempController extends Controller {
public function aussentemp() {
$day = $_GET['day'] ?? date('Y-m-d');
$datum = \DateTime::createFromFormat('Y-m-d', $day);
if (!$datum) {
$day = date('Y-m-d');
}
$array = Messungen::getAussentempByDay($day);
$labels = [];
$aussentemp = [];
foreach ($array as $row) {
$labels[] = date('H:i:s', strtotime($row['ts']));
$aussentemp[] = (float)$row['aussentemp'];
}
$prevDay = date('Y-m-d', strtotime($day . ' -1 day'));
$nextDay = date('Y-m-d', strtotime($day . ' +1 day'));
return $this->render('aussentemp', [
'title' => 'Aussentemperatur Modul',
'xtitle' => 'Zeit',
'ytitle' => 'Temperatur',
'array' => $array,
'labels' => $labels,
'aussentemp' => $aussentemp,
'day' => $day,
'prevDay' => $prevDay,
'nextDay' => $nextDay,
]);
}
}
@@ -1,58 +0,0 @@
<?php
namespace MVC\Controller;
use MVC\Core\Controller;
use MVC\Model\Messungen;
class PufferController extends Controller {
public function puffer() {
// Tag holen
$day = $_GET['day'] ?? date('Y-m-d');
// Validierung
$datum = \DateTime::createFromFormat('Y-m-d', $day);
if (!$datum) {
$day = date('Y-m-d');
}
// Daten laden
$array = Messungen::getPufferByDay($day);
// Chart Daten vorbereiten
$labels = [];
$pufferOben = [];
$pufferUnten = [];
foreach ($array as $row) {
// Zeit kürzen für Chart
$labels[] = date('H:i:s', strtotime($row['ts']));
$pufferOben[] = (float)$row['kessel_oben'];
$pufferUnten[] = (float)$row['kessel_unten'];
}
// Navigation
$prevDay = date('Y-m-d', strtotime($day . ' -1 day'));
$nextDay = date('Y-m-d', strtotime($day . ' +1 day'));
return $this->render('puffer', [
'title' => 'Puffer Übersicht',
'xtitle' => 'Zeit',
'ytitle' => 'Temperatur',
'array' => $array,
'labels' => $labels,
'pufferOben' => $pufferOben,
'pufferUnten' => $pufferUnten,
'day' => $day,
'prevDay' => $prevDay,
'nextDay' => $nextDay,
]);
}
}
@@ -1,58 +0,0 @@
<?php
namespace MVC\Controller;
use MVC\Core\Controller;
use MVC\Model\Messungen;
class VorlaufController extends Controller {
public function vorlauf() {
// Tag holen
$day = $_GET['day'] ?? date('Y-m-d');
// Validierung
$datum = \DateTime::createFromFormat('Y-m-d', $day);
if (!$datum) {
$day = date('Y-m-d');
}
// Daten laden
$array = Messungen::getVorlaufByDay($day);
// Chart Daten vorbereiten
$labels = [];
$vorlaufIst = [];
$vorlaufSoll = [];
foreach ($array as $row) {
// Zeit kürzen für Chart
$labels[] = date('H:i:s', strtotime($row['ts']));
$vorlaufIst[] = (float)$row['vorlauf_ist'];
$vorlaufSoll[] = (float)$row['vorlauf_soll'];
}
// Navigation
$prevDay = date('Y-m-d', strtotime($day . ' -1 day'));
$nextDay = date('Y-m-d', strtotime($day . ' +1 day'));
return $this->render('vorlauf', [
'title' => 'Vorlauf Übersicht',
'xtitle' => 'Zeit',
'ytitle' => 'Temperatur',
'array' => $array,
'labels' => $labels,
'vorlaufIst' => $vorlaufIst,
'vorlaufSoll' => $vorlaufSoll,
'day' => $day,
'prevDay' => $prevDay,
'nextDay' => $nextDay,
]);
}
}
@@ -1,112 +0,0 @@
<!DOCTYPE html>
<html lang="de" class="<?= ($_SESSION['theme'] ?? 'light') === 'dark' ? 'dark-mode' : '' ?>">
<head>
<meta charset="UTF-8">
<title><?= $this->getBlock('title') ?></title>
<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;
}
body {
font-family: 'Segoe UI', sans-serif;
margin: 0;
background: var(--bg);
color: var(--text);
transition: 0.3s;
height: 100vh;
overflow: hidden;
display: flex;
flex-direction: column;
}
.main-header {
height: 60px;
background: var(--header);
color: white;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
z-index: 10;
}
.main-container {
flex: 1;
display: flex;
overflow: hidden;
}
.sidebar {
width: 260px;
background: var(--sidebar);
padding: 20px;
border-right: 1px solid var(--border);
overflow-y: auto;
}
.content {
flex: 1;
padding: 40px;
overflow-y: auto;
}
.btn-toggle {
padding: 8px 15px;
border: 1px solid rgba(255,255,255,0.3);
border-radius: 5px;
cursor: pointer;
background: transparent;
color: white;
font-size: 0.9rem;
}
.btn-toggle:hover {
background: rgba(255,255,255,0.1);
}
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.25);
}
.logo {
font-size: 1.4rem; font-weight: bold;
}
</style>
</head>
<body>
<header class="main-header">
<div class="logo"><?= $this->getBlock('header_title') ?></div>
<div class="header-actions">
<?= $this->getBlock('header_actions') ?>
</div>
</header>
<div class="main-container">
<div class="sidebar">
<?= $this->getBlock('sidebar') ?>
</div>
<div class="content">
<?= $this->getBlock('content') ?>
</div>
</div>
<script>
function toggleTheme() { fetch('?service=toggle_theme').then(() => location.reload()); }
</script>
</body>
</html>
@@ -1,42 +0,0 @@
<?php
namespace MVC\Model;
use MVC\Core\Model;
/**
* Model für die Tabelle 'messungen'
* Verfügbare Spalten: 'id', 'ts', 'aussentemp', 'vorlauf_ist', 'vorlauf_soll', 'kessel_oben', 'kessel_unten', 'wohnung', 'abgas', 'stellung', 'stellung_proz', 'mischeron', 'abweichung', 'neigung', 'rtsoll', 'misch_begin', 'nachtabs'
*/
class Messungen extends Model {
protected static $table = "messungen";
// Hier kannst du spezifische Methoden für dieses Model hinzufügen
/**
* Beispiel: Suche nach einer spezifischen Spalte
*/
public static function getByFirstColumn($value) {
return self::where("id = ?", $value);
}
public static function getAbgasByDay($day) {
$sql = "SELECT ts, abgas FROM " . self::$table . " WHERE ts LIKE '$day%' ORDER BY ts ASC";
return self::query($sql)->fetch_all(MYSQLI_ASSOC);
}
public static function getVorlaufByDay($day) {
$sql = "SELECT ts, vorlauf_ist, vorlauf_soll FROM " . self::$table . " WHERE ts LIKE '$day%' ORDER BY ts ASC";
return self::query($sql)->fetch_all(MYSQLI_ASSOC);
}
public static function getPufferByDay($day) {
$sql = "SELECT ts, kessel_oben, kessel_unten FROM " . self::$table . " WHERE ts LIKE '$day%' ORDER BY ts ASC";
return self::query($sql)->fetch_all(MYSQLI_ASSOC);
}
public static function getAussentempByDay($day) {
$sql = "SELECT ts, aussentemp FROM " . self::$table . " WHERE ts LIKE '$day%' ORDER BY ts ASC";
return self::query($sql)->fetch_all(MYSQLI_ASSOC);
}
}
-126
View File
@@ -1,126 +0,0 @@
<?php
/**
* @var array $array
* @var string $prevDay
* @var string $nextDay
* @var string $day
* @var string $title
* @var string $xtitle
* @var string $ytitle
* @var string $labels
* @var string $abgas
*/
?>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-zoom"></script>
<div style="height:800px;">
<canvas id="logChart"></canvas>
</div>
<script>
const ctx = document.getElementById('logChart').getContext('2d');
const logChart = new Chart(ctx, {
type: 'line',
data: {
labels: <?php echo json_encode($labels); ?>,
datasets: [{
label: 'Abgas',
data: <?php echo json_encode($abgas); ?>,
borderColor: 'rgba(75, 192, 192, 1)',
backgroundColor: 'rgba(75, 192, 192, 0.2)',
fill: true,
tension: 0.1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: true,
text: '<?= $title ?>'
},
legend: {
display: true,
position: 'top'
},
zoom: {
pan: {
enabled: true,
mode: 'x'
},
zoom: {
wheel: {
enabled: true
},
pinch: {
enabled: true
},
mode: 'x'
}
}
},
interaction: {
mode: 'index',
intersect: false
},
scales: {
x: {
title: {
display: true,
text: '<?= $xtitle ?>'
}
},
y: {
min: 0,
max: 600,
title: {
display: true,
text: '<?= $ytitle ?>'
},
ticks: {
stepSize: 20
},
beginAtZero: true
}
}
}
});
</script>
<div style="margin-bottom:20px;">
<a href="?service=abgas&day=<?php echo $prevDay; ?>">
← Vorheriger Tag
</a>
<strong style="margin:0 20px;">
<?php echo $day; ?>
</strong>
<a href="?service=abgas&day=<?php echo $nextDay; ?>">
Nächster Tag →
</a>
</div>
-126
View File
@@ -1,126 +0,0 @@
<?php
/**
* @var array $array
* @var string $prevDay
* @var string $nextDay
* @var string $day
* @var string $title
* @var string $xtitle
* @var string $ytitle
* @var string $labels
* @var string $aussentemp
*/
?>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-zoom"></script>
<div style="height:800px;">
<canvas id="logChart"></canvas>
</div>
<script>
const ctx = document.getElementById('logChart').getContext('2d');
const logChart = new Chart(ctx, {
type: 'line',
data: {
labels: <?php echo json_encode($labels); ?>,
datasets: [{
label: 'Aussentemperatur',
data: <?php echo json_encode($aussentemp); ?>,
borderColor: 'rgba(75, 192, 192, 1)',
backgroundColor: 'rgba(75, 192, 192, 0.2)',
fill: true,
tension: 0.1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: true,
text: '<?= $title ?>'
},
legend: {
display: true,
position: 'top'
},
zoom: {
pan: {
enabled: true,
mode: 'x'
},
zoom: {
wheel: {
enabled: true
},
pinch: {
enabled: true
},
mode: 'x'
}
}
},
interaction: {
mode: 'index',
intersect: false
},
scales: {
x: {
title: {
display: true,
text: '<?= $xtitle ?>'
}
},
y: {
min: 0,
max: 50,
title: {
display: true,
text: '<?= $ytitle ?>'
},
ticks: {
stepSize: 20
},
beginAtZero: true
}
}
}
});
</script>
<div style="margin-bottom:20px;">
<a href="?service=aussentemp&day=<?php echo $prevDay; ?>">
← Vorheriger Tag
</a>
<strong style="margin:0 20px;">
<?php echo $day; ?>
</strong>
<a href="?service=aussentemp&day=<?php echo $nextDay; ?>">
Nächster Tag →
</a>
</div>
-127
View File
@@ -1,127 +0,0 @@
<div class="docs-container">
<h1>🏛️ Framework Architektur & Dokumentation</h1>
<div class="toc">
<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="#best-practices">7. Best Practices</a></li>
</ul>
</div>
<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="files">
<h2>2. Datei-Referenz (Deep Dive)</h2>
<h3>Framework Core</h3>
<ul>
<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. <strong>Wichtig:</strong> Niemals in Git committen!</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>
<p>Die Klasse <code>MVC\Classes\Api</code> stellt die Methode <code>jsonResponse($data, $status)</code> bereit, die automatisch die korrekten Header setzt und die Antwort einheitlich formatiert.</p>
</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>databaseMethods.php (Die Power-Tools)</h3>
<p>Diese Datei enthält prozedurale Funktionen für alle CRUD-Operationen:</p>
<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>
<li><code>DatabaseShowWithFK($table, $fkMap, ...)</code>: Ermöglicht das automatische Auflösen von Fremdschlüsseln (Joins ohne SQL-Schreiben).</li>
</ul>
<h3>Models</h3>
<p>Models vereinfachen den Zugriff. Beispiel:</p>
<pre>
$user = User::find(1); // Holt User mit ID 1
$admins = User::where("role = ?", "admin");
</pre>
</section>
<section id="classes">
<h2>5. Helper Klassen (MVC\Classes)</h2>
<p>Nützliche Helfer, die in jedem Controller verfügbar sind:</p>
<ul>
<li><strong>Request:</strong> Zugriff auf <code>method()</code>, <code>queryParams()</code>, <code>headers()</code> und den <code>body()</code>.</li>
<li><strong>Client:</strong> Ermittelt <code>ip()</code>, <code>userAgent()</code> und sogar das <code>country()</code> via IP-API.</li>
<li><strong>Server:</strong> Informationen über den Host, Ports und Pfade.</li>
<li><strong>Cookie:</strong> Einfaches Interface zum <code>set()</code>, <code>read()</code> und <code>Delete()</code> von Cookies.</li>
</ul>
</section>
<section id="generators">
<h2>6. CLI-Generatoren</h2>
<p>Diese Scripte beschleunigen die Entwicklung massiv:</p>
<h3>create_module.php</h3>
<p>Erstellt ein komplettes Feature-Bundle bestehend aus einem Controller und einer View. Registriert danach den Service in der <code>index.php</code>.</p>
<h3>create_model.php</h3>
<p>Generiert eine Model-Klasse basierend auf einer vorhandenen Datenbank-Tabelle. Analysiert die Spalten und erstellt entsprechende PHP-Kommentare für die IDE.</p>
</section>
<section id="best-practices">
<h2>7. Best Practices</h2>
<div class="bp-grid">
<div class="bp-card">
<h4>Naming</h4>
<p>Controller immer mit <code>Controller</code>-Suffix (z.B. <code>UserController</code>). Models im Singular (z.B. <code>User</code>).</p>
</div>
<div class="bp-card">
<h4>Logic Placement</h4>
<p>Halte Controller "schlank". Komplexe SQL-Logik oder Berechnungen gehören in das jeweilige Model.</p>
</div>
<div class="bp-card">
<h4>Security</h4>
<p>Nutze <code>['auth']</code> Middleware konsequent für alle internen Seiten. Verwende niemals <code>$_GET</code> oder <code>$_POST</code> direkt, sondern die <code>Request</code>-Klasse.</p>
</div>
<div class="bp-card">
<h4>API First</h4>
<p>Wenn du Daten für das Frontend bereitstellst, nutze den <code>ApiController</code>. So bleibt die Geschäftslogik vom HTML getrennt.</p>
</div>
</div>
</section>
</div>
<style>
.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; }
h3 { color: var(--text); opacity: 0.9; }
pre { background: #1e1e1e; color: #d4d4d4; padding: 15px; border-radius: 5px; overflow-x: auto; border: 1px solid #333; font-size: 0.9rem; }
.bp-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-top: 20px; }
.bp-card { background: var(--sidebar); padding: 20px; border-radius: 8px; border: 1px solid var(--border); }
.bp-card h4 { margin-top: 0; color: var(--accent); }
.bp-card p { font-size: 0.9rem; margin-bottom: 0; }
code { background: var(--sidebar); padding: 2px 5px; border-radius: 3px; font-family: monospace; }
</style>
-2
View File
@@ -1,2 +0,0 @@
<h1>Dashboard</h1>
<p>Willkommen in deinem neuen MVC-System mit integriertem Header.</p>
-60
View File
@@ -1,60 +0,0 @@
<style>
.login-container {
max-width: 400px;
margin: 0 auto;
padding: 20px;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--sidebar);
}
.login-container h2 {
text-align: center;
margin-top: 0;
}
.login-container input {
width: 100%;
padding: 10px;
margin: 10px 0;
border: 1px solid var(--border);
border-radius: 4px;
box-sizing: border-box;
}
.login-container button {
width: 100%;
padding: 10px;
background: var(--header);
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.login-container button:hover {
opacity: 0.9;
}
.error-msg {
color: #e74c3c;
background: rgba(231, 76, 60, 0.1);
padding: 10px;
border-radius: 4px;
margin-bottom: 15px;
text-align: center;
}
</style>
<div class="login-container">
<h2>🔑 Login</h2>
<?php if (isset($error)): ?>
<div class="error-msg"><?= $error ?></div>
<?php endif; ?>
<form method="POST">
<div>
<label>Benutzername</label>
<input type="text" name="username" required autofocus placeholder="Admin / User">
</div>
<div>
<label>Passwort</label>
<input type="password" name="password" required placeholder="********">
</div>
<button type="submit">Anmelden</button>
</form>
</div>
-116
View File
@@ -1,116 +0,0 @@
<?php
/**
* @var array $array
* @var string $prevDay
* @var string $nextDay
* @var string $day
* @var string $title
* @var string $xtitle
* @var string $ytitle
* @var string $labels
* @var string $pufferOben
* @var string $pufferUnten
*/
?>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-zoom"></script>
<div style="height:800px;">
<canvas id="logChart"></canvas>
</div>
<script>
const ctx = document.getElementById('logChart').getContext('2d');
new Chart(ctx, {
type: 'line',
data: {
labels: <?php echo json_encode($labels); ?>,
datasets: [
{
label: 'Puffer Oben',
data: <?php echo json_encode($pufferOben); ?>,
borderColor: 'rgba(54, 162, 235, 1)',
backgroundColor: 'rgba(54, 162, 235, 0.2)',
tension: 0.2,
fill: false
},
{
label: 'Puffer Unten',
data: <?php echo json_encode($pufferUnten); ?>,
borderColor: 'rgba(255, 99, 132, 1)',
backgroundColor: 'rgba(255, 99, 132, 0.2)',
tension: 0.2,
fill: false
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: true,
text: '<?= $title ?>'
},
legend: {
display: true,
position: 'top'
},
zoom: {
pan: {
enabled: true,
mode: 'x'
},
zoom: {
wheel: {
enabled: true
},
pinch: {
enabled: true
},
mode: 'x'
}
}
},
interaction: {
mode: 'index',
intersect: false
},
scales: {
x: {
title: {
display: true,
text: '<?= $xtitle ?>'
}
},
y: {
title: {
display: true,
text: '<?= $ytitle ?>'
},
beginAtZero: false
}
}
}
});
</script>
<div style="margin-bottom:20px;">
<a href="?service=puffer&day=<?php echo $prevDay; ?>">← Vorheriger Tag</a>
<strong style="margin:0 20px;">
<?php echo $day; ?>
</strong>
<a href="?service=puffer&day=<?php echo $nextDay; ?>">Nächster Tag →</a>
</div>
-116
View File
@@ -1,116 +0,0 @@
<?php
/**
* @var array $array
* @var string $prevDay
* @var string $nextDay
* @var string $day
* @var string $title
* @var string $xtitle
* @var string $ytitle
* @var string $labels
* @var string $vorlaufIst
* @var string $vorlaufSoll
*/
?>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-zoom"></script>
<div style="height:800px;">
<canvas id="logChart"></canvas>
</div>
<script>
const ctx = document.getElementById('logChart').getContext('2d');
new Chart(ctx, {
type: 'line',
data: {
labels: <?php echo json_encode($labels); ?>,
datasets: [
{
label: 'Vorlauf IST',
data: <?php echo json_encode($vorlaufIst); ?>,
borderColor: 'rgba(54, 162, 235, 1)',
backgroundColor: 'rgba(54, 162, 235, 0.2)',
tension: 0.2,
fill: false
},
{
label: 'Vorlauf SOLL',
data: <?php echo json_encode($vorlaufSoll); ?>,
borderColor: 'rgba(255, 99, 132, 1)',
backgroundColor: 'rgba(255, 99, 132, 0.2)',
tension: 0.2,
fill: false
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: true,
text: 'Vorlauf IST vs SOLL'
},
legend: {
display: true,
position: 'top'
},
zoom: {
pan: {
enabled: true,
mode: 'x'
},
zoom: {
wheel: {
enabled: true
},
pinch: {
enabled: true
},
mode: 'x'
}
}
},
interaction: {
mode: 'index',
intersect: false
},
scales: {
x: {
title: {
display: true,
text: '<?= $xtitle ?>'
}
},
y: {
title: {
display: true,
text: '<?= $ytitle ?>'
},
beginAtZero: false
}
}
}
});
</script>
<div style="margin-bottom:20px;">
<a href="?service=vorlauf&day=<?php echo $prevDay; ?>">← Vorheriger Tag</a>
<strong style="margin:0 20px;">
<?php echo $day; ?>
</strong>
<a href="?service=vorlauf&day=<?php echo $nextDay; ?>">Nächster Tag →</a>
</div>
-6
View File
@@ -1,6 +0,0 @@
[database]
APP_ENV = "dev"
DB_DEV_HOST = "blackpi.ipv64.net"
DB_DEV_USERNAME = "worker"
DB_DEV_PASSWORD = "bBHofurUJWKzPE8Z"
DB_DEV_DATABASE = "HEIZUNG"
-79
View File
@@ -1,79 +0,0 @@
<?php
/**
* MVC Model Generator
* Nutzung: php create_model.php <TabellenName> <Spalte1,Spalte2,...>
*/
if (php_sapi_name() !== 'cli') {
die("Dieses Script muss über die CLI ausgeführt werden.\n");
}
$tableName = $argv[1] ?? null;
$columnsInput = $argv[2] ?? null;
// Interaktive Abfrage, falls Argumente fehlen
if (!$tableName) {
echo "Tabellenname eingeben (z.B. Benutzer): ";
$handle = fopen("php://stdin", "r");
$tableName = trim(fgets($handle));
fclose($handle);
}
if (!$columnsInput) {
echo "Spalten kommagetrennt eingeben (z.B. NAME,EMAIL,ROLLE): ";
$handle = fopen("php://stdin", "r");
$columnsInput = trim(fgets($handle));
fclose($handle);
}
if (!$tableName) die("Fehler: Kein Tabellenname angegeben.\n");
// Namen formatieren
$className = str_replace(' ', '', ucwords(str_replace('_', ' ', strtolower($tableName))));
$modelFile = __DIR__ . "/WEBROOT/src/MVC/Model/{$className}.php";
// Spalten aufbereiten
$columns = array_map('trim', explode(',', $columnsInput));
$columnList = implode("', '", $columns);
// Model-Inhalt generieren
$content = <<<PHPMODEL
<?php
namespace MVC\Model;
use MVC\Core\Model;
/**
* Model für die Tabelle '{$tableName}'
* Verfügbare Spalten: '{$columnList}'
*/
class {$className} extends Model {
protected static \$table = "{$tableName}";
// Hier kannst du spezifische Methoden für dieses Model hinzufügen
/**
* Beispiel: Suche nach einer spezifischen Spalte
*/
public static function getByFirstColumn(\$value) {
return self::where("{$columns[0]} = ?", \$value);
}
}
PHPMODEL;
// Datei schreiben
if (!is_dir(dirname($modelFile))) {
mkdir(dirname($modelFile), 0755, true);
}
if (!file_exists($modelFile)) {
file_put_contents($modelFile, $content);
echo "✅ Model erstellt: WEBROOT/src/MVC/Model/{$className}.php\n";
echo "--- INFO ---\n";
echo "Tabelle: {$tableName}\n";
echo "Klasse: MVC\\Model\\{$className}\n";
echo "Spalten: " . implode(', ', $columns) . "\n";
} else {
echo "⚠️ Model {$className}.php existiert bereits.\n";
}
-91
View File
@@ -1,91 +0,0 @@
<?php
/**
* MVC Module Generator for Gemini CLI Project
* Nutzung: php create_module.php <name>
*/
if (php_sapi_name() !== 'cli') {
die("Dieses Script kann nur über die Kommandozeile ausgeführt werden.\n");
}
$name = $argv[1] ?? null;
if (!$name) {
echo "Bitte gib einen Namen für das Modul an (z.B. mein_projekt): ";
$handle = fopen("php://stdin", "r");
$name = trim(fgets($handle));
fclose($handle);
}
if (!$name) {
die("Fehler: Kein Name angegeben.\n");
}
// Namen formatieren
$key = strtolower(str_replace(' ', '_', $name));
$className = str_replace(' ', '', ucwords(str_replace('_', ' ', $key)));
$controllerFile = __DIR__ . "/WEBROOT/src/MVC/Controller/{$className}Controller.php";
$viewFile = __DIR__ . "/WEBROOT/src/MVC/View/{$key}.php";
// 1. Controller erstellen
$controllerContent = <<<PHPCTRL
<?php
namespace MVC\Controller;
use MVC\Core\Controller;
// use MVC\Model\YourModel; // Importiere dein Model
class {$className}Controller extends Controller {
public function {$key}() {
// Nutze Models für den Datenbankzugriff
// \$data = YourModel::all();
return \$this->render('{$key}', [
'title' => '{$className} Modul',
'message' => 'Willkommen in deinem neuen Modul {$className}!'
]);
}
}
PHPCTRL;
if (!file_exists($controllerFile)) {
file_put_contents($controllerFile, $controllerContent);
echo "✅ Controller erstellt: WEBROOT/src/MVC/Controller/{$className}Controller.php\n";
} else {
echo "⚠️ Controller existiert bereits.\n";
}
// 2. View erstellen
$viewContent = <<<HTML
<h2><?= htmlspecialchars(\$title) ?></h2>
<div class="card">
<p><?= htmlspecialchars(\$message) ?></p>
<p>Hier kannst du nun dein Projekt aufbauen.</p>
</div>
<style>
.card {
background: var(--card-bg);
padding: 20px;
border-radius: 8px;
border: 1px solid var(--card-border);
}
</style>
HTML;
if (!file_exists($viewFile)) {
file_put_contents($viewFile, $viewContent);
echo "✅ View erstellt: WEBROOT/src/MVC/View/{$key}.php\n";
} else {
echo "⚠️ View existiert bereits.\n";
}
// 3. Anleitung ausgeben
echo "\n--- NÄCHSTE SCHRITTE ---\n";
echo "1. Registriere das Modul in WEBROOT/index.php beim Router:\n";
echo " \$router->add('{$key}', \\MVC\\Controller\\{$className}Controller::class, '{$key}', ['auth']);\n\n";
echo "2. Füge den Service in der Seitenverwaltung (Admin) oder in config.php hinzu:\n";
echo " ID: {$key}\n";
echo " Name: " . ucfirst($name) . "\n";
echo "------------------------\n";
+60 -134
View File
@@ -1,166 +1,92 @@
# 🏗️ Backend MVC Framework - Das Handbuch
# 🏗️ Backend MVC Framework - Die Entwickler-Bibel
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.
Dieses Framework ist eine performante, leichtgewichtige und modulare PHP-Architektur für professionelle Backends und Dashboards.
---
## 🏛️ 1. Architektur & Verzeichnisstruktur
## 🏛️ 1. Kern-Architektur & Lifecycle
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.
### 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.
### 📂 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.
### 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.
---
## 🚦 2. Der Request-Lifecycle (Wie eine Seite entsteht)
## 🚦 2. Routing & Sicherheit
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
### Routen definieren (`src/config/routes.php`)
```php
namespace MVC\Model;
use MVC\Core\Model;
$router->add('service_id', ControllerClass, 'method', ['middleware']);
```
### 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 = "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');
protected static $table = "Products";
}
// Nutzung
$all = Product::all();
$one = Product::find(1);
```
---
## 🧱 5. Das modulare Block-Layout
## 🛠️ 4. CLI Werkzeuge (`make.php`)
Das Layout-System ermöglicht es, verschiedene Teile der Seite (Header, Sidebar, Content) unabhängig voneinander zu befüllen.
Beschleunige die Entwicklung durch automatische Code-Generierung:
### 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);
```
* **Modul generieren:**
`php make.php module News -l "📰 News" -a`
*(Erstellt Controller, View, Route und Nav-Eintrag)*
### Blöcke im Template (`main.php`) ausgeben:
```html
<aside class="sidebar">
<?= $this->getBlock('sidebar') ?>
</aside>
```
* **Model generieren:**
`php make.php model Orders ID,USER_ID,TOTAL`
*(Erstellt Model-Klasse mit Spalten-Dokumentation)*
---
## 🚀 6. CLI-Werkzeuge für Entwickler
## 🧱 5. UI & Layout System
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
```
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') ?>`.
---
## 🛡️ 7. Sicherheit & Best Practices
## 🛡️ 6. Best Practices für Entwickler
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.
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.
---
## ⚡ 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.*
*Für eine detaillierte Dokumentation starte das Framework und rufe den Service `docs` auf.*
+2444 -278
View File
File diff suppressed because it is too large. Load diff