Compare commits

...
15 Commits
Author SHA1 Message Date
Bruno Eckhardt e900655b47 new user mgmt but not integrated in the init.php 2026-05-26 11:39:50 +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
52 changed files with 2669 additions and 381 deletions

No files matched your search

-1
View File
@@ -1 +0,0 @@
<?php header('Content-Type: application/json'); echo json_encode(['status' => 'online']);
-33
View File
@@ -1,33 +0,0 @@
<?php
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('test1', \MVC\Controller\TestController::class, 'service1');
$router->add('test2', \MVC\Controller\TestController::class, 'service2');
$router->add('aussentemp', \MVC\Controller\AussentempController::class, 'aussentemp', ['auth']);
$layout = new \MVC\Layout\Layout('main');
$layout->setBlock('title', 'MVC Starter');
$layout->setBlock('header_title', '🚀 MVC Dashboard');
$layout->setBlock('header_actions', '<button class="btn-toggle" onclick="toggleTheme()">🌓 Theme umschalten</button>');
// Sidebar Block
ob_start();
?>
<nav>
<a href="?service=home">🏠 Home</a>
<a href="?service=test1">🛠️ Test 1</a>
<a href="?service=test2">📊 Test 2</a>
</nav>
<?php
$layout->setBlock('sidebar', ob_get_clean());
$layout->setBlock('content', $router->dispatch($service));
echo $layout->render();
} catch (\Exception $e) {
http_response_code(404); echo "<h1>404</h1><p>" . $e->getMessage() . "</p>";
}
@@ -1,18 +0,0 @@
<?php
namespace MVC\Controller;
use MVC\Core\Controller;
// use MVC\Model\YourModel; // Importiere dein Model
class AussentempController extends Controller {
public function aussentemp() {
// Nutze Models für den Datenbankzugriff
// $data = YourModel::all();
return $this->render('aussentemp', [
'title' => 'Aussentemp Modul',
'message' => 'Willkommen in deinem neuen Modul Aussentemp!'
]);
}
}
@@ -1,22 +0,0 @@
<?php
namespace MVC\Model;
use MVC\Core\Model;
/**
* Model für die Tabelle 'messungen'
* Verfügbare Spalten: 'id', 'ts', 'aussentemp'
*/
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);
}
}
@@ -1,14 +0,0 @@
<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>
-6
View File
@@ -1,6 +0,0 @@
[database]
APP_ENV = "dev"
DB_DEV_HOST = "localhost"
DB_DEV_USERNAME = "root"
DB_DEV_PASSWORD = ""
DB_DEV_DATABASE = "mein_projekt"
-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";
File renamed without changes.
@@ -5,9 +5,9 @@
}
],
"settings": {
"workbench.colorTheme": "Default Dark Modern",
"workbench.colorTheme": "Dark Modern",
"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": {
+14
View File
@@ -0,0 +1,14 @@
<?php
header('Content-Type: application/json');
require_once __DIR__ . '/../../src/MVC/autoloader.php';
use MVC\Controller\ApiController;
$api = (new ApiController());
try {
echo ($api->get());
} catch (\Exception $e) {
echo ($e->getMessage());
}
+74
View File
@@ -0,0 +1,74 @@
<?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']);
$service = $_GET['service'] ?? 'home';
try {
$router = new \MVC\Core\Router();
// Load routes from config
require_once __DIR__ . '/src/config/routes.php';
$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', $router->dispatch($service));
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>";
}
}
+17
View File
@@ -0,0 +1,17 @@
<?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;
}
}
@@ -0,0 +1,31 @@
<?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';
}
}
@@ -0,0 +1,20 @@
<?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));
}
}
@@ -0,0 +1,76 @@
<?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';
}
/**
* 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') {
$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,98 @@
<?php
namespace MVC\Classes;
use MVC\Classes\Client;
use MVC\Classes\Request;
use MVC\Classes\Logger;
use MVC\Model\Mailing;
class Mailer {
private static function getConfig() {
return parse_ini_file(__DIR__ . '/../../config/env.ini');
}
/**
* Sends a standard email and optionally logs it.
*/
public static function send($subject, $message, $url = null, bool $log = true) {
$config = self::getConfig();
$from = $config['MAIL_FROM'] ?? "bruno.eckhardt07@gmail.com";
$to = $config['MAIL_TO'] ?? "bruno.eckhardt07@gmail.com";
$headers = "From: " . $from . "\r\n";
$headers .= "Reply-To: " . $from . "\r\n";
$headers .= "X-Mailer: PHP/" . phpversion();
// mail() unterdrückt Fehlermeldungen standardmäßig nicht,
// wir prüfen den Rückgabewert.
$success = @mail($to, $subject, $message, $headers);
if (!$success) {
Logger::log("Mail dispatch failed 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 = $config['MAIL_FROM'] ?? "bruno.eckhardt07@gmail.com";
$to = $config['MAIL_TO'] ?? "bruno.eckhardt07@gmail.com";
$headers = "From: " . $from . "\r\n";
$headers .= "Reply-To: " . $from . "\r\n";
$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;
}
/**
* 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,33 @@
<?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';
}
}
@@ -0,0 +1,25 @@
<?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';
}
}
@@ -0,0 +1,64 @@
<?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 (Mailer::send($subject, $body)) {
$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,14 @@
<?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',
]);
}
}
@@ -0,0 +1,42 @@
<?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;
}
}
@@ -0,0 +1,13 @@
<?php
namespace MVC\Controller;
use MVC\Core\Controller;
class DocsController extends Controller {
public function index() {
return $this->render('docs', [
'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() {
@@ -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 TestController extends Controller {
public function service1() {
@@ -0,0 +1,83 @@
<?php
namespace MVC\Controller;
use MVC\Core\Controller;
use MVC\Model\Users;
class UserMgmtController extends Controller {
public function user_mgmt() {
$action = $_GET['action'] ?? 'list';
$message = null;
// 1. Handle Delete (delete())
if ($action === 'delete' && isset($_GET['id'])) {
if (Users::delete("ID = ?", $_GET['id'])) {
$message = ['type' => 'success', 'text' => 'Benutzer erfolgreich gelöscht.'];
}
$action = 'list';
}
// 2. Handle Create (insert())
if ($action === 'create' && $_SERVER['REQUEST_METHOD'] === 'POST') {
$data = [
'USERNAME' => $_POST['username'],
'PASSWORD' => password_hash($_POST['password'], PASSWORD_DEFAULT),
'EMAIL' => $_POST['email'],
'ROLE' => $_POST['role']
];
if (Users::insert($data)) {
$message = ['type' => 'success', 'text' => 'Benutzer erstellt.'];
$action = 'list';
} else {
$message = ['type' => 'error', 'text' => 'Fehler beim Erstellen.'];
}
}
// 3. Handle Update (update())
if ($action === 'edit' && $_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['id'])) {
$data = [
'USERNAME' => $_POST['username'],
'EMAIL' => $_POST['email'],
'ROLE' => $_POST['role']
];
// Nur Passwort updaten wenn ausgefüllt
if (!empty($_POST['password'])) {
$data['PASSWORD'] = password_hash($_POST['password'], PASSWORD_DEFAULT);
}
if (Users::update($data, "ID = ?", $_POST['id'])) {
$message = ['type' => 'success', 'text' => 'Benutzer aktualisiert.'];
$action = 'list';
} else {
$message = ['type' => 'error', 'text' => 'Fehler beim Update.'];
}
}
// --- View Logic ---
$users = [];
$editUser = null;
if ($action === 'list') {
// Test select() & where() & all()
if (!empty($_GET['search'])) {
$users = Users::where("USERNAME LIKE ?", "%" . $_GET['search'] . "%");
} else if (isset($_GET['simple'])) {
$users = Users::select(["ID", "USERNAME", "ROLE"]);
} else {
$users = Users::all();
}
} else if ($action === 'edit' && isset($_GET['id'])) {
// Test find()
$editUser = Users::find($_GET['id']);
}
return $this->render('user_mgmt', [
'action' => $action,
'users' => $users,
'editUser' => $editUser,
'message' => $message
]);
}
}
@@ -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;
@@ -42,6 +42,14 @@ 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) {
@@ -51,7 +59,7 @@ class Router {
if ($middleware === 'auth') {
if (empty($_SESSION['user_logged_in'])) {
header('Location: login.php');
header('Location: ?service=login');
exit;
}
}
@@ -15,6 +15,7 @@
.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); }
.logo { font-size: 1.4rem; font-weight: bold; }
</style>
</head>
@@ -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,21 @@
<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;">Betreff</label>
<input type="text" name="subject" required style="width: 100%; padding: 10px; border-radius: 4px; border: 1px solid var(--border); background: var(--bg); color: var(--text);">
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px;">Nachricht</label>
<textarea name="body" required style="width: 100%; height: 150px; padding: 10px; border-radius: 4px; border: 1px solid var(--border); background: var(--bg); color: var(--text);"></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>
+223
View File
@@ -0,0 +1,223 @@
<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="#mailing">7. Mailing-System</a></li>
<li><a href="#logging">8. Error-Logging</a></li>
<li><a href="#private-services">9. Private Services</a></li>
<li><a href="#best-practices">10. Best Practices</a></li>
<li><a href="#model-functions">11. Model Core Funktionen</a></li>
</ul>
</div>
<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.</li>
<li><strong>autoloader.php:</strong> Registriert einen PSR-4 ähnlichen Autoloader für den <code>MVC\</code> Namespace.</li>
</ul>
</section>
<section id="api">
<h2>3. API-System</h2>
<p>APIs liegen im Verzeichnis <code>WEBROOT/api/v1/</code>. Sie nutzen die <code>ApiController</code>-Klasse.</p>
<pre>
// Beispiel: WEBROOT/api/v1/get.php
use MVC\Classes\Api;
use MVC\Controller\ApiController;
Api::jsonResponse((new ApiController())->get());
</pre>
</section>
<section id="database">
<h2>4. Datenbank & Models</h2>
<p>Das Framework nutzt <code>mysqli</code> mit Prepared Statements für maximale Sicherheit.</p>
<h3>Installation (install_db.php)</h3>
<p>Mit dem Befehl <code>php install_db.php</code> können vordefinierte Tabellen und deren Models automatisch erstellt werden. Standardmäßig sind folgende Tabellen enthalten:</p>
<ul>
<li><strong>Mailing:</strong> Für das E-Mail-Logging (wird vom Mailer genutzt).</li>
<li><strong>Users:</strong> Eine Grundstruktur für Benutzer (ID, Username, Password, Email, Role, Created_at).</li>
</ul>
<h3>databaseMethods.php (Die Power-Tools)</h3>
<ul>
<li><code>DatabaseShow($table, $columns, $where, ...$values)</code></li>
<li><code>DatabaseInsert($table, $columns, ...$values)</code></li>
<li><code>DatabaseUpdate($table, $set, $where, ...$values)</code></li>
<li><code>DatabaseDelete($table, $where, ...$values)</code></li>
</ul>
</section>
<section id="classes">
<h2>5. Helper Klassen (MVC\Classes)</h2>
<ul>
<li><strong>Request:</strong> Zugriff auf <code>method()</code>, <code>queryParams()</code>, <code>headers()</code>.</li>
<li><strong>Client:</strong> Ermittelt <code>ip()</code>, <code>userAgent()</code>, <code>country()</code>.</li>
<li><strong>Cookie:</strong> Interface zum <code>set()</code>, <code>read()</code>, <code>Delete()</code> von Cookies.</li>
</ul>
</section>
<section id="generators">
<h2>6. CLI-Generatoren</h2>
<p>Das Framework bietet ein zentrales CLI-Tool zur Automatisierung von Aufgaben.</p>
<h3>Modul-Generator</h3>
<p>Erstellt Controller, View, Route und Navigationspunkt in einem Schritt.</p>
<pre>php make.php module [Name] [Optionen]</pre>
<ul>
<li><code>-c, --controller</code>: Nur den Controller erstellen.</li>
<li><code>-v, --view</code>: Nur die View erstellen.</li>
<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><strong>Beispiel:</strong> <code>php make.php module profile -l "👤 Profil" -a</code></p>
<h3>Model-Generator</h3>
<p>Erstellt ein Model basierend auf einer Datenbank-Tabelle.</p>
<pre>php make.php model [Tabellenname] [Spalten,kommagetrennt]</pre>
<p><strong>Beispiel:</strong> <code>php make.php model Products NAME,PRICE,STOCK</code></p>
</section>
<section id="mailing">
<h2>7. Mailing-System</h2>
<p>Das Framework verfügt über eine integrierte <code>Mailer</code>-Klasse für den E-Mail-Versand und die Protokollierung.</p>
<h3>Konfiguration</h3>
<p>In der <code>env.ini</code> können Absender und Empfänger unter <code>[mail]</code> definiert werden.</p>
<h3>Nutzung</h3>
<pre>
use MVC\Classes\Mailer;
// Einfache Mail
Mailer::send("Betreff", "Inhalt");
// Error Mail (wird speziell markiert)
Mailer::error("Fehlertitel", "Details");
</pre>
<h3>Logging & Fallback</h3>
<p>Der Mailer versucht standardmäßig in die Datenbank-Tabelle <code>Mailing</code> zu loggen. Falls diese nicht existiert, erfolgt ein automatischer Fallback in Textdateien im Ordner <code>/logs/</code>.</p>
<p><strong>Installation:</strong> Führe <code>php install_db.php</code> aus, um die Datenbank-Struktur anzulegen.</p>
</section>
<section id="logging">
<h2>8. Error-Logging</h2>
<p>Das Framework verfügt über ein zentrales Logging-System, das in der <code>index.php</code> global registriert ist.</p>
<h3>Automatische Erfassung</h3>
<p>Alle unangefangenen Exceptions und PHP-Fehler (Warnings, Notices, etc.) werden automatisch in die Datei <code>/logs/error.log</code> geschrieben, inklusive Stacktrace bei Exceptions.</p>
<h3>Manuelle Nutzung</h3>
<pre>
use MVC\Classes\Logger;
// Einfache Nachricht in system.log
Logger::log("Etwas ist passiert");
// Spezifisches Level und Datei
Logger::log("Kritischer Fehler", "ERROR", "error.log");
</pre>
<p>Die Log-Dateien befinden sich im Hauptverzeichnis des Projekts im Ordner <code>/logs/</code>.</p>
</section>
<section id="private-services">
<h2>9. Private Services (Admin)</h2>
<p>Das Framework bringt 3 Schutzklassen in der <code>/config/routes.php</code> mit:</p>
<ul>
<li><strong>Example: </strong><code>$router->add('home', \MVC\Controller\HomeController::class, 'index');</code></li>
<ul><li>Dieser Service ist öffentlich verfügbar.</li></ul>
<li><strong>Example: </strong><code>$router->add('home', \MVC\Controller\HomeController::class, 'index', ['auth']);</code></li>
<ul><li>Dieser Service ist mit Login verfügbar.</li></ul>
<li><strong>Example: </strong><code>$router->add('home', \MVC\Controller\HomeController::class, 'index', ['auth', 'admin']);</code></li>
<ul><li>Dieser Service ist mit Login und Admin-Rechten verfügbar.</li></ul>
</ul>
<ul>
<li><strong>Mail Service (admin_mail):</strong> Ein Formular zum Testen des Mail-Versands.</li>
<li><strong>User Service (admin_users):</strong> Ermöglicht das Erstellen neuer Benutzer direkt in die Datenbank (Passwörter werden automatisch gehasht).</li>
</ul>
<p>Diese Services werden über den <code>AdminController</code> gesteuert.</p>
</section>
<section id="best-practices">
<h2>10. Best Practices</h2>
<p><strong>Naming:</strong> Controller immer mit <code>Controller</code>-Suffix. Models im Singular.</p>
<p><strong>Security:</strong> Nutze <code>['auth']</code> Middleware konsequent für alle internen Seiten.</p>
</section>
<section id="model-functions">
<h2>11. Model Core Funktionen</h2>
<p>Die Basisklasse <code>MVC\Core\Model</code> bietet ein objektorientiertes Interface für Datenbankoperationen.</p>
<h3>all()</h3>
<p>Gibt alle Datensätze der Tabelle zurück.</p>
<pre><code>$users = Users::all();</code></pre>
<h3>select($columns)</h3>
<p>Gibt spezifische Spalten zurück. Akzeptiert einen String oder ein Array.</p>
<pre><code>// Als String
$names = Users::select("USERNAME, EMAIL");
// Als Array
$data = Users::select(["USERNAME", "ROLE"]);</code></pre>
<h3>find($id, $column = 'ID')</h3>
<p>Sucht einen einzelnen Datensatz anhand einer Spalte (Standard: ID).</p>
<pre><code>$user = Users::find(1);
$userByMail = Users::find("admin@example.com", "EMAIL");</code></pre>
<h3>where($where, ...$params)</h3>
<p>Filtert Datensätze mit Prepared Statements.</p>
<pre><code>$admins = Users::where("ROLE = ?", "admin");
$activeUsers = Users::where("STATUS = ? AND ROLE = ?", "active", "user");</code></pre>
<h3>insert($data)</h3>
<p>Fügt einen neuen Datensatz hinzu.</p>
<pre><code>Users::insert([
'USERNAME' => 'new_user',
'PASSWORD' => password_hash('secret', PASSWORD_DEFAULT),
'ROLE' => 'user'
]);</code></pre>
<h3>update($data, $where, ...$whereParams)</h3>
<p>Aktualisiert bestehende Datensätze.</p>
<pre><code>Users::update(['ROLE' => 'admin'], "ID = ?", 5);</code></pre>
<h3>delete($where, ...$params)</h3>
<p>Löscht Datensätze.</p>
<pre><code>Users::delete("ID = ?", 10);</code></pre>
<h3>query($sql, ...$params)</h3>
<p>Führt eine native SQL-Abfrage mit Prepared Statements aus.</p>
<pre><code>$result = Users::query("SELECT COUNT(*) as total FROM Users");</code></pre>
</section>
</div>
<style>
.docs-container { max-width: 1000px; color: var(--text); }
.toc { background: var(--sidebar); padding: 20px; border-radius: 8px; margin-bottom: 30px; border: 1px solid var(--border); }
.toc h3 { margin-top: 0; }
section { margin-bottom: 50px; padding-bottom: 20px; border-bottom: 1px solid var(--border); }
h2 { color: var(--accent); font-size: 1.8rem; margin-bottom: 20px; }
pre { background: #1e1e1e; color: #d4d4d4; padding: 15px; border-radius: 5px; overflow-x: auto; border: 1px solid #333; font-size: 0.9rem; }
</style>
+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,92 @@
<h2>👥 User Management (Full CRUD Demo)</h2>
<?php if (isset($message)): ?>
<div style="padding:15px;margin-bottom:20px;border-radius:5px;background:<?= $message['type']==='success'?'rgba(46,204,113,0.2)':'rgba(231,76,60,0.2)'?>; border: 1px solid <?= $message['type']==='success'?'#2ecc71':'#e74c3c'?>;">
<?= $message['text'] ?>
</div>
<?php endif; ?>
<div style="margin-bottom: 20px;">
<a href="?service=user_mgmt&action=list" class="btn">Alle Benutzer</a>
<a href="?service=user_mgmt&action=list&simple=1" class="btn">Nur Basisdaten (select())</a>
<a href="?service=user_mgmt&action=create" class="btn btn-primary">➕ Neu erstellen (insert())</a>
</div>
<?php if ($action === 'list'): ?>
<form method="GET" style="margin-bottom: 20px; display: flex; gap: 10px;">
<input type="hidden" name="service" value="user_mgmt">
<input type="text" name="search" placeholder="Suche nach Username (where())..." value="<?= htmlspecialchars($_GET['search'] ?? '') ?>" style="flex: 1; padding: 10px; border-radius: 4px; border: 1px solid var(--border); background: var(--bg); color: var(--text);">
<button type="submit" class="btn">Suchen</button>
</form>
<table style="width: 100%; border-collapse: collapse;">
<thead>
<tr style="background: var(--sidebar); text-align: left;">
<th style="padding: 12px; border: 1px solid var(--border);">ID</th>
<th style="padding: 12px; border: 1px solid var(--border);">Username</th>
<?php if (!isset($_GET['simple'])): ?>
<th style="padding: 12px; border: 1px solid var(--border);">Email</th>
<?php endif; ?>
<th style="padding: 12px; border: 1px solid var(--border);">Role</th>
<th style="padding: 12px; border: 1px solid var(--border);">Aktionen</th>
</tr>
</thead>
<tbody>
<?php foreach ($users as $user): ?>
<tr>
<td style="padding: 12px; border: 1px solid var(--border);"><?= $user['ID'] ?></td>
<td style="padding: 12px; border: 1px solid var(--border);"><?= htmlspecialchars($user['USERNAME']) ?></td>
<?php if (!isset($_GET['simple'])): ?>
<td style="padding: 12px; border: 1px solid var(--border);"><?= htmlspecialchars($user['EMAIL']) ?></td>
<?php endif; ?>
<td style="padding: 12px; border: 1px solid var(--border);"><span style="background: var(--accent); color: white; padding: 2px 6px; border-radius: 4px; font-size: 0.8rem;"><?= $user['ROLE'] ?></span></td>
<td style="padding: 12px; border: 1px solid var(--border);">
<a href="?service=user_mgmt&action=edit&id=<?= $user['ID'] ?>" style="color: var(--accent); text-decoration: none;">Bearbeiten (find())</a> |
<a href="?service=user_mgmt&action=delete&id=<?= $user['ID'] ?>" style="color: #e74c3c; text-decoration: none;" onclick="return confirm('Sicher?')">Löschen (delete())</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php elseif ($action === 'create' || $action === 'edit'): ?>
<h3><?= $action === 'edit' ? '✏️ Benutzer bearbeiten' : '➕ Neuen Benutzer anlegen' ?></h3>
<form method="POST" action="?service=user_mgmt&action=<?= $action ?>" style="background: var(--sidebar); padding: 25px; border-radius: 8px; border: 1px solid var(--border);">
<?php if ($action === 'edit'): ?>
<input type="hidden" name="id" value="<?= $editUser['ID'] ?>">
<?php endif; ?>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px;">Username</label>
<input type="text" name="username" required value="<?= htmlspecialchars($editUser['USERNAME'] ?? '') ?>" style="width: 100%; padding: 10px; border-radius: 4px; border: 1px solid var(--border); background: var(--bg); color: var(--text);">
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px;">E-Mail</label>
<input type="email" name="email" value="<?= htmlspecialchars($editUser['EMAIL'] ?? '') ?>" style="width: 100%; padding: 10px; border-radius: 4px; border: 1px solid var(--border); background: var(--bg); color: var(--text);">
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px;">Passwort <?= $action === 'edit' ? '(leer lassen zum Behalten)' : '' ?></label>
<input type="password" name="password" <?= $action === 'create' ? 'required' : '' ?> style="width: 100%; padding: 10px; border-radius: 4px; border: 1px solid var(--border); background: var(--bg); color: var(--text);">
</div>
<div style="margin-bottom: 20px;">
<label style="display: block; margin-bottom: 5px;">Rolle</label>
<select name="role" style="width: 100%; padding: 10px; border-radius: 4px; border: 1px solid var(--border); background: var(--bg); color: var(--text);">
<option value="user" <?= ($editUser['ROLE'] ?? '') === 'user' ? 'selected' : '' ?>>User</option>
<option value="admin" <?= ($editUser['ROLE'] ?? '') === 'admin' ? 'selected' : '' ?>>Admin</option>
</select>
</div>
<button type="submit" class="btn btn-primary"><?= $action === 'edit' ? 'Speichern (update())' : 'Erstellen (insert())' ?></button>
<a href="?service=user_mgmt&action=list" class="btn">Abbrechen</a>
</form>
<?php endif; ?>
<style>
.btn { display: inline-block; padding: 10px 20px; text-decoration: none; border-radius: 4px; background: var(--sidebar); color: var(--text); border: 1px solid var(--border); cursor: pointer; font-size: 0.9rem; }
.btn:hover { background: rgba(0,0,0,0.1); }
.btn-primary { background: var(--accent); color: white; border: none; }
.btn-primary:hover { opacity: 0.9; }
</style>
+19
View File
@@ -0,0 +1,19 @@
[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"
@@ -0,0 +1,14 @@
<?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'],
'docs' => ['label' => '📚 Dokumentation'],
'user_mgmt' => ['label' => '👥 User Management'],
];
+17
View File
@@ -0,0 +1,17 @@
<?php
/**
* Define your routes here.
* Format: $router->add(service_name, controller_class, method_name, options);
*/
$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('user_mgmt', \MVC\Controller\UserMgmtController::class, 'user_mgmt', ['auth', 'admin']);
@@ -18,3 +18,22 @@ function db_connect() {
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;
}
}
+147
View File
@@ -0,0 +1,147 @@
<?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/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";
}
File renamed without changes.
+1114 -113
View File
File diff suppressed because it is too large. Load diff