diff --git a/WEBROOT/config.php b/WEBROOT/config.php index ada395d..145d952 100644 --- a/WEBROOT/config.php +++ b/WEBROOT/config.php @@ -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', ), -); +); \ No newline at end of file diff --git a/WEBROOT/index.php b/WEBROOT/index.php index 8828b18..bd86b45 100644 --- a/WEBROOT/index.php +++ b/WEBROOT/index.php @@ -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 setBlock('header', ob_get_clean()); + $layout->setBlock('header', ob_get_clean()); -// Sidebar Block -ob_start(); -?> + // Sidebar Block + ob_start(); + ?> $group): ?> + $isOpen = !empty($group['defaultOpen']) || in_array($selectedService, $visibleServices, true); + ?>
@@ -138,48 +169,31 @@ ob_start();
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', "

Kein aktiver Service verfügbar.

"); } - $layout->setBlock('content', $content); - logging(); -} else { - $layout->setBlock('content', "

Kein aktiver Service verfügbar.

"); -} -echo $layout->render(); \ No newline at end of file + echo $layout->render(); + +} catch (\Exception $e) { + // Central Error Handling + error_log($e->getMessage()); + http_response_code(500); + + if ($isAdmin) { + echo "

Internal Server Error

"; + echo "

Message: " . $e->getMessage() . "

"; + echo "

File: " . $e->getFile() . " on line " . $e->getLine() . "

"; + echo "
" . $e->getTraceAsString() . "
"; + } else { + echo "

Hoppla!

"; + echo "

Ein interner Fehler ist aufgetreten. Bitte versuchen Sie es später erneut.

