This commit is contained in:
Bruno Eckhardt committed 2026-05-21 13:38:18 +02:00
1 parent d24a674643
commit 3a86acc057
22 files changed
+916 -334

No files matched your search

+8 -2
View File
@@ -17,7 +17,7 @@ $serviceGroups = array (
array (
0 => 'home_info',
1 => 'links',
2 => 'tester',
3 => 'documentation',
),
),
'Ausbildung' =>
@@ -192,6 +192,12 @@ $services = array (
'active' => true,
'adminOnly' => false,
),
'documentation' =>
array (
'name' => 'Dokumentation',
'active' => true,
'adminOnly' => false,
),
);
$linkGroups = array (
@@ -382,4 +388,4 @@ $linkButtons = array (
'adminOnly' => false,
'group' => 'tools',
),
);
);
+75 -61
View File
@@ -88,37 +88,68 @@ if ((new getSpecificData())->getWebsite() != "backend.blackpi.ipv64.net") {
}
// MVC Logic
$layout = new \MVC\Layout\Layout('main');
$layout->setBlock('title', $title);
try {
$router = new \MVC\Core\Router();
// Header Block
ob_start();
?>
// Register Routes
$router->add('home_info', \MVC\Controller\ServiceController::class, 'home_info', ['auth']);
$router->add('links', \MVC\Controller\ServiceController::class, 'links', ['auth']);
$router->add('documentation', \MVC\Controller\DocumentationController::class, 'links', ['auth']);
$router->add('logout', \MVC\Controller\ServiceController::class, 'logout', ['auth']);
$router->add('public_request_logs', \MVC\Controller\LogController::class, 'public_request_logs', ['auth']);
$router->add('backend_request_logs', \MVC\Controller\LogController::class, 'backend_request_logs', ['auth']);
$router->add('personal_request_logs', \MVC\Controller\LogController::class, 'personal_request_logs', ['auth', 'admin']);
$router->add('public_request_chart', \MVC\Controller\LogController::class, 'public_request_chart', ['auth']);
$router->add('backend_request_chart', \MVC\Controller\LogController::class, 'backend_request_chart', ['auth']);
$router->add('user_control', \MVC\Controller\AdminController::class, 'user_control', ['auth', 'admin']);
$router->add('services_control', \MVC\Controller\AdminController::class, 'services_control', ['auth', 'admin']);
$router->add('variablen_control', \MVC\Controller\AdminController::class, 'variablen_control', ['auth', 'admin']);
$router->add('data_info', \MVC\Controller\AdminController::class, 'data_info', ['auth', 'admin']);
$router->add('tabellen', \MVC\Controller\AdminController::class, 'tabellen', ['auth', 'admin']);
$router->add('berichte', \MVC\Controller\ReportController::class, 'berichte', ['auth']);
$router->add('berichte_arbeit_gen', \MVC\Controller\ReportController::class, 'berichte_arbeit_gen', ['auth']);
$router->add('zeiterfassung', \MVC\Controller\ReportController::class, 'zeiterfassung', ['auth']);
$router->add('print_static', \MVC\Controller\ReportController::class, 'print_static', ['auth']);
$router->add('api_tester', \MVC\Controller\TestController::class, 'api_tester', ['auth', 'admin']);
$router->add('weather_chart', \MVC\Controller\TestController::class, 'weather_chart', ['auth', 'admin']);
$router->add('domains', \MVC\Controller\TestController::class, 'domains', ['auth']);
$router->add('documentation', \MVC\Controller\DocumentationController::class, 'index', ['auth']);
$layout = new \MVC\Layout\Layout('main');
$layout->setBlock('title', $title);
// Header Block
ob_start();
?>
Backend
<button id="darkModeToggle">Dark Mode</button>
<button id="logoutButton">Logout</button>
<?php
$layout->setBlock('header', ob_get_clean());
$layout->setBlock('header', ob_get_clean());
// Sidebar Block
ob_start();
?>
// Sidebar Block
ob_start();
?>
<?php foreach ($serviceGroups as $groupKey => $group): ?>
<?php
$visibleServices = [];
$visibleServices = [];
foreach ($group['services'] as $serviceKey) {
if (!isset($services[$serviceKey])) continue;
if (!$services[$serviceKey]['active']) continue;
if (!empty($services[$serviceKey]['adminOnly']) && !$isAdmin) continue;
if (!in_array($serviceKey, $userRights, true)) continue;
$visibleServices[] = $serviceKey;
}
foreach ($group['services'] as $serviceKey) {
if (!isset($services[$serviceKey])) continue;
if (!$services[$serviceKey]['active']) continue;
if (!empty($services[$serviceKey]['adminOnly']) && !$isAdmin) continue;
if (!in_array($serviceKey, $userRights, true)) continue;
$visibleServices[] = $serviceKey;
}
if (empty($visibleServices)) continue;
if (empty($visibleServices)) continue;
$isOpen = !empty($group['defaultOpen']) || in_array($selectedService, $visibleServices, true);
?>
$isOpen = !empty($group['defaultOpen']) || in_array($selectedService, $visibleServices, true);
?>
<div class="service-group <?= $isOpen ? 'open' : '' ?>" data-group="<?= $groupKey ?>">
<div class="group-title">
@@ -138,48 +169,31 @@ ob_start();
</div>
<?php endforeach; ?>
<?php
$layout->setBlock('sidebar', ob_get_clean());
$layout->setBlock('sidebar', ob_get_clean());
// Content Block
if ($selectedService) {
$controllers = [
'home_info' => \MVC\Controller\ServiceController::class,
'links' => \MVC\Controller\ServiceController::class,
'logout' => \MVC\Controller\ServiceController::class,
'public_request_logs' => \MVC\Controller\LogController::class,
'backend_request_logs' => \MVC\Controller\LogController::class,
'personal_request_logs' => \MVC\Controller\LogController::class,
'public_request_chart' => \MVC\Controller\LogController::class,
'backend_request_chart' => \MVC\Controller\LogController::class,
'user_control' => \MVC\Controller\AdminController::class,
'services_control' => \MVC\Controller\AdminController::class,
'variablen_control' => \MVC\Controller\AdminController::class,
'data_info' => \MVC\Controller\AdminController::class,
'tabellen' => \MVC\Controller\AdminController::class,
'berichte' => \MVC\Controller\ReportController::class,
'berichte_arbeit_gen' => \MVC\Controller\ReportController::class,
'zeiterfassung' => \MVC\Controller\ReportController::class,
'print_static' => \MVC\Controller\ReportController::class,
'api_tester' => \MVC\Controller\TestController::class,
'weather_chart' => \MVC\Controller\TestController::class,
'domains' => \MVC\Controller\TestController::class,
];
$controllerClass = $controllers[$selectedService] ?? null;
if ($controllerClass) {
$controller = new $controllerClass();
$content = $controller->$selectedService();
// Content Block
if ($selectedService) {
$content = $router->dispatch($selectedService);
$layout->setBlock('content', $content);
logging();
} else {
// Fallback to old services if not yet refactored
ob_start();
include "services/$selectedService.php";
$content = ob_get_clean();
$layout->setBlock('content', "<p>Kein aktiver Service verfügbar.</p>");
}
$layout->setBlock('content', $content);
logging();
} else {
$layout->setBlock('content', "<p>Kein aktiver Service verfügbar.</p>");
}
echo $layout->render();
echo $layout->render();
} catch (\Exception $e) {
// Central Error Handling
error_log($e->getMessage());
http_response_code(500);
if ($isAdmin) {
echo "<h1>Internal Server Error</h1>";
echo "<p><strong>Message:</strong> " . $e->getMessage() . "</p>";
echo "<p><strong>File:</strong> " . $e->getFile() . " on line " . $e->getLine() . "</p>";
echo "<pre>" . $e->getTraceAsString() . "</pre>";
} else {
echo "<h1>Hoppla!</h1>";
echo "<p>Ein interner Fehler ist aufgetreten. Bitte versuchen Sie es später erneut.</p>";
}
}
+26 -47
View File
@@ -3,37 +3,31 @@
namespace MVC\Controller;
use MVC\Core\Controller;
use MVC\Model\User;
use MVC\Model\Variable;
class AdminController extends Controller {
public function user_control() {
global $isAdmin, $superuser;
$specificData = new \getSpecificData();
// Form Processing
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['add_user']) && $isAdmin) {
$userCtrl = new \UserController();
$userCtrl->setUser(
$_POST['name'],
$_POST['nachname'],
$_POST['email'],
$_POST['passwort'],
$_POST['rolle'],
$_POST['admin'] ?? 0,
$_POST['home_ip']
);
User::insert([
'NAME' => $_POST['name'],
'NACHNAME' => $_POST['nachname'],
'EMAIL' => $_POST['email'],
'PASSWORD' => password_hash($_POST['passwort'], PASSWORD_DEFAULT),
'ROLLE' => $_POST['rolle'],
'ADMIN' => $_POST['admin'] ?? 0,
'HOME_IP' => $_POST['home_ip']
]);
header("Location: " . $specificData->getReferer());
exit;
}
if (isset($_POST['delete_user']) && $isAdmin) {
$userCtrl = new \UserController();
$userCtrl->DeleteUser(
$_POST['delete_name'],
$_POST['delete_nachname'],
$_POST['delete_email']
);
User::delete("NAME = ? AND NACHNAME = ? AND EMAIL = ?", $_POST['delete_name'], $_POST['delete_nachname'], $_POST['delete_email']);
header("Location: " . $specificData->getReferer());
exit;
}
@@ -53,17 +47,7 @@ class AdminController extends Controller {
}
}
// Data Fetching
if ($isAdmin) {
$users = \DatabaseShow("Benutzer", "NAME, NACHNAME, EMAIL, ROLLE, ADMIN, HOME_IP");
} else {
$users = \DatabaseShow(
"Benutzer",
"NAME, NACHNAME, EMAIL, ROLLE, ADMIN, HOME_IP",
"NAME = ?",
$specificData->getUsername()
);
}
$users = $isAdmin ? User::all() : User::getByName($specificData->getUsername());
return $this->render('user_control', [
'users' => $users,
@@ -73,6 +57,19 @@ class AdminController extends Controller {
]);
}
public function variablen_control() {
global $isAdmin;
if (!$isAdmin) return "Access Denied";
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['toggle_variable'])) {
Variable::toggle($_POST['variablenname']);
header("Location: " . (new \getSpecificData())->getReferer());
exit;
}
return $this->render('variablen_control', ['variablen' => Variable::all()]);
}
public function services_control() {
global $isAdmin, $superuser, $serviceGroups, $services, $linkGroups, $linkButtons;
if (!$isAdmin) return "Access Denied";
@@ -188,24 +185,6 @@ class AdminController extends Controller {
]);
}
public function variablen_control() {
global $isAdmin;
if (!$isAdmin) return "Access Denied";
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['toggle_variable'])) {
$name = $_POST['variablenname'];
$current = (new \getSpecificData())->getLoggingStatus($name)[0]['WERT'];
$newValue = ($current === "ON") ? "OFF" : "ON";
\DatabaseUpdate("Variablen", "WERT = ?", "VARIABLENNAME = ?", $newValue, $name);
header("Location: " . (new \getSpecificData())->getReferer());
exit;
}
$variablen = \DatabaseShow("Variablen", "VARIABLENNAME, WERT, ANZEIGENAME");
return $this->render('variablen_control', ['variablen' => $variablen]);
}
public function data_info() {
global $isAdmin;
if (!$isAdmin) return "Access Denied";
@@ -0,0 +1,24 @@
<?php
namespace MVC\Controller;
use MVC\Core\Controller;
use MVC\Model\Documentation;
class DocumentationController extends Controller {
public function index() {
$query = $_GET['q'] ?? '';
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
$limit = 5;
$results = Documentation::search($query, $page, $limit);
return $this->render('documentation', [
'docs' => $results['rows'],
'total' => $results['total'],
'pages' => $results['pages'],
'currentPage' => $results['current_page'],
'query' => $query
]);
}
}
+23 -53
View File
@@ -3,6 +3,7 @@
namespace MVC\Controller;
use MVC\Core\Controller;
use MVC\Model\Log;
class LogController extends Controller {
protected function getDevIpsSql() {
@@ -11,34 +12,10 @@ class LogController extends Controller {
}
protected function handleLogs($websiteFilter, $serviceName) {
$conn = \db_connect();
$devips_sql = $this->getDevIpsSql();
$filter = $_GET['filter'] ?? 'today';
switch ($filter) {
case 'today':
$sql = "SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE` FROM logs WHERE DATE(FIRST_REQUEST) = CURDATE() AND $websiteFilter AND CLIENT_IP NOT IN ($devips_sql) ORDER BY FIRST_REQUEST DESC";
break;
case 'counterperip':
$sql = "SELECT CLIENT_IP, LAND, USER_AGENT, SUM(counter) AS gesamt_counter FROM logs WHERE $websiteFilter AND CLIENT_IP NOT IN ($devips_sql) GROUP BY CLIENT_IP ORDER BY gesamt_counter DESC";
break;
case 'node1':
case 'node2':
case 'node3':
case 'node4':
$node = $filter;
$sql = "SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE` FROM logs WHERE `SERVER_IP` = '$node' AND $websiteFilter ORDER BY `FIRST_REQUEST` DESC";
break;
default:
$sql = "SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE` FROM logs WHERE $websiteFilter AND CLIENT_IP NOT IN ($devips_sql) ORDER BY FIRST_REQUEST DESC";
}
$daten = [];
if ($erg = $conn->query($sql)) {
while ($row = $erg->fetch_assoc()) {
$daten[] = $row;
}
}
$daten = Log::getFiltered($websiteFilter, $devips_sql, $filter);
return $this->render('logs', [
'daten' => $daten,
@@ -60,64 +37,57 @@ class LogController extends Controller {
}
public function public_request_chart() {
$conn = \db_connect();
$sql_total = "SELECT DATE(FIRST_REQUEST) as tag, COUNT(*) as anzahl FROM logs GROUP BY DATE(FIRST_REQUEST) ORDER BY DATE(FIRST_REQUEST)";
$result_total = $conn->query($sql_total);
$totalData = Log::getChartData();
$totalDataAssoc = [];
$allLabels = [];
while($row = $result_total->fetch_assoc()) {
$date = $row['tag'];
$totalDataAssoc[$date] = (int)$row['anzahl'];
$allLabels[] = $date;
foreach ($totalData as $row) {
$totalDataAssoc[$row['tag']] = (int)$row['anzahl'];
$allLabels[] = $row['tag'];
}
$nodes = ['node1','node2','node3','node4'];
$nodeDataAssoc = [];
$finalNodeData = [];
foreach ($nodes as $node) {
$sql = "SELECT DATE(FIRST_REQUEST) as tag, COUNT(*) as anzahl FROM logs WHERE SERVER_IP = '$node' GROUP BY DATE(FIRST_REQUEST)";
$result = $conn->query($sql);
$data = [];
while($row = $result->fetch_assoc()) {
$date = $row['tag'];
$data[$date] = (int)$row['anzahl'];
if (!in_array($date, $allLabels)) $allLabels[] = $date;
$nodeData = Log::getNodeChartData($node);
$nodeAssoc = [];
foreach ($nodeData as $row) {
$nodeAssoc[$row['tag']] = (int)$row['anzahl'];
if (!in_array($row['tag'], $allLabels)) $allLabels[] = $row['tag'];
}
$nodeDataAssoc[$node] = $data;
// Re-map to all labels
$finalNodeData[$node] = $nodeAssoc;
}
sort($allLabels);
$finalTotalData = [];
$finalNodeData = [];
$mappedNodeData = [];
foreach ($allLabels as $label) {
$finalTotalData[] = $totalDataAssoc[$label] ?? 0;
foreach ($nodes as $node) {
$finalNodeData[$node][] = $nodeDataAssoc[$node][$label] ?? 0;
$mappedNodeData[$node][] = $finalNodeData[$node][$label] ?? 0;
}
}
return $this->render('public_chart', [
'allLabels' => $allLabels,
'finalTotalData' => $finalTotalData,
'finalNodeData' => $finalNodeData
'finalNodeData' => $mappedNodeData
]);
}
public function backend_request_chart() {
$conn = \db_connect();
$sql = "SELECT DATE(FIRST_REQUEST) as tag, COUNT(*) as anzahl FROM logs WHERE WEBSITE = 'backend.blackpi.ipv64.net' GROUP BY DATE(FIRST_REQUEST) ORDER BY DATE(FIRST_REQUEST)";
$result = $conn->query($sql);
$data = Log::getChartData("WEBSITE = 'backend.blackpi.ipv64.net'");
$labels = [];
$data = [];
while($row = $result->fetch_assoc()) {
$counts = [];
foreach ($data as $row) {
$labels[] = $row['tag'];
$data[] = (int)$row['anzahl'];
$counts[] = (int)$row['anzahl'];
}
return $this->render('backend_chart', [
'labels' => $labels,
'data' => $data
'data' => $counts
]);
}
}
+15 -47
View File
@@ -3,16 +3,18 @@
namespace MVC\Controller;
use MVC\Core\Controller;
use MVC\Model\CompanyReport;
use MVC\Model\SchoolReport;
use MVC\Model\Overtime;
class ReportController extends Controller {
public function berichte() {
$mode = $_GET['mode'] ?? 'fk';
global $Berufsschulberichte;
if ($mode === 'normal') {
$data = \Databaseshow("Firmenberichte");
$data = CompanyReport::all();
} else {
$data = \DatabaseShowWithFK("Berufsschulberichte", $Berufsschulberichte);
$data = SchoolReport::allWithFK();
}
$data = array_reverse($data);
@@ -30,36 +32,12 @@ class ReportController extends Controller {
public function berichte_arbeit_gen() {
if (empty($_SESSION['user_logged_in'])) return "Access Denied";
$conn = \db_connect();
$conn->query("SET @row := 0");
$wochenQuery = "
SELECT MIN(DATUM) AS start_date, MAX(DATUM) AS end_date
FROM (
SELECT DATUM, DATE_SUB(DATUM, INTERVAL (@row := @row + 1) DAY) AS grp
FROM Firmenberichte
ORDER BY DATUM
) AS t
GROUP BY grp
ORDER BY start_date;
";
$resultWochen = $conn->query($wochenQuery);
$wochenArray = [];
while($row = $resultWochen->fetch_assoc()) $wochenArray[] = $row;
$wochenArray = CompanyReport::getWochen();
$startDate = $_POST['start_date'] ?? date('Y-m-d', strtotime('-5 days'));
$endDate = $_POST['end_date'] ?? date('Y-m-d');
$berichteQuery = "SELECT `DATUM`, `PROZESS_1`, `PROZESS_2`, `PROZESS_3`, `PROZESS_4` FROM Firmenberichte WHERE `DATUM` BETWEEN ? AND ?";
$stmt = $conn->prepare($berichteQuery);
$stmt->bind_param("ss", $startDate, $endDate);
$stmt->execute();
$result = $stmt->get_result();
$berichteArray = [];
while($row = $result->fetch_assoc()) $berichteArray[] = $row;
$stmt->close();
$conn->close();
$berichteArray = CompanyReport::getByDateRange($startDate, $endDate);
$pdfFile = null;
if (isset($_POST['generate_pdf']) && count($berichteArray) > 0) {
@@ -120,31 +98,21 @@ class ReportController extends Controller {
}
public function zeiterfassung() {
$conn = \db_connect();
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_overtime'])) {
$datum = $_POST['ot_date'];
$minuten = intval($_POST['ot_minutes']);
$grund = $conn->real_escape_string($_POST['ot_reason']);
\DatabaseInsert("Ueberstunden", "DATUM, MINUTEN, GRUND", $datum, $minuten, $grund);
Overtime::insert([
'DATUM' => $_POST['ot_date'],
'MINUTEN' => (int)$_POST['ot_minutes'],
'GRUND' => $_POST['ot_reason']
]);
\redirectToWebsite((new \getSpecificData())->getReferer());
exit;
}
$minuten_berichte = 0;
$res1 = $conn->query("SELECT SUM(OVERTIME) AS summe FROM Firmenberichte");
if ($row = $res1->fetch_object()) $minuten_berichte = intval($row->summe);
$minuten_abbau = 0;
$res2 = $conn->query("SELECT SUM(MINUTEN) AS summe FROM Ueberstunden WHERE GRUND = 'Abbau'");
if ($row = $res2->fetch_object()) $minuten_abbau = intval($row->summe);
$minuten_aufbau = 0;
$res3 = $conn->query("SELECT SUM(MINUTEN) AS summe FROM Ueberstunden WHERE GRUND = 'Aufbau'");
if ($row = $res3->fetch_object()) $minuten_aufbau = intval($row->summe);
$minuten_berichte = CompanyReport::getTotalOvertime();
$minuten_abbau = Overtime::getSumByReason('Abbau');
$minuten_aufbau = Overtime::getSumByReason('Aufbau');
$gesamt_minuten = $minuten_berichte - $minuten_abbau + $minuten_aufbau;
$conn->close();
return $this->render('zeiterfassung', [
'gesamt_minuten' => $gesamt_minuten,
+9 -18
View File
@@ -3,6 +3,7 @@
namespace MVC\Controller;
use MVC\Core\Controller;
use MVC\Model\Domain;
class TestController extends Controller {
public function api_tester() {
@@ -30,19 +31,13 @@ class TestController extends Controller {
}
public function weather_chart() {
$conn = \db_connect();
$sql = "SELECT ZEIT, AKTUELL FROM Wetter";
$result = $conn->query($sql);
$res = Domain::query("SELECT ZEIT, AKTUELL FROM Wetter");
$labels = [];
$data = [];
if ($result->num_rows > 0) {
while($row = $result->fetch_assoc()) {
$labels[] = $row['ZEIT'];
$data[] = (float)$row['AKTUELL'];
}
while($row = $res->fetch_assoc()) {
$labels[] = $row['ZEIT'];
$data[] = (float)$row['AKTUELL'];
}
$conn->close();
return $this->render('weather_chart', [
'labels' => $labels,
@@ -57,26 +52,22 @@ class TestController extends Controller {
if (isset($_POST["add_domain"])) {
$subdomain = trim($_POST["subdomain"]);
if ($subdomain != "") {
\DatabaseInsert("Domains", "SUBDOMAIN", $subdomain);
Domain::insert(['SUBDOMAIN' => $subdomain]);
$statusMsg = "Domain wurde hinzugefügt.";
}
}
if (isset($_POST["delete_domain"])) {
$id = intval($_POST["id"]);
\DatabaseDelete("Domains", "ID = ?", $id);
Domain::delete("ID = ?", intval($_POST["id"]));
$statusMsg = "Domain wurde gelöscht.";
}
if (isset($_POST["save_domain"])) {
$id = intval($_POST["id"]);
$subdomain = trim($_POST["subdomain"]);
\DatabaseUpdate("Domains", "SUBDOMAIN = ?", "ID = ?", $subdomain, $id);
Domain::update(['SUBDOMAIN' => trim($_POST["subdomain"])], "ID = ?", intval($_POST["id"]));
$statusMsg = "Domain wurde gespeichert.";
}
}
$domains = \DatabaseShow("Domains", "*");
return $this->render('domains', [
'domains' => $domains,
'domains' => Domain::all(),
'statusMsg' => $statusMsg
]);
}
+7
View File
@@ -15,4 +15,11 @@ class Controller {
}
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;
}
}
+54
View File
@@ -0,0 +1,54 @@
<?php
namespace MVC\Core;
abstract class Model {
protected static $table;
public static function all() {
return \DatabaseShow(static::$table, "*");
}
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 = \db_connect();
if (empty($params)) {
return $conn->query($sql);
}
$stmt = $conn->prepare($sql);
// This is a simplified version, real projects use more robust parameter binding
$types = str_repeat('s', count($params));
$stmt->bind_param($types, ...$params);
$stmt->execute();
return $stmt->get_result();
}
}
+59
View File
@@ -0,0 +1,59 @@
<?php
namespace MVC\Core;
class Router {
protected $routes = [];
protected $middlewares = [];
public function add($service, $controller, $method, $middlewares = []) {
$this->routes[$service] = [
'controller' => $controller,
'method' => $method,
'middlewares' => $middlewares
];
}
public function dispatch($service) {
if (!isset($this->routes[$service])) {
throw new \Exception("Service '$service' not found.");
}
$route = $this->routes[$service];
// Run 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();
}
protected function runMiddleware($middleware) {
if ($middleware === 'admin') {
if (!isset($_SESSION['is_admin']) || $_SESSION['is_admin'] !== true) {
die("Access Denied: Admin rights required.");
}
}
if ($middleware === 'auth') {
if (empty($_SESSION['user_logged_in'])) {
header('Location: login.php');
exit;
}
}
}
}
+98 -102
View File
@@ -1,125 +1,121 @@
# 🏗️ Backend MVC Dokumentation
# 🏗️ Backend MVC - Umfassende Dokumentation
Dieses Projekt nutzt eine moderne **Model-View-Controller (MVC)** Struktur mit einem **modularen Block-Layout**. Dies trennt die Geschäftslogik vom Design und macht den Code wartbar und erweiterbar.
Diese Dokumentation beschreibt die Architektur, die Funktionsweise und die Besonderheiten des neuen Backend-Systems.
---
## 📁 Verzeichnisstruktur
## 🏛️ 1. Architektur-Übersicht
- `WEBROOT/src/MVC/Core/`: Basisklassen (`Controller`, `View`).
- `WEBROOT/src/MVC/Controller/`: Hier liegt die Logik (PHP-Klassen).
- `WEBROOT/src/MVC/View/`: Hier liegen die Templates (HTML/PHP).
- `WEBROOT/src/MVC/Layout/`: Das globale Layout-System und die Blöcke.
- `WEBROOT/index.php`: Der "Entry Point", der alles zusammenfügt.
Das System folgt dem **MVC (Model-View-Controller)** Pattern. Das Ziel ist eine strikte Trennung von Datenverarbeitung, Geschäftslogik und Benutzeroberfläche.
### 📂 Verzeichnisstruktur & Verantwortlichkeiten
- **`WEBROOT/index.php`**: Der zentrale Einstiegspunkt. Hier wird das Routing initialisiert, die Layout-Blöcke definiert und das Error-Handling gesteuert.
- **`src/MVC/Core/`**: Das Herzstück. Enthält die Basisklassen `Controller`, `Model`, `View`, `Router` und `Layout`. Diese Klassen sollten selten angefasst werden.
- **`src/MVC/Model/`**: Daten-Logik. Jede Klasse repräsentiert eine Datenbanktabelle oder einen logischen Datenbereich.
- **`src/MVC/Controller/`**: Geschäftslogik. Controller nehmen Anfragen entgegen, nutzen Models zur Datenbeschaffung und geben Daten an Views weiter.
- **`src/MVC/View/`**: Präsentationsschicht. Reine HTML/PHP-Dateien für die Anzeige.
- **`src/MVC/Layout/`**: Definition des Rahmendesigns und Verwaltung der Inhaltsblöcke.
---
## 🚀 Einen neuen Service erstellen
## 🚦 2. Routing & Middleware (Der Verkehrspolizist)
1. **Dateien anlegen:** Nutze das Script `php create_module.php <name>`.
2. **Controller registrieren:** Trage den neuen Controller in der `$controllers`-Liste in `WEBROOT/index.php` ein.
3. **In Config eintragen:** Füge den Service in der `config.php` (oder über die Admin-Oberfläche) hinzu, damit er in der Sidebar erscheint.
---
## 💡 Arbeiten mit Controller & View
### Daten vom Controller an die View übergeben
Im Controller nutzt du die `render()` Methode:
Das Routing wird in der `index.php` über den `Router` konfiguriert.
### Routen registrieren
```php
public function mein_service() {
$userCount = 42;
return $this->render('meine_view', [
'anzahl' => $userCount,
'status' => 'aktiv'
]);
}
$router->add($service_id, $controller_class, $method_name, $middlewares);
```
- **`$service_id`**: Der Wert des URL-Parameters `?service=...`.
- **`$middlewares`**: Ein Array von Sicherheitsstufen (z.B. `['auth', 'admin']`).
### Verfügbare Middlewares
- `auth`: Stellt sicher, dass die Session `user_logged_in` aktiv ist. Leitet sonst zum Login um.
- `admin`: Stellt sicher, dass die Session `is_admin` wahr ist. Gibt sonst "Access Denied" aus.
---
## 🏛️ 3. Der Model-Layer (Datenabstraktion)
Alle Models erben von `MVC\Core\Model`. Dies gibt ihnen Zugriff auf mächtige Standardmethoden:
### CRUD-Operationen
- `Model::all()`: Gibt alle Datensätze der Tabelle zurück.
- `Model::find($id)`: Sucht einen Datensatz primär über die ID.
- `Model::where($sql, ...$params)`: Filtert Datensätze mit Prepared Statements.
- `Model::insert($data)`: Erwartet ein assoziatives Array (z.B. `['NAME' => 'Max']`).
- `Model::update($data, $where, ...$whereParams)`: Aktualisiert Felder basierend auf einer Bedingung.
- `Model::delete($where, ...$params)`: Löscht Datensätze.
- `Model::query($sql, ...$params)`: Ermöglicht komplexe, manuelle SQL-Abfragen.
### 💡 Besonderheit: Statische Bindung
In den Models wird die Tabelle über `protected static $table` definiert. Die Basisklasse nutzt `static::`, um immer auf die Tabelle des Kind-Models zuzugreifen.
---
## 🎮 4. Controller & AJAX (Die Logik)
Controller fungieren als Vermittler. Sie verarbeiten sowohl klassische Seitenaufrufe als auch AJAX-Requests.
### Seiten rendern
```php
return $this->render('view_name', ['key' => 'value']);
```
In der View (`meine_view.php`) sind diese Variablen direkt verfügbar:
### JSON-Antworten (für AJAX)
Für moderne Oberflächen, die Daten nachladen oder umschalten ohne Refresh:
```php
return $this->json(['success' => true, 'data' => $result]);
```
---
## 🧱 5. Das modulare Block-Layout
Das Layout ist in "Blöcke" unterteilt, die in der `index.php` befüllt werden.
### Blöcke setzen
```php
$layout->setBlock('header', $html_content);
$layout->setBlock('sidebar', $sidebar_html);
```
### Blöcke in der View nutzen (Layout-Template)
In `Layout/Templates/main.php` werden die Blöcke mit `$this->getBlock('name')` ausgegeben.
---
## 🛡️ 6. Error-Handling & Sicherheit
### Zentrales Catch-System
Die gesamte Applikation läuft in einem `try-catch` Block.
- Tritt ein Fehler (Exception) auf, wird dieser im System-Log gespeichert.
- **Admins** sehen den "Stacktrace" (Pfad des Fehlers) zur schnellen Behebung.
- **Benutzer** sehen eine generische, freundliche Fehlermeldung.
### SQL-Injection Schutz
Durch die konsequente Nutzung von `Model::where`, `Model::insert` etc., werden automatisch **Prepared Statements** genutzt. SQL-Injections sind dadurch nahezu ausgeschlossen.
### XSS-Schutz
Variablen in der View sollten **immer** mit `htmlspecialchars()` ausgegeben werden:
```html
<p>Es sind <?= $anzahl ?> Benutzer im Status <?= $status ?>.</p>
<p><?= htmlspecialchars($user_name) ?></p>
```
---
## 🛠️ Mehrere Funktionen in einer View nutzen
## 🔍 7. Besonderheiten & Tipps
Wenn du innerhalb eines Projekts verschiedene Aktionen ausführen willst (z.B. Anzeigen, Bearbeiten, Löschen), gibt es zwei gängige Wege:
### 1. Über URL-Parameter (Empfohlen für einfache Logik)
Du nutzt einen Parameter wie `?action=edit` in deinen Links.
**Im Controller:**
```php
public function mein_service() {
$action = $_GET['action'] ?? 'list';
$data = [];
if ($action === 'edit') {
$data = ["mode" => "Bearbeiten"];
} else {
$data = ["mode" => "Listenansicht"];
}
return $this->render('mein_view', $data);
}
```
### 2. Formular-Aktionen (POST)
Wenn du Buttons für verschiedene Funktionen hast, kannst du diese im Controller abfangen:
**In der View:**
```html
<form method="POST">
<button type="submit" name="save_data">Speichern</button>
<button type="submit" name="delete_data">Löschen</button>
</form>
```
**Im Controller:**
```php
public function mein_service() {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['save_data'])) {
// Logik für Speichern
}
if (isset($_POST['delete_data'])) {
// Logik für Löschen
}
// Nach POST immer Redirect, um "F5-Resubmit" zu verhindern
header("Location: ?service=mein_service");
exit;
}
return $this->render('mein_view');
}
```
- **Namespace-Beachtung**: Da wir Namespaces nutzen (`MVC\Controller`), müssen globale PHP-Funktionen oder Klassen aus dem `src/utils` Ordner mit einem Backslash aufgerufen werden (z.B. `\db_connect()` oder `\getSpecificData()`).
- **Globaler State**: Variablen aus der `config.php` (wie `$Berechtigungen`) müssen in Controllern oft via `global $variable;` bekannt gemacht werden, bevor sie an Models übergeben werden.
- **Post-Redirect-Get**: Nach dem Speichern von Daten (`POST`) im Controller immer ein `header("Location: ...")` ausführen, um doppelte Einsendungen beim Neuladen zu verhindern.
- **Statische Files**: Bilder, CSS und JS gehören in den Ordner `WEBROOT/static/`.
---
## 🧱 Das Block-Layout System
Das Layout besteht aus Blöcken. In der `index.php` kannst du diese Blöcke füllen, bevor das Layout gerendert wird:
- `title`: Der Tab-Titel.
- `header`: Der obere Balken.
- `sidebar`: Das Seitenmenü.
- `content`: Der Hauptinhalt (kommt meist aus dem Controller).
- `scripts`: Zusätzliches JavaScript für die Seite.
Beispiel für zusätzliche Scripte im Controller:
```php
// In index.php könnte man das Layout erweitern:
$layout->setBlock('scripts', '<script src="mein_script.js"></script>');
## 🚀 Modul-Generator
Nutze das Script im Hauptverzeichnis für schnelle Ergebnisse:
```bash
php create_module.php mein_name
```
---
## ⚠️ Wichtige Regeln (Best Practices)
1. **Kein HTML im Controller:** Controller bereiten nur Daten vor. HTML gehört ausschließlich in die `View`.
2. **Globale Klassen:** Nutze `\` vor globalen Funktionen oder Klassen (z.B. `\DatabaseShow()`), wenn du dich innerhalb eines Namespaces befindest.
3. **Security:** Nutze immer `htmlspecialchars()` beim Ausgeben von Variablen in der View, um XSS-Angriffe zu verhindern.
4. **Redirects:** Nach einem `POST` (Daten speichern) solltest du immer ein `header("Location: ...")` machen, damit die Seite beim Neuladen die Daten nicht doppelt sendet.
5. **Namespaces:** Achte darauf, dass neue Controller immer `namespace MVC\Controller;` am Anfang stehen haben.
Dieses erstellt automatisch Controller und View nach den oben genannten Standards.
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace MVC\Model;
use MVC\Core\Model;
class CompanyReport extends Model {
protected static $table = "Firmenberichte";
public static function getByDateRange($start, $end) {
$sql = "SELECT `DATUM`, `PROZESS_1`, `PROZESS_2`, `PROZESS_3`, `PROZESS_4` FROM Firmenberichte WHERE `DATUM` BETWEEN ? AND ?";
return self::query($sql, $start, $end)->fetch_all(MYSQLI_ASSOC);
}
public static function getWochen() {
self::query("SET @row := 0");
$sql = "SELECT MIN(DATUM) AS start_date, MAX(DATUM) AS end_date FROM (SELECT DATUM, DATE_SUB(DATUM, INTERVAL (@row := @row + 1) DAY) AS grp FROM Firmenberichte ORDER BY DATUM) AS t GROUP BY grp ORDER BY start_date";
return self::query($sql)->fetch_all(MYSQLI_ASSOC);
}
public static function getTotalOvertime() {
$res = self::query("SELECT SUM(OVERTIME) AS summe FROM Firmenberichte");
return intval($res->fetch_object()->summe ?? 0);
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace MVC\Model;
use MVC\Core\Model;
class Documentation extends Model {
protected static $table = "Dokumentation";
public static function search($query, $page = 1, $limit = 5) {
$offset = ($page - 1) * $limit;
$search = "%$query%";
$countRes = \DatabaseShow(self::$table, "COUNT(*) as total", "TITEL LIKE ? OR INHALT LIKE ? OR KATEGORIE LIKE ?", $search, $search, $search);
$total = $countRes[0]['total'] ?? 0;
$sql = "SELECT * FROM Dokumentation WHERE TITEL LIKE ? OR INHALT LIKE ? OR KATEGORIE LIKE ? ORDER BY ERSTELLT_AM DESC LIMIT ? OFFSET ?";
$result = self::query($sql, $search, $search, $search, $limit, $offset);
$rows = $result->fetch_all(MYSQLI_ASSOC);
return [
'rows' => $rows,
'total' => $total,
'pages' => ceil($total / $limit),
'current_page' => $page
];
}
}
+9
View File
@@ -0,0 +1,9 @@
<?php
namespace MVC\Model;
use MVC\Core\Model;
class Domain extends Model {
protected static $table = "Domains";
}
+43
View File
@@ -0,0 +1,43 @@
<?php
namespace MVC\Model;
use MVC\Core\Model;
class Log extends Model {
protected static $table = "logs";
public static function getFiltered($websiteFilter, $devIpsSql, $filter) {
switch ($filter) {
case 'today':
$sql = "SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE` FROM logs WHERE DATE(FIRST_REQUEST) = CURDATE() AND $websiteFilter AND CLIENT_IP NOT IN ($devIpsSql) ORDER BY FIRST_REQUEST DESC";
break;
case 'counterperip':
$sql = "SELECT CLIENT_IP, LAND, USER_AGENT, SUM(counter) AS gesamt_counter FROM logs WHERE $websiteFilter AND CLIENT_IP NOT IN ($devIpsSql) GROUP BY CLIENT_IP ORDER BY gesamt_counter DESC";
break;
case 'node1':
case 'node2':
case 'node3':
case 'node4':
$sql = "SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE` FROM logs WHERE `SERVER_IP` = ? AND $websiteFilter ORDER BY `FIRST_REQUEST` DESC";
$res = self::query($sql, $filter);
return $res->fetch_all(MYSQLI_ASSOC);
default:
$sql = "SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE` FROM logs WHERE $websiteFilter AND CLIENT_IP NOT IN ($devIpsSql) ORDER BY FIRST_REQUEST DESC";
}
$res = self::query($sql);
return $res->fetch_all(MYSQLI_ASSOC);
}
public static function getChartData($websiteFilter = null) {
$where = $websiteFilter ? "WHERE $websiteFilter" : "";
$sql = "SELECT DATE(FIRST_REQUEST) as tag, COUNT(*) as anzahl FROM logs $where GROUP BY DATE(FIRST_REQUEST) ORDER BY DATE(FIRST_REQUEST)";
return self::query($sql)->fetch_all(MYSQLI_ASSOC);
}
public static function getNodeChartData($node) {
$sql = "SELECT DATE(FIRST_REQUEST) as tag, COUNT(*) as anzahl FROM logs WHERE SERVER_IP = ? GROUP BY DATE(FIRST_REQUEST)";
return self::query($sql, $node)->fetch_all(MYSQLI_ASSOC);
}
}
+14
View File
@@ -0,0 +1,14 @@
<?php
namespace MVC\Model;
use MVC\Core\Model;
class Overtime extends Model {
protected static $table = "Ueberstunden";
public static function getSumByReason($reason) {
$res = self::query("SELECT SUM(MINUTEN) AS summe FROM Ueberstunden WHERE GRUND = ?", $reason);
return intval($res->fetch_object()->summe ?? 0);
}
}
+14
View File
@@ -0,0 +1,14 @@
<?php
namespace MVC\Model;
use MVC\Core\Model;
class SchoolReport extends Model {
protected static $table = "Berufsschulberichte";
public static function allWithFK() {
global $Berufsschulberichte;
return \DatabaseShowWithFK(self::$table, $Berufsschulberichte);
}
}
+18
View File
@@ -0,0 +1,18 @@
<?php
namespace MVC\Model;
use MVC\Core\Model;
class User extends Model {
protected static $table = "Benutzer";
public static function getByName($name) {
return self::where("NAME = ?", $name);
}
public static function isAdmin($userId) {
$user = self::find($userId);
return ($user['ADMIN'] ?? 0) == 1;
}
}
+22
View File
@@ -0,0 +1,22 @@
<?php
namespace MVC\Model;
use MVC\Core\Model;
class Variable extends Model {
protected static $table = "Variablen";
public static function getByName($name) {
return self::where("VARIABLENNAME = ?", $name)[0] ?? null;
}
public static function toggle($name) {
$var = self::getByName($name);
if ($var) {
$newValue = ($var['WERT'] === "ON") ? "OFF" : "ON";
return self::update(['WERT' => $newValue], "VARIABLENNAME = ?", $name);
}
return false;
}
}
+115
View File
@@ -0,0 +1,115 @@
<div class="documentation-container">
<h2>📚 Projekt-Dokumentation</h2>
<div class="search-bar">
<form method="GET">
<input type="hidden" name="service" value="documentation">
<input type="text" name="q" value="<?= htmlspecialchars($query) ?>" placeholder="Suchen...">
<button type="submit">Suchen</button>
</form>
</div>
<div class="doc-list">
<?php if (empty($docs)): ?>
<p>Keine Einträge gefunden.</p>
<?php else: ?>
<?php foreach ($docs as $doc): ?>
<div class="doc-item">
<span class="category-badge"><?= htmlspecialchars($doc['KATEGORIE']) ?></span>
<h3><?= htmlspecialchars($doc['TITEL']) ?></h3>
<p><?= nl2br(htmlspecialchars($doc['INHALT'])) ?></p>
<small>Erstellt am: <?= date('d.m.Y H:i', strtotime($doc['ERSTELLT_AM'])) ?></small>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
<?php if ($pages > 1): ?>
<div class="pagination">
<?php for ($i = 1; $i <= $pages; $i++): ?>
<a href="?service=documentation&q=<?= urlencode($query) ?>&page=<?= $i ?>"
class="<?= $i === $currentPage ? 'active' : '' ?>">
<?= $i ?>
</a>
<?php endfor; ?>
</div>
<?php endif; ?>
</div>
<style>
.documentation-container {
max-width: 800px;
margin: 0 auto;
}
.search-bar {
margin-bottom: 25px;
}
.search-bar form {
display: flex;
gap: 10px;
}
.search-bar input {
flex: 1;
padding: 10px;
border-radius: 6px;
border: 1px solid var(--input-border);
background: var(--input-bg);
color: var(--input-text);
}
.doc-item {
background: var(--card-bg);
border: 1px solid var(--card-border);
padding: 20px;
border-radius: 10px;
margin-bottom: 20px;
position: relative;
}
.category-badge {
position: absolute;
top: 15px;
right: 15px;
background: var(--btn-bg);
color: white;
padding: 4px 10px;
border-radius: 15px;
font-size: 0.75rem;
font-weight: bold;
}
.doc-item h3 {
margin-top: 0;
color: var(--btn-bg);
}
.doc-item p {
line-height: 1.6;
}
.pagination {
display: flex;
justify-content: center;
gap: 8px;
margin-top: 30px;
}
.pagination a {
display: inline-block;
padding: 8px 14px;
background: var(--card-bg);
border: 1px solid var(--card-border);
border-radius: 5px;
text-decoration: none;
color: var(--text);
}
.pagination a.active {
background: var(--btn-bg);
color: white;
border-color: var(--btn-bg);
}
</style>
+5 -4
View File
@@ -34,11 +34,12 @@ $controllerContent = <<<PHP
namespace MVC\Controller;
use MVC\Core\Controller;
// use MVC\Model\YourModel; // Importiere dein Model
class {$className}Controller extends Controller {
public function {$key}() {
// Hier kannst du Daten aus der Datenbank laden
// \$data = \DatabaseShow("DeineTabelle");
// Nutze Models für den Datenbankzugriff
// \$data = YourModel::all();
return \$this->render('{$key}', [
'title' => '{$className} Modul',
@@ -82,8 +83,8 @@ if (!file_exists($viewFile)) {
// 3. Anleitung ausgeben
echo "\n--- NÄCHSTE SCHRITTE ---\n";
echo "1. Registriere den Controller in WEBROOT/index.php im \$controllers Array:\n";
echo " '{$key}' => \\MVC\\Controller\\{$className}Controller::class,\n\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";
+224
View File
@@ -0,0 +1,224 @@
<?php
/**
* MVC Project Bootstrapper
* Erstellt ein neues Projekt-Grundgerüst basierend auf der aktuellen Architektur.
*/
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/static',
'WEBROOT/src',
'WEBROOT/src/MVC',
'WEBROOT/src/MVC/Core',
'WEBROOT/src/MVC/Controller',
'WEBROOT/src/MVC/Model',
'WEBROOT/src/MVC/View',
'WEBROOT/src/MVC/Layout',
'WEBROOT/src/MVC/Layout/Templates',
];
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";
}
}
// 2. Kern-Dateien definieren
$files = [];
// --- CORE: Controller ---
$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;
// --- CORE: View ---
$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.";
}
}
PHP;
// --- CORE: Model ---
$files['WEBROOT/src/MVC/Core/Model.php'] = <<<'PHP'
<?php
namespace MVC\Core;
abstract class Model {
protected static $table;
public static function all() { /* Implement logic */ return []; }
public static function find($id) { /* Implement logic */ return null; }
public static function query($sql, ...$params) { /* Implement logic */ return null; }
}
PHP;
// --- CORE: Router ---
$files['WEBROOT/src/MVC/Core/Router.php'] = <<<'PHP'
<?php
namespace MVC\Core;
class Router {
protected $routes = [];
public function add($service, $controller, $method, $middlewares = []) {
$this->routes[$service] = ['controller' => $controller, 'method' => $method, 'middlewares' => $middlewares];
}
public function dispatch($service) {
if (!isset($this->routes[$service])) throw new \Exception("Service '$service' not found.");
$route = $this->routes[$service];
foreach ($route['middlewares'] as $mw) { $this->runMiddleware($mw); }
$controller = new $route['controller']();
$method = $route['method'];
return $controller->$method();
}
protected function runMiddleware($mw) {
if ($mw === 'auth' && empty($_SESSION['user_logged_in'])) { header('Location: login.php'); exit; }
}
}
PHP;
// --- CORE: Layout ---
$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 not found.";
}
}
PHP;
// --- CORE: Autoloader ---
$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;
$file = $base_dir . str_replace('\\', '/', substr($class, $len)) . '.php';
if (file_exists($file)) require $file;
});
PHP;
// --- LAYOUT TEMPLATE ---
$files['WEBROOT/src/MVC/Layout/Templates/main.php'] = <<<'HTML'
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8"><title><?= $this->getBlock('title') ?></title>
<style>
body { font-family: sans-serif; display: flex; margin: 0; }
.sidebar { width: 250px; background: #eee; height: 100vh; padding: 20px; }
.content { flex: 1; padding: 20px; }
</style>
</head>
<body>
<div class="sidebar"><?= $this->getBlock('sidebar') ?></div>
<div class="content"><?= $this->getBlock('content') ?></div>
</body>
</html>
HTML;
// --- DEFAULT CONTROLLER ---
$files['WEBROOT/src/MVC/Controller/HomeController.php'] = <<<'PHP'
<?php
namespace MVC\Controller;
use MVC\Core\Controller;
class HomeController extends Controller {
public function index() {
return $this->render('home', ['welcome' => 'Willkommen in deinem neuen MVC Projekt!']);
}
}
PHP;
// --- DEFAULT VIEW ---
$files['WEBROOT/src/MVC/View/home.php'] = <<<'HTML'
<h1>Dashboard</h1>
<p><?= htmlspecialchars($welcome) ?></p>
HTML;
// --- ENTRY POINT: index.php ---
$files['WEBROOT/index.php'] = <<<'PHP'
<?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');
$layout = new \MVC\Layout\Layout('main');
$layout->setBlock('title', 'Mein Neues Projekt');
$layout->setBlock('sidebar', '<a href="?service=home">Home</a>');
$layout->setBlock('content', $router->dispatch($service));
echo $layout->render();
} catch (\Exception $e) {
echo "<h1>Fehler</h1><p>" . $e->getMessage() . "</p>";
}
PHP;
// 3. Dateien schreiben
foreach ($files as $file => $content) {
file_put_contents($baseDir . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $file), $content);
echo "📄 Datei erstellt: $file\n";
}
echo "\n✨ Projekt '$projectName' wurde erfolgreich initialisiert!\n";
echo "Gehe in den Ordner und starte deinen Webserver in WEBROOT.\n";
PHP;