Files
MVC/init.php
T
2026-06-02 15:39:31 +02:00

3413 lines
120 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* Ultimate MVC Project Bootstrapper (Fixed Version with Header & Auth)
* Erstellt ein voll funktionsfähiges Projekt inkl. Dev-Server, Header, DarkMode & Auth-System.
*/
if (php_sapi_name() !== 'cli') {
die("Dieses Script muss über die CLI ausgeführt werden.\n");
}
$projectName = $argv[1] ?? null;
if (!$projectName) {
echo "Bitte gib einen Projektnamen an: ";
$handle = fopen("php://stdin", "r");
$projectName = trim(fgets($handle));
fclose($handle);
}
if (!$projectName) die("Fehler: Kein Name angegeben.\n");
$baseDir = getcwd() . DIRECTORY_SEPARATOR . $projectName;
// 1. Verzeichnisstruktur erstellen
$dirs = [
'',
'WEBROOT',
'WEBROOT/api',
'WEBROOT/api/v1',
'WEBROOT/commands',
'WEBROOT/static',
'WEBROOT/src',
'WEBROOT/src/config',
'WEBROOT/src/database',
'WEBROOT/src/utils',
'WEBROOT/src/MVC',
'WEBROOT/src/MVC/Core',
'WEBROOT/src/MVC/Classes',
'WEBROOT/src/MVC/Controller',
'WEBROOT/src/MVC/Model',
'WEBROOT/src/MVC/View',
'WEBROOT/src/MVC/View/admin',
'WEBROOT/src/MVC/View/templates',
'WEBROOT/src/MVC/Middleware',
'WEBROOT/src/MVC/Layout',
'WEBROOT/src/MVC/Layout/Templates',
'WEBROOT/storage',
'WEBROOT/storage/uploads',
'WEBROOT/logs',
];
foreach ($dirs as $dir) {
$path = $baseDir . ($dir ? DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $dir) : '');
if (!is_dir($path)) {
mkdir($path, 0755, true);
echo "📁 Ordner erstellt: $dir\n";
}
}
$files = [];
// --- DEV SERVER: server.php ---
$files['server.php'] = <<<'PHP'
<?php
$port = 8000;
$host = 'localhost';
$root = __DIR__ . '/WEBROOT';
echo "\n🚀 Starte MVC Dev-Server auf http://$host:$port\n";
echo "Drücke STRG+C zum Beenden.\n\n";
passthru("php -S $host:$port -t \"$root\" \"$root/index.php\"");
PHP;
$files['WEBROOT/logs/error.log'] = <<<'LOG'
LOG;
$files['.htaccess'] = <<<'HTACCESS'
RewriteEngine On
RewriteRule ^(Commands|config_backups|controller|modules)($|/.*) - [F,L,NC]
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
# -----------------------------------
# .php entfernen
# -----------------------------------
# browser requestet datei.php -> redirect zu /datei
RewriteCond %{THE_REQUEST} \s/+(.+?)\.php[\s?] [NC]
RewriteRule ^ /%1 [R=301,L]
# /datei -> datei.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{DOCUMENT_ROOT}/$1.php -f
RewriteRule ^(.+)$ $1.php [L]
HTACCESS;
$files[$projectName . '.code-workspace'] = <<<VSCODE
{
"folders": [
{
"path": "."
}
],
"settings": {
"workbench.colorTheme": "Dark 2026",
"workbench.colorCustomizations": {
"activityBar.background": "#162df7ec",
"activityBar.foreground": "#000000",
"breadcrumb.foreground": "#FFFFFF",
},
"files.exclude": {
"*.git*": true,
"WEBROOT/config_backups": true,
"WEBROOT/static": true,
"WEBROOT/templates": true,
},
"editor.renderWhitespace": "all",
"editor.smoothScrolling": true,
"editor.fontSize": 14,
"editor.formatOnSave": true,
"editor.minimap.enabled": true,
"editor.minimap.scale": 2,
"editor.minimap.side": "right",
"editor.minimap.maxColumn": 100,
"editor.minimap.renderCharacters": true,
"editor.minimap.showSlider": "always",
"files.autoSave": "off",
"files.autoSaveDelay": 3000,
"window.title": "MVC",
"workbench.editor.wrapTabs": true,
},
"extensions": {
"recommendations": [
"oderwat.indent-rainbow",
"pranaygp.vscode-css-peek",
"wayou.vscode-todo-highlight",
"mrmlnc.vscode-mustache",
"fabiospampinato.vscode-highlight",
"junstyle.php-cs-fixer",
"mtea.gettext-editor",
"neilbrayfield.php-docblocker",
"bmewburn.vscode-intelephense-client",
"mohamed-ali.html-css-class-completion",
"rifi2k.format-html-in-php",
"mrorz.language-gettext",
"voldemortensen.rainbow-tags",
]
}
}
VSCODE;
$files['WEBROOT/src/utils/databaseMethods.php'] = <<<'PHP'
<?php
function DatabaseCreateTable($table, $columnsDefinition) {
include_once(__DIR__ . '/../database/databaseConnect.php');
$conn = db_connect();
$sql = "CREATE TABLE IF NOT EXISTS `$table` ($columnsDefinition)";
$stmt = $conn->prepare($sql);
if (!$stmt) {
die("Fehler beim Prepare: " . $conn->error);
}
if (!$stmt->execute()) {
die("Fehler beim Execute: " . $stmt->error);
}
return true;
}
function DatabaseShowTables() {
include_once(__DIR__ . '/../database/databaseConnect.php');
$conn = db_connect();
$tablesResult = $conn->query("SHOW TABLES");
if (!$tablesResult) {
die("Fehler beim Laden der Tabellen: " . $conn->error);
}
$tables = [];
while ($tableRow = $tablesResult->fetch_array()) {
$tables[] = $tableRow[0];
}
return $tables;
}
function DatabaseShowTableStructure($table) {
include_once(__DIR__ . '/../database/databaseConnect.php');
$conn = db_connect();
$stmt = $conn->prepare("SHOW COLUMNS FROM `$table`");
if (!$stmt) {
die("Fehler beim Prepare: " . $conn->error);
}
if (!$stmt->execute()) {
die("Fehler beim Execute: " . $stmt->error);
}
$result = $stmt->get_result();
$columns = [];
while ($column = $result->fetch_assoc()) {
$columns[] = [
"Field" => $column["Field"],
"Type" => $column["Type"],
"Null" => $column["Null"],
"Key" => $column["Key"],
"Default" => $column["Default"],
"Extra" => $column["Extra"]
];
}
return $columns;
}
function DatabaseShow($table, $columns = "*", $where = "", ...$values) {
include_once(__DIR__ . '/../database/databaseConnect.php');
$conn = db_connect();
$sql = "SELECT $columns FROM `$table`";
if (!empty($where)) {
$sql .= " WHERE $where";
}
$stmt = $conn->prepare($sql);
if (!$stmt) {
die("Fehler beim Prepare: " . $conn->error);
}
if (!$stmt->execute($values)) {
die("Fehler beim Execute: " . $stmt->error);
}
$result = $stmt->get_result();
return $result->fetch_all(MYSQLI_ASSOC);
}
function DatabaseInsert($table, $columns, ...$values) {
include_once(__DIR__ . '/../database/databaseConnect.php');
$conn = db_connect();
$placeholders = rtrim(str_repeat("?,", count($values)), ",");
$sql = "INSERT INTO `$table` ($columns) VALUES ($placeholders)";
$stmt = $conn->prepare($sql);
if (!$stmt) {
die("Fehler beim Prepare: " . $conn->error);
}
if (!$stmt->execute($values)) {
die("Fehler beim Execute: " . $stmt->error);
}
return true;
}
function DatabaseDelete($table, $where, ...$values) {
include_once(__DIR__ . '/../database/databaseConnect.php');
$conn = db_connect();
$sql = "DELETE FROM `$table` WHERE $where";
$stmt = $conn->prepare($sql);
if (!$stmt) {
die("Fehler beim Prepare: " . $conn->error);
}
if (!$stmt->execute($values)) {
die("Fehler beim Execute: " . $stmt->error);
}
return true;
}
function DatabaseUpdate($table, $set, $where, ...$values) {
include_once(__DIR__ . '/../database/databaseConnect.php');
$conn = db_connect();
$sql = "UPDATE `$table` SET $set WHERE $where";
$stmt = $conn->prepare($sql);
if (!$stmt) {
die("Fehler beim Prepare: " . $conn->error);
}
if (!$stmt->execute($values)) {
die("Fehler beim Execute: " . $stmt->error);
}
return true;
}
function getFKValue($conn, $table, $key, $value, $id, &$cache) {
if ($id === null) return null;
$cacheKey = $table . "_" . $id;
if (isset($cache[$cacheKey])) {
return $cache[$cacheKey];
}
$stmt = $conn->prepare("SELECT `$value` FROM `$table` WHERE `$key` = ? LIMIT 1");
if (!$stmt) {
die("FK Prepare Fehler: " . $conn->error);
}
$stmt->bind_param("i", $id);
$stmt->execute();
$result = $stmt->get_result()->fetch_assoc();
$valueResult = $result[$value] ?? null;
$cache[$cacheKey] = $valueResult;
return $valueResult;
}
function DatabaseShowWithFK($table, $fkMap = [], $columns = "*", $where = "", ...$values) {
include_once(__DIR__ . '/../database/databaseConnect.php');
$conn = db_connect();
$cache = [];
$select = is_array($columns) ? implode(", ", $columns) : $columns;
$sql = "SELECT $select FROM `$table`";
if (!empty($where)) {
$sql .= " WHERE $where";
}
$stmt = $conn->prepare($sql);
if (!$stmt) die("Fehler beim Prepare: " . $conn->error);
if (!empty($values)) {
$stmt->execute($values);
} else {
$stmt->execute();
}
$result = $stmt->get_result();
if (!$result) die("Fehler beim get_result: " . $conn->error);
$rows = $result->fetch_all(MYSQLI_ASSOC);
foreach ($rows as &$row) {
foreach ($fkMap as $fkColumn => $fkInfo) {
if (!isset($row[$fkColumn]) || $row[$fkColumn] === null) {
continue;
}
$row[$fkColumn] = getFKValue(
$conn,
$fkInfo['table'],
$fkInfo['key'],
$fkInfo['value'],
$row[$fkColumn],
$cache
);
}
}
return [
"rows" => $rows
];
}
PHP;
// --- CONFIG: env.ini ---
$files['WEBROOT/src/config/env.ini'] = <<<INI
[database]
APP_ENV = "dev"
DB_ROOT_USERNAME = "root"
DB_ROOT_PASSWORD = ""
DB_DEV_HOST = "localhost"
DB_DEV_USERNAME = "worker"
DB_DEV_PASSWORD = "worker_password"
DB_DEV_DATABASE = "project_db"
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"
INI;
// --- DB: databaseConnect.php ---
$files['WEBROOT/src/database/databaseConnect.php'] = <<<'PHP'
<?php
function db_connect() {
$config = parse_ini_file(__DIR__ . '/../config/env.ini');
$env = strtoupper($config['APP_ENV']);
try {
$conn = new mysqli(
$config["DB_{$env}_HOST"],
$config["DB_{$env}_USERNAME"],
$config["DB_{$env}_PASSWORD"],
$config["DB_{$env}_DATABASE"]
);
$conn->set_charset("utf8mb4");
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;
}
}
PHP;
// -- CLASSES --
$files['WEBROOT/src/MVC/Classes/Api.php'] = <<<'PHP'
<?php
namespace MVC\Classes;
class Api {
public static function jsonResponse($data, int $statusCode = 200) {
header('Content-Type: application/json');
http_response_code($statusCode);
echo json_encode(
[
'status' => $statusCode === 200 ? 'success' : 'error',
'data' => $data,
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE
);
exit;
}
}
PHP;
$files['WEBROOT/src/MVC/Classes/Client.php'] = <<<'PHP'
<?php
namespace MVC\Classes;
class Client {
public static function ip() {
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ipList = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
return trim($ipList[0]);
} else if (!empty($_SERVER['REMOTE_ADDR'])) {
return $_SERVER['REMOTE_ADDR'];
} else {
return 'UNKNOWN';
}
}
public static function port() {
return $_SERVER['REMOTE_PORT'] ?? 'UNKNOWN';
}
public static function country() {
$response = file_get_contents('http://ip-api.com/json/{' . self::ip() . '}?fields=country,countryCode');
$data = json_decode($response);
return $data->country ?? 'UNKNOWN';
}
public static function userAgent() {
return $_SERVER['HTTP_USER_AGENT'] ?? 'UNKNOWN';
}
}
PHP;
$files['WEBROOT/src/MVC/Classes/Cookie.php'] = <<<'PHP'
<?php
namespace MVC\Classes;
class Cookie {
public function set(string $CookieName, int $CookieValue, int $CookieTime) {
setcookie($CookieName, $CookieValue, time()+($CookieTime*60));
}
public function read(string $CookieName) {
if (isset($_COOKIE[$CookieName])) {
return $_COOKIE[$CookieName];
}
return 'Cookie not set';
}
public function Delete(string $CookieName) {
setcookie($CookieName, "", time()-(3600*24));
}
}
PHP;
$files['WEBROOT/src/MVC/Classes/Request.php'] = <<<'PHP'
<?php
namespace MVC\Classes;
class Request {
public static function method() {
return $_SERVER['REQUEST_METHOD'] ?? 'UNKNOWN';
}
public static function uri() {
return $_SERVER['REQUEST_URI'] ?? 'UNKNOWN';
}
public static function headers() {
return getallheaders();
}
public static function body() {
return file_get_contents('php://input');
}
public static function queryParams() {
return $_GET;
}
public static function time() {
return $_SERVER['REQUEST_TIME'] ?? 'UNKNOWN';
}
public static function referrer() {
return $_SERVER['HTTP_REFERER'] ?? 'UNKNOWN';
}
}
PHP;
$files['WEBROOT/src/MVC/Classes/Logger.php'] = <<<'PHP'
<?php
namespace MVC\Classes;
class Logger {
private static function getLogDir() { return dirname(__DIR__, 3) . DIRECTORY_SEPARATOR . 'logs'; }
public static function log($message, $level = 'INFO', $file = 'system.log') {
$logDir = self::getLogDir();
if (!is_dir($logDir) && !mkdir($logDir, 0777, true)) return;
$timestamp = date('Y-m-d H:i:s');
file_put_contents($logDir . DIRECTORY_SEPARATOR . $file, "[$timestamp] [$level] $message\n", FILE_APPEND | LOCK_EX);
}
public static function logException(\Throwable $e) {
$msg = 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($msg, 'CRITICAL', 'error.log');
}
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'];
self::log("$errstr in $errfile on line $errline", $levels[$errno] ?? 'UNKNOWN', 'error.log');
}
}
PHP;
$files['WEBROOT/src/MVC/Classes/Mailer.php'] = <<<'PHP'
<?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);
}
}
PHP;
$files['WEBROOT/src/MVC/Classes/Server.php'] = <<<'PHP'
<?php
namespace MVC\Classes;
class Server {
public static function documentRoot() {
return $_SERVER['DOCUMENT_ROOT'] ?? 'UNKNOWN';
}
public static function currentDir() {
return __DIR__;
}
public static function currentScript() {
return $_SERVER['SCRIPT_NAME'] ?? 'UNKNOWN';
}
public static function name() {
return $_SERVER['SERVER_NAME'] ?? 'UNKNOWN';
}
public static function port() {
return $_SERVER['SERVER_PORT'] ?? 'UNKNOWN';
}
}
PHP;
$files['WEBROOT/src/MVC/Classes/Auth.php'] = <<<'PHP'
<?php
namespace MVC\Classes;
use MVC\Model\Users;
class Auth {
/**
* Startet die Session falls noch nicht geschehen
*/
private static function init() {
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
}
/**
* Versucht einen Benutzer einzuloggen
*/
public static function login(string $username, string $password): bool {
self::init();
$user = Users::getByUsername($username);
if ($user && password_verify($password, $user['PASSWORD'])) {
$_SESSION['user_logged_in'] = true;
$_SESSION['user_id'] = $user['ID'];
$_SESSION['username'] = $user['USERNAME'];
$_SESSION['role'] = $user['ROLE'];
$_SESSION['is_admin'] = ($user['ROLE'] === 'admin');
return true;
}
return false;
}
/**
* Loggt den aktuellen Benutzer aus
*/
public static function logout() {
self::init();
session_destroy();
}
/**
* Prüft ob ein Benutzer eingeloggt ist
*/
public static function check(): bool {
self::init();
return isset($_SESSION['user_logged_in']) && $_SESSION['user_logged_in'] === true;
}
/**
* Gibt den aktuellen Benutzer-Datensatz zurück
*/
public static function user() {
self::init();
if (!self::check()) return null;
return [
'id' => $_SESSION['user_id'],
'username' => $_SESSION['username'],
'role' => $_SESSION['role'],
'is_admin' => $_SESSION['is_admin']
];
}
/**
* Prüft ob der Benutzer eine bestimmte Rolle hat
*/
public static function hasRole(string $role): bool {
self::init();
return self::check() && $_SESSION['role'] === $role;
}
/**
* Prüft ob der Benutzer Admin ist
*/
public static function isAdmin(): bool {
return self::hasRole('admin');
}
}
PHP;
$files['WEBROOT/src/MVC/Classes/Form.php'] = <<<'PHP'
<?php
namespace MVC\Classes;
class Form {
/**
* Startet ein HTML Formular
*/
public static function open(string $service, string $method = 'POST', array $options = []): string {
$enctype = isset($options['enctype']) ? ' enctype="' . $options['enctype'] . '"' : '';
$class = isset($options['class']) ? ' class="' . $options['class'] . '"' : '';
$id = isset($options['id']) ? ' id="' . $options['id'] . '"' : '';
return "<form action=\"?service=$service\" method=\"$method\"$enctype$class$id>";
}
/**
* Schließt ein HTML Formular
*/
public static function close(): string {
return "</form>";
}
/**
* Erstellt ein Input-Feld mit Label und Styling
*/
public static function input(string $name, string $label = '', string $type = 'text', $value = '', array $options = []): string {
$placeholder = $options['placeholder'] ?? '';
$required = isset($options['required']) && $options['required'] ? 'required' : '';
$class = $options['class'] ?? 'form-control';
$html = "<div class=\"form-group\">";
if ($label) {
$html .= "<label for=\"$name\">$label</label>";
}
$html .= "<input type=\"$type\" name=\"$name\" id=\"$name\" value=\"" . htmlspecialchars($value) . "\" class=\"$class\" placeholder=\"$placeholder\" $required>";
$html .= "</div>";
return $html;
}
/**
* Erstellt eine Textarea
*/
public static function textarea(string $name, string $label = '', string $value = '', array $options = []): string {
$placeholder = $options['placeholder'] ?? '';
$rows = $options['rows'] ?? 3;
$class = $options['class'] ?? 'form-control';
$html = "<div class=\"form-group\">";
if ($label) {
$html .= "<label for=\"$name\">$label</label>";
}
$html .= "<textarea name=\"$name\" id=\"$name\" class=\"$class\" rows=\"$rows\" placeholder=\"$placeholder\">" . htmlspecialchars($value) . "</textarea>";
$html .= "</div>";
return $html;
}
/**
* Erstellt einen Submit-Button
*/
public static function submit(string $label = 'Speichern', string $class = 'btn btn-primary'): string {
return "<button type=\"submit\" class=\"$class\">$label</button>";
}
/**
* Einfache Validierung von Formulardaten
*/
public static function validate(array $data, array $rules): array {
$errors = [];
foreach ($rules as $field => $fieldRules) {
$value = $data[$field] ?? null;
$rulesArray = explode('|', $fieldRules);
foreach ($rulesArray as $rule) {
if ($rule === 'required' && empty($value)) {
$errors[$field] = "Das Feld $field ist erforderlich.";
}
if ($rule === 'email' && !filter_var($value, FILTER_VALIDATE_EMAIL)) {
$errors[$field] = "Bitte geben Sie eine gültige E-Mail Adresse an.";
}
if (strpos($rule, 'min:') === 0) {
$min = (int)substr($rule, 4);
if (strlen($value) < $min) {
$errors[$field] = "Das Feld $field muss mindestens $min Zeichen lang sein.";
}
}
}
}
return $errors;
}
}
PHP;
$files['WEBROOT/src/MVC/Classes/Storage.php'] = <<<'PHP'
<?php
namespace MVC\Classes;
class Storage {
private static function getBaseDir() {
return dirname(__DIR__, 3) . DIRECTORY_SEPARATOR . 'storage';
}
/**
* Lädt eine Datei hoch
*/
public static function upload(string $fileKey, string $subDir = 'uploads'): array {
if (!isset($_FILES[$fileKey]) || $_FILES[$fileKey]['error'] !== UPLOAD_ERR_OK) {
return ['success' => false, 'error' => 'Keine Datei oder Upload-Fehler.'];
}
$baseDir = self::getBaseDir();
$targetDir = $baseDir . DIRECTORY_SEPARATOR . $subDir;
if (!is_dir($targetDir)) {
mkdir($targetDir, 0755, true);
}
$fileName = basename($_FILES[$fileKey]['name']);
$targetPath = $targetDir . DIRECTORY_SEPARATOR . $fileName;
// Sicherheitsprüfung: existiert bereits?
if (file_exists($targetPath)) {
$fileName = time() . '_' . $fileName;
$targetPath = $targetDir . DIRECTORY_SEPARATOR . $fileName;
}
if (move_uploaded_file($_FILES[$fileKey]['tmp_name'], $targetPath)) {
return [
'success' => true,
'name' => $fileName,
'path' => $subDir . '/' . $fileName,
'size' => $_FILES[$fileKey]['size']
];
}
return ['success' => false, 'error' => 'Fehler beim Verschieben der Datei.'];
}
/**
* Listet Dateien in einem Verzeichnis auf
*/
public static function listFiles(string $subDir = 'uploads'): array {
$dir = self::getBaseDir() . DIRECTORY_SEPARATOR . $subDir;
if (!is_dir($dir)) return [];
$files = [];
foreach (scandir($dir) as $file) {
if ($file === '.' || $file === '..') continue;
$path = $dir . DIRECTORY_SEPARATOR . $file;
$files[] = [
'name' => $file,
'size' => round(filesize($path) / 1024, 2),
'date' => date('Y-m-d H:i:s', filemtime($path))
];
}
return $files;
}
/**
* Löscht eine Datei
*/
public static function delete(string $path): bool {
$fullPath = self::getBaseDir() . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $path);
if (file_exists($fullPath) && is_file($fullPath)) {
return unlink($fullPath);
}
return false;
}
/**
* Erzwingt den Download einer Datei
*/
public static function download(string $path) {
$fullPath = self::getBaseDir() . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $path);
if (file_exists($fullPath) && is_file($fullPath)) {
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . basename($fullPath) . '"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($fullPath));
readfile($fullPath);
exit;
}
die("Datei nicht gefunden.");
}
}
PHP;
// --- CORE ---
$files['WEBROOT/src/MVC/Core/Controller.php'] = <<<'PHP'
<?php
namespace MVC\Core;
class Controller {
protected $view;
public function __construct() {
$this->view = new View(__DIR__ . '/../View');
}
protected function render($template, $data = []) {
foreach ($data as $key => $value) {
$this->view->assign($key, $value);
}
return $this->view->render($template);
}
protected function json($data, $status = 200) {
header('Content-Type: application/json');
http_response_code($status);
echo json_encode($data);
exit;
}
}
PHP;
$files['WEBROOT/src/MVC/Core/Model.php'] = <<<'PHP'
<?php
namespace MVC\Core;
require_once __DIR__ . '/../../database/databaseConnect.php';
require_once __DIR__ . '/../../utils/databaseMethods.php';
abstract class Model {
protected static $table;
protected static $conn;
protected static function getConnection() {
if (!self::$conn) {
self::$conn = \db_connect();
}
return self::$conn;
}
public static function all() {
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;
}
public static function where($where, ...$params) {
return \DatabaseShow(static::$table, "*", $where, ...$params);
}
public static function insert($data) {
$columns = implode(", ", array_keys($data));
$values = array_values($data);
return \DatabaseInsert(static::$table, $columns, ...$values);
}
public static function update($data, $where, ...$whereParams) {
$sets = [];
$values = [];
foreach ($data as $column => $value) {
$sets[] = "$column = ?";
$values[] = $value;
}
$setString = implode(", ", $sets);
return \DatabaseUpdate(static::$table, $setString, $where, ...array_merge($values, $whereParams));
}
public static function delete($where, ...$params) {
return \DatabaseDelete(static::$table, $where, ...$params);
}
public static function query($sql, ...$params) {
$conn = self::getConnection();
if (empty($params)) {
return $conn->query($sql);
}
$stmt = $conn->prepare($sql);
$types = str_repeat('s', count($params));
$stmt->bind_param($types, ...$params);
$stmt->execute();
return $stmt->get_result();
}
}
PHP;
$files['WEBROOT/src/MVC/Core/View.php'] = <<<'PHP'
<?php
namespace MVC\Core;
class View {
protected $data = [];
protected $viewPath;
public function __construct($viewPath) {
$this->viewPath = $viewPath;
}
public function assign($key, $value) {
$this->data[$key] = $value;
}
public function render($template) {
extract($this->data);
$file = $this->viewPath . '/' . $template . '.php';
if (file_exists($file)) {
ob_start();
include $file;
return ob_get_clean();
}
return "View $template not found at $file";
}
}
PHP;
$files['WEBROOT/src/MVC/Core/Router.php'] = <<<'PHP'
<?php
namespace MVC\Core;
class Router {
protected $routes = [];
protected $globalMiddlewares = [];
public function addGlobal($middleware) {
$this->globalMiddlewares[] = $middleware;
}
public function add($service, $controller, $method, $middlewares = [], $isApi = null) {
// Auto-detect API route if $isApi is not explicitly set
if ($isApi === null) {
$isApi = strpos($service, 'api/') === 0;
}
$this->routes[$service] = [
'controller' => $controller,
'method' => $method,
'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];
// 2. Run Route-Specific Middlewares
foreach ($route['middlewares'] as $middleware) {
$this->runMiddleware($middleware);
}
$controllerClass = $route['controller'];
$method = $route['method'];
if (!class_exists($controllerClass)) {
throw new \Exception("Controller class '$controllerClass' not found.");
}
$controller = new $controllerClass();
if (!method_exists($controller, $method)) {
throw new \Exception("Method '$method' not found in controller '$controllerClass'.");
}
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) {
Middleware::run($middleware);
}
}
PHP;
$files['WEBROOT/src/MVC/Core/Middleware.php'] = <<<'PHP'
<?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,
'block' => \MVC\Middleware\BlockMiddleware::class,
'global' => \MVC\Middleware\EveryRequestMiddleware::class,
];
/**
* Führt eine Middleware aus.
*/
public static function run($name) {
if (!isset(self::$map[$name])) {
return; // Oder Exception werfen
}
$class = self::$map[$name];
$middleware = new $class();
if (!$middleware instanceof MiddlewareInterface) {
throw new \Exception("Middleware '$class' muss MiddlewareInterface implementieren.");
}
$middleware->handle();
}
}
PHP;
$files['WEBROOT/src/MVC/Core/MiddlewareInterface.php'] = <<<'PHP'
<?php
namespace MVC\Core;
interface MiddlewareInterface {
/**
* Hauptmethode der Middleware.
*/
public function handle();
}
PHP;
$files['WEBROOT/src/MVC/Middleware/AdminMiddleware.php'] = <<<'PHP'
<?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.");
}
}
}
PHP;
$files['WEBROOT/src/MVC/Middleware/AuthMiddleware.php'] = <<<'PHP'
<?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;
}
}
}
PHP;
$files['WEBROOT/src/MVC/Middleware/BlockMiddleware.php'] = <<<'PHP'
<?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');
http_response_code(403);
die("Ihre IP-Adresse wurde blockiert.");
}
}
}
PHP;
$files['WEBROOT/src/MVC/Middleware/EveryRequestMiddleware.php'] = <<<'PHP'
<?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';
$service = $_GET['service'] ?? 'home';
Logger::log("IP: $ip - Service: $service", 'ACCESS', 'access.log');
}
}
PHP;
$files['WEBROOT/src/MVC/Layout/Layout.php'] = <<<'PHP'
<?php
namespace MVC\Layout;
class Layout {
protected $blocks = [];
protected $template;
public function __construct($template = 'main') {
$this->template = $template;
}
public function setBlock($name, $content) {
$this->blocks[$name] = $content;
}
public function getBlock($name) {
return $this->blocks[$name] ?? '';
}
public function render() {
$layoutFile = __DIR__ . '/Templates/' . $this->template . '.php';
if (file_exists($layoutFile)) {
ob_start();
include $layoutFile;
return ob_get_clean();
}
return "Layout template nicht gefunden.";
}
}
PHP;
$files['WEBROOT/src/MVC/autoloader.php'] = <<<'PHP'
<?php
spl_autoload_register(function ($class) {
$prefix = 'MVC\\';
$base_dir = __DIR__ . '/';
$len = strlen($prefix);
if (strncmp($prefix, $class, $len) !== 0) {
return;
}
$relative_class = substr($class, $len);
$file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
if (file_exists($file)) {
require $file;
}
});
PHP;
// --- LAYOUT TEMPLATE ---
$files['WEBROOT/src/MVC/Layout/Templates/main.php'] = <<<'HTML'
<!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>
HTML;
// --- CONTROLLERS ---
$files['WEBROOT/src/MVC/Controller/AdminController.php'] = <<<'PHP' <?php
namespace MVC\Controller;
use MVC\Core\Controller;
use MVC\Classes\Mailer;
use MVC\Model\Users;
class AdminController extends Controller {
public function mail_service() {
$msg = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$to = $_POST['to'] ?? '';
if ($to !== '' && !filter_var($to, FILTER_VALIDATE_EMAIL)) $msg = ["type" => "error", "text" => "Bitte eine gültige Empfänger-Adresse angeben."];
elseif (Mailer::send($_POST['subject'] ?? '', $_POST['body'] ?? '', null, true, $to)) $msg = ["type" => "success", "text" => "Mail gesendet!"];
else $msg = ["type" => "error", "text" => "Fehler!"];
}
return $this->render('admin/mail', ['message' => $msg]);
}
public function user_service() {
$msg = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$user = $_POST['username'] ?? '';
$pass = $_POST['password'] ?? '';
if ($user && $pass) {
if (Users::insert(['USERNAME' => $user, 'PASSWORD' => password_hash($pass, PASSWORD_DEFAULT), 'EMAIL' => $_POST['email'] ?? '', 'ROLE' => $_POST['role'] ?? 'user'])) $msg = ["type" => "success", "text" => "User $user erstellt!"];
else $msg = ["type" => "error", "text" => "Fehler (DB)!"];
} else $msg = ["type" => "error", "text" => "Name/Passwort fehlen!"];
}
return $this->render('admin/users', ['message' => $msg]);
}
}
PHP;
$files['WEBROOT/src/MVC/View/admin/mail.php'] = <<<'HTML'
<h2>📧 E-Mail Service</h2>
<?php if (isset($message)): ?> <div
style="padding: 15px; margin-bottom: 20px; border-radius: 5px; background: <?= $message['type'] === 'success' ? 'rgba(46, 204, 113, 0.2)' : 'rgba(231, 76, 60, 0.2)' ?>; 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>
HTML;
$files['WEBROOT/src/MVC/View/admin/users.php'] = <<<'HTML' <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)'?>;">
<?= $message['text']?></div>
<?php endif; ?>
<form method="POST" style="background:var(--sidebar);padding:20px;border-radius:8px;">
<input type="text" name="username" placeholder="Benutzername" required
style="width:100%;margin-bottom:10px;padding:10px;background:var(--bg);color:var(--text);border:1px solid var(--border);">
<input type="password" name="password" placeholder="Passwort" required
style="width:100%;margin-bottom:10px;padding:10px;background:var(--bg);color:var(--text);border:1px solid var(--border);">
<input type="email" name="email" placeholder="E-Mail"
style="width:100%;margin-bottom:10px;padding:10px;background:var(--bg);color:var(--text);border:1px solid var(--border);">
<select name="role"
style="width:100%;margin-bottom:10px;padding:10px;background:var(--bg);color:var(--text);border:1px solid var(--border);">
<option value="user">User</option>
<option value="admin">Admin</option>
</select>
<button type="submit"
style="background:var(--accent);color:white;padding:10px 20px;border:none;border-radius:4px;cursor:pointer;">Erstellen</button>
</form>
HTML;
$files['WEBROOT/src/MVC/Controller/ApiController.php'] = <<<'PHP' <?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',
]);
}
}
PHP;
$files['WEBROOT/src/MVC/Controller/HomeController.php'] = <<<'PHP'
<?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() {
return $this->render('home');
}
public function toggle_theme() {
$_SESSION['theme'] = ($_SESSION['theme'] ?? 'light') === 'dark' ? 'light' : 'dark';
return $this->json(
[
'success' => true
]
);
}
}
PHP;
$files['WEBROOT/src/MVC/Controller/AuthController.php'] = <<<'PHP'
<?php
namespace MVC\Controller;
use MVC\Core\Controller;
use MVC\Model\Users;
class AuthController extends Controller {
public function login() {
if (isset($_SESSION['user_logged_in'])) {
header('Location: ?service=home');
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
$user = Users::getByUsername($username);
if ($user && password_verify($password, $user['PASSWORD'])) {
$_SESSION['user_logged_in'] = true;
$_SESSION['username'] = $user['USERNAME'];
$_SESSION['role'] = $user['ROLE'];
$_SESSION['is_admin'] = ($user['ROLE'] === 'admin');
header('Location: ?service=home');
exit;
} else {
return $this->render('login', ['error' => 'Ungültige Anmeldedaten']);
}
}
return $this->render('login');
}
public function logout() {
session_destroy();
header('Location: ?service=login');
exit;
}
}
PHP;
$files['WEBROOT/src/MVC/Controller/DocsController.php'] = <<<'PHP'
<?php
namespace MVC\Controller;
use MVC\Core\Controller;
class DocsController extends Controller {
public function index() {
return $this->render('docs', [
'title' => '📚 System-Dokumentation'
]);
}
}
PHP;
$files['WEBROOT/src/MVC/Controller/TestController.php'] = <<<'PHP'
<?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'
]
]
);
}
}
PHP;
$files['WEBROOT/src/MVC/Controller/TemplateController.php'] = <<<'PHP'
<?php
namespace MVC\Controller;
use MVC\Core\Controller;
use MVC\Classes\Storage;
use MVC\Classes\Form;
class TemplateController extends Controller {
public function upload() {
$message = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$upload = Storage::upload('file_upload');
if ($upload['success']) {
$message = [
'type' => 'success',
'text' => 'Datei "' . $upload['name'] . '" erfolgreich hochgeladen.'
];
} else {
$message = [
'type' => 'error',
'text' => $upload['error']
];
}
}
return $this->render('templates/upload', [
'message' => $message,
'current_service' => 'template_upload'
]);
}
public function download() {
$files = Storage::listFiles();
return $this->render('templates/download', [
'files' => $files
]);
}
public function data_form() {
$errors = [];
$message = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$errors = Form::validate($_POST, [
'firstname' => 'required',
'lastname' => 'required',
'email' => 'required|email'
]);
if (empty($errors)) {
$message = ['type' => 'success', 'text' => 'Daten erfolgreich validiert und gespeichert!'];
}
}
return $this->render('templates/data_form', [
'current_service' => 'template_form',
'errors' => $errors,
'message' => $message
]);
}
public function data_table() {
return $this->render('templates/data_table');
}
public function download_file() {
$fileName = $_GET['file'] ?? null;
if ($fileName) {
// Wir hängen 'uploads/' davor, da Storage::upload standardmäßig dorthin speichert
Storage::download('uploads/' . $fileName);
}
header('Location: ?service=template_download');
exit;
}
}
PHP;
// --- API ---
$files['WEBROOT/api/v1/get.php'] = <<<'PHP'
<?php
header('Content-Type: application/json');
require_once __DIR__ . '/../../src/MVC/autoloader.php';
use MVC\Controller\ApiController;
$api = (new ApiController());
try {
echo ($api->get());
} catch (\Exception $e) {
echo ($e->getMessage());
}
PHP;
// --- VIEWS ---
$files['WEBROOT/src/MVC/View/home.php'] = "<h1>Dashboard</h1><p>Willkommen in deinem neuen MVC-System mit integriertem Header.</p>";
$files['WEBROOT/src/MVC/View/test1.php'] = "<h1>Service 1</h1><p><?= \$msg ?></p>";
$files['WEBROOT/src/MVC/View/test2.php'] = "<h1>System Check</h1>
<ul><?php foreach(\$data as \$k => \$v): ?><li><b><?= \$k ?>:</b> <?= \$v ?></li><?php endforeach; ?></ul>";
$files['WEBROOT/src/MVC/View/docs.php'] = <<<'HTML' <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>
HTML;
$files['WEBROOT/src/MVC/View/login.php'] = <<<'HTML' <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>
HTML;
$files['WEBROOT/src/MVC/View/templates/data_form.php'] = <<<'HTML' <h2>📝 Daten-Formular</h2>
<?php use MVC\Classes\Form; ?>
<?php if (isset($message)): ?>
<div style="padding:15px;margin-bottom:20px;border-radius:5px;background:rgba(46,204,113,0.2);">
<?= $message['text']?></div>
<?php endif; ?>
<?php if (!empty($errors)): ?>
<div
style="padding:15px;margin-bottom:20px;border-radius:5px;background:rgba(231,76,60,0.2);color:#c0392b;">
<ul>
<?php foreach($errors as $error): ?>
<li><?= $error ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<div class="card">
<?= Form::open($current_service) ?>
<div class="form-row">
<div class="form-group col">
<?= Form::input('firstname', 'Vorname', 'text', $_POST['firstname'] ?? '', ['placeholder' => 'Max', 'required' => true]) ?>
</div>
<div class="form-group col">
<?= Form::input('lastname', 'Nachname', 'text', $_POST['lastname'] ?? '', ['placeholder' => 'Mustermann', 'required' => true]) ?>
</div>
</div>
<?= Form::input('email', 'E-Mail Adresse', 'email', $_POST['email'] ?? '', ['placeholder' => 'max@example.com', 'required' => true]) ?>
<div class="form-group">
<label for="category">Kategorie</label>
<select name="category" id="category" class="form-control">
<option value="support">Support</option>
<option value="sales">Vertrieb</option>
<option value="other">Sonstiges</option>
</select>
</div>
<div class="form-group">
<label>Interessen</label>
<div class="checkbox-group">
<label class="checkbox-label"><input type="checkbox" name="interests[]"
value="coding"> Coding</label>
<label class="checkbox-label"><input type="checkbox" name="interests[]"
value="design"> Design</label>
<label class="checkbox-label"><input type="checkbox" name="interests[]"
value="marketing"> Marketing</label>
</div>
</div>
<div class="form-group">
<label>Newsletter</label>
<div class="radio-group">
<label class="radio-label"><input type="radio" name="newsletter" value="yes"
checked> Ja, ich möchte den Newsletter</label>
<label class="radio-label"><input type="radio" name="newsletter" value="no"> Nein,
danke</label>
</div>
</div>
<?= Form::submit('Daten speichern') ?>
<button type="reset" class="btn btn-secondary">Abbrechen</button>
<?= Form::close() ?>
</div>
<style>
.card {
background: var(--sidebar);
padding: 25px;
border-radius: 8px;
border: 1px solid var(--border);
}
.form-row {
display: flex;
gap: 20px;
margin-bottom: 20px;
}
.col {
flex: 1;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 600;
}
.form-control {
width: 100%;
padding: 12px;
border: 1px solid var(--border);
border-radius: 4px;
background: var(--bg);
color: var(--text);
box-sizing: border-box;
}
.checkbox-group,
.radio-group {
display: flex;
flex-direction: column;
gap: 10px;
margin-top: 5px;
}
.checkbox-label,
.radio-label {
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
font-weight: normal;
}
.btn {
padding: 12px 24px;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
margin-right: 10px;
}
.btn-primary {
background: var(--accent);
color: white;
}
.btn-secondary {
background: #95a5a6;
color: white;
}
.btn:hover {
opacity: 0.9;
}
</style>
HTML;
$files['WEBROOT/src/MVC/View/templates/data_table.php'] = <<<'HTML' <h2>📊 Datenübersicht</h2>
<div class="toolbar">
<div class="search-box">
<input type="text" placeholder="Suchen..." id="tableSearch" class="form-control">
</div>
<div class="actions">
<button class="btn btn-primary">+ Neuer Eintrag</button>
<button class="btn btn-secondary">Exportieren</button>
</div>
</div>
<div class="card">
<table class="data-table">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Status</th>
<th>Datum</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
<?php for($i=1; $i<=5; $i++): ?>
<tr>
<td>#<?= 1000 + $i ?></td>
<td><strong>Beispiel Datensatz <?= $i ?></strong></td>
<td><span class="badge badge-success">Aktiv</span></td>
<td><?= date('d.m.Y') ?></td>
<td>
<button class="btn-icon">✏️</button>
<button class="btn-icon">🗑️</button>
</td>
</tr>
<?php endfor; ?>
</tbody>
</table>
</div>
<style>
.toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
gap: 20px;
}
.search-box {
flex: 1;
max-width: 300px;
}
.form-control {
width: 100%;
padding: 10px;
border: 1px solid var(--border);
border-radius: 4px;
background: var(--bg);
color: var(--text);
}
.btn {
padding: 10px 18px;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
}
.btn-primary {
background: var(--accent);
color: white;
}
.btn-secondary {
background: var(--sidebar);
border: 1px solid var(--border);
color: var(--text);
}
.card {
background: var(--sidebar);
border-radius: 8px;
border: 1px solid var(--border);
overflow: hidden;
}
.data-table {
width: 100%;
border-collapse: collapse;
}
.data-table th,
.data-table td {
text-align: left;
padding: 15px;
border-bottom: 1px solid var(--border);
}
.data-table th {
background: rgba(0, 0, 0, 0.03);
font-weight: bold;
}
.badge {
padding: 4px 8px;
border-radius: 12px;
font-size: 0.75rem;
font-weight: bold;
}
.badge-success {
background: #2ecc71;
color: white;
}
.btn-icon {
background: none;
border: none;
cursor: pointer;
font-size: 1.1rem;
padding: 5px;
}
</style>
HTML;
$files['WEBROOT/src/MVC/View/templates/download.php'] = <<<'HTML' <h2>📥 Datei Downloads
</h2>
<div class="card">
<table class="data-table">
<thead>
<tr>
<th>Dateiname</th>
<th>Größe</th>
<th>Datum</th>
<th>Aktion</th>
</tr>
</thead>
<tbody>
<?php if (empty($files)): ?>
<tr>
<td colspan="4" style="text-align: center; padding: 20px;">Keine Dateien
verfügbar.</td>
</tr>
<?php else: ?>
<?php foreach ($files as $file): ?>
<tr>
<td><strong><?= htmlspecialchars($file['name']) ?></strong></td>
<td><?= htmlspecialchars($file['size']) ?> KB</td>
<td><?= htmlspecialchars($file['date']) ?></td>
<td>
<a href="?service=download_file&file=<?= urlencode($file['name']) ?>"
class="btn-download">💾 Download</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
<style>
.card {
background: var(--sidebar);
padding: 20px;
border-radius: 8px;
border: 1px solid var(--border);
}
.data-table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
}
.data-table th,
.data-table td {
text-align: left;
padding: 12px 15px;
border-bottom: 1px solid var(--border);
}
.data-table th {
background: rgba(0, 0, 0, 0.02);
color: var(--text);
font-weight: bold;
}
.data-table tr:hover {
background: rgba(0, 0, 0, 0.01);
}
.btn-download {
display: inline-block;
padding: 6px 12px;
background: var(--accent);
color: white;
border-radius: 4px;
text-decoration: none;
font-size: 0.85rem;
font-weight: bold;
}
.btn-download:hover {
opacity: 0.9;
}
</style>
HTML;
$files['WEBROOT/src/MVC/View/templates/upload.php'] = <<<'HTML' <h2>📤 Datei Upload</h2>
<?php use MVC\Classes\Form; ?>
<?php if (isset($message)): ?>
<div
class="alert <?= $message['type'] === 'success' ? 'alert-success' : 'alert-error' ?>">
<?= htmlspecialchars($message['text']) ?>
</div>
<?php endif; ?>
<div class="card">
<?= Form::open($current_service, 'POST', ['enctype' => 'multipart/form-data']) ?>
<?= Form::input('file_upload', 'Datei auswählen', 'file', '', ['required' => true]) ?>
<?= Form::textarea('description', 'Beschreibung (optional)', '', ['placeholder' => 'Kurze Beschreibung der Datei...']) ?>
<?= Form::submit('Hochladen') ?>
<?= Form::close() ?>
</div>
<style>
.card {
background: var(--sidebar);
padding: 25px;
border-radius: 8px;
border: 1px solid var(--border);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: var(--text);
}
.form-control {
width: 100%;
padding: 12px;
border: 1px solid var(--border);
border-radius: 4px;
background: var(--bg);
color: var(--text);
box-sizing: border-box;
}
.form-control:focus {
border-color: var(--accent);
outline: none;
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}
.btn {
padding: 12px 24px;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
transition: opacity 0.2s;
}
.btn-primary {
background: var(--accent);
color: white;
}
.btn:hover {
opacity: 0.9;
}
.alert {
padding: 15px;
border-radius: 4px;
margin-bottom: 20px;
border-left: 5px solid;
}
.alert-success {
background: rgba(46, 204, 113, 0.1);
border-color: #2ecc71;
color: #27ae60;
}
.alert-error {
background: rgba(231, 76, 60, 0.1);
border-color: #e74c3c;
color: #c0392b;
}
</style>
HTML;
// --- CONFIG: routes.php ---
$files['WEBROOT/src/config/routes.php'] = <<<'PHP' <?php
/**
* 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');
PHP;
// --- CONFIG: navigation.php ---
$files['WEBROOT/src/config/navigation.php'] = <<<'PHP'
<?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'],
];
PHP;
// --- ENTRY POINT ---
$files['WEBROOT/index.php'] = <<<'PHP'
<?php
session_start();
require_once __DIR__ . '/src/MVC/autoloader.php';
use MVC\Classes\Logger;
set_exception_handler([Logger::class, 'logException']);
set_error_handler([Logger::class, 'logError']);
// 1. Determine the service
$service = $_GET['service'] ?? null;
// Fallback to path-based routing if no service parameter is given
if ($service === null) {
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$service = trim($uri, '/');
if (empty($service)) {
$service = 'home';
}
}
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 (isset($_SESSION['user_logged_in'])) {
$headerActions = '<span style="margin-right: 15px; font-size: 0.9rem;">👤 ' . htmlspecialchars($_SESSION['username']) . '</span>' . '<a href="?service=logout" class="btn-toggle" style="text-decoration: none; background: rgba(255,255,255,0.2); margin-right: 10px;">🚪 Logout</a> ' . $headerActions;
} else {
$headerActions = '<a href="?service=login" class="btn-toggle" style="text-decoration: none; background: var(--accent); color: white; margin-right: 10px;">🔑 Login</a> ' . $headerActions;
}
$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 = isset($_SESSION['user_logged_in']);
$isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
// Check visibility based on Router middleware
$requiresAuth = $router->hasMiddleware($navKey, 'auth');
$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 (!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', $output);
echo $layout->render();
} catch (\Throwable $e) {
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>";
}
PHP;
// --- GENERATOR: make.php ---
$files['make.php'] = <<<'PHP'
<?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";
}
PHP;
$files['install_db.php'] = <<<'PHP' <?php
if (php_sapi_name() !== 'cli') die("CLI only.\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 "🚀 Datenbank-Tabellen-Installation...\n";
$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\n\nnamespace MVC\Model;\n\nuse MVC\Core\Model;\n\nclass Mailing extends Model {\n protected static \$table = \"Mailing\";\n public static function log(\$subject, \$message, \$url, \$ip, \$date) {\n return self::insert(['DATUM' => \$date, 'SUBJECT' => \$subject, 'MESSAGE' => \$message, 'URL' => \$url, 'CLIENT_IP' => \$ip]);\n }\n}"
],
'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\n\nnamespace MVC\Model;\n\nuse MVC\Core\Model;\n\nclass Users extends Model {\n protected static \$table = \"Users\";\n public static function getByUsername(\$username) {\n \$result = self::where(\"USERNAME = ?\", \$username);\n return \$result[0] ?? null;\n }\n}"
]
];
foreach ($tables as $name => $info) {
echo "--- $name ---\n";
try {
if (DatabaseCreateTable($name, $info['columns'])) {
$path = __DIR__ . "/WEBROOT/src/MVC/Model/{$name}.php";
if (!file_exists($path)) {
if (!is_dir(dirname($path))) mkdir(dirname($path), 0755, true);
file_put_contents($path, $info['model']);
echo "✅ bereit.\n";
} else {
echo "ℹ️ Model existiert bereits.\n";
}
}
} catch (Exception $e) { echo "❌: " . $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";
PHP;
// 3. Dateien schreiben
foreach ($files as $file => $content) {
// Docker-Dateien und die .code-workspace Datei sollen im Verzeichnis der init.php (Root) landen
if ($file === 'Dockerfile' || $file === 'docker-compose.yml' || str_ends_with($file, '.code-workspace')) {
$path = getcwd() . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $file);
} else {
$path = $baseDir . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $file);
}
file_put_contents($path, $content);
echo "📄 Datei erstellt: $file\n";
}
echo "\n✨ Projekt '$projectName' wurde initialisiert!\n";
echo "1. Richte deine Datenbankverbindung in WEBROOT/src/config/env.ini ein.\n";
echo "2. Datenbank installieren: php install_db.php\n";
echo " (Ohne Tabelle werden Logs in /logs/ gespeichert)\n";
echo "3. CLI-Tool nutzen: php make.php help\n";
echo "4. Starten: php server.php\n";
echo "5. Öffnen: http://localhost:8000\n";