"; + } +} \ No newline at end of file diff --git a/WEBROOT/src/MVC/Controller/AdminController.php b/WEBROOT/src/MVC/Controller/AdminController.php index 78a9e3a..c9a97ee 100644 --- a/WEBROOT/src/MVC/Controller/AdminController.php +++ b/WEBROOT/src/MVC/Controller/AdminController.php @@ -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"; diff --git a/WEBROOT/src/MVC/Controller/DocumentationController.php b/WEBROOT/src/MVC/Controller/DocumentationController.php new file mode 100644 index 0000000..17e4c1e --- /dev/null +++ b/WEBROOT/src/MVC/Controller/DocumentationController.php @@ -0,0 +1,24 @@ +render('documentation', [ + 'docs' => $results['rows'], + 'total' => $results['total'], + 'pages' => $results['pages'], + 'currentPage' => $results['current_page'], + 'query' => $query + ]); + } +} diff --git a/WEBROOT/src/MVC/Controller/LogController.php b/WEBROOT/src/MVC/Controller/LogController.php index 09dd328..b3d7e4a 100644 --- a/WEBROOT/src/MVC/Controller/LogController.php +++ b/WEBROOT/src/MVC/Controller/LogController.php @@ -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 ]); } } diff --git a/WEBROOT/src/MVC/Controller/ReportController.php b/WEBROOT/src/MVC/Controller/ReportController.php index 86c1ec8..7ed7df7 100644 --- a/WEBROOT/src/MVC/Controller/ReportController.php +++ b/WEBROOT/src/MVC/Controller/ReportController.php @@ -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, diff --git a/WEBROOT/src/MVC/Controller/TestController.php b/WEBROOT/src/MVC/Controller/TestController.php index 347421b..d299a7b 100644 --- a/WEBROOT/src/MVC/Controller/TestController.php +++ b/WEBROOT/src/MVC/Controller/TestController.php @@ -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 ]); } diff --git a/WEBROOT/src/MVC/Core/Controller.php b/WEBROOT/src/MVC/Core/Controller.php index 0f6809b..e337837 100644 --- a/WEBROOT/src/MVC/Core/Controller.php +++ b/WEBROOT/src/MVC/Core/Controller.php @@ -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; + } } diff --git a/WEBROOT/src/MVC/Core/Model.php b/WEBROOT/src/MVC/Core/Model.php new file mode 100644 index 0000000..cccb241 --- /dev/null +++ b/WEBROOT/src/MVC/Core/Model.php @@ -0,0 +1,54 @@ + $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(); + } +} diff --git a/WEBROOT/src/MVC/Core/Router.php b/WEBROOT/src/MVC/Core/Router.php new file mode 100644 index 0000000..44f059f --- /dev/null +++ b/WEBROOT/src/MVC/Core/Router.php @@ -0,0 +1,59 @@ +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; + } + } + } +} diff --git a/WEBROOT/src/MVC/MVC_GUIDE.md b/WEBROOT/src/MVC/MVC_GUIDE.md index 5095e0d..811aa33 100644 --- a/WEBROOT/src/MVC/MVC_GUIDE.md +++ b/WEBROOT/src/MVC/MVC_GUIDE.md @@ -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 `. -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 -

Es sind Benutzer im Status .

+

``` --- -## 🛠️ 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 -
- - -
-``` - -**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', ''); +## 🚀 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. diff --git a/WEBROOT/src/MVC/Model/CompanyReport.php b/WEBROOT/src/MVC/Model/CompanyReport.php new file mode 100644 index 0000000..02f7d31 --- /dev/null +++ b/WEBROOT/src/MVC/Model/CompanyReport.php @@ -0,0 +1,25 @@ +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); + } +} diff --git a/WEBROOT/src/MVC/Model/Documentation.php b/WEBROOT/src/MVC/Model/Documentation.php new file mode 100644 index 0000000..297dccf --- /dev/null +++ b/WEBROOT/src/MVC/Model/Documentation.php @@ -0,0 +1,29 @@ +fetch_all(MYSQLI_ASSOC); + + return [ + 'rows' => $rows, + 'total' => $total, + 'pages' => ceil($total / $limit), + 'current_page' => $page + ]; + } +} diff --git a/WEBROOT/src/MVC/Model/Domain.php b/WEBROOT/src/MVC/Model/Domain.php new file mode 100644 index 0000000..e5634f9 --- /dev/null +++ b/WEBROOT/src/MVC/Model/Domain.php @@ -0,0 +1,9 @@ +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); + } +} diff --git a/WEBROOT/src/MVC/Model/Overtime.php b/WEBROOT/src/MVC/Model/Overtime.php new file mode 100644 index 0000000..65e6d38 --- /dev/null +++ b/WEBROOT/src/MVC/Model/Overtime.php @@ -0,0 +1,14 @@ +fetch_object()->summe ?? 0); + } +} diff --git a/WEBROOT/src/MVC/Model/SchoolReport.php b/WEBROOT/src/MVC/Model/SchoolReport.php new file mode 100644 index 0000000..e5b0a4c --- /dev/null +++ b/WEBROOT/src/MVC/Model/SchoolReport.php @@ -0,0 +1,14 @@ + $newValue], "VARIABLENNAME = ?", $name); + } + return false; + } +} diff --git a/WEBROOT/src/MVC/View/documentation.php b/WEBROOT/src/MVC/View/documentation.php new file mode 100644 index 0000000..333db76 --- /dev/null +++ b/WEBROOT/src/MVC/View/documentation.php @@ -0,0 +1,115 @@ +
+

📚 Projekt-Dokumentation

+ + + +
+ +

Keine Einträge gefunden.

+ + +
+ +

+

+ Erstellt am: +
+ + +
+ + 1): ?> + + +
+ + diff --git a/create_module.php b/create_module.php index 6690962..fc5b357 100644 --- a/create_module.php +++ b/create_module.php @@ -34,11 +34,12 @@ $controllerContent = <<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"; diff --git a/init_mvc.php b/init_mvc.php new file mode 100644 index 0000000..e614992 --- /dev/null +++ b/init_mvc.php @@ -0,0 +1,224 @@ +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' +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' +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' +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' + + + + <?= $this->getBlock('title') ?> + + + + +
getBlock('content') ?>
+ + +HTML; + +// --- DEFAULT CONTROLLER --- +$files['WEBROOT/src/MVC/Controller/HomeController.php'] = <<<'PHP' +render('home', ['welcome' => 'Willkommen in deinem neuen MVC Projekt!']); + } +} +PHP; + +// --- DEFAULT VIEW --- +$files['WEBROOT/src/MVC/View/home.php'] = <<<'HTML' +

Dashboard

+

+HTML; + +// --- ENTRY POINT: index.php --- +$files['WEBROOT/index.php'] = <<<'PHP' +add('home', \MVC\Controller\HomeController::class, 'index'); + + $layout = new \MVC\Layout\Layout('main'); + $layout->setBlock('title', 'Mein Neues Projekt'); + $layout->setBlock('sidebar', 'Home'); + $layout->setBlock('content', $router->dispatch($service)); + + echo $layout->render(); +} catch (\Exception $e) { + echo "

Fehler

" . $e->getMessage() . "

"; +} +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;