Compare commits

..
Author SHA1 Message Date
Bruno Eckhardt ab8f36b17a add 2026-07-14 13:55:51 +02:00
Bruno Eckhardt 2062d64ad9 add 2026-07-14 13:45:03 +02:00
Tower ce0d16c1ed add and delete connections 2026-07-13 20:41:23 +02:00
Bruno Eckhardt b1416aa933 Merge branch 'domain-umzug' 2026-07-13 14:49:30 +02:00
Bruno Eckhardt c8f4f47439 change domain name 2026-07-13 14:47:33 +02:00
Bruno Eckhardt 86c20a63bf add new DB connection 2026-07-13 14:21:24 +02:00
Bruno Eckhardt 1fba004b9b Merge branch 'auto-merge' 2026-07-13 13:32:11 +02:00
Bruno Eckhardt 9622e35ca3 fixes 2026-07-13 13:32:01 +02:00
Bruno Eckhardt 2748349060 Merge branch 'auto-merge' 2026-07-13 13:02:52 +02:00
Bruno Eckhardt ebbb6551f7 delete comments 2026-07-13 13:02:40 +02:00
Bruno Eckhardt 992ac5eb6a Merge branch 'auto-merge' 2026-07-13 12:35:11 +02:00
Bruno Eckhardt d37937f4d8 add form fixes 2026-07-13 12:34:59 +02:00
Bruno Eckhardt 523400ac0e Merge branch 'auto-merge' 2026-07-13 12:31:02 +02:00
Bruno Eckhardt 2064546b19 add auto merge script 2026-07-13 12:30:48 +02:00
Bruno Eckhardt d71e2c1e2d api controller fixes 2026-07-13 10:45:32 +02:00
Bruno Eckhardt 62dadd175c Merge branch 'vergabe-service' 2026-07-08 09:40:41 +02:00
Bruno Eckhardt 3c9eb7091f feat: search persons and onjects 2026-07-08 09:40:32 +02:00
Bruno Eckhardt 7abcac5f04 feat: autofill and archive deleted things 2026-07-08 09:31:20 +02:00
Bruno Eckhardt 7d03579d31 Merge branch 'vergabe-service' 2026-07-08 08:59:19 +02:00
Bruno Eckhardt 742c5d5b56 feat: autofill 2026-07-08 08:59:03 +02:00
Bruno Eckhardt 0952d6de13 Merge branch 'berufsschulberichte' 2026-07-08 08:15:57 +02:00
Tower 5a9a08421f new berufsschulreport service 2026-07-07 22:13:12 +02:00
Tower ffcc8821ed update 2026-07-07 19:19:57 +02:00
Bruno Eckhardt 72f188e239 raw form 2026-07-07 15:44:07 +02:00
Bruno Eckhardt 6ec159f75c fix email 2026-07-07 14:27:37 +02:00
Bruno Eckhardt d6e70a08eb Merge branch 'termin-service' 2026-07-07 10:08:34 +02:00
Bruno Eckhardt 52446cc019 fixes 2026-07-07 10:07:22 +02:00
Bruno Eckhardt 10472843a7 Merge branch 'termin-service' 2026-07-07 09:07:27 +02:00
Bruno Eckhardt 771f9e7bf6 fix UTF8 formating and right timestamp in DB when updated 2026-07-07 09:05:38 +02:00
Tower a066c59576 client ip changes 2026-07-06 22:30:24 +02:00
Tower 388f9b8f0d add termin mailing 2026-07-06 22:26:26 +02:00
Tower b08857a4cd delete comments and change some words 2026-07-06 21:51:40 +02:00
Tower 6d68fbc25d fix format and delete option 2026-07-06 20:39:21 +02:00
Bruno Eckhardt dceb02a3de fixes 2026-07-06 15:51:27 +02:00
Bruno Eckhardt cd325270f0 add new calender / termine service 2026-07-06 15:18:11 +02:00
Bruno Eckhardt efa9584baf hotfix 2026-07-06 14:33:32 +02:00
Bruno Eckhardt ac202be4a7 add includes 2026-07-06 14:25:06 +02:00
Bruno Eckhardt 24e11e14a3 fix logout error logging 2026-07-06 14:21:53 +02:00
Bruno Eckhardt 5305fcabb6 logout fix 2026-07-06 14:13:33 +02:00
Bruno Eckhardt 3f7c840c39 style fixes 2026-07-06 13:50:12 +02:00
Bruno Eckhardt 378c462b1c new einkaufslisten service 2026-07-06 13:40:55 +02:00
Bruno Eckhardt 5f1aa1c6fc mobile optimizing 2026-07-06 08:47:33 +02:00
Bruno Eckhardt 5ac4fa4ac6 add showing entries and delete option 2026-07-06 08:40:00 +02:00
Bruno Eckhardt 3eb451069b delete wrong letter 2026-07-06 08:22:44 +02:00
Tower caf62ea701 fix logout 2026-07-05 19:13:08 +02:00
Tower 82fe6e85d8 mobile css fixes 2026-07-05 19:06:04 +02:00
Tower 40c26e625b add new vergabe service for papa 2026-07-01 16:39:56 +02:00
127 changed files with 8884 additions and 4335 deletions

No files matched your search

+1 -7
View File
@@ -5,18 +5,12 @@
}
],
"settings": {
"workbench.colorTheme": "Dark Modern",
"workbench.colorTheme": "Default Dark Modern",
"workbench.colorCustomizations": {
"activityBar.background": "#ff000075",
"activityBar.foreground": "#000000",
"breadcrumb.foreground": "#FFFFFF",
},
"files.exclude": {
"*.git*": true,
"WEBROOT/config_backups": true,
"WEBROOT/static": true,
"WEBROOT/templates": true,
},
"editor.renderWhitespace": "all",
"editor.smoothScrolling": true,
"editor.fontSize": 14,
-166
View File
@@ -1,166 +0,0 @@
# 🏗️ Backend MVC Framework - Das Handbuch
Dieses Framework ist eine performante, leichtgewichtige und modulare PHP-Architektur, die speziell für die Entwicklung von professionellen Backends und Dashboards entwickelt wurde. Es implementiert das **Model-View-Controller (MVC)** Pattern und trennt strikt zwischen Datenlogik, Geschäftslogik und UI-Design.
---
## 🏛️ 1. Architektur & Verzeichnisstruktur
Das Herzstück des Frameworks liegt in `WEBROOT/src/MVC/`. Die Struktur ist darauf ausgelegt, dass das Projekt beliebig wachsen kann, ohne unübersichtlich zu werden.
### 📂 Ordner-Struktur im Detail
* **`WEBROOT/index.php`**: Der zentrale Einstiegspunkt (Front Controller). Jede Anfrage läuft hier auf. Hier werden Routen registriert und das Layout zusammengesetzt.
* **`src/MVC/Core/`**:
* `Controller.php`: Basisklasse für alle Controller. Bietet Methoden für Rendering und JSON-Output.
* `Model.php`: Basisklasse für Datenbankzugriffe. Implementiert sichere CRUD-Methoden via Prepared Statements.
* `View.php`: Die Engine, die Daten in HTML-Templates extrahiert.
* `Router.php`: Verwaltet Routen und schaltet Middlewares (Auth/Admin) vor.
* `Layout/Layout.php`: Das Block-System für modulares UI-Design.
* **`src/MVC/Controller/`**: Hier landen deine PHP-Klassen für die Logik. Beispiel: `UserController.php`, `LogController.php`.
* **`src/MVC/Model/`**: Jede Klasse entspricht einer Datenbanktabelle. Hier wird definiert, *wie* Daten geholt oder gespeichert werden.
* **`src/MVC/View/`**: Reine HTML-Dateien mit minimalem PHP-Code für die Anzeige.
* **`src/MVC/Layout/Templates/`**: Enthält das Grundgerüst (`main.php`), in das die Views eingebettet werden.
---
## 🚦 2. Der Request-Lifecycle (Wie eine Seite entsteht)
1. Ein Benutzer ruft `?service=benutzer` auf.
2. Die `index.php` lädt den **Autoloader** und initialisiert den **Router**.
3. Der **Router** prüft, ob eine Middleware (z.B. `auth`) für diesen Service konfiguriert ist.
4. Wenn der Zugriff erlaubt ist, erstellt der Router eine Instanz des jeweiligen **Controllers** und ruft die Zielmethode auf.
5. Der **Controller** nutzt ein **Model**, um Daten aus der Datenbank zu holen.
6. Der Controller übergibt diese Daten an eine **View** (`$this->render()`).
7. Die **index.php** nimmt das HTML der View entgegen, packt es in den `content`-Block des **Layouts** und gibt die fertige Seite aus.
---
## 🏛️ 3. Arbeiten mit dem Model-Layer (Datenbank)
Vermeide SQL direkt im Controller! Nutze immer Models für eine saubere Abstraktion.
### Beispiel: Ein eigenes Model erstellen
```php
namespace MVC\Model;
use MVC\Core\Model;
class Product extends Model {
protected static $table = "Produkte"; // Tabellenname in der DB
public static function getExpensiveProducts() {
return self::where("PREIS > ?", 100); // Sicher gegen SQL-Injection!
}
}
```
### Verfügbare Methoden der Basisklasse:
* `all()`: Lädt alles aus der Tabelle.
* `find($id)`: Findet einen Datensatz per ID.
* `where($sql, ...$params)`: Filtert mit Prepared Statements.
* `insert($array)`: Speichert neue Daten (z.B. `['TITEL' => 'Test']`).
* `update($array, $where, ...$params)`: Aktualisiert bestehende Daten.
* `delete($where, ...$params)`: Löscht Datensätze sicher.
---
## 🎮 4. Controller & Formularverarbeitung
Controller steuern den Ablauf. Sie entscheiden, was bei `GET` und was bei `POST` passiert.
### Beispiel: Daten verarbeiten
```php
public function save() {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Daten via Model speichern
User::update(['STATUS' => 'aktiv'], "ID = ?", $_POST['id']);
// WICHTIG: Danach immer Redirect (Post-Redirect-Get Pattern)
header("Location: ?service=user_list");
exit;
}
return $this->render('edit_view');
}
```
---
## 🧱 5. Das modulare Block-Layout
Das Layout-System ermöglicht es, verschiedene Teile der Seite (Header, Sidebar, Content) unabhängig voneinander zu befüllen.
### Blöcke in `index.php` befüllen:
```php
$layout = new \MVC\Layout\Layout('main');
$layout->setBlock('title', 'Admin Dashboard');
$layout->setBlock('sidebar', $sidebarHtml);
$layout->setBlock('content', $controllerOutput);
```
### Blöcke im Template (`main.php`) ausgeben:
```html
<aside class="sidebar">
<?= $this->getBlock('sidebar') ?>
</aside>
```
---
## 🚀 6. CLI-Werkzeuge für Entwickler
Das Framework kommt mit automatisierten Scripten, um lästige Routinearbeiten zu vermeiden.
### Projekt initialisieren
Erstellt ein komplettes Skelett inkl. Dev-Server und API.
```bash
php init_mvc.php MeinProjekt
```
### Modul (Controller + View) erstellen
Erstellt zwei Dateien und gibt dir den Code für den Router aus.
```bash
php create_module.php kunden_verwaltung
```
### Model erstellen
Erstellt eine Model-Klasse mit Tabellenzuweisung und dokumentierten Spalten.
```bash
php create_model.php Kunden ID,NAME,EMAIL,TELEFON
```
---
## 🛡️ 7. Sicherheit & Best Practices
1. **XSS-Schutz**: Gib niemals Variablen direkt in der View aus. Nutze immer `<?= htmlspecialchars($var) ?>`.
2. **SQL-Injection**: Nutze niemals Variablen direkt in einem SQL-String. Nutze die Methoden `where()`, `update()` etc. des Models. Sie verwenden intern Prepared Statements.
3. **Namespaces**: Alle Klassen im `src/MVC` Ordner nutzen Namespaces. Achte beim Aufruf globaler Klassen (außerhalb des MVC-Ordners) auf den führenden Backslash, z.B. `\db_connect()`.
4. **Error Handling**: Fehler werden zentral in der `index.php` gefangen. Admins erhalten detaillierte Informationen, User sehen eine sichere Fehlermeldung.
---
## ⚡ 8. AJAX & API Entwicklung
Um Daten ohne Seiten-Reload zu laden, bietet der Controller die `json()` Methode:
```php
public function getStatus() {
$data = ['online' => true, 'time' => date('H:i')];
return $this->json($data); // Setzt Header und beendet Script
}
```
In deinem JavaScript kannst du dies dann einfach per `fetch()` abrufen.
---
## 🛠️ 9. Installation & Start
1. Stelle sicher, dass PHP 8.x installiert ist.
2. Passe die `src/config/env.ini` mit deinen Datenbankdaten an.
3. Starte den internen Server: `php server.php`.
4. Öffne `http://localhost:8000` im Browser.
---
*Dieses Framework wurde für maximale Produktivität entwickelt. Bei Fragen schau in die integrierte Dokumentation im Backend.*
+2 -2
View File
@@ -1,7 +1,7 @@
<?php
// loginTest.php
$loginUrl = "https://backend.blackpi.ipv64.net/login.php"; // URL zu deinem Login-Script
$loginUrl = "https://backend.mrhossa.de/login.php"; // URL zu deinem Login-Script
// Testfälle
$tests = [
@@ -51,4 +51,4 @@ foreach ($tests as $test) {
} else {
echo "[FAIL] {$test['description']}\n";
}
}
}
+76
View File
@@ -0,0 +1,76 @@
<?php
include_once(__DIR__ . "/../controller/DatabaseController.php");
include_once(__DIR__ . "/../modules/Mail.php");
date_default_timezone_set("Europe/Berlin");
$jetzt = new DateTime();
$heute = $jetzt->format("Y-m-d");
// Alle heutigen Termine laden
$termine = DatabaseShow(
"Termine",
"*",
"DATUM = ? AND ERINNERUNG_AM IS NULL",
$heute
);
foreach ($termine as $termin) {
$terminZeit = new DateTime($termin["DATUM"] . " " . $termin["ZEIT"]);
$sekundenBisTermin = $terminZeit->getTimestamp() - $jetzt->getTimestamp();
// Nur Termine innerhalb der nächsten 30 Minuten
if ($sekundenBisTermin > 0 && $sekundenBisTermin <= 1800) {
// Benutzer laden
$benutzer = DatabaseShow(
"Benutzer",
"*",
"NAME = ?",
$termin["ERSTELLT_VON"]
);
if (empty($benutzer)) {
continue;
}
$benutzer = $benutzer[0];
if (empty($benutzer["EMAIL"])) {
continue;
}
$subject = "Terminerinnerung";
$message = "Hallo {$benutzer["NAME"]},\n\n";
$message .= "Du hast in weniger als 30 Minuten einen Termin.\n\n";
$message .= "Termin: {$termin["NAME"]}\n";
$message .= "Datum: {$termin["DATUM"]}\n";
$message .= "Uhrzeit: {$termin["ZEIT"]}\n";
if (!empty($termin["NOTIZ"])) {
$notizClean = mb_convert_encoding($termin["NOTIZ"], "UTF-8", "auto");
$message .= "Notiz: {$notizClean}\n";
}
sendMail(
$subject,
$message,
$_SERVER['SCRIPT_NAME'] ?? 'CLI/Unbekannt',
true,
$benutzer["EMAIL"],
);
print_r("{$benutzer["EMAIL"]}\n{$message}");
DatabaseUpdate(
"Termine",
"ERINNERUNG_AM = ?",
"ID = ?",
date('d-m-Y H:i:s'),
$termin["ID"]
);
}
}
+3 -3
View File
@@ -1,8 +1,8 @@
<?php
include_once( __DIR__ . '/../src/utils/databaseMethods.php');
include_once( __DIR__ . '/../src/classes/getData.php');
include_once( __DIR__ . '/../src/database/databaseConnect.php');
include_once(__DIR__ . "/../controller/DataController.php");
include_once(__DIR__ . "/../controller/DatabaseConnect.php");
include_once(__DIR__ . "/../controller/DatabaseController.php");
$data = new getSpecificData();
+4 -4
View File
@@ -1,8 +1,8 @@
<?php
include_once( __DIR__ . '/../../src/utils/databaseMethods.php');
include_once( __DIR__ . '/../../src/classes/getData.php');
include_once( __DIR__ . '/../../src/controller/apiController.php');
include_once( __DIR__ . '/../../src/utils/mailer.php');
include_once( __DIR__ . '/../../controller/DatabaseController.php');
include_once( __DIR__ . '/../../controller/DataController.php');
include_once( __DIR__ . '/../../controller/ApiController.php');
include_once(__DIR__ . '/../../modules/Mail.php');
header("Content-Type: application/json");
header("Access-Control-Allow-Origin: *");
+2 -2
View File
@@ -1,6 +1,6 @@
<?php
include_once( __DIR__ . '/../../src/utils/databaseMethods.php');
include_once( __DIR__ . '/../../src/classes/getData.php');
include_once( __DIR__ . '/../../controller/DatabaseController.php' );
include_once( __DIR__ . '/../../controller/DataController.php' );
header("Content-Type: application/json");
header("Access-Control-Allow-Origin: *");
+3 -3
View File
@@ -1,8 +1,8 @@
<?php
include_once( __DIR__ . '/../../src/utils/databaseMethods.php');
include_once( __DIR__ . '/../../src/classes/getData.php');
include_once(__DIR__ . '/../../src/controller/apiController.php');
include_once(__DIR__ . '/../../controller/DatabaseController.php');
include_once(__DIR__ . '/../../controller/DataController.php');
include_once(__DIR__ . '/../../controller/ApiController.php');
header('Content-Type: application/json');
+3 -3
View File
@@ -1,8 +1,8 @@
<?php
include_once( __DIR__ . '/../../src/utils/databaseMethods.php');
include_once( __DIR__ . '/../../src/classes/getData.php');
include_once(__DIR__ . '/../../src/controller/apiController.php');
include_once(__DIR__ . '/../../controller/DatabaseController.php');
include_once(__DIR__ . '/../../controller/DataController.php');
include_once(__DIR__ . '/../../controller/ApiController.php');
header('Content-Type: application/json');
+3 -4
View File
@@ -1,9 +1,8 @@
<?php
include_once( __DIR__ . '/../../src/utils/databaseMethods.php');
include_once( __DIR__ . '/../../src/utils/requestLogger.php');
include_once( __DIR__ . '/../../src/classes/getData.php');
include_once(__DIR__ . '/../../src/controller/apiController.php');
include_once(__DIR__ . '/../../controller/DatabaseController.php');
include_once(__DIR__ . '/../../controller/DataController.php');
include_once(__DIR__ . '/../../controller/ApiController.php');
header('Content-Type: application/json');
+11
View File
@@ -0,0 +1,11 @@
{
"name": "hydrax/webroot",
"autoload": {
"psr-4": {
"Hydrax\\Webroot\\": "src/"
}
},
"require": {
"maxmind-db/reader": "^1.13"
}
}
+83
View File
@@ -0,0 +1,83 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "142b6c705fb936ed070059ea58265eae",
"packages": [
{
"name": "maxmind-db/reader",
"version": "v1.13.1",
"source": {
"type": "git",
"url": "https://github.com/maxmind/MaxMind-DB-Reader-php.git",
"reference": "2194f58d0f024ce923e685cdf92af3daf9951908"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/maxmind/MaxMind-DB-Reader-php/zipball/2194f58d0f024ce923e685cdf92af3daf9951908",
"reference": "2194f58d0f024ce923e685cdf92af3daf9951908",
"shasum": ""
},
"require": {
"php": ">=7.2"
},
"conflict": {
"ext-maxminddb": "<1.11.1 || >=2.0.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "3.*",
"phpstan/phpstan": "*",
"phpunit/phpunit": ">=8.0.0,<10.0.0",
"squizlabs/php_codesniffer": "4.*"
},
"suggest": {
"ext-bcmath": "bcmath or gmp is required for decoding larger integers with the pure PHP decoder",
"ext-gmp": "bcmath or gmp is required for decoding larger integers with the pure PHP decoder",
"ext-maxminddb": "A C-based database decoder that provides significantly faster lookups",
"maxmind-db/reader-ext": "C extension for significantly faster IP lookups (install via PIE: pie install maxmind-db/reader-ext)"
},
"type": "library",
"autoload": {
"psr-4": {
"MaxMind\\Db\\": "src/MaxMind/Db"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "Gregory J. Oschwald",
"email": "goschwald@maxmind.com",
"homepage": "https://www.maxmind.com/"
}
],
"description": "MaxMind DB Reader API",
"homepage": "https://github.com/maxmind/MaxMind-DB-Reader-php",
"keywords": [
"database",
"geoip",
"geoip2",
"geolocation",
"maxmind"
],
"support": {
"issues": "https://github.com/maxmind/MaxMind-DB-Reader-php/issues",
"source": "https://github.com/maxmind/MaxMind-DB-Reader-php/tree/v1.13.1"
},
"time": "2025-11-21T22:24:26+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {},
"platform-dev": {},
"plugin-api-version": "2.9.0"
}
+145 -129
View File
@@ -1,43 +1,47 @@
<?php
$superuser = array (
'admin' =>
array (
$superuser = array(
'admin' =>
array(
'password' => 'bBHofurUJWKzPE8Z:',
'active' => true,
),
);
$serviceGroups = array (
'Info' =>
array (
$serviceGroups = array(
'Info' =>
array(
'label' => 'Home',
'defaultOpen' => true,
'services' =>
array (
'services' =>
array(
0 => 'home_info',
1 => 'links',
3 => 'documentation',
1 => 'vergabe',
2 => 'links',
3 => 'einkaufsliste',
4 => 'termine',
),
),
'Ausbildung' =>
array (
'Ausbildung' =>
array(
'label' => 'Ausbildung',
'defaultOpen' => true,
'services' =>
array (
'services' =>
array(
0 => 'print_static',
1 => 'zeiterfassung',
2 => 'berichte_arbeit_gen',
3 => 'berichte',
4 => 'firmenberichte',
5 => 'berufsschulberichte',
),
),
'Logs' =>
array (
'Logs' =>
array(
'label' => 'Webserver',
'defaultOpen' => true,
'services' =>
array (
'defaultOpen' => false,
'services' =>
array(
0 => 'public_request_logs',
1 => 'public_request_chart',
2 => 'backend_request_logs',
@@ -45,24 +49,24 @@ $serviceGroups = array (
4 => 'personal_request_logs',
),
),
'System' =>
array (
'System' =>
array(
'label' => 'Verwaltung',
'defaultOpen' => false,
'services' =>
array (
'services' =>
array(
0 => 'data_info',
1 => 'services_control',
2 => 'user_control',
3 => 'variablen_control',
),
),
'Test' =>
array (
'Test' =>
array(
'label' => 'Dev / Test',
'defaultOpen' => false,
'services' =>
array (
'services' =>
array(
0 => 'api_tester',
1 => 'weather_chart',
2 => 'tabellen',
@@ -71,327 +75,339 @@ $serviceGroups = array (
),
);
$services = array (
'home_info' =>
array (
$services = array(
'home_info' =>
array(
'name' => 'Home',
'active' => true,
'adminOnly' => false,
),
'print_static' =>
array (
'vergabe' =>
array(
'name' => 'Vergabe',
'active' => true,
'adminOnly' => false,
),
'print_static' =>
array(
'name' => 'Berichte Drucken',
'active' => true,
'adminOnly' => false,
),
'api_tester' =>
array (
'api_tester' =>
array(
'name' => 'API Testing',
'active' => true,
'adminOnly' => true,
),
'weather_chart' =>
array (
'weather_chart' =>
array(
'name' => 'Wetter Statistik',
'active' => true,
'adminOnly' => true,
),
'data_info' =>
array (
'data_info' =>
array(
'name' => 'Data Info',
'active' => true,
'adminOnly' => true,
),
'links' =>
array (
'links' =>
array(
'name' => 'Links',
'active' => true,
'adminOnly' => false,
),
'public_request_logs' =>
array (
'public_request_logs' =>
array(
'name' => 'Public Logs',
'active' => true,
'adminOnly' => false,
),
'public_request_chart' =>
array (
'public_request_chart' =>
array(
'name' => 'Public Statistik',
'active' => true,
'adminOnly' => false,
),
'backend_request_logs' =>
array (
'backend_request_logs' =>
array(
'name' => 'Backend Logs',
'active' => true,
'adminOnly' => false,
),
'backend_request_chart' =>
array (
'backend_request_chart' =>
array(
'name' => 'Backend Statistik',
'active' => true,
'adminOnly' => false,
),
'personal_request_logs' =>
array (
'personal_request_logs' =>
array(
'name' => 'Persönliche Logs',
'active' => true,
'adminOnly' => true,
),
'services_control' =>
array (
'services_control' =>
array(
'name' => 'Seiten',
'active' => true,
'adminOnly' => true,
),
'zeiterfassung' =>
array (
'zeiterfassung' =>
array(
'name' => 'Zeit Erfassung',
'active' => true,
'adminOnly' => false,
),
'berichte_arbeit_gen' =>
array (
'berichte_arbeit_gen' =>
array(
'name' => 'Berichte PDF',
'active' => true,
'adminOnly' => false,
),
'user_control' =>
array (
'user_control' =>
array(
'name' => 'Benutzer',
'active' => true,
'adminOnly' => true,
),
'variablen_control' =>
array (
'variablen_control' =>
array(
'name' => 'Variablen',
'active' => true,
'adminOnly' => true,
),
'tabellen' =>
array (
'tabellen' =>
array(
'name' => 'Tabellen',
'active' => true,
'adminOnly' => true,
),
'berichte' =>
array (
'berichte' =>
array(
'name' => 'Berichte Einsehen',
'active' => true,
'adminOnly' => false,
),
'domains' =>
array (
'domains' =>
array(
'name' => 'Domains',
'active' => true,
'adminOnly' => false,
),
'tester' =>
array (
'name' => 'TESTER',
'einkaufsliste' =>
array(
'name' => 'Einkaufsliste',
'active' => true,
'adminOnly' => false,
),
'documentation' =>
array (
'name' => 'Dokumentation',
'termine' =>
array(
'name' => 'Termine',
'active' => true,
'adminOnly' => false,
),
'logout' =>
array (
'name' => 'Abmelden',
'firmenberichte' =>
array(
'name' => 'Firmenberichte',
'active' => true,
'adminOnly' => false,
),
'berufsschulberichte' =>
array(
'name' => 'Berufsschulberichte',
'active' => true,
'adminOnly' => false,
),
);
$linkGroups = array (
'info' =>
array (
$linkGroups = array(
'info' =>
array(
'label' => 'Informationen',
'color' => '#3498db',
'defaultOpen' => false,
),
'tools' =>
array (
'tools' =>
array(
'label' => 'IoT',
'color' => '#2ecc71',
'defaultOpen' => false,
),
'admin' =>
array (
'admin' =>
array(
'label' => 'Administration',
'color' => '#e74c3c',
'defaultOpen' => false,
),
'devices' =>
array (
'devices' =>
array(
'label' => 'Geräte',
'color' => '#b7db33',
'defaultOpen' => false,
),
);
$linkButtons = array (
0 =>
array (
$linkButtons = array(
0 =>
array(
'label' => 'Gitea',
'url' => 'http://gitea.blackpi.ipv64.net',
'url' => 'http://gitea.mrhossa.de',
'newTab' => true,
'adminOnly' => false,
'group' => 'admin',
),
1 =>
array (
1 =>
array(
'label' => 'HAProxy',
'url' => 'http://stats.blackpi.ipv64.net',
'url' => 'http://stats.mrhossa.de',
'newTab' => true,
'adminOnly' => false,
'group' => 'admin',
),
2 =>
array (
2 =>
array(
'label' => 'Public',
'url' => 'https://blackpi.ipv64.net',
'url' => 'https://mrhossa.de',
'newTab' => true,
'adminOnly' => false,
'group' => 'info',
),
3 =>
array (
3 =>
array(
'label' => 'NAS-1',
'url' => 'http://nas-1.blackpi.ipv64.net',
'newTab' => true,
'adminOnly' => false,
'group' => 'devices',
),
4 =>
array (
4 =>
array(
'label' => 'NAS-2',
'url' => 'http://nas-2.blackpi.ipv64.net',
'newTab' => true,
'adminOnly' => false,
'group' => 'devices',
),
5 =>
array (
5 =>
array(
'label' => 'FritzBox-1',
'url' => 'http://fritzbox-1.blackpi.ipv64.net',
'newTab' => true,
'adminOnly' => false,
'group' => 'devices',
),
6 =>
array (
6 =>
array(
'label' => 'FritzBox-2',
'url' => 'http://fritzbox-2.blackpi.ipv64.net',
'newTab' => true,
'adminOnly' => false,
'group' => 'devices',
),
7 =>
array (
7 =>
array(
'label' => 'GS1910-24',
'url' => 'http://switch.blackpi.ipv64.net',
'newTab' => true,
'adminOnly' => false,
'group' => 'admin',
),
8 =>
array (
8 =>
array(
'label' => 'NWA5121-NI Innen',
'url' => 'http://ap-1.blackpi.ipv64.net',
'newTab' => true,
'adminOnly' => false,
'group' => 'devices',
),
9 =>
array (
9 =>
array(
'label' => 'NWA5121-NI Aussen',
'url' => 'http://ap-2.blackpi.ipv64.net',
'newTab' => true,
'adminOnly' => false,
'group' => 'devices',
),
10 =>
array (
10 =>
array(
'label' => 'ZyWALL 110',
'url' => 'http://10.233.1248',
'newTab' => true,
'adminOnly' => false,
'group' => 'admin',
),
11 =>
array (
11 =>
array(
'label' => 'Ender 3 V3 KE',
'url' => 'http://10.233.1.52',
'newTab' => true,
'adminOnly' => false,
'group' => 'tools',
),
12 =>
array (
12 =>
array(
'label' => 'Ubuntu NAS',
'url' => 'http://10.233.1.32:9000',
'newTab' => true,
'adminOnly' => false,
'group' => 'tools',
),
13 =>
array (
13 =>
array(
'label' => 'Backend',
'url' => 'https://backend.blackpi.ipv64.net',
'newTab' => true,
'adminOnly' => false,
'group' => 'info',
),
14 =>
array (
14 =>
array(
'label' => 'Dev Backend',
'url' => 'https://dev-backend.blackpi.ipv64.net',
'newTab' => true,
'adminOnly' => false,
'group' => 'info',
),
15 =>
array (
15 =>
array(
'label' => 'Dev Pubic',
'url' => 'https://dev-public.blackpi.ipv64.net',
'newTab' => true,
'adminOnly' => false,
'group' => 'info',
),
16 =>
array (
16 =>
array(
'label' => 'Local Backend',
'url' => 'http://backend.local.ipv64.net',
'newTab' => true,
'adminOnly' => false,
'group' => 'info',
),
17 =>
array (
17 =>
array(
'label' => 'Local Pubic',
'url' => 'http://public.local.ipv64.net',
'newTab' => true,
'adminOnly' => false,
'group' => 'info',
),
18 =>
array (
18 =>
array(
'label' => 'Proxmox',
'url' => 'https://100.88.208.86:8006',
'newTab' => true,
'adminOnly' => true,
'group' => 'devices',
),
19 =>
array (
19 =>
array(
'label' => 'Flatnotes',
'url' => 'http://10.233.1.130:8080',
'newTab' => true,
'adminOnly' => false,
'group' => 'tools',
),
);
);
@@ -1,10 +1,9 @@
<?php
require(__DIR__ . '/../utils/formatter.php');
include_once(__DIR__ . '/DatabaseController.php');
class ApiController {
use ReponseFormatter;
class ApiController
{
/**
* method for pretty jsonsuccess output
@@ -12,8 +11,15 @@ class ApiController {
* @param array $data
* @return string
*/
public function jsonSuccess($data) {
return $this->formatJson("success", $data);
public function jsonSuccess($data)
{
return json_encode(
[
'status' => 'success',
'data' => $data,
],
JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE
);
}
/**
@@ -22,8 +28,16 @@ class ApiController {
* @param array $error
* @return string
*/
public function jsonError ($error) {
return $this->formatJson("failed", $error);
public function jsonError($error)
{
echo json_encode(
[
'status' => 'failed',
'data' => 'none',
'error' => $error
],
JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE
);
}
/**
@@ -31,7 +45,8 @@ class ApiController {
*
* @return array
*/
private function getAuthKey () {
private function getAuthKey()
{
$validTokens = DatabaseShow(
"API_Credentials",
"TOKEN"
@@ -47,13 +62,18 @@ class ApiController {
* @param string $function
* @return void
*/
public function urlAuth ($token, $function = '') {
public function urlAuth($token, $function = '')
{
$validTokens = $this->getAuthKey();
if (!in_array($token, $validTokens)) {
http_response_code(401);
$error = json_encode([
"error" => "Unauthorized",
"debug_received_token" => $token,
]);
$error = $this->formatJson("error", $token);
echo $this->jsonError($error);
DatabaseInsert(
"API_LOGS",
@@ -75,7 +95,8 @@ class ApiController {
return true;
}
public function requestSave($function = '') {
public function requestSave($function = '')
{
DatabaseInsert(
"API_LOGS",
"CLIENT_IP, TOKEN, API, FUNKTION, JSON",
@@ -91,7 +112,8 @@ class ApiController {
* create new api token
*
*/
private function createToken() {
private function createToken()
{
return bin2hex(random_bytes(32));
}
@@ -100,8 +122,9 @@ class ApiController {
*
* @return bool
*/
public function saveToken() {
try {
public function saveToken()
{
try {
DatabaseInsert(
"API_Credentials",
"TOKEN, BENUTZER",
@@ -113,4 +136,4 @@ class ApiController {
}
return true;
}
}
}
@@ -1,39 +1,39 @@
<?php
class cookieController {
public function setCookie(string $CookieName, int $CookieValue, int $CookieTime) {
setcookie($CookieName, $CookieValue, time()+($CookieTime*60));
}
public function readCookieValue(string $CookieName) {
if (isset($_COOKIE[$CookieName])) {
return $_COOKIE[$CookieName];
}
return 'Cookie not set';
}
public function DeleteCookie(string $CookieName) {
setcookie($CookieName, "", time()-(3600*24));
}
}
// USE:
/**
* Cookie setzen:
* $cookieManager = new cookieController();
* $cookieManager->SetCookie("USER", "7", 60);
*/
/**
* Cookie auslesen:
* $cookieValue = $cookieManager->readCookieValueCookie("USER");
* echo $cookieValue;
*/
/**
* Cookie loeschen:
* $cookieManager->DeleteCookie("USER");
<?php
class cookieController {
public function setCookie(string $CookieName, int $CookieValue, int $CookieTime) {
setcookie($CookieName, $CookieValue, time()+($CookieTime*60));
}
public function readCookieValue(string $CookieName) {
if (isset($_COOKIE[$CookieName])) {
return $_COOKIE[$CookieName];
}
return 'Cookie not set';
}
public function DeleteCookie(string $CookieName) {
setcookie($CookieName, "", time()-(3600*24));
}
}
// USE:
/**
* Cookie setzen:
* $cookieManager = new cookieController();
* $cookieManager->SetCookie("USER", "7", 60);
*/
/**
* Cookie auslesen:
* $cookieValue = $cookieManager->readCookieValueCookie("USER");
* echo $cookieValue;
*/
/**
* Cookie loeschen:
* $cookieManager->DeleteCookie("USER");
*/
+312
View File
@@ -0,0 +1,312 @@
<?php
require_once(__DIR__ . '/../vendor/autoload.php');
use MaxMind\Db\Reader;
class getSpecificData
{
public function getWeather()
{
$url = "https://api.openweathermap.org/data/2.5/weather?q=Jena,DE&APPID=5bc3e1a438c58fca444c4370ac0517b2&units=metric";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$response = curl_exec($ch);
if (curl_errno($ch)) {
echo "cURL Fehler: " . curl_error($ch);
exit;
}
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200) {
$data = json_decode($response, true);
return [
'aktuell' => $data['main']['temp'],
'min' => $data['main']['temp_min'],
'max' => $data['main']['temp_max'],
'gefuehlt' => $data['main']['feels_like']
];
} else {
return "API Fehler. HTTP Code: " . $httpCode;
}
}
public function getUserAgent(): string
{
return $_SERVER['HTTP_USER_AGENT'] ?? 'Unbekannt';
}
public function getRequestURL(): string
{
return $_SERVER['REQUEST_URI'] ?? 'Unbekannt';
}
public function getCookie(string $name): ?string
{
return $_COOKIE[$name] ?? 'Unbekannt';
}
public function getSessionID(): string
{
return $session_id ?? 'Unbekannt ';
}
public function getServerPort(): string
{
return $_SERVER['SERVER_PORT'] ?? 'Unbekannt';
}
public function getClientPort(): string
{
return $_SERVER['REMOTE_PORT'] ?? 'Unbekannt';
}
public function getDocumentRoot(): string
{
return $_SERVER['DOCUMENT_ROOT'] ?? 'Unbekannt';
}
public function getQuery(): string
{
return $_SERVER['QUERY_STRING'] ?? 'Unbekannt';
}
public function getRequestMethod(): string
{
return $_SERVER['REQUEST_METHOD'] ?? 'Unbekannt';
}
public function getCurrentDir(): string
{
return __DIR__;
}
public function getReferer(): string
{
return $_SERVER['HTTP_REFERER'] ?? 'Unbekannt';
}
public function getWebsite(): string
{
return $_SERVER['HTTP_HOST'] ?? 'Unbekannt';
}
public function getDatabase(): string
{
$conn = db_connect();
return $conn->host_info;
}
public function getLoggingStatus($variable)
{
$status = DatabaseShow(
"Variablen",
"WERT",
"VARIABLENNAME = ?",
$variable
);
return $status;
}
public function getServerName(): string
{
$Servername = $_SERVER['SERVER_NAME'] ?? 'Unbekannt';
if (in_array($Servername, ['Unbekannt', '0.0.0.0', '127.0.0.1'])) {
return "Lokal";
} else {
return $Servername;
}
}
public function getServerIP(): string
{
$ServerIP = $_SERVER['SERVER_ADDR'] ?? 'Unbekannt';
if (in_array($ServerIP, ['Unbekannt', '0.0.0.0', '127.0.0.1'])) {
return "Lokal";
} else {
return $ServerIP;
}
}
public function getClientIP(): string
{
if (PHP_SAPI === 'cli') {
return 'CLI';
}
$xff = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? null;
if (!empty($xff)) {
$ips = array_map('trim', explode(',', $xff));
return $ips[0];
}
return $_SERVER['REMOTE_ADDR'] ?? 'UNKNOWN';
}
public function getRequestTime(): string
{
date_default_timezone_set('Europe/Berlin');
$timestamp = $_SERVER['REQUEST_TIME'] ?? time();
return date('Y-m-d H:i:s', $timestamp);
}
function getGeoIPOLD($ClientIP)
{
if (in_array($ClientIP, ['0.0.0.0', '127.0.0.1', '172.30.0.1'])) {
return "Lokal";
} elseif (in_array($ClientIP, ['Unbekannt', '0.0.0.0', '127.0.0.1'])) {
return "Lokal";
} else {
$response = file_get_contents("http://ip-api.com/json/{$ClientIP}?fields=country,countryCode");
$data = json_decode($response);
return $data->country;
}
}
function getGeoIP($ClientIP)
{
try {
$reader = new Reader(__DIR__ . '/../static/GEOIP/GeoLite2-Country.mmdb');
$record = $reader->get($ClientIP);
$countryNameDe = $record['country']['names']['de'] ?? 'Unbekannt';
$reader->close();
return $countryNameDe;
} catch (\Exception $e) {
echo "Fehler: " . $e->getMessage();
}
}
function getWhitelistedIPs()
{
include_once 'DatabaseController.php';
$data = DatabaseShow(
"Whitelisted",
"`IP-ADDRESSE`"
);
$results = [];
foreach ($data as $row) {
$results[] = $row['IP-ADDRESSE'];
}
return $results;
}
function getServerNode($ServerIP)
{
$nodes = [
'node1' => '172.30.0.11',
'node1' => '172.30.0.21',
'node2' => '172.30.0.12',
'node2' => '172.30.0.22',
'node3' => '172.30.0.13',
'node3' => '172.30.0.23',
'node4' => '172.30.0.14',
'node4' => '172.30.0.24',
'Lokal' => 'Lokal',
];
foreach ($nodes as $node => $ip) {
if ($ServerIP == $ip) {
return $node;
break;
}
}
}
function getClientStatus($ClientIP)
{
if (in_array($ClientIP, $this->getWhitelistedIPs())) {
return "Trusted";
}
return "Untrusted";
}
function getIsAdmin()
{
return $_SESSION['is_admin'] ?? false;
}
function getIsSuperuser()
{
return $_SESSION['is_superuser'] ?? false;
}
function getUsername()
{
return $_SESSION['username'] ?? 'Gast';
}
public function getAllData(): array
{
return [
'Server - Document Root' => $this->getDocumentRoot(),
'Server - Current Dir' => $this->getCurrentDir(),
'Server - Name' => $this->getServerName(),
'Server - IP' => $this->getServerIP(),
'Server - Port' => $this->getServerPort(),
'Server - Node' => $this->getServerNode($this->getServerIP()),
'Server - Database' => $this->getDatabase(),
'Request - URL' => $this->getRequestURL(),
'Request - Method' => $this->getRequestMethod(),
'Request - Query' => $this->getQuery(),
'Request - Referer' => $this->getReferer(),
'Request - Time' => $this->getRequestTime(),
'Client - IP' => $this->getClientIP(),
'Client - Port' => $this->getClientPort(),
'Client - Country' => $this->getGeoIP($this->getClientIP()),
'Client - User Agent' => $this->getUserAgent(),
'Client - Cookie USER' => $this->getCookie('USER'),
'Client - Cookie SESSSRV' => $this->getCookie('SESSSRV'),
'Client - Session ID' => $this->getSessionID(),
'Access - Whitelisted' => $this->getClientStatus($this->getClientIP()),
'Access - Whitelisted IPs' => $this->getWhitelistedIPs(),
'User - Username' => $this->getUsername(),
'User - Is Admin' => $this->getIsAdmin(),
'User - Is Superuser' => $this->getIsSuperuser(),
'System - Website' => $this->getWebsite(),
'Logging - Public' => $this->getLoggingStatus('GENERAL_PUBLIC_LOGGING')[0]['WERT'],
'Logging - Public DEV' => $this->getLoggingStatus('DEV_PUBLIC_LOGGING')[0]['WERT'],
'Logging - Backend' => $this->getLoggingStatus('GENERAL_BACKEND_LOGGING')[0]['WERT'],
'Logging - Backend DEV' => $this->getLoggingStatus('DEV_BACKEND_LOGGING')[0]['WERT'],
];
}
public function ShowAllData(): void
{
$SpecificData = (new getSpecificData())->getAllData();
echo "<pre>";
foreach ($SpecificData as $key => $value) {
if (is_array($value)) {
$value = implode(', ', $value);
}
printf("%-22s : %s\n", $key, $value);
}
echo "</pre>";
}
}
+61
View File
@@ -0,0 +1,61 @@
<?php
include_once __DIR__ . '/../modules/Mail.php';
include_once __DIR__ . '/../controller/RoutesController.php';
include_once 'DataController.php';
function db_connect()
{
$servers = [
'local' => [
'hostaddr' => '127.0.0.1',
'username' => 'worker',
'password' => 'bBHofurUJWKzPE8Z',
'database' => 'DEV',
],
'live' => [
'hostaddr' => '152.53.159.169',
'username' => 'worker',
'password' => 'bBHofurUJWKzPE8Z',
'database' => 'LIVE',
],
'dev' => [
'hostaddr' => '152.53.159.169',
'username' => 'worker',
'password' => 'bBHofurUJWKzPE8Z',
'database' => 'DEV',
],
'error' => [
'hostaddr' => '10.233.1.244',
'username' => 'error',
'password' => 'bBHofurUJWKzPE8Z',
'database' => 'DEV',
]
];
$enviroment = 'vpn';
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
try {
$conn = new mysqli(
$servers[$enviroment]['hostaddr'],
$servers[$enviroment]['username'],
$servers[$enviroment]['password'],
$servers[$enviroment]['database'],
);
$conn->set_charset("utf8mb4");
return $conn;
} catch (mysqli_sql_exception $e) {
errorMail(
"Database Connection failed",
$e->getMessage(),
false,
);
redirectToWebsite("https://mrhossa.de");
throw $e;
}
}
@@ -1,270 +1,270 @@
<?php
function DatabaseCreateTable($table, $columnsDefinition) {
include_once(__DIR__ . '/../database/databaseConnect.php');
$conn = db_connect();
$sql = "CREATE TABLE IF NOT EXISTS `$table` ($columnsDefinition)";
$stmt = $conn->prepare($sql);
if (!$stmt) {
die("Fehler beim Prepare: " . $conn->error);
}
if (!$stmt->execute()) {
die("Fehler beim Execute: " . $stmt->error);
}
return true;
}
function DatabaseShowTables() {
include_once(__DIR__ . '/../database/databaseConnect.php');
$conn = db_connect();
$tablesResult = $conn->query("SHOW TABLES");
if (!$tablesResult) {
die("Fehler beim Laden der Tabellen: " . $conn->error);
}
$tables = [];
while ($tableRow = $tablesResult->fetch_array()) {
$tables[] = $tableRow[0];
}
return $tables;
}
function DatabaseShowTableStructure($table) {
include_once(__DIR__ . '/../database/databaseConnect.php');
$conn = db_connect();
$stmt = $conn->prepare("SHOW COLUMNS FROM `$table`");
if (!$stmt) {
die("Fehler beim Prepare: " . $conn->error);
}
if (!$stmt->execute()) {
die("Fehler beim Execute: " . $stmt->error);
}
$result = $stmt->get_result();
$columns = [];
while ($column = $result->fetch_assoc()) {
$columns[] = [
"Field" => $column["Field"],
"Type" => $column["Type"],
"Null" => $column["Null"],
"Key" => $column["Key"],
"Default" => $column["Default"],
"Extra" => $column["Extra"]
];
}
return $columns;
}
function DatabaseShow($table, $columns = "*", $where = "", ...$values) {
include_once(__DIR__ . '/../database/databaseConnect.php');
$conn = db_connect();
$sql = "SELECT $columns FROM `$table`";
if (!empty($where)) {
$sql .= " WHERE $where";
}
$stmt = $conn->prepare($sql);
if (!$stmt) {
die("Fehler beim Prepare: " . $conn->error);
}
if (!$stmt->execute($values)) {
die("Fehler beim Execute: " . $stmt->error);
}
$result = $stmt->get_result();
return $result->fetch_all(MYSQLI_ASSOC);
}
function DatabaseInsert($table, $columns, ...$values) {
include_once(__DIR__ . '/../database/databaseConnect.php');
$conn = db_connect();
$placeholders = rtrim(str_repeat("?,", count($values)), ",");
$sql = "INSERT INTO `$table` ($columns) VALUES ($placeholders)";
$stmt = $conn->prepare($sql);
if (!$stmt) {
die("Fehler beim Prepare: " . $conn->error);
}
if (!$stmt->execute($values)) {
die("Fehler beim Execute: " . $stmt->error);
}
return true;
}
function DatabaseDelete($table, $where, ...$values) {
include_once(__DIR__ . '/../database/databaseConnect.php');
$conn = db_connect();
$sql = "DELETE FROM `$table` WHERE $where";
$stmt = $conn->prepare($sql);
if (!$stmt) {
die("Fehler beim Prepare: " . $conn->error);
}
if (!$stmt->execute($values)) {
die("Fehler beim Execute: " . $stmt->error);
}
return true;
}
function DatabaseUpdate($table, $set, $where, ...$values) {
include_once(__DIR__ . '/../database/databaseConnect.php');
$conn = db_connect();
$sql = "UPDATE `$table` SET $set WHERE $where";
$stmt = $conn->prepare($sql);
if (!$stmt) {
die("Fehler beim Prepare: " . $conn->error);
}
if (!$stmt->execute($values)) {
die("Fehler beim Execute: " . $stmt->error);
}
return true;
}
function getFKValue($conn, $table, $key, $value, $id, &$cache) {
if ($id === null) return null;
$cacheKey = $table . "_" . $id;
if (isset($cache[$cacheKey])) {
return $cache[$cacheKey];
}
$stmt = $conn->prepare("SELECT `$value` FROM `$table` WHERE `$key` = ? LIMIT 1");
if (!$stmt) {
die("FK Prepare Fehler: " . $conn->error);
}
$stmt->bind_param("i", $id);
$stmt->execute();
$result = $stmt->get_result()->fetch_assoc();
$valueResult = $result[$value] ?? null;
$cache[$cacheKey] = $valueResult;
return $valueResult;
}
function DatabaseShowWithFK($table, $fkMap = [], $columns = "*", $where = "", ...$values) {
include_once(__DIR__ . '/../database/databaseConnect.php');
$conn = db_connect();
$cache = [];
$select = is_array($columns) ? implode(", ", $columns) : $columns;
$sql = "SELECT $select FROM `$table`";
if (!empty($where)) {
$sql .= " WHERE $where";
}
$stmt = $conn->prepare($sql);
if (!$stmt) die("Fehler beim Prepare: " . $conn->error);
if (!empty($values)) {
$stmt->execute($values);
} else {
$stmt->execute();
}
$result = $stmt->get_result();
if (!$result) die("Fehler beim get_result: " . $conn->error);
$rows = $result->fetch_all(MYSQLI_ASSOC);
foreach ($rows as &$row) {
foreach ($fkMap as $fkColumn => $fkInfo) {
if (!isset($row[$fkColumn]) || $row[$fkColumn] === null) {
continue;
}
$row[$fkColumn] = getFKValue(
$conn,
$fkInfo['table'],
$fkInfo['key'],
$fkInfo['value'],
$row[$fkColumn],
$cache
);
}
}
return [
"rows" => $rows
];
}
$Berufsschulberichte = [
"FACH_S1" => ["table" => "Fach", "key" => "ID", "value" => "Fach"],
"LEHRER_S1" => ["table" => "Lehrer", "key" => "ID", "value" => "NAME"],
"RAUM_S1" => ["table" => "Raum", "key" => "ID", "value" => "Raum_NR"],
"FACH_S2" => ["table" => "Fach", "key" => "ID", "value" => "Fach"],
"LEHRER_S2" => ["table" => "Lehrer", "key" => "ID", "value" => "NAME"],
"RAUM_S2" => ["table" => "Raum", "key" => "ID", "value" => "Raum_NR"],
"FACH_S3" => ["table" => "Fach", "key" => "ID", "value" => "Fach"],
"LEHRER_S3" => ["table" => "Lehrer", "key" => "ID", "value" => "NAME"],
"RAUM_S3" => ["table" => "Raum", "key" => "ID", "value" => "Raum_NR"],
"FACH_S4" => ["table" => "Fach", "key" => "ID", "value" => "Fach"],
"LEHRER_S4" => ["table" => "Lehrer", "key" => "ID", "value" => "NAME"],
"RAUM_S4" => ["table" => "Raum", "key" => "ID", "value" => "Raum_NR"],
"FACH_S5" => ["table" => "Fach", "key" => "ID", "value" => "Fach"],
"LEHRER_S5" => ["table" => "Lehrer", "key" => "ID", "value" => "NAME"],
"RAUM_S5" => ["table" => "Raum", "key" => "ID", "value" => "Raum_NR"],
"FACH_S6" => ["table" => "Fach", "key" => "ID", "value" => "Fach"],
"LEHRER_S6" => ["table" => "Lehrer", "key" => "ID", "value" => "NAME"],
"RAUM_S6" => ["table" => "Raum", "key" => "ID", "value" => "Raum_NR"],
"FACH_S7" => ["table" => "Fach", "key" => "ID", "value" => "Fach"],
"LEHRER_S7" => ["table" => "Lehrer", "key" => "ID", "value" => "NAME"],
"RAUM_S7" => ["table" => "Raum", "key" => "ID", "value" => "Raum_NR"],
"FACH_S8" => ["table" => "Fach", "key" => "ID", "value" => "Fach"],
"LEHRER_S8" => ["table" => "Lehrer", "key" => "ID", "value" => "NAME"],
"RAUM_S8" => ["table" => "Raum", "key" => "ID", "value" => "Raum_NR"],
];
$Stundenplan = [
"MO" => ["table" => "Fach", "key" => "ID", "value" => "Fach"],
"DI" => ["table" => "Fach", "key" => "ID", "value" => "Fach"],
"MI" => ["table" => "Fach", "key" => "ID", "value" => "Fach"],
"DO" => ["table" => "Fach", "key" => "ID", "value" => "Fach"],
"FR" => ["table" => "Fach", "key" => "ID", "value" => "Fach"],
];
$Berechtigungen = [
"BENUTZER_ID" => ["table" => "Benutzer", "key" => "ID", "value" => "NAME"],
"SEITEN_ID" => ["table" => "Backend_Seiten", "key" => "ID", "value" => "DATEINAME"]
<?php
function DatabaseCreateTable($table, $columnsDefinition) {
include_once 'DatabaseConnect.php';
$conn = db_connect();
$sql = "CREATE TABLE IF NOT EXISTS `$table` ($columnsDefinition)";
$stmt = $conn->prepare($sql);
if (!$stmt) {
die("Fehler beim Prepare: " . $conn->error);
}
if (!$stmt->execute()) {
die("Fehler beim Execute: " . $stmt->error);
}
return true;
}
function DatabaseShowTables() {
include_once 'DatabaseConnect.php';
$conn = db_connect();
$tablesResult = $conn->query("SHOW TABLES");
if (!$tablesResult) {
die("Fehler beim Laden der Tabellen: " . $conn->error);
}
$tables = [];
while ($tableRow = $tablesResult->fetch_array()) {
$tables[] = $tableRow[0];
}
return $tables;
}
function DatabaseShowTableStructure($table) {
include_once 'DatabaseConnect.php';
$conn = db_connect();
$stmt = $conn->prepare("SHOW COLUMNS FROM `$table`");
if (!$stmt) {
die("Fehler beim Prepare: " . $conn->error);
}
if (!$stmt->execute()) {
die("Fehler beim Execute: " . $stmt->error);
}
$result = $stmt->get_result();
$columns = [];
while ($column = $result->fetch_assoc()) {
$columns[] = [
"Field" => $column["Field"],
"Type" => $column["Type"],
"Null" => $column["Null"],
"Key" => $column["Key"],
"Default" => $column["Default"],
"Extra" => $column["Extra"]
];
}
return $columns;
}
function DatabaseShow($table, $columns = "*", $where = "", ...$values) {
include_once 'DatabaseConnect.php';
$conn = db_connect();
$sql = "SELECT $columns FROM `$table`";
if (!empty($where)) {
$sql .= " WHERE $where";
}
$stmt = $conn->prepare($sql);
if (!$stmt) {
die("Fehler beim Prepare: " . $conn->error);
}
if (!$stmt->execute($values)) {
die("Fehler beim Execute: " . $stmt->error);
}
$result = $stmt->get_result();
return $result->fetch_all(MYSQLI_ASSOC);
}
function DatabaseInsert($table, $columns, ...$values) {
include_once 'DatabaseConnect.php';
$conn = db_connect();
$placeholders = rtrim(str_repeat("?,", count($values)), ",");
$sql = "INSERT INTO `$table` ($columns) VALUES ($placeholders)";
$stmt = $conn->prepare($sql);
if (!$stmt) {
die("Fehler beim Prepare: " . $conn->error);
}
if (!$stmt->execute($values)) {
die("Fehler beim Execute: " . $stmt->error);
}
return true;
}
function DatabaseDelete($table, $where, ...$values) {
include_once 'DatabaseConnect.php';
$conn = db_connect();
$sql = "DELETE FROM `$table` WHERE $where";
$stmt = $conn->prepare($sql);
if (!$stmt) {
die("Fehler beim Prepare: " . $conn->error);
}
if (!$stmt->execute($values)) {
die("Fehler beim Execute: " . $stmt->error);
}
return true;
}
function DatabaseUpdate($table, $set, $where, ...$values) {
include_once 'DatabaseConnect.php';
$conn = db_connect();
$sql = "UPDATE `$table` SET $set WHERE $where";
$stmt = $conn->prepare($sql);
if (!$stmt) {
die("Fehler beim Prepare: " . $conn->error);
}
if (!$stmt->execute($values)) {
die("Fehler beim Execute: " . $stmt->error);
}
return true;
}
function getFKValue($conn, $table, $key, $value, $id, &$cache) {
if ($id === null) return null;
$cacheKey = $table . "_" . $id;
if (isset($cache[$cacheKey])) {
return $cache[$cacheKey];
}
$stmt = $conn->prepare("SELECT `$value` FROM `$table` WHERE `$key` = ? LIMIT 1");
if (!$stmt) {
die("FK Prepare Fehler: " . $conn->error);
}
$stmt->bind_param("i", $id);
$stmt->execute();
$result = $stmt->get_result()->fetch_assoc();
$valueResult = $result[$value] ?? null;
$cache[$cacheKey] = $valueResult;
return $valueResult;
}
function DatabaseShowWithFK($table, $fkMap = [], $columns = "*", $where = "", ...$values) {
include_once 'DatabaseConnect.php';
$conn = db_connect();
$cache = [];
$select = is_array($columns) ? implode(", ", $columns) : $columns;
$sql = "SELECT $select FROM `$table`";
if (!empty($where)) {
$sql .= " WHERE $where";
}
$stmt = $conn->prepare($sql);
if (!$stmt) die("Fehler beim Prepare: " . $conn->error);
if (!empty($values)) {
$stmt->execute($values);
} else {
$stmt->execute();
}
$result = $stmt->get_result();
if (!$result) die("Fehler beim get_result: " . $conn->error);
$rows = $result->fetch_all(MYSQLI_ASSOC);
foreach ($rows as &$row) {
foreach ($fkMap as $fkColumn => $fkInfo) {
if (!isset($row[$fkColumn]) || $row[$fkColumn] === null) {
continue;
}
$row[$fkColumn] = getFKValue(
$conn,
$fkInfo['table'],
$fkInfo['key'],
$fkInfo['value'],
$row[$fkColumn],
$cache
);
}
}
return [
"rows" => $rows
];
}
$Berufsschulberichte = [
"FACH_S1" => ["table" => "Fach", "key" => "ID", "value" => "Fach"],
"LEHRER_S1" => ["table" => "Lehrer", "key" => "ID", "value" => "NAME"],
"RAUM_S1" => ["table" => "Raum", "key" => "ID", "value" => "Raum_NR"],
"FACH_S2" => ["table" => "Fach", "key" => "ID", "value" => "Fach"],
"LEHRER_S2" => ["table" => "Lehrer", "key" => "ID", "value" => "NAME"],
"RAUM_S2" => ["table" => "Raum", "key" => "ID", "value" => "Raum_NR"],
"FACH_S3" => ["table" => "Fach", "key" => "ID", "value" => "Fach"],
"LEHRER_S3" => ["table" => "Lehrer", "key" => "ID", "value" => "NAME"],
"RAUM_S3" => ["table" => "Raum", "key" => "ID", "value" => "Raum_NR"],
"FACH_S4" => ["table" => "Fach", "key" => "ID", "value" => "Fach"],
"LEHRER_S4" => ["table" => "Lehrer", "key" => "ID", "value" => "NAME"],
"RAUM_S4" => ["table" => "Raum", "key" => "ID", "value" => "Raum_NR"],
"FACH_S5" => ["table" => "Fach", "key" => "ID", "value" => "Fach"],
"LEHRER_S5" => ["table" => "Lehrer", "key" => "ID", "value" => "NAME"],
"RAUM_S5" => ["table" => "Raum", "key" => "ID", "value" => "Raum_NR"],
"FACH_S6" => ["table" => "Fach", "key" => "ID", "value" => "Fach"],
"LEHRER_S6" => ["table" => "Lehrer", "key" => "ID", "value" => "NAME"],
"RAUM_S6" => ["table" => "Raum", "key" => "ID", "value" => "Raum_NR"],
"FACH_S7" => ["table" => "Fach", "key" => "ID", "value" => "Fach"],
"LEHRER_S7" => ["table" => "Lehrer", "key" => "ID", "value" => "NAME"],
"RAUM_S7" => ["table" => "Raum", "key" => "ID", "value" => "Raum_NR"],
"FACH_S8" => ["table" => "Fach", "key" => "ID", "value" => "Fach"],
"LEHRER_S8" => ["table" => "Lehrer", "key" => "ID", "value" => "NAME"],
"RAUM_S8" => ["table" => "Raum", "key" => "ID", "value" => "Raum_NR"],
];
$Stundenplan = [
"MO" => ["table" => "Fach", "key" => "ID", "value" => "Fach"],
"DI" => ["table" => "Fach", "key" => "ID", "value" => "Fach"],
"MI" => ["table" => "Fach", "key" => "ID", "value" => "Fach"],
"DO" => ["table" => "Fach", "key" => "ID", "value" => "Fach"],
"FR" => ["table" => "Fach", "key" => "ID", "value" => "Fach"],
];
$Berechtigungen = [
"BENUTZER_ID" => ["table" => "Benutzer", "key" => "ID", "value" => "NAME"],
"SEITEN_ID" => ["table" => "Backend_Seiten", "key" => "ID", "value" => "DATEINAME"]
];
@@ -1,102 +1,102 @@
<?php
function logging() {
include_once( __DIR__ . '/../classes/getData.php');
include_once( __DIR__ . '/../database/databaseConnect.php');
include_once( __DIR__ . '/../utils/databaseMethods.php');
include_once( __DIR__ . '/../utils/mailer.php');
if ((new getSpecificData())->getLoggingStatus("GENERAL_BACKEND_LOGGING")[0]['WERT'] == 'ON') {
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
try {
$ClientIP = (new getSpecificData())->getClientIP();
$UserAgent = (new getSpecificData())->getUserAgent();
$RequestURL = (new getSpecificData())->getRequestURL();
$USERCOOKIE = (new getSpecificData())->getCookie("USER");
$SessionID = (new getSpecificData())->getSessionID();
$REQUESTTIME = (new getSpecificData())->getRequestTime();
$QUERY = (new getSpecificData())->getQuery();
$LAND = (new getSpecificData())->getGeoIP($ClientIP);
$Whitelisted = (new getSpecificData())->getWhitelistedIPs();
$node = (new getSpecificData())->getServerNode((new getSpecificData())->getServerIP());
$username = $_SESSION['username'] ?? 'Unbekannt';
$website = (new getSpecificData())->getWebsite();
if ((new getSpecificData())->getLoggingStatus("DEV_BACKEND_LOGGING")[0]['WERT'] == 'OFF') {
if (in_array($ClientIP, $Whitelisted)) {
return;
}
}
$db_conn = db_connect();
$stmt = $db_conn->prepare("SELECT * FROM logs WHERE
`CLIENT_IP` LIKE ? and
`REQUEST_URL` = ? and
`REQUEST_URL` <> '/favicon.ico'
");
$stmt->bind_param("ss", $ClientIP, $RequestURL);
$stmt->execute();
$stmt->store_result();
if ($RequestURL !== '/favicon.ico') {
if ($stmt->num_rows > 0) { //wenn mindestens eine zeile vorhanden ist für die client ip dann führe updates durch, wenn nicht mache neue zeile
$updateCounter = $db_conn->prepare("
UPDATE logs
SET COUNTER = COUNTER + 1
WHERE `CLIENT_IP` = ?
AND `REQUEST_URL` = ?
");
$updateCounter->bind_param("ss", $ClientIP, $RequestURL);
$updateCounter->execute();
$updateLastRequest = $db_conn->prepare("
UPDATE logs
SET `LAST_REQUEST` = ?
WHERE `CLIENT_IP` = ?
AND `REQUEST_URL` = ?
");
$updateLastRequest->bind_param("sss", $REQUESTTIME, $ClientIP, $RequestURL);
$updateLastRequest->execute();
$updateCookieValue = $db_conn->prepare("
UPDATE logs
SET `USER_COOKIE_VALUE` = ?
WHERE `CLIENT_IP` = ?
AND `REQUEST_URL` = ?
");
$updateCookieValue->bind_param("sss", $USERCOOKIE, $ClientIP, $RequestURL);
$updateCookieValue->execute();
} else {
DatabaseInsert(
"logs",
"CLIENT_IP, LAND, SERVER_IP, USER_AGENT, REQUEST_URL, USER_COOKIE_VALUE, USER_SESSION_VALUE, FIRST_REQUEST, QUERY, WEBSITE, USER",
$ClientIP,
$LAND,
$node,
$UserAgent,
$RequestURL,
$USERCOOKIE,
$SessionID,
$REQUESTTIME,
$QUERY,
$website,
$username
);
}
}
} catch (mysqli_sql_exception $e) {
errorMail(
"Logging failed",
$e->getMessage(),
true,
);
throw $e;
}
}
<?php
function logging() {
include_once 'DataController.php';
include_once 'DatabaseConnect.php';
include_once 'DatabaseController.php';
include_once __DIR__ . '/../modules/Mail.php';
if ((new getSpecificData())->getLoggingStatus("GENERAL_BACKEND_LOGGING")[0]['WERT'] == 'ON') {
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
try {
$ClientIP = (new getSpecificData())->getClientIP();
$UserAgent = (new getSpecificData())->getUserAgent();
$RequestURL = (new getSpecificData())->getRequestURL();
$USERCOOKIE = (new getSpecificData())->getCookie("USER");
$SessionID = (new getSpecificData())->getSessionID();
$REQUESTTIME = (new getSpecificData())->getRequestTime();
$QUERY = (new getSpecificData())->getQuery();
$LAND = (new getSpecificData())->getGeoIP($ClientIP);
$Whitelisted = (new getSpecificData())->getWhitelistedIPs();
$node = (new getSpecificData())->getServerNode((new getSpecificData())->getServerIP());
$username = $_SESSION['username'] ?? 'Unbekannt';
$website = (new getSpecificData())->getWebsite();
if ((new getSpecificData())->getLoggingStatus("DEV_BACKEND_LOGGING")[0]['WERT'] == 'OFF') {
if (in_array($ClientIP, $Whitelisted)) {
return;
}
}
$db_conn = db_connect();
$stmt = $db_conn->prepare("SELECT * FROM logs WHERE
`CLIENT_IP` LIKE ? and
`REQUEST_URL` = ? and
`REQUEST_URL` <> '/favicon.ico'
");
$stmt->bind_param("ss", $ClientIP, $RequestURL);
$stmt->execute();
$stmt->store_result();
if ($RequestURL !== '/favicon.ico') {
if ($stmt->num_rows > 0) { //wenn mindestens eine zeile vorhanden ist für die client ip dann führe updates durch, wenn nicht mache neue zeile
$updateCounter = $db_conn->prepare("
UPDATE logs
SET COUNTER = COUNTER + 1
WHERE `CLIENT_IP` = ?
AND `REQUEST_URL` = ?
");
$updateCounter->bind_param("ss", $ClientIP, $RequestURL);
$updateCounter->execute();
$updateLastRequest = $db_conn->prepare("
UPDATE logs
SET `LAST_REQUEST` = ?
WHERE `CLIENT_IP` = ?
AND `REQUEST_URL` = ?
");
$updateLastRequest->bind_param("sss", $REQUESTTIME, $ClientIP, $RequestURL);
$updateLastRequest->execute();
$updateCookieValue = $db_conn->prepare("
UPDATE logs
SET `USER_COOKIE_VALUE` = ?
WHERE `CLIENT_IP` = ?
AND `REQUEST_URL` = ?
");
$updateCookieValue->bind_param("sss", $USERCOOKIE, $ClientIP, $RequestURL);
$updateCookieValue->execute();
} else {
DatabaseInsert(
"logs",
"CLIENT_IP, LAND, SERVER_IP, USER_AGENT, REQUEST_URL, USER_COOKIE_VALUE, USER_SESSION_VALUE, FIRST_REQUEST, QUERY, WEBSITE, USER",
$ClientIP,
$LAND,
$node,
$UserAgent,
$RequestURL,
$USERCOOKIE,
$SessionID,
$REQUESTTIME,
$QUERY,
$website,
$username
);
}
}
} catch (mysqli_sql_exception $e) {
errorMail(
"Logging failed",
$e->getMessage(),
true,
);
throw $e;
}
}
}
@@ -1,10 +1,9 @@
<?php
include_once(__DIR__ . '/../utils/formatter.php');
require_once(__DIR__ . '/DatabaseController.php');
class RightsController {
use ReponseFormatter;
/**
* save new site with sitename
*
@@ -1,30 +1,30 @@
<?php
function redirectToWebsite(string $website) {
$website = trim($website);
if ($website === '') {
die("Ungültige URL.");
}
$blocked_schemes = ['javascript:', 'data:', 'file:'];
foreach ($blocked_schemes as $scheme) {
if (stripos($website, $scheme) === 0) {
die("Ungültige URL.");
}
}
if (preg_match('/^https?:\/\//i', $website)) {
header("Location: " . $website);
exit;
}
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? "https://" : "http://";
$domain = $_SERVER['HTTP_HOST'];
$website = ltrim($website, "/");
$redirectUrl = $protocol . $domain . "/" . $website;
header("Location: " . $redirectUrl);
exit;
<?php
function redirectToWebsite(string $website): void
{
$website = trim($website);
if ($website === '') {
die("Ungültige URL.");
}
$blocked_schemes = ['javascript:', 'data:', 'file:'];
foreach ($blocked_schemes as $scheme) {
if (stripos($website, $scheme) === 0) {
die("Ungültige URL.");
}
}
if (preg_match('/^https?:\/\//i', $website)) {
header("Location: " . $website);
exit;
}
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? "https://" : "http://";
$domain = $_SERVER['HTTP_HOST'];
$website = ltrim($website, "/");
$redirectUrl = $protocol . $domain . "/" . $website;
header("Location: " . $redirectUrl);
exit;
}
@@ -1,6 +1,5 @@
<?php
class UserController {
public function setUser($name, $nachname, $email, $passwort, $rolle, $admin, $home_ip) {
+73 -119
View File
@@ -15,21 +15,15 @@ if (empty($_SESSION['user_logged_in'])) {
exit;
}
require_once __DIR__ . '/src/MVC/autoloader.php';
include_once( __DIR__ . '/config.php');
include_once( __DIR__ . '/src/classes/getData.php');
include_once( __DIR__ . '/src/controller/apiController.php');
include_once( __DIR__ . '/src/controller/cookieController.php');
include_once( __DIR__ . '/src/controller/rightsController.php');
include_once( __DIR__ . '/src/controller/userController.php');
include_once( __DIR__ . '/src/database/databaseConnect.php');
include_once( __DIR__ . '/src/utils/Debugging/ConsoleLogger.php');
include_once( __DIR__ . '/src/utils/databaseMethods.php');
include_once( __DIR__ . '/src/utils/formatter.php');
include_once( __DIR__ . '/src/utils/mailer.php');
include_once( __DIR__ . '/src/utils/requestLogger.php');
include_once( __DIR__ . '/src/utils/router.php');
include_once 'config.php';
include_once 'controller/CookieController.php';
include_once 'controller/DatabaseConnect.php';
include_once 'controller/DatabaseController.php';
include_once 'controller/DataController.php';
include_once 'controller/LogController.php';
include_once 'controller/UserController.php';
include_once 'modules/ConsoleLog.php';
// include 'controller/RoutesController.php';
$user_id = DatabaseShow(
"Benutzer",
@@ -58,7 +52,8 @@ if (
$selectedService = $_GET['service'];
}
if (!$selectedService &&
if (
!$selectedService &&
isset($defaultService) &&
isset($services[$defaultService]) &&
$services[$defaultService]['active']
@@ -74,126 +69,85 @@ if (!$selectedService) {
}
}
}
if ($selectedService && !in_array($selectedService, $userRights, true)) {
$selectedService = null;
}
$isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
if ((new getSpecificData())->getWebsite() != "backend.blackpi.ipv64.net") {
if ((new getSpecificData())->getWebsite() != "backend.mrhossa.de") {
$title = "DEV Backend";
} else {
$title = "Backend";
}
// MVC Logic
try {
$router = new \MVC\Core\Router();
?>
<!DOCTYPE html>
<html lang="de">
// 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']);
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= $title ?></title>
<link rel="stylesheet" href="style.css">
</head>
$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']);
<body>
<header class="main-header">
Backend
<button id="darkModeToggle">Dark Mode</button>
<button id="logoutButton">Logout</button>
</header>
<div class="main">
<div class="sidebar">
<?php foreach ($serviceGroups as $groupKey => $group): ?>
<?php
$visibleServices = [];
$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']);
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;
}
$router->add('api_tester', \MVC\Controller\ApiController::class, 'api_tester', ['auth', 'admin']);
$router->add('weather_chart', \MVC\Controller\StatsController::class, 'weather_chart', ['auth', 'admin']);
$router->add('domains', \MVC\Controller\DomainController::class, 'domains', ['auth']);
$router->add('documentation', \MVC\Controller\DocumentationController::class, 'index', ['auth']);
if (empty($visibleServices)) continue;
$layout = new \MVC\Layout\Layout('main');
$layout->setBlock('title', $title);
$isOpen = !empty($group['defaultOpen']) || in_array($selectedService, $visibleServices, true);
?>
<div class="service-group <?= $isOpen ? 'open' : '' ?>" data-group="<?= $groupKey ?>">
// Header Block
ob_start();
?>
Backend
<button id="darkModeToggle">Dark Mode</button>
<button id="logoutButton">Logout</button>
<?php
$layout->setBlock('header', ob_get_clean());
<div class="group-title">
<?= htmlspecialchars($group['label']) ?>
<span class="arrow">▸</span>
</div>
// Sidebar Block
ob_start();
?>
<?php foreach ($serviceGroups as $groupKey => $group): ?>
<?php
$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;
}
if (empty($visibleServices)) continue;
$isOpen = !empty($group['defaultOpen']) || in_array($selectedService, $visibleServices, true);
?>
<div class="service-group <?= $isOpen ? 'open' : '' ?>" data-group="<?= $groupKey ?>">
<div class="group-title">
<?= htmlspecialchars($group['label']) ?>
<span class="arrow">▸</span>
<div class="group-services">
<?php foreach ($visibleServices as $serviceKey): ?>
<a href="?service=<?= $serviceKey ?>">
<button class="<?= $selectedService === $serviceKey ? 'active' : '' ?>">
<?= htmlspecialchars($services[$serviceKey]['name']) ?>
</button>
</a>
<?php endforeach; ?>
</div>
</div>
<?php endforeach; ?>
</div>
<div class="content">
<?php
if ($selectedService) {
include "services/$selectedService.php";
logging();
} else {
echo "<p>Kein aktiver Service verfügbar.</p>";
}
?>
</div>
</div>
<script src="main.js"></script>
</body>
<div class="group-services">
<?php foreach ($visibleServices as $serviceKey): ?>
<a href="?service=<?= $serviceKey ?>">
<button class="<?= $selectedService === $serviceKey ? 'active' : '' ?>">
<?= htmlspecialchars($services[$serviceKey]['name']) ?>
</button>
</a>
<?php endforeach; ?>
</div>
</div>
<?php endforeach; ?>
<?php
$layout->setBlock('sidebar', ob_get_clean());
// Content Block
if ($selectedService) {
$content = $router->dispatch($selectedService);
$layout->setBlock('content', $content);
logging();
} else {
$layout->setBlock('content', "<p>Kein aktiver Service verfügbar.</p>");
}
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>";
}
}
</html>
+5 -5
View File
@@ -1,9 +1,9 @@
<?php
include_once( __DIR__ . '/config.php');
include_once( __DIR__ . '/src/utils/databaseMethods.php');
include_once( __DIR__ . '/src/utils/requestLogger.php');
include_once( __DIR__ . '/src/utils/mailer.php');
include_once 'controller/DatabaseController.php';
include_once 'controller/RoutesController.php';
include_once 'controller/LogController.php';
include_once 'modules/Mail.php';
include_once 'config.php';
session_set_cookie_params([
'lifetime' => 0,
+25
View File
@@ -0,0 +1,25 @@
<?php
include_once(__DIR__ . '/modules/Mail.php');
include_once(__DIR__ . '/controller/DataController.php');
try {
session_start();
$_SESSION = array();
session_destroy();
http_response_code(200);
exit();
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => 'An error occurred while logging out.']);
sendMail(
"[ERROR] Logout",
"$e",
(new getSpecificData())->getRequestURL(),
true,
);
exit();
}
+4 -1
View File
@@ -27,5 +27,8 @@ document.querySelectorAll('.group-title').forEach(title => {
const logoutButton = document.getElementById('logoutButton');
logoutButton.addEventListener('click', () => {
window.location.href = '?service=logout';
fetch('logout', { method: 'POST' })
.then(() => {
window.location.href = 'login.php';
});
});
File renamed without changes.
@@ -1,12 +1,13 @@
<?php
include_once __DIR__ . '/databaseMethods.php';
include_once __DIR__ . '/../classes/getData.php';
include_once __DIR__ . '/../controller/DatabaseController.php';
include_once __DIR__ . '/../controller/DataController.php';
function sendMail($subject, $message, $url, bool $log) {
function sendMail($subject, $message, $url, bool $log, $to = "bruno.eckhardt07@gmail.com")
{
$headers = "From: bruno.eckhardt07@gmail.com\r\n";
mail(
"bruno.eckhardt07@gmail.com",
$to,
$subject,
$message,
$headers
@@ -14,17 +15,19 @@ function sendMail($subject, $message, $url, bool $log) {
if ($log == true) {
DatabaseInsert(
"Mailing",
"DATUM, SUBJECT, MESSAGE, URL, CLIENT_IP",
"DATUM, SUBJECT, MESSAGE, MAIL_AN, URL, CLIENT_IP",
(new getSpecificData())->getRequestTime(),
$subject,
$message,
$to,
$url,
(new getSpecificData())->getClientIP(),
);
}
}
function errorMail($title, $error, bool $log) {
function errorMail($title, $error, bool $log)
{
$headers = "From: bruno.eckhardt07@gmail.com\r\n";
mail(
"bruno.eckhardt07@gmail.com",
@@ -42,4 +45,4 @@ function errorMail($title, $error, bool $log) {
(new getSpecificData())->getClientIP(),
);
}
}
}
@@ -1,4 +1,60 @@
<?php
// ======================================================
// API TESTER
// Modul für index.php
// ======================================================
// API Basis-Verzeichnis
$apiDir = realpath(__DIR__ . '/../api');
// Alle PHP APIs rekursiv finden
$apiOptions = [];
if ($apiDir !== false) {
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator(
$apiDir,
RecursiveDirectoryIterator::SKIP_DOTS
)
);
foreach ($iterator as $file) {
if (!$file->isFile()) {
continue;
}
if ($file->getExtension() !== 'php') {
continue;
}
// relativen Pfad erzeugen
$relativePath = str_replace(
$apiDir . DIRECTORY_SEPARATOR,
'',
$file->getPathname()
);
// Windows kompatibel
$relativePath = str_replace('\\', '/', $relativePath);
$apiOptions[] = 'api/' . $relativePath;
}
}
// sortieren
sort($apiOptions);
// Standard Token
$defaultToken = "15a437d128c918633e57dc7d18118a073b807644f21731ed5a7ebcf3eca9e242";
?>
<style>
/* ======================================================
API TESTER STYLES
====================================================== */
.api-tester-container {
width: 100%;
max-width: 1000px;
@@ -153,9 +209,11 @@ select {
}
@media (max-width: 700px) {
.tester-grid {
grid-template-columns: 1fr;
}
.tester-row {
flex-direction: column;
align-items: stretch;
@@ -164,20 +222,32 @@ select {
</style>
<div class="api-tester-container">
<h2>🛠️ API Tester</h2>
<!-- API Auswahl -->
<div class="tester-form-group">
<label for="endpoint">API Endpunkt</label>
<select id="endpoint" class="tester-input" onchange="checkEndpoint()">
<?php foreach ($apiOptions as $opt): ?>
<option value="<?= htmlspecialchars($opt) ?>"><?= htmlspecialchars($opt) ?></option>
<option value="<?= htmlspecialchars($opt) ?>">
<?= htmlspecialchars($opt) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<!-- Methode + Function -->
<div class="tester-grid">
<div class="tester-form-group">
<label for="method">HTTP Methode</label>
<select id="method" class="tester-input">
<option value="GET">GET</option>
<option value="POST">POST</option>
@@ -187,77 +257,146 @@ select {
<option value="OPTIONS">OPTIONS</option>
</select>
</div>
<div class="tester-form-group" id="function-group">
<label for="function-header">Function Header</label>
<input type="text" id="function-header" class="tester-input" placeholder="z.B. IPs, Ueberstunden">
</div>
</div>
<!-- Token -->
<div class="tester-form-group">
<label for="token">Authorization Token</label>
<div class="tester-row">
<input type="text" id="token" class="tester-input" value="<?= htmlspecialchars($defaultToken) ?>">
<label class="tester-checkbox">
<input type="checkbox" id="send-token" checked>
Mitsenden
</label>
</div>
</div>
<!-- Eigene Header -->
<div class="tester-form-group">
<label for="custom-headers">Zusätzliche Header (JSON)</label>
<textarea id="custom-headers" class="tester-textarea" placeholder='{"X-Test": "123", "Client": "Frontend"}'></textarea>
<label for="custom-headers">
Zusätzliche Header (JSON)
</label>
<textarea id="custom-headers" class="tester-textarea" placeholder='{
"X-Test": "123",
"Client": "Frontend"
}'></textarea>
</div>
<!-- JSON Body -->
<div class="tester-form-group" id="json-body-group">
<label for="json-body">JSON Body</label>
<textarea id="json-body" class="tester-textarea" placeholder='{"test": true}'></textarea>
<label for="json-body">
JSON Body
</label>
<textarea id="json-body" class="tester-textarea" placeholder='{
"test": true
}'></textarea>
</div>
<!-- Datei Upload -->
<div class="tester-form-group hidden" id="file-group">
<label for="files">Dateien Upload</label>
<input type="file" id="files" class="tester-input" multiple>
</div>
<!-- Buttons -->
<div class="tester-grid">
<button class="tester-btn-send" onclick="sendApiRequest()">Anfrage senden 🚀</button>
<button class="tester-btn-send" onclick="clearResponse()">Ausgabe leeren 🧹</button>
<button class="tester-btn-send" onclick="sendApiRequest()">
Anfrage senden 🚀
</button>
<button class="tester-btn-send" onclick="clearResponse()">
Ausgabe leeren 🧹
</button>
</div>
<!-- Ausgabe -->
<div id="tester-response-container">
<div style="text-align:center; opacity:0.7; padding:15px;">Bereit für Anfragen.</div>
<div style="text-align:center; opacity:0.7; padding:15px;">
Bereit für Anfragen.
</div>
</div>
</div>
<script>
// ======================================================
// CHECK ENDPOINT
// ======================================================
function checkEndpoint() {
const endpoint = document.getElementById('endpoint').value;
const fileGroup = document.getElementById('file-group');
const functionGroup = document.getElementById('function-group');
const jsonBodyGroup = document.getElementById('json-body-group');
const methodSelect = document.getElementById('method');
// reset
fileGroup.classList.add('hidden');
functionGroup.classList.remove('hidden');
jsonBodyGroup.classList.remove('hidden');
// upload api
if (endpoint.includes('upload.php')) {
fileGroup.classList.remove('hidden');
jsonBodyGroup.classList.add('hidden');
methodSelect.value = 'POST';
}
// get api
if (endpoint.includes('get.php')) {
methodSelect.value = 'GET';
}
}
// ======================================================
// CLEAR RESPONSE
// ======================================================
function clearResponse() {
document.getElementById('tester-response-container').innerHTML = `
<div style="text-align:center; opacity:0.7; padding:15px;">Ausgabe geleert.</div>
<div style="text-align:center; opacity:0.7; padding:15px;">
Ausgabe geleert.
</div>
`;
}
// ======================================================
// SEND REQUEST
// ======================================================
async function sendApiRequest() {
const endpoint = document.getElementById('endpoint').value;
const method = document.getElementById('method').value;
const token = document.getElementById('token').value;
@@ -268,74 +407,174 @@ async function sendApiRequest() {
const sendToken = document.getElementById('send-token').checked;
const responseContainer = document.getElementById('tester-response-container');
responseContainer.innerHTML = `<div style="text-align:center; padding:15px;">Anfrage läuft...</div>`;
responseContainer.innerHTML = `
<div style="text-align:center; padding:15px;">
Anfrage läuft...
</div>
`;
let headers = {};
if (sendToken && token.trim() !== '') headers['Authorization'] = token;
if (functionHeader.trim() !== '') headers['Function'] = functionHeader;
// Token
if (sendToken && token.trim() !== '') {
headers['Authorization'] = token;
}
// Function Header
if (functionHeader.trim() !== '') {
headers['Function'] = functionHeader;
}
// Custom Headers
if (customHeadersInput.trim() !== '') {
try {
const customHeaders = JSON.parse(customHeadersInput);
headers = { ...headers, ...customHeaders };
headers = {
...headers,
...customHeaders
};
} catch (e) {
responseContainer.innerHTML = `<div style="color:#dc3545; padding:15px;">Fehlerhafte Header JSON Struktur.</div>`;
responseContainer.innerHTML = `
<div style="color:#dc3545; padding:15px;">
Fehlerhafte Header JSON Struktur.
</div>
`;
return;
}
}
let options = { method: method, headers: headers };
let options = {
method: method,
headers: headers
};
// Upload
if (endpoint.includes('upload.php') && fileInput.files.length > 0) {
const formData = new FormData();
for (let i = 0; i < fileInput.files.length; i++) formData.append('files[]', fileInput.files[i]);
for (let i = 0; i < fileInput.files.length; i++) {
formData.append('files[]', fileInput.files[i]);
}
options.body = formData;
} else {
if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(method) && jsonBodyInput.trim() !== '') {
// JSON Body
if (
['POST', 'PUT', 'PATCH', 'DELETE']
.includes(method) &&
jsonBodyInput.trim() !== ''
) {
try {
JSON.parse(jsonBodyInput);
headers['Content-Type'] = 'application/json';
options.body = jsonBodyInput;
} catch (e) {
responseContainer.innerHTML = `<div style="color:#dc3545; padding:15px;">Fehlerhafte JSON Body Struktur.</div>`;
responseContainer.innerHTML = `
<div style="color:#dc3545; padding:15px;">
Fehlerhafte JSON Body Struktur.
</div>
`;
return;
}
}
}
const startedAt = performance.now();
try {
const response = await fetch(endpoint, options);
const duration = (performance.now() - startedAt).toFixed(0);
const status = response.status;
const rawText = await response.text();
let resultText = rawText;
// JSON hübsch formatieren
try {
const json = JSON.parse(rawText);
resultText = JSON.stringify(json, null, 2);
} catch (e) {}
let badgeClass = status >= 200 && status < 300 ? 'tester-status-200' : (status >= 300 && status < 400 ? 'tester-status-warning' : 'tester-status-error');
let badgeClass = 'tester-status-error';
if (status >= 200 && status < 300) {
badgeClass = 'tester-status-200';
} else if (status >= 300 && status < 400) {
badgeClass = 'tester-status-warning';
}
responseContainer.innerHTML = `
<div class="tester-badge ${badgeClass}">Status: ${status}</div>
<div class="tester-response-info">⏱️ ${duration} ms</div>
<button class="tester-small-btn" onclick="copyResponse()">Antwort kopieren</button>
<div class="tester-badge ${badgeClass}">
Status: ${status}
</div>
<div class="tester-response-info">
⏱️ ${duration} ms
</div>
<button
class="tester-small-btn"
onclick="copyResponse()"
>
Antwort kopieren
</button>
<pre id="tester-output-content"></pre>
`;
document.getElementById('tester-output-content').textContent = resultText;
} catch (error) {
responseContainer.innerHTML = `<div style="color:#dc3545; padding:15px;">Fehler: ${error.message}</div>`;
responseContainer.innerHTML = `
<div style="color:#dc3545; padding:15px;">
Fehler: ${error.message}
</div>
`;
}
}
// ======================================================
// COPY RESPONSE
// ======================================================
function copyResponse() {
const content = document.getElementById('tester-output-content');
if (!content) return;
if (!content) {
return;
}
navigator.clipboard.writeText(content.textContent);
alert('Antwort kopiert.');
}
// Initial
checkEndpoint();
</script>
</script>
+105
View File
@@ -0,0 +1,105 @@
<?php
$conn = db_connect();
$sql = "SELECT DATE(FIRST_REQUEST) as tag, COUNT(*) as anzahl FROM logs WHERE WEBSITE = 'backend.mrhossa.de' GROUP BY DATE(FIRST_REQUEST) ORDER BY DATE(FIRST_REQUEST)";
$result = $conn->query($sql);
$labels = [];
$data = [];
if ($result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
$labels[] = $row['tag'];
$data[] = (int)$row['anzahl'];
}
}
$conn->close();
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Logs Chart</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-zoom"></script>
</head>
<body>
<canvas id="logChart" width="600" height="400"></canvas>
<script>
const ctx = document.getElementById('logChart').getContext('2d');
const logChart = new Chart(ctx, {
type: 'line',
data: {
labels: <?php echo json_encode($labels); ?>,
datasets: [{
label: 'Logs pro Tag',
data: <?php echo json_encode($data); ?>,
borderColor: 'rgba(75, 192, 192, 1)',
backgroundColor: 'rgba(75, 192, 192, 0.2)',
fill: true,
tension: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: true,
text: 'Logs pro Tag'
},
legend: {
display: true,
position: 'top'
},
zoom: {
pan: {
enabled: true,
mode: 'x',
},
zoom: {
wheel: {
enabled: true,
},
mode: 'x',
}
}
},
interaction: {
mode: 'index',
intersect: false
},
scales: {
x: {
title: {
display: true,
text: 'Datum'
}
},
y: {
min: 0,
max: 100,
title: {
display: true,
text: 'Anzahl Logs'
},
ticks: {
stepSize: 20
},
beginAtZero: true
}
}
}
});
</script>
</body>
</html>
+174
View File
@@ -0,0 +1,174 @@
<?php
$conn = db_connect();
// Dev-IPs
$devips = (new getSpecificData())->getWhitelistedIPs();
$devips_sql = "'" . implode("','", $devips) . "'";
// Filter
$filter = $_GET['filter'] ?? 'today';
$customQuery = $_POST['custom_query'] ?? '';
$useCustom = false;
// Custom Query Schutz
if ($customQuery) {
$forbidden = ['DELETE', 'UPDATE', 'INSERT', 'DROP', 'ALTER', 'TRUNCATE'];
$upper = strtoupper($customQuery);
foreach ($forbidden as $bad) {
if (strpos($upper, $bad) !== false) {
echo "<b style='color:red'>Gefährliche Query blockiert ($bad)</b>";
return;
}
}
if (stripos(trim($customQuery), "SELECT") !== 0) {
echo "<b style='color:red'>Nur SELECT erlaubt</b>";
return;
}
$sql = $customQuery;
$useCustom = true;
}
// Standard Queries
if (!$useCustom) {
switch ($filter) {
case 'today':
$sql = "
SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE`
FROM logs
WHERE DATE(FIRST_REQUEST) = CURDATE()
AND (WEBSITE = 'backend.mrhossa.de')
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 (WEBSITE = 'backend.mrhossa.de')
AND CLIENT_IP NOT IN ($devips_sql)
GROUP BY CLIENT_IP
ORDER BY gesamt_counter DESC
";
break;
case 'node1':
$sql = "
SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE`
FROM logs
WHERE `SERVER_IP` = 'node1'
AND (WEBSITE = 'backend.mrhossa.de')
ORDER BY `FIRST_REQUEST` DESC
";
break;
case 'node2':
$sql = "
SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE`
FROM logs
WHERE `SERVER_IP` = 'node2'
AND (WEBSITE = 'backend.mrhossa.de')
ORDER BY `FIRST_REQUEST` DESC
";
break;
case 'node3':
$sql = "
SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE`
FROM logs
WHERE `SERVER_IP` = 'node3'
AND (WEBSITE = 'backend.mrhossa.de')
ORDER BY `FIRST_REQUEST` DESC
";
break;
case 'node4':
$sql = "
SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE`
FROM logs
WHERE `SERVER_IP` = 'node4'
AND (WEBSITE = 'backend.mrhossa.de')
ORDER BY `FIRST_REQUEST` DESC
";
break;
default:
$sql = "
SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE`
FROM logs
WHERE (WEBSITE = 'backend.mrhossa.de')
AND CLIENT_IP NOT IN ($devips_sql)
ORDER BY FIRST_REQUEST DESC
";
}
}
?>
<h1>📊 Logs</h1>
<form method="GET" action="index.php">
<input type="hidden" name="service" value="<?= htmlspecialchars($_GET['service'] ?? 'logs') ?>">
<select name="filter" onchange="this.form.submit()">
<?php
$options = ['alle', 'today', 'node1', 'node2', 'node3', 'node4', 'counterperip'];
foreach ($options as $opt) {
$sel = ($filter === $opt) ? 'selected' : '';
echo "<option value='$opt' $sel>$opt</option>";
}
?>
</select>
</form>
<!-- <br>
<form method="POST">
<textarea name="custom_query" style="width:100%;height:60px;"><?= htmlspecialchars($customQuery) ?></textarea>
<br>
<button type="submit">Query ausführen</button>
</form>
<hr> -->
<br>
<?php
$daten = [];
if ($erg = $conn->query($sql)) {
// echo "<pre style='background:var(--bg-soft,#f4f4f4);padding:10px;'>$sql</pre>";
while ($row = $erg->fetch_object()) {
$daten[] = $row;
}
}
?>
<?php if (!$daten): ?>
<b>Keine Daten gefunden</b>
<?php else: ?>
<div style="overflow-x:auto;">
<table border="1" cellpadding="5" cellspacing="0" style="border-collapse:collapse;width:100%;">
<tr>
<?php foreach ($daten[0] as $key => $_): ?>
<th><?= htmlspecialchars($key) ?></th>
<?php endforeach; ?>
</tr>
<?php foreach ($daten as $row): ?>
<tr>
<?php foreach ($row as $val): ?>
<td><?= htmlspecialchars($val ?? '') ?></td>
<?php endforeach; ?>
</tr>
<?php endforeach; ?>
</table>
</div>
<?php endif; ?>
+173
View File
@@ -0,0 +1,173 @@
<?php
$mode = $_GET['mode'] ?? 'fk';
if ($mode === 'normal') {
$data = Databaseshow("Firmenberichte");
} else {
$data = DatabaseShowWithFK(
"Berufsschulberichte",
$Berufsschulberichte,
);
}
$data = array_reverse($data);
$displayData = (is_array($data) && isset($data['rows'])) ? $data['rows'] : $data;
$count = is_array($displayData) ? count($displayData) : 0;
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="../style.css">
<style>
.debug-table-container {
background: #252525;
border-radius: 8px;
padding: 0;
margin-top: 20px;
overflow-x: auto;
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
}
table {
width: 100%;
border-collapse: collapse;
color: #ccc;
white-space: nowrap;
}
th {
background: #333;
text-align: left;
padding: 15px 12px;
border-bottom: 2px solid #3498db;
color: #3498db;
text-transform: uppercase;
font-size: 11px;
letter-spacing: 1px;
position: sticky;
top: 0;
z-index: 10;
}
td {
padding: 10px 12px;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
font-size: 13px;
}
tr:nth-child(even) {
background: rgba(255, 255, 255, 0.02);
}
tr:hover {
background: rgba(52, 152, 219, 0.1);
color: #fff;
}
/* Highlight für DATUM Spalte */
td:first-child,
th:first-child {
position: sticky;
left: 0;
background: #252525;
z-index: 5;
border-right: 1px solid rgba(255, 255, 255, 0.1);
}
th:first-child {
z-index: 11;
background: #333;
}
.debug-raw {
background: #1a1a1a;
color: #00ff00;
padding: 20px;
border-radius: 8px;
font-family: 'Fira Code', monospace;
font-size: 12px;
overflow: auto;
border-left: 4px solid #3498db;
margin-top: 10px;
}
.badge-count {
background: #3498db;
color: white;
padding: 3px 10px;
border-radius: 20px;
font-size: 12px;
font-weight: bold;
}
.empty-note {
padding: 40px;
text-align: center;
color: #777;
font-style: italic;
}
</style>
</head>
<body class="<?= ($_SESSION['dark_mode'] ?? true) ? 'dark-mode' : '' ?>">
<div class="main">
<div class="content">
<h2 style="display: flex; justify-content: space-between; align-items: center;">
<span>📊 Berichte-Vorschau:</span>
<div>
<a href="?service=<?= $selectedService ?>&mode=fk">
<button <?= $mode === 'fk' ? 'style="background:#3498db;color:white;"' : '' ?>>
Berufsschulberichte
</button>
</a>
<a href="?service=<?= $selectedService ?>&mode=normal">
<button <?= $mode === 'normal' ? 'style="background:#3498db;color:white;"' : '' ?>>
Arbeitsberichte
</button>
</a>
</div>
</h2>
<?php if ($count > 0 && is_array($displayData[0])): ?>
<div class="debug-table-container">
<table>
<thead>
<tr>
<?php foreach (array_keys($displayData[0]) as $column): ?>
<th><?= htmlspecialchars($column) ?></th>
<?php endforeach; ?>
</tr>
</thead>
<tbody>
<?php foreach ($displayData as $row): ?>
<tr>
<?php foreach ($row as $key => $cell): ?>
<td><?= htmlspecialchars($cell ?? '-') ?></td>
<?php endforeach; ?>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php else: ?>
<div class="debug-table-container">
<div class="empty-note">
Keine tabellarischen Daten gefunden.<br>
<small>Struktur: <?= is_array($data) ? 'Array ('.count($data).' Keys)' : gettype($data) ?></small>
</div>
</div>
<?php endif; ?>
</div>
</div>
</body>
</html>
+171
View File
@@ -0,0 +1,171 @@
<?php
// Zugriff nur, wenn angemeldet
if (empty($_SESSION['user_logged_in'])) {
echo "<p>Zugriff verweigert.</p>";
exit;
}
$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;
}
// Standard-Datenbereich: letzte 5 Tage
$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();
// PDF-Generierung
function generatePDF($berichteArray) {
$Einzelstunden = "2";
$Gesamtstunden = "8";
$data = [
'Text1' => '', // Nr
'Text2' => $berichteArray[0]['DATUM'] ?? '',
'Text3' => end($berichteArray)['DATUM'] ?? '',
'Text4' => 'Eckhardt',
'Text5' => 'Bruno Renato',
'Text6' => '1',
];
$fieldIndex = 7;
foreach ($berichteArray as $bericht) {
for ($i = 1; $i <= 4; $i++) {
$data["Text$fieldIndex"] = $bericht["PROZESS_$i"] ?? '';
$fieldIndex++;
$data["Text$fieldIndex"] = $Einzelstunden;
$fieldIndex++;
}
$data["Text$fieldIndex"] = $Gesamtstunden;
$fieldIndex++;
}
$fdfFields = array_map(function($field, $value){
$value = str_replace(['(', ')'], ['\(', '\)'], $value);
return "<< /T ($field) /V ($value) >>";
}, array_keys($data), $data);
$fdf = "%FDF-1.2\n1 0 obj\n<<\n/FDF << /Fields [\n" .
implode("\n", $fdfFields) .
"\n] >> >>\nendobj\ntrailer\n<< /Root 1 0 R >>\n%%EOF";
$fdfFile = tempnam(sys_get_temp_dir(), 'fdf_') . '.fdf';
file_put_contents($fdfFile, $fdf);
$inputPdf = (new getSpecificData())->getDocumentRoot() . '/templates/ausbildungsnachweis.pdf';
ConsoleLog((new getSpecificData())->getDocumentRoot() . '/templates/ausbildungsnachweis.pdf');
$outputPdf = (new getSpecificData())->getDocumentRoot() . '/static/ausbildungsnachweis_' . $berichteArray[0]['DATUM'] . '-' . end($berichteArray)['DATUM'] . '.pdf';
exec("pdftk $inputPdf fill_form $fdfFile output $outputPdf flatten");
unlink($fdfFile);
return $outputPdf;
}
// Handle PDF generation
$pdfFile = null;
if (isset($_POST['generate_pdf']) && count($berichteArray) > 0) {
$pdfFile = generatePDF($berichteArray);
}
?>
<div class="content-box">
<h2>Ausbildungsberichte PDF erstellen</h2>
<form method="post" class="overtime-container">
<label for="start_date">Von:</label>
<input type="date" name="start_date" id="start_date" value="<?= htmlspecialchars($startDate) ?>">
<label for="end_date">Bis:</label>
<input type="date" name="end_date" id="end_date" value="<?= htmlspecialchars($endDate) ?>">
<button type="submit" name="generate_pdf">PDF erstellen</button>
</form>
<?php if ($pdfFile): ?>
<div class="overtime-result">
<?= htmlspecialchars(basename($pdfFile)) ?> erfolgreich erstellt!
</div>
<?php elseif(isset($_POST['generate_pdf'])): ?>
<div class="overtime-result negative">
Keine Berichte im gewählten Zeitraum gefunden.
</div>
<?php endif; ?>
<?php if(!empty($wochenArray)): ?>
<div class="wochen-liste">
<h3>Verfügbare Wochenabschnitte:</h3>
<ul>
<?php
$outputDir = (new getSpecificData())->getDocumentRoot() . '/static/';
foreach($wochenArray as $woche):
$pdfName = 'ausbildungsnachweis_' . $woche['start_date'] . '-' . $woche['end_date'] . '.pdf';
$pdfPath = $outputDir . $pdfName;
$exists = file_exists($pdfPath);
?>
<li>
<code><?= htmlspecialchars($woche['start_date']) ?></code> bis
<code><?= htmlspecialchars($woche['end_date']) ?></code>
<?php if($exists): ?>
<span style="color:green;">(PDF vorhanden)</span>
<a href="<?= htmlspecialchars('/static/' . $pdfName) ?>" download class="btn btn-success">
Download
</a>
<?php else: ?>
<span style="color:red;">(noch keine PDF)</span>
<form method="post" style="display:inline;">
<input type="hidden" name="start_date" value="<?= htmlspecialchars($woche['start_date']) ?>">
<input type="hidden" name="end_date" value="<?= htmlspecialchars($woche['end_date']) ?>">
<button type="submit" name="generate_pdf" class="btn btn-primary">
PDF erzeugen
</button>
</form>
<?php endif; ?>
</li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
</div>
+212
View File
@@ -0,0 +1,212 @@
<?php
include_once(__DIR__ . '/../controller/DataController.php');
include_once(__DIR__ . '/../controller/DatabaseController.php');
$alleFaecher = DatabaseShow("Fach", "ID, FACH");
$alleRaeume = DatabaseShow("Raum", "ID, RAUM_NR");
$alleLehrer = DatabaseShow("Lehrer", "ID, NAME");
/*
1 => ["fach_id" => 1, "raum_id" => 5, "lehrer_id" => 8], // LF1
1 => ["fach_id" => 2, "raum_id" => 4, "lehrer_id" => 11], // LF2
1 => ["fach_id" => 3, "raum_id" => 1, "lehrer_id" => 2], // LF3
1 => ["fach_id" => 4, "raum_id" => 1, "lehrer_id" => 5], // LF4
6 => ["fach_id" => 5, "raum_id" => 3, "lehrer_id" => 1], // LF5
1 => ["fach_id" => 6, "raum_id" => 5, "lehrer_id" => 4], // Sozi
1 => ["fach_id" => 8, "raum_id" => 5, "lehrer_id" => 7], // VWL
1 => ["fach_id" => 9, "raum_id" => 5, "lehrer_id" => 3], // DE
1 => ["fach_id" => 10, "raum_id" => 5, "lehrer_id" => 3], // ENG
*/
$Stundenplan = [
"MO" => [
1 => ["fach_id" => 3, "raum_id" => 1, "lehrer_id" => 2], // LF3
2 => ["fach_id" => 3, "raum_id" => 1, "lehrer_id" => 2], // LF3
3 => ["fach_id" => 3, "raum_id" => 1, "lehrer_id" => 2], // LF3
4 => ["fach_id" => 6, "raum_id" => 5, "lehrer_id" => 4], // Sozi
5 => ["fach_id" => 10, "raum_id" => 5, "lehrer_id" => 3], // Eng
6 => ["fach_id" => 10, "raum_id" => 5, "lehrer_id" => 3], // Eng
7 => ["fach_id" => 2, "raum_id" => 3, "lehrer_id" => 11], // LF2
8 => ["fach_id" => 2, "raum_id" => 3, "lehrer_id" => 11], // LF2
],
"DI" => [
1 => ["fach_id" => 5, "raum_id" => 3, "lehrer_id" => 1], // LF5
2 => ["fach_id" => 5, "raum_id" => 3, "lehrer_id" => 1], // LF5
3 => ["fach_id" => 5, "raum_id" => 1, "lehrer_id" => 5], // LF5
4 => ["fach_id" => 2, "raum_id" => 3, "lehrer_id" => 6], // LF2
5 => ["fach_id" => 2, "raum_id" => 3, "lehrer_id" => 6], // LF2
],
"MI" => [
1 => ["fach_id" => 8, "raum_id" => 5, "lehrer_id" => 7], // VWL
2 => ["fach_id" => 8, "raum_id" => 5, "lehrer_id" => 7], // VWL
3 => ["fach_id" => 9, "raum_id" => 5, "lehrer_id" => 3], // DE
4 => ["fach_id" => 9, "raum_id" => 5, "lehrer_id" => 3], // DE
5 => ["fach_id" => 2, "raum_id" => 4, "lehrer_id" => 6], // LF2
6 => ["fach_id" => 2, "raum_id" => 4, "lehrer_id" => 6], // LF2
7 => ["fach_id" => 8, "raum_id" => 5, "lehrer_id" => 7], // VWL
],
"DO" => [
1 => ["fach_id" => 3, "raum_id" => 1, "lehrer_id" => 2], // LF3
2 => ["fach_id" => 3, "raum_id" => 1, "lehrer_id" => 2], // LF3
3 => ["fach_id" => 3, "raum_id" => 1, "lehrer_id" => 2], // LF3
4 => ["fach_id" => 6, "raum_id" => 5, "lehrer_id" => 4], // Sozi
5 => ["fach_id" => 6, "raum_id" => 5, "lehrer_id" => 4], // Sozi
6 => ["fach_id" => 4, "raum_id" => 1, "lehrer_id" => 5], // LF4
7 => ["fach_id" => 4, "raum_id" => 1, "lehrer_id" => 5], // LF4
8 => ["fach_id" => 4, "raum_id" => 1, "lehrer_id" => 5], // LF4
],
"FR" => [
1 => ["fach_id" => 1, "raum_id" => 5, "lehrer_id" => 8], // LF1
2 => ["fach_id" => 1, "raum_id" => 5, "lehrer_id" => 8], // LF1
3 => ["fach_id" => 1, "raum_id" => 5, "lehrer_id" => 8], // LF1
4 => ["fach_id" => 5, "raum_id" => 3, "lehrer_id" => 1], // LF5
5 => ["fach_id" => 5, "raum_id" => 3, "lehrer_id" => 1], // LF5
6 => ["fach_id" => 5, "raum_id" => 3, "lehrer_id" => 1], // LF5
]
];
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Berufsschulbericht</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="main">
<div class="content" style="max-width: 1100px; margin: 0 auto; padding: 20px;">
<div class="form-container">
<h3 style="margin-top: 0;">Berufsschulbericht erfassen</h3>
<form method="post">
<div class="form-group" style="max-width: 220px; margin-bottom: 25px;">
<label for="datum">Datum auswählen</label>
<input type="date" name="datum" id="datum" required>
</div>
<div class="vergabe-grid"
style="font-weight: bold; padding-bottom: 8px; margin-bottom: 12px; border-bottom: 1px solid var(--card-border);">
<div>Stunde</div>
<div>Fach</div>
<div>Raum</div>
<div>Lehrer</div>
<div>Inhalt / Thema</div>
</div>
<?php for ($i = 1; $i <= 8; $i++): ?>
<div class="vergabe-grid" style="margin-bottom: 10px; align-items: center;">
<div>
<span class="badge"
style="display: inline-block; min-width: 90px; text-align: center; padding: 8px 12px; font-weight: bold;">Stunde
<?= $i ?></span>
</div>
<div class="form-group">
<select name="stunden[<?= $i ?>][fach_id]" data-stunde="<?= $i ?>" data-type="fach_id">
<option value="">-- Fach --</option>
<?php foreach ($alleFaecher as $row): ?>
<option value="<?= $row['ID'] ?>"><?= htmlspecialchars($row['FACH']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<select name="stunden[<?= $i ?>][raum_id]" data-stunde="<?= $i ?>" data-type="raum_id">
<option value="">-- Raum --</option>
<?php foreach ($alleRaeume as $row): ?>
<option value="<?= $row['ID'] ?>"><?= htmlspecialchars($row['RAUM_NR']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<select name="stunden[<?= $i ?>][lehrer_id]" data-stunde="<?= $i ?>" data-type="lehrer_id">
<option value="">-- Lehrer --</option>
<?php foreach ($alleLehrer as $row): ?>
<option value="<?= $row['ID'] ?>"><?= htmlspecialchars($row['NAME']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<input type="text" name="stunden[<?= $i ?>][inhalt_text]" placeholder="Inhalt der Stunde..."
style="margin: 0; padding: 10px;">
</div>
</div>
<?php endfor; ?>
<div style="margin-top: 25px; text-align: right;">
<button type="submit" class="btn-submit-vergabe"
style="width: 220px; display: inline-flex; font-weight: bold;">
Bericht speichern
</button>
</div>
</form>
</div>
</div>
</div>
<script>
const stundenplan = <?= json_encode($Stundenplan) ?>;
const wochentage = ["SO", "MO", "DI", "MI", "DO", "FR", "SA"];
document.getElementById('datum').addEventListener('change', function() {
const gewaehltesDatum = new Date(this.value);
if (isNaN(gewaehltesDatum)) return;
let tagKey = wochentage[gewaehltesDatum.getDay()];
if (tagKey === "SO" || tagKey === "SA") {
tagKey = "MO";
}
const tagesPlan = stundenplan[tagKey] || {};
document.querySelectorAll('.vergabe-grid select').forEach(select => {
const stunde = select.getAttribute('data-stunde');
const type = select.getAttribute('data-type');
const vordefinierteId = (tagesPlan[stunde] && tagesPlan[stunde][type]) ? tagesPlan[stunde][
type
] : "";
select.value = vordefinierteId;
});
});
</script>
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$datum = $_POST['datum'] ?? null;
$werte = [$datum];
for ($i = 1; $i <= 8; $i++) {
$stunde = $_POST['stunden'][$i] ?? [];
$werte[] = !empty($stunde['fach_id']) ? $stunde['fach_id'] : null;
$werte[] = !empty($stunde['lehrer_id']) ? $stunde['lehrer_id'] : null;
$werte[] = !empty($stunde['raum_id']) ? $stunde['raum_id'] : null;
$werte[] = !empty($stunde['inhalt_text']) ? $stunde['inhalt_text'] : null;
}
DatabaseInsert(
"Berufsschulberichte",
"DATUM, FACH_S1, LEHRER_S1, RAUM_S1, INHALT_S1, FACH_S2, LEHRER_S2, RAUM_S2, INHALT_S2, FACH_S3, LEHRER_S3, RAUM_S3, INHALT_S3, FACH_S4, LEHRER_S4, RAUM_S4, INHALT_S4, FACH_S5, LEHRER_S5, RAUM_S5, INHALT_S5, FACH_S6, LEHRER_S6, RAUM_S6, INHALT_S6, FACH_S7, LEHRER_S7, RAUM_S7, INHALT_S7, FACH_S8, LEHRER_S8, RAUM_S8, INHALT_S8",
...$werte
);
echo "<div class='status-msg' style='position: fixed; bottom: 20px; right: 20px; z-index: 10000;'>Bericht gespeichert!</div>";
}
?>
</body>
</html>
@@ -1,3 +1,20 @@
<?php
$data = new getSpecificData();
$allData = $data->getAllData();
// Daten nach Gruppen sortieren
$groupedData = [];
foreach ($allData as $key => $value) {
if (strpos($key, ' - ') !== false) {
[$group, $field] = explode(' - ', $key, 2);
} else {
$group = 'Other';
$field = $key;
}
$groupedData[$group][$field] = $value;
}
?>
<h2>System & Client Info</h2>
<?php foreach ($groupedData as $groupName => $fields): ?>
@@ -19,6 +36,7 @@
</div>
<?php endforeach; ?>
<!-- ===== JS für einklappbare Gruppen ===== -->
<script>
document.querySelectorAll('.group-header').forEach(header => {
header.addEventListener('click', () => {
@@ -29,6 +47,7 @@ document.querySelectorAll('.group-header').forEach(header => {
});
</script>
<!-- ===== CSS für einklappbare Gruppen ===== -->
<style>
.group-container {
margin-bottom: 15px;
@@ -66,6 +85,7 @@ body.dark-mode .group-header:hover {
.group-content.open {
max-height: 2000px;
/* ausreichend hoch für alle Elemente */
padding: 10px 15px;
}
@@ -98,6 +118,7 @@ body.dark-mode .group-header:hover {
word-break: break-word;
}
/* Dark Mode Anpassungen */
body.dark-mode .group-header {
background-color: var(--card-bg);
color: var(--text);
@@ -106,4 +127,4 @@ body.dark-mode .group-header {
body.dark-mode .info-list li {
border-bottom: 1px solid #333;
}
</style>
</style>
+112
View File
@@ -0,0 +1,112 @@
<?php
$statusMsg = "";
/* =========================
Neue Domain hinzufügen
========================= */
if (isset($_POST["add_domain"])) {
$subdomain = trim($_POST["subdomain"]);
if ($subdomain != "") {
DatabaseInsert("Domains", "SUBDOMAIN", $subdomain);
$statusMsg = "Domain wurde hinzugefügt.";
}
}
/* =========================
Domain löschen
========================= */
if (isset($_POST["delete_domain"])) {
$id = intval($_POST["id"]);
DatabaseDelete("Domains", "ID = ?", $id);
$statusMsg = "Domain wurde gelöscht.";
}
/* =========================
Domain bearbeiten
========================= */
if (isset($_POST["save_domain"])) {
$id = intval($_POST["id"]);
$subdomain = trim($_POST["subdomain"]);
DatabaseUpdate("Domains", "SUBDOMAIN = ?", "ID = ?", $subdomain, $id);
$statusMsg = "Domain wurde gespeichert.";
}
/* =========================
Domains laden
========================= */
$domains = DatabaseShow("Domains", "*");
?>
<h2>
Domain Verwaltung
</h2>
<?php if ($statusMsg): ?>
<div class="status-msg">
<?= htmlspecialchars($statusMsg) ?>
</div>
<?php endif; ?>
<div class="admin-grid">
<?php foreach ($domains as $domain): ?>
<form method="post" class="admin-card">
<input type="hidden" name="id" value="<?= $domain["ID"] ?>">
<label>Subdomain</label>
<input type="text" name="subdomain" value="<?= htmlspecialchars($domain["SUBDOMAIN"]) ?>">
<div style="margin-top:10px; display:flex; gap:10px;">
<button type="submit" name="save_domain" class="btn-save">
Speichern
</button>
<button type="submit" name="delete_domain" class="btn-delete">
Löschen
</button>
</div>
</form>
<?php endforeach; ?>
<!-- Neue Domain -->
<form method="post" class="admin-card new-item">
<h3>Neu</h3>
<label>Subdomain</label>
<input type="text" name="subdomain" placeholder="z.B. kleinerpenis.mrhossa.de">
<button type="submit" name="add_domain" class="btn-add">
Domain hinzufügen
</button>
</form>
</div>
+91
View File
@@ -0,0 +1,91 @@
<?php
include_once __DIR__ . '/../controller/DatabaseController.php';
include_once __DIR__ . '/../controller/RoutesController.php';
$view = $_GET['view'] ?? 'show';
$selectedService = $_GET['service'] ?? null;
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_entry'])) {
$artikel = $_POST['artikel'];
$menge = $_POST['menge'];
DatabaseInsert(
"Einkaufsliste",
"PRODUKT, MENGE",
$artikel,
$menge
);
redirectToWebsite((new getSpecificData())->getReferer());
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_entry'])) {
$id = $_POST['id'];
DatabaseDelete("Einkaufsliste", "ID = ?", $id);
redirectToWebsite((new getSpecificData())->getReferer());
exit;
}
$entries = DatabaseShow(
"Einkaufsliste"
);
?>
<div>
<div class="vergabe-form">
<form method="POST" class="vergabe-grid">
<div class="form-group">
<label>Artikel</label>
<input type="text" name="artikel" required>
</div>
<div class="form-group">
<label>Menge</label>
<input type="text" name="menge" required>
</div>
<button type="submit" name="add_entry" class="btn-submit-vergabe">
💾 Speichern
</button>
</form>
</div>
<div class="table-container" style="margin-top: 30px;">
<h3>Einkaufsliste</h3>
<?php if (!empty($entries)): ?>
<table class="db-table mobile-responsive-table">
<thead>
<tr>
<th>Artikel</th>
<th>Menge</th>
<th style="width: 100px;">Aktion</th>
</tr>
</thead>
<tbody>
<?php foreach ($entries as $entry): ?>
<tr>
<td data-label="Artikel"><?= htmlspecialchars($entry['PRODUKT'] ?? '') ?></td>
<td data-label="Menge"><?= htmlspecialchars($entry['MENGE'] ?? '') ?></td>
<td data-label="Aktion">
<form method="POST" onsubmit="return confirm('Möchtest du diesen Eintrag wirklich löschen?');" style="margin: 0;">
<input type="hidden" name="id" value="<?= $entry['ID'] ?? '' ?>">
<button type="submit" name="delete_entry" class="btn-delete">
🗑️ Löschen
</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php else: ?>
<p>Keine Einträge vorhanden.</p>
<?php endif; ?>
</div>
</div>
+97
View File
@@ -0,0 +1,97 @@
<?php
include_once(__DIR__ . '/../controller/DataController.php');
include_once(__DIR__ . '/../controller/DatabaseController.php');
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_bericht'])) {
$prozess1 = $_POST['prozess1'];
$prozess2 = $_POST['prozess2'];
$prozess3 = $_POST['prozess3'];
$prozess4 = $_POST['prozess4'];
$startzeit = $_POST['startzeit'];
$endzeit = $_POST['endzeit'];
$datum = $_POST['datum'];
// IHK konforme zeiten berechnen und Überstunden berechnen
$soll = new DateTime($startzeit);
$soll->modify("+8 hours 30 minutes");
$randomMin = rand(1, 15);
$endePublicDt = clone $soll;
$endePublicDt->modify("+{$randomMin} minutes");
$endePublic = $endePublicDt->format("H:i");
$real = DateTime::createFromFormat("H:i", $endzeit);
$beginnDt = new DateTime($startzeit);
$arbeitsDiff = $beginnDt->diff($real);
$arbeitsMinuten = ($arbeitsDiff->h * 60) + $arbeitsDiff->i;
$sollMinuten = 8 * 60 + 30;
if ($arbeitsMinuten <= $sollMinuten) {
$overtime = 0;
} else {
$diff = $soll->diff($real);
$overtime = ($diff->h * 60) + $diff->i;
}
DatabaseInsert(
"Firmenberichte",
"DATUM, PROZESS_1, PROZESS_2, PROZESS_3, PROZESS_4, BEGINN, ENDE_REAL, ENDE_PUBLIC, OVERTIME",
$datum,
$prozess1,
$prozess2,
$prozess3,
$prozess4,
$startzeit,
$endzeit,
$endePublic,
$overtime
);
redirectToWebsite((new getSpecificData())->getReferer());
exit;
}
?>
<div>
<div class="berichte-container">
<form method="POST" class="berichte-form">
<div class="form-group">
<label>Prozess 1</label>
<input type="text" name="prozess1" required>
</div>
<div class="form-group">
<label>Prozess 2</label>
<input type="text" name="prozess2" required>
</div>
<div class="form-group">
<label>Prozess 3</label>
<input type="text" name="prozess3" required>
</div>
<div class="form-group">
<label>Prozess 4</label>
<input type="text" name="prozess4" required>
</div>
<div class="form-group">
<label>Startzeit</label>
<input type="time" name="startzeit" required>
</div>
<div class="form-group">
<label>Endzeit</label>
<input type="time" name="endzeit" required>
</div>
<div class="form-group">
<label>Datum</label>
<input type="date" name="datum" required>
</div>
<button type="submit" name="add_bericht" class="btn-submit-bericht">
Bericht Speichern
</button>
</form>
</div>
</div>
@@ -1,3 +1,17 @@
<?php
$username = $_SESSION['username'] ?? 'Gast';
$is_superuser = $_SESSION['is_superuser'] ?? false;
$is_admin = $_SESSION['is_admin'] ?? false;
$db_res = DatabaseShow(
"Benutzer",
"NACHNAME, EMAIL, ROLLE, HOME_IP",
"NAME = ?",
$username
);
?>
<div class="home-dashboard">
<div class="links-container">
@@ -7,16 +21,16 @@
<p style="margin: 5px 0;"><strong>Name:</strong> <code><?= htmlspecialchars($username) ?></code></p>
<?php if ($username != 'gast'): ?>
<p style="margin: 5px 0;"><strong>Nachname:</strong>
<code><?= htmlspecialchars($db_res[0]['NACHNAME'] ?? '') ?></code>
<code><?= htmlspecialchars($db_res[0]['NACHNAME']) ?></code>
</p>
<p style="margin: 5px 0;"><strong>E-Mail:</strong>
<code><?= htmlspecialchars($db_res[0]['EMAIL'] ?? '') ?></code>
<code><?= htmlspecialchars($db_res[0]['EMAIL']) ?></code>
</p>
<p style="margin: 5px 0;"><strong>Benutzerrolle:</strong>
<code><?= htmlspecialchars($db_res[0]['ROLLE'] ?? '') ?></code>
<code><?= htmlspecialchars($db_res[0]['ROLLE']) ?></code>
</p>
<p style="margin: 5px 0;"><strong>Home IP:</strong>
<code><?= htmlspecialchars($db_res[0]['HOME_IP'] ?? '') ?></code>
<code><?= htmlspecialchars($db_res[0]['HOME_IP']) ?></code>
</p>
<?php endif ?>
</div>
@@ -26,17 +40,17 @@
<h3 style="background-color: #28a745;">🛡️ Status & Rechte</h3>
<div class="group-links"
style="padding: 10px; display: flex; flex-direction: row; gap: 10px; flex-wrap: wrap;">
<?php if ((new \getSpecificData())->getIsAdmin()): ?>
<?php if ((new getSpecificData())->getIsAdmin()): ?>
<span
style="background: #dc3545; color: white; padding: 4px 10px; border-radius: 15px; font-size: 0.8rem; font-weight: bold;">Admin</span>
<?php endif; ?>
<?php if ((new \getSpecificData())->getIsSuperuser()): ?>
<?php if ((new getSpecificData())->getIsSuperuser()): ?>
<span
style="background: #ffc107; color: #000; padding: 4px 10px; border-radius: 15px; font-size: 0.8rem; font-weight: bold;">Superuser</span>
<?php endif; ?>
<?php if (!(new \getSpecificData())->getIsSuperuser() && !(new \getSpecificData())->getIsAdmin()): ?>
<?php if (!(new getSpecificData())->getIsSuperuser() && !(new getSpecificData())->getIsAdmin()): ?>
<span
style="background: #6c757d; color: white; padding: 4px 10px; border-radius: 15px; font-size: 0.8rem; font-weight: bold;">Standard-Nutzer</span>
<?php endif; ?>
@@ -54,19 +68,19 @@
</p>
<?php if ($is_admin): ?>
<p style="margin: 5px 0;"><strong>Client IP:</strong>
<code><?= htmlspecialchars((new \getSpecificData())->getClientIP()) ?></code>
<code><?= htmlspecialchars((new getSpecificData())->getClientIP()) ?></code>
</p>
<p style="margin: 5px 0;"><strong>Server IP:</strong>
<code><?= htmlspecialchars((new \getSpecificData())->getServerIP()) ?></code>
<code><?= htmlspecialchars((new getSpecificData())->getServerIP()) ?></code>
</p>
<p style="margin: 5px 0;"><strong>Geo IP:</strong>
<code><?= htmlspecialchars((new \getSpecificData())->getGeoIP((new \getSpecificData())->getClientIP())) ?></code>
<code><?= htmlspecialchars((new getSpecificData())->getGeoIP((new getSpecificData())->getClientIP())) ?></code>
</p>
<p style="margin: 5px 0;"><strong>Database IP:</strong>
<code><?= htmlspecialchars((new \getSpecificData())->getDatabase()) ?></code>
<code><?= htmlspecialchars((new getSpecificData())->getDatabase()) ?></code>
</p>
<p style="margin: 5px 0;"><strong>Wetter:</strong>
<code><?= htmlspecialchars((new \getSpecificData())->getWeather()['aktuell']) ?></code>
<code><?= htmlspecialchars((new getSpecificData())->getWeather()['aktuell']) ?></code>
</p>
<?php endif ?>
</div>
@@ -10,11 +10,14 @@ if (!isset($linkGroups) || !is_array($linkGroups)) {
}
?>
<!-- <h2>Externe Links</h2> -->
<div class="links-container">
<?php foreach ($linkGroups as $groupKey => $group): ?>
<?php
// Links dieser Gruppe sammeln
$groupLinks = array_filter($linkButtons, function($button) use ($groupKey, $isAdmin) {
if (($button['group'] ?? null) !== $groupKey) {
return false;
+6
View File
@@ -0,0 +1,6 @@
<?php
session_start();
session_destroy();
header('Location: login.php');
exit;
@@ -0,0 +1,97 @@
<?php
$conn = db_connect();
// Dev-IPs
$devips = (new getSpecificData())->getWhitelistedIPs();
$devips_sql = "'" . implode("','", $devips) . "'";
// Filter & Custom Query
$customQuery = $_POST['custom_query'] ?? '';
$sql = '';
$useCustom = false;
// Custom Query Schutz
if ($customQuery) {
$forbidden = ['DELETE', 'UPDATE', 'INSERT', 'DROP', 'ALTER', 'TRUNCATE'];
$upper = strtoupper($customQuery);
foreach ($forbidden as $bad) {
if (strpos($upper, $bad) !== false) {
echo "<b style='color:red'>Gefährliche Query blockiert ($bad)</b>";
return;
}
}
if (stripos(trim($customQuery), "SELECT") !== 0) {
echo "<b style='color:red'>Nur SELECT erlaubt</b>";
return;
}
$sql = $customQuery;
$useCustom = true;
}
// Standard Query (falls kein Custom Query)
if (!$useCustom) {
// Einfache Standard-Query für Logs ohne Dev-IPs
$sql = "
SELECT WEBSITE, REQUEST_URL, FIRST_REQUEST, COUNTER
FROM logs
WHERE CLIENT_IP = '" . (new getSpecificData())->getClientIP() . "'
ORDER BY FIRST_REQUEST DESC
";
}
if (isset($_POST['delete_logs'])) {
DatabaseDelete(
"logs",
"CLIENT_IP = ?",
(new getSpecificData())->getClientIP()
);
header("Location: " . (new getSpecificData())->getReferer());
exit;
}
?>
<h1>📊 Logs</h1>
<?php
$daten = [];
if ($erg = $conn->query($sql)) {
while ($row = $erg->fetch_object()) {
$daten[] = $row;
}
}
?>
<?php if (!$daten): ?>
<b>Keine Daten gefunden</b>
<?php else: ?>
<div style="overflow-x:auto;">
<?php if ((new getSpecificData())->getIsAdmin()): ?>
<form method="post">
<button name="delete_logs" class="btn-delete" style="margin-bottom: 10px">Lösche Logs</button>
</form>
<?php endif; ?>
<table border="1" cellpadding="5" cellspacing="0" style="border-collapse:collapse;width:100%;">
<tr>
<?php foreach ($daten[0] as $key => $_): ?>
<th><?= htmlspecialchars($key) ?></th>
<?php endforeach; ?>
</tr>
<?php foreach ($daten as $row): ?>
<tr>
<?php foreach ($row as $val): ?>
<td><?= htmlspecialchars($val ?? '') ?></td>
<?php endforeach; ?>
</tr>
<?php endforeach; ?>
</table>
</div>
<?php endif; ?>
+77
View File
@@ -0,0 +1,77 @@
<?php
$dir = __DIR__ . "/../static";
$files = array_diff(scandir($dir), ['.', '..']);
echo '<button onclick="printFiles()">Alle Dateien drucken</button>';
echo '<div class="print-container">';
foreach ($files as $file) {
$url = "static/" . rawurlencode($file);
$ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
echo '<div class="print-page">';
if (in_array($ext, ['png','jpg','jpeg','webp'])) {
echo "<img src='$url'>";
}
elseif ($ext === "pdf") {
echo "<embed src='$url' type='application/pdf'>";
}
else {
echo "<p>$file kann nicht direkt gedruckt werden.</p>";
}
echo '</div>';
}
echo '</div>';
?>
<script>
function printFiles() {
console.log("Ich wurde gedrückt");
window.print();
}
</script>
<style>
.print-page {
width: 100%;
height: 100vh;
page-break-after: always;
}
.print-page img {
max-width: 100%;
}
.print-page embed {
width: 100%;
height: 100vh;
}
@media print {
body * {
visibility: hidden;
}
.print-container,
.print-container * {
visibility: visible;
}
.print-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
}
}
</style>
+176
View File
@@ -0,0 +1,176 @@
<?php
$conn = db_connect();
// Dataset: Gesamte Logs
$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);
$totalDataAssoc = [];
$allLabels = [];
if ($result_total->num_rows > 0) {
while($row = $result_total->fetch_assoc()) {
$date = $row['tag'];
$totalDataAssoc[$date] = (int)$row['anzahl'];
$allLabels[] = $date;
}
}
// Nodes
$nodes = ['node1','node2','node3','node4'];
$nodeDataAssoc = [];
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 = [];
if ($result->num_rows > 0) {
while($row = $result->fetch_assoc()) {
$date = $row['tag'];
$data[$date] = (int)$row['anzahl'];
if (!in_array($date, $allLabels)) {
$allLabels[] = $date;
}
}
}
$nodeDataAssoc[$node] = $data;
}
// Alle Labels chronologisch sortieren
sort($allLabels);
// Finales Array: Gesamt + Nodes
$finalTotalData = [];
$finalNodeData = [];
foreach ($allLabels as $label) {
$finalTotalData[] = isset($totalDataAssoc[$label]) ? $totalDataAssoc[$label] : 0;
foreach ($nodes as $node) {
if (!isset($finalNodeData[$node])) $finalNodeData[$node] = [];
$finalNodeData[$node][] = isset($nodeDataAssoc[$node][$label]) ? $nodeDataAssoc[$node][$label] : 0;
}
}
$conn->close();
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Logs Chart</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-zoom"></script>
</head>
<body>
<canvas id="logChart" width="900" height="500"></canvas>
<script>
const ctx = document.getElementById('logChart').getContext('2d');
const logChart = new Chart(ctx, {
type: 'line',
data: {
labels: <?php echo json_encode($allLabels); ?>,
datasets: [{
label: 'Gesamt',
data: <?php echo json_encode($finalTotalData); ?>,
borderColor: 'rgb(255, 0, 0)',
backgroundColor: 'rgba(0, 0, 0, 0.2)',
fill: false,
tension: 0
},
{
label: 'Node1',
data: <?php echo json_encode($finalNodeData['node1']); ?>,
borderColor: 'rgba(75, 192, 192, 1)',
backgroundColor: 'rgba(75, 192, 192, 0.2)',
fill: false,
tension: 0,
hidden: true,
},
{
label: 'Node2',
data: <?php echo json_encode($finalNodeData['node2']); ?>,
borderColor: 'rgba(255, 99, 132, 1)',
backgroundColor: 'rgba(255, 99, 132, 0.2)',
fill: false,
tension: 0,
hidden: true,
},
{
label: 'Node3',
data: <?php echo json_encode($finalNodeData['node3']); ?>,
borderColor: 'rgba(255, 206, 86, 1)',
backgroundColor: 'rgba(255, 206, 86, 0.2)',
fill: false,
tension: 0,
hidden: true,
},
{
label: 'Node4',
data: <?php echo json_encode($finalNodeData['node4']); ?>,
borderColor: 'rgba(54, 162, 235, 1)',
backgroundColor: 'rgba(54, 162, 235, 0.2)',
fill: false,
tension: 0,
hidden: true,
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: true,
text: 'Logs pro Tag nach Node und Gesamt'
},
legend: {
display: true,
position: 'top'
},
zoom: {
pan: {
enabled: true,
mode: 'x'
},
zoom: {
wheel: {
enabled: true
},
mode: 'x'
}
}
},
interaction: {
mode: 'index',
intersect: false
},
scales: {
x: {
title: {
display: true,
text: 'Datum'
}
},
y: {
beginAtZero: true,
title: {
display: true,
text: 'Anzahl Logs'
}
}
}
}
});
</script>
</body>
</html>
+174
View File
@@ -0,0 +1,174 @@
<?php
$conn = db_connect();
// Dev-IPs
$devips = (new getSpecificData())->getWhitelistedIPs();
$devips_sql = "'" . implode("','", $devips) . "'";
// Filter
$filter = $_GET['filter'] ?? 'today';
$customQuery = $_POST['custom_query'] ?? '';
$useCustom = false;
// Custom Query Schutz
if ($customQuery) {
$forbidden = ['DELETE', 'UPDATE', 'INSERT', 'DROP', 'ALTER', 'TRUNCATE'];
$upper = strtoupper($customQuery);
foreach ($forbidden as $bad) {
if (strpos($upper, $bad) !== false) {
echo "<b style='color:red'>Gefährliche Query blockiert ($bad)</b>";
return;
}
}
if (stripos(trim($customQuery), "SELECT") !== 0) {
echo "<b style='color:red'>Nur SELECT erlaubt</b>";
return;
}
$sql = $customQuery;
$useCustom = true;
}
// Standard Queries
if (!$useCustom) {
switch ($filter) {
case 'today':
$sql = "
SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE`
FROM logs
WHERE CLIENT_IP NOT IN ($devips_sql)
AND (WEBSITE != 'backend.mrhossa.de' OR WEBSITE IS NULL)
AND DATE(FIRST_REQUEST) = CURDATE()
ORDER BY FIRST_REQUEST DESC
";
break;
case 'counterperip':
$sql = "
SELECT CLIENT_IP, LAND, USER_AGENT, SUM(counter) AS gesamt_counter
FROM logs
WHERE CLIENT_IP NOT IN ($devips_sql)
AND (WEBSITE != 'backend.mrhossa.de' OR WEBSITE IS NULL)
GROUP BY CLIENT_IP
ORDER BY gesamt_counter DESC
";
break;
case 'node1':
$sql = "
SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE`
FROM logs
WHERE `SERVER_IP` = 'node1'
AND (WEBSITE != 'backend.mrhossa.de' OR WEBSITE IS NULL)
ORDER BY `FIRST_REQUEST` DESC
";
break;
case 'node2':
$sql = "
SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE`
FROM logs
WHERE `SERVER_IP` = 'node2'
AND (WEBSITE != 'backend.mrhossa.de' OR WEBSITE IS NULL)
ORDER BY `FIRST_REQUEST` DESC
";
break;
case 'node3':
$sql = "
SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE`
FROM logs
WHERE `SERVER_IP` = 'node3'
AND (WEBSITE != 'backend.mrhossa.de' OR WEBSITE IS NULL)
ORDER BY `FIRST_REQUEST` DESC
";
break;
case 'node4':
$sql = "
SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE`
FROM logs
WHERE `SERVER_IP` = 'node4'
AND (WEBSITE != 'backend.mrhossa.de' OR WEBSITE IS NULL)
ORDER BY `FIRST_REQUEST` DESC
";
break;
default:
$sql = "
SELECT `CLIENT_IP`, `LAND`, `REQUEST_URL`, `COUNTER`, `FIRST_REQUEST`, `WEBSITE`
FROM logs
WHERE CLIENT_IP NOT IN ($devips_sql)
AND (WEBSITE != 'backend.mrhossa.de' OR WEBSITE IS NULL)
ORDER BY FIRST_REQUEST DESC
";
}
}
?>
<h1>📊 Logs</h1>
<form method="GET" action="index.php">
<input type="hidden" name="service" value="<?= htmlspecialchars($_GET['service'] ?? 'logs') ?>">
<select name="filter" onchange="this.form.submit()">
<?php
$options = ['alle', 'today', 'node1', 'node2', 'node3', 'node4', 'counterperip'];
foreach ($options as $opt) {
$sel = ($filter === $opt) ? 'selected' : '';
echo "<option value='$opt' $sel>$opt</option>";
}
?>
</select>
</form>
<!-- <br>
<form method="POST">
<textarea name="custom_query" style="width:100%;height:60px;"><?= htmlspecialchars($customQuery) ?></textarea>
<br>
<button type="submit">Query ausführen</button>
</form>
<hr> -->
<br>
<?php
$daten = [];
if ($erg = $conn->query($sql)) {
// echo "<pre style='background:var(--bg-soft,#f4f4f4);padding:10px;'>$sql</pre>";
while ($row = $erg->fetch_object()) {
$daten[] = $row;
}
}
?>
<?php if (!$daten): ?>
<b>Keine Daten gefunden</b>
<?php else: ?>
<div style="overflow-x:auto;">
<table border="1" cellpadding="5" cellspacing="0" style="border-collapse:collapse;width:100%;">
<tr>
<?php foreach ($daten[0] as $key => $_): ?>
<th><?= htmlspecialchars($key) ?></th>
<?php endforeach; ?>
</tr>
<?php foreach ($daten as $row): ?>
<tr>
<?php foreach ($row as $val): ?>
<td><?= htmlspecialchars($val ?? '') ?></td>
<?php endforeach; ?>
</tr>
<?php endforeach; ?>
</table>
</div>
<?php endif; ?>
+321
View File
@@ -0,0 +1,321 @@
<?php
include_once(__DIR__ . '/../controller/RightsController.php');
include_once(__DIR__ . '/../config.php');
$configFile = 'config.php';
$backupDir = 'config_backups';
if (!is_dir($backupDir)) {
mkdir($backupDir, 0755, true);
}
require $configFile;
if (isset($_POST['password'])) {
if (isset($superuser['admin']['password']) && $_POST['password'] === $superuser['admin']['password']) {
$_SESSION['logged_in'] = true;
} else {
$error = "Falsches Passwort!";
}
}
if (isset($_GET['logout'])) {
session_destroy();
header("Location: " . $_SERVER['PHP_SELF']);
exit;
}
if (isset($_POST['logout'])) {
unset($_SESSION['logged_in']);
header("Location: " . (new getSpecificData())->getReferer());
exit;
}
if (isset($_POST['save']) && ($_SESSION['logged_in'] ?? false)) {
copy($configFile, $backupDir . '/config_' . date('Y-m-d_H-i-s') . '.php');
$newServices = [];
$serviceToGroupMapping = [];
if(isset($_POST['services'])){
foreach ($_POST['services'] as $id => $s) {
$finalId = !empty($s['new_id']) ? trim($s['new_id']) : $id;
if (empty($finalId)) continue;
$newServices[$finalId] = [
'name' => $s['name'],
'active' => isset($s['active']),
'adminOnly' => isset($s['adminOnly'])
];
if(!empty($s['targetGroup'])) {
$serviceToGroupMapping[$s['targetGroup']][] = $finalId;
}
}
(new RightsController())->RightsHandler($s['name']);
}
$newServiceGroups = [];
if(isset($_POST['serviceGroups'])){
foreach ($_POST['serviceGroups'] as $id => $groupData) {
$finalId = !empty($groupData['new_id']) ? trim($groupData['new_id']) : $id;
if (empty($finalId)) continue;
$currentServices = $serviceToGroupMapping[$finalId] ?? ($serviceGroups[$id]['services'] ?? []);
$newServiceGroups[$finalId] = [
'label' => $groupData['label'],
'defaultOpen' => isset($groupData['defaultOpen']),
'services' => array_values(array_unique($currentServices))
];
}
}
$newLinkGroups = [];
if(isset($_POST['linkGroups'])){
foreach ($_POST['linkGroups'] as $id => $lg) {
$finalId = !empty($lg['new_id']) ? trim($lg['new_id']) : $id;
if (empty($finalId)) continue;
$newLinkGroups[$finalId] = [
'label' => $lg['label'],
'color' => $lg['color'],
'defaultOpen' => isset($lg['defaultOpen'])
];
}
}
$newLinkButtons = [];
if(isset($_POST['linkButtons'])){
foreach ($_POST['linkButtons'] as $btn) {
if (empty($btn['label'])) continue;
$newLinkButtons[] = [
'label' => $btn['label'],
'url' => $btn['url'],
'newTab' => isset($btn['newTab']),
'adminOnly' => isset($btn['adminOnly']),
'group' => $btn['group']
];
}
}
$content = "<?php\n\n";
$content .= '$superuser = ' . var_export($superuser, true) . ";\n\n";
$content .= '$serviceGroups = ' . var_export($newServiceGroups, true) . ";\n\n";
$content .= '$services = ' . var_export($newServices, true) . ";\n\n";
$content .= '$linkGroups = ' . var_export($newLinkGroups, true) . ";\n\n";
$content .= '$linkButtons = ' . var_export($newLinkButtons, true) . ";\n";
if (file_put_contents($configFile, $content)) {
if (function_exists('opcache_invalidate')) { @opcache_invalidate($configFile, true); }
$_SESSION['status_message'] = "✅ Konfiguration erfolgreich aktualisiert!";
header("Location: " . $_SERVER['PHP_SELF']);
exit;
}
}
$message = $_SESSION['status_message'] ?? null;
unset($_SESSION['status_message']);
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Admin Site Control</title>
<link rel="stylesheet" href="style.css">
</head>
<body class="<?= ($_SESSION['dark_mode'] ?? false) ? 'dark-mode' : '' ?>">
<div class="content">
<?php if (!($_SESSION['logged_in'] ?? false)): ?>
<div class="login-container">
<div class="login-box">
<form method="post">
<h2>🔐 Login</h2>
<?php if(isset($error)): ?>
<div class="status-msg" style="background:#dc3545; color:#fff;"><?= htmlspecialchars($error) ?>
</div>
<?php endif; ?>
<input type="password" name="password" placeholder="Passwort" required>
<button type="submit">Einloggen</button>
</form>
</div>
</div>
<?php else: ?>
<div class="content">
<h2>Seitenverwaltung</h2>
<form method="post">
<button type="submit" name="logout" id="logoutSiteButton">Logout</button>
</form>
<?php if($message): ?><div class="status-msg"><?= htmlspecialchars($message) ?></div><?php endif; ?>
<form method="post">
<!-- Sidebar Gruppen -->
<h2>📦 Sidebar Gruppen
<button type="button" class="btn-add" onclick="addNew('serviceGroup')">+ Neue Gruppe</button>
</h2>
<div class="admin-grid" id="container-serviceGroup">
<?php foreach ($serviceGroups as $id => $group): ?>
<div class="admin-card">
<span class="badge">ID: <?= htmlspecialchars($id) ?></span>
<input type="text" name="serviceGroups[<?= $id ?>][label]"
value="<?= htmlspecialchars($group['label']) ?>" placeholder="Anzeigename">
<label>
<input type="checkbox" name="serviceGroups[<?= $id ?>][defaultOpen]"
<?= ($group['defaultOpen'] ?? false) ? 'checked' : '' ?>>
Offen
</label>
</div>
<?php endforeach; ?>
</div>
<!-- Sidebar Services -->
<h2>🖥 Sidebar Services
<button type="button" class="btn-add" onclick="addNew('service')">+ Neuer Service</button>
</h2>
<div class="admin-grid" id="container-service">
<?php foreach ($services as $id => $s): ?>
<div class="admin-card">
<span class="badge">Datei: <?= htmlspecialchars($id) ?>.php</span>
<input type="text" name="services[<?= $id ?>][name]" value="<?= htmlspecialchars($s['name']) ?>"
placeholder="Name">
<label>Sidebar-Gruppe:</label>
<select name="services[<?= $id ?>][targetGroup]">
<?php foreach ($serviceGroups as $gid => $g): ?>
<option value="<?= $gid ?>"
<?= (isset($g['services']) && in_array($id, $g['services'])) ? 'selected' : '' ?>>
<?= htmlspecialchars($g['label']) ?>
</option>
<?php endforeach; ?>
</select>
<label>
<input type="checkbox" name="services[<?= $id ?>][active]"
<?= ($s['active'] ?? false) ? 'checked' : '' ?>>
Aktiv
</label>
|
<label>
<input type="checkbox" name="services[<?= $id ?>][adminOnly]"
<?= ($s['adminOnly'] ?? false) ? 'checked' : '' ?>>
Nur Admins
</label>
</div>
<?php endforeach; ?>
</div>
<!-- Dashboard Kategorien -->
<h2>🎨 Dashboard Kategorien
<button type="button" class="btn-add" onclick="addNew('linkGroup')">+ Neue Kategorie</button>
</h2>
<div class="admin-grid" id="container-linkGroup">
<?php foreach ($linkGroups as $id => $lg): ?>
<div class="admin-card">
<span class="badge">ID: <?= htmlspecialchars($id) ?></span>
<input type="text" name="linkGroups[<?= $id ?>][label]"
value="<?= htmlspecialchars($lg['label']) ?>" placeholder="Gruppen-Label">
<label>Farbe:</label>
<input type="color" name="linkGroups[<?= $id ?>][color]"
value="<?= $lg['color'] ?? '#3498db' ?>">
<label>
<input type="checkbox" name="linkGroups[<?= $id ?>][defaultOpen]"
<?= ($lg['defaultOpen'] ?? false) ? 'checked' : '' ?>>
Offen
</label>
</div>
<?php endforeach; ?>
</div>
<!-- Dashboard Kacheln -->
<h2>🔘 Dashboard Kacheln
<button type="button" class="btn-add" onclick="addNew('linkButton')">+ Neue Kachel</button>
</h2>
<div class="admin-grid" id="container-linkButton">
<?php foreach ($linkButtons as $key => $btn): ?>
<div class="admin-card">
<input type="text" name="linkButtons[<?= $key ?>][label]"
value="<?= htmlspecialchars($btn['label']) ?>" placeholder="Name">
<input type="text" name="linkButtons[<?= $key ?>][url]"
value="<?= htmlspecialchars($btn['url']) ?>" placeholder="URL">
<label>Dashboard Kategorie:</label>
<select name="linkButtons[<?= $key ?>][group]">
<?php foreach ($linkGroups as $id => $lg): ?>
<option value="<?= $id ?>" <?= ($btn['group'] == $id) ? 'selected' : '' ?>>
<?= htmlspecialchars($lg['label']) ?>
</option>
<?php endforeach; ?>
</select>
<div style="margin-top:5px;">
<label>
<input type="checkbox" name="linkButtons[<?= $key ?>][newTab]"
<?= ($btn['newTab'] ?? false) ? 'checked' : '' ?>>
Neuer Tab
</label>
|
<label>
<input type="checkbox" name="linkButtons[<?= $key ?>][adminOnly]"
<?= ($btn['adminOnly'] ?? false) ? 'checked' : '' ?>>
Nur Admins
</label>
</div>
</div>
<?php endforeach; ?>
</div>
<div class="save-bar">
<button type="submit" name="save" class="btn-save">💾 Gesamte Konfiguration speichern</button>
</div>
</form>
</div>
<?php endif; ?>
</div>
<script>
function addNew(type) {
const container = document.getElementById('container-' + type);
const tempId = 'new_' + Date.now();
const card = document.createElement('div');
card.className = 'admin-card new-item';
let html = `<div><span class="badge">Neu hinzufügen</span></div>`;
if (type === 'serviceGroup') {
html +=
`<input type="text" name="serviceGroups[${tempId}][new_id]" placeholder="ID">
<input type="text" name="serviceGroups[${tempId}][label]" placeholder="Name">
<label><input type="checkbox" name="serviceGroups[${tempId}][defaultOpen]" checked> Ausgeklappt</label>`;
} else if (type === 'service') {
html += `<input type="text" name="services[${tempId}][new_id]" placeholder="Datei ohne .php">
<input type="text" name="services[${tempId}][name]" placeholder="Anzeigename">
<label>Gruppe:</label>
<select name="services[${tempId}][targetGroup]">
<?php foreach ($serviceGroups as $gid => $gl): ?>
<option value="<?= $gid ?>"><?= $gl['label'] ?></option>
<?php endforeach; ?>
</select>
<label><input type="checkbox" name="services[${tempId}][active]" checked> Aktiv</label>`;
} else if (type === 'linkGroup') {
html += `<input type="text" name="linkGroups[${tempId}][new_id]" placeholder="ID">
<input type="text" name="linkGroups[${tempId}][label]" placeholder="Label">
<input type="color" name="linkGroups[${tempId}][color]" value="#3498db">`;
} else if (type === 'linkButton') {
html += `<input type="text" name="linkButtons[${tempId}][label]" placeholder="Anzeigename">
<input type="text" name="linkButtons[${tempId}][url]" placeholder="https://...">
<label>Kategorie:</label>
<select name="linkButtons[${tempId}][group]">
<?php foreach ($linkGroups as $gid => $gl): ?>
<option value="<?= $gid ?>"><?= $gl['label'] ?></option>
<?php endforeach; ?>
</select>
<label><input type="checkbox" name="linkButtons[${tempId}][newTab]" checked> Tab</label>
| <label><input type="checkbox" name="linkButtons[${tempId}][adminOnly]"> Nur Admins</label>`;
}
card.innerHTML = html;
container.appendChild(card);
card.scrollIntoView({
behavior: 'smooth'
});
}
</script>
</body>
</html>
+54
View File
@@ -0,0 +1,54 @@
<?php
$tables = DatabaseShowTables();
$selectedTable = $_GET['table'] ?? null;
?>
<h2>Datenbank Struktur</h2>
<form method="GET">
<input type="hidden" name="service" value="tabellen">
<label for="table">Tabelle auswählen:</label>
<select name="table" id="table" onchange="this.form.submit()">
<option value="">-- Bitte wählen --</option>
<?php foreach ($tables as $table): ?>
<option value="<?= htmlspecialchars($table) ?>" <?= ($selectedTable === $table) ? 'selected' : '' ?>>
<?= htmlspecialchars($table) ?>
</option>
<?php endforeach; ?>
</select>
</form>
<?php
if ($selectedTable && in_array($selectedTable, $tables)) {
$structure = DatabaseShowTableStructure($selectedTable);
echo "<div class='table-container'>";
echo "<table class='db-table'>";
echo "<tr>
<th>Field</th>
<th>Type</th>
<th>Null</th>
<th>Key</th>
<th>Default</th>
<th>Extra</th>
</tr>";
foreach ($structure as $column) {
echo "<tr>";
echo "<td>" . ($column['Field']) . "</td>";
echo "<td>" . ($column['Type']) . "</td>";
echo "<td>" . ($column['Null']) . "</td>";
echo "<td>" . ($column['Key']) . "</td>";
echo "<td>" . ($column['Default']) . "</td>";
echo "<td>" . ($column['Extra']) . "</td>";
echo "</tr>";
}
echo "</table>";
echo "</div>";
}
?>
+227
View File
@@ -0,0 +1,227 @@
<?php
include_once(__DIR__ . '/../controller/DatabaseController.php');
include_once(__DIR__ . '/../controller/DataController.php');
$msg = "";
$username = $_SESSION['username'] ?? 'Gast';
if (isset($_GET['action']) && $_GET['action'] === 'delete') {
if (isset($_GET['serien_id']) && !empty($_GET['serien_id'])) {
$serienId = $_GET['serien_id'];
DatabaseDelete("Termine", "SERIEN_ID = ?", $serienId);
$msg = "Komplette Terminserie erfolgreich gelöscht!";
} elseif (isset($_GET['id'])) {
$deleteId = (int)$_GET['id'];
DatabaseDelete("Termine", "ID = ?", $deleteId);
$msg = "Termin erfolgreich gelöscht!";
}
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_termin'])) {
$datum = $_POST['datum'] ?? '';
$zeit = $_POST['zeit'] ?? '';
$name = $_POST['name'] ?? '';
$notiz = $_POST['notiz'] ?? '';
$wiederholung = $_POST['wiederholung'] ?? 'none';
$enddatum = $_POST['enddatum'] ?? '';
if (!empty($datum) && !empty($zeit) && !empty($name)) {
if ($wiederholung !== 'none' && !empty($enddatum)) {
$currentDate = new DateTime($datum);
$endDate = new DateTime($enddatum);
if ($endDate < $currentDate) {
$msg = "Das Enddatum darf nicht vor dem Startdatum liegen.";
} else {
$insertedCount = 0;
$serienId = uniqid('serie_', true);
$intervalMapping = [
'daily' => '+1 day',
'weekly' => '+1 week',
'monthly' => '+1 month'
];
$modifier = $intervalMapping[$wiederholung] ?? null;
if ($modifier) {
while ($currentDate <= $endDate) {
DatabaseInsert(
"Termine",
"DATUM, ZEIT, NAME, NOTIZ, ERSTELLT_VON, SERIEN_ID, INTERVALL, SERIEN_ENDE",
$currentDate->format('Y-m-d'),
$zeit,
$name,
$notiz,
$username,
$serienId,
$wiederholung,
$enddatum
);
$insertedCount++;
$currentDate->modify($modifier);
}
$msg = "Serientermin ($insertedCount Termine) erfolgreich eingetragen!";
}
}
} else {
DatabaseInsert(
"Termine",
"DATUM, ZEIT, NAME, NOTIZ, ERSTELLT_VON",
$datum,
$zeit,
$name,
$notiz,
$username
);
$msg = "Termin erfolgreich eingetragen!";
}
} else {
$msg = "Bitte alle Pflichtfelder ausfüllen.";
}
}
$termine = DatabaseShow(
"Termine",
"MAX(ID) as ID, MIN(DATUM) as DATUM, ZEIT, NAME, NOTIZ, ERSTELLT_VON, SERIEN_ID, INTERVALL, SERIEN_ENDE",
"ERSTELLT_VON = ? GROUP BY IFNULL(SERIEN_ID, ID) ORDER BY DATUM ASC, ZEIT ASC",
$username
);
$intervallNamen = [
'daily' => 'Täglich',
'weekly' => 'Wöchentlich',
'monthly' => 'Monatlich'
];
?>
<?php if (!empty($msg)): ?>
<div class="status-msg"><?= htmlspecialchars($msg) ?></div>
<?php endif; ?>
<div class="form-container">
<h3>📅 Neuen Termin vergeben</h3>
<form method="POST" class="vergabe-form">
<div class="vergabe-grid">
<div class="form-group">
<label for="datum">Start-Datum *</label>
<input type="date" id="datum" name="datum" required>
</div>
<div class="form-group">
<label for="zeit">Uhrzeit *</label>
<input type="time" id="zeit" name="zeit" required
style="width:100%; padding:10px; border-radius:5px; border:1px solid var(--input-border); background:var(--input-bg); color:var(--input-text);">
</div>
<div class="form-group">
<label for="name">Name / Zweck *</label>
<input type="text" id="name" name="name" placeholder="z.B. Kundentermin" required>
</div>
<div class="form-group">
<label for="notiz">Notiz (Optional)</label>
<input type="text" id="notiz" name="notiz" placeholder="Details zum Termin...">
</div>
<div class="form-group">
<label for="wiederholung">Wiederholung</label>
<select id="wiederholung" name="wiederholung" onchange="toggleEnddatum()"
style="width:100%; padding:10px; border-radius:5px; border:1px solid var(--input-border); background:var(--input-bg); color:var(--input-text);">
<option value="none">Keine Wiederholung</option>
<option value="daily">Täglich</option>
<option value="weekly">Wöchentlich</option>
<option value="monthly">Monatlich</option>
</select>
</div>
<div class="form-group" id="enddatum-group" style="display: none;">
<label for="enddatum">Wiederholen bis *</label>
<input type="date" id="enddatum" name="enddatum">
</div>
<button type="submit" name="add_termin" class="btn-submit-vergabe">Speichern</button>
</div>
</form>
</div>
<div class="form-container">
<h3>📋 Aktuelle Terminbelegungen</h3>
<div class="table-container">
<table class="db-table mobile-responsive-table">
<thead>
<tr>
<th>Datum / Turnus</th>
<th>Uhrzeit</th>
<th>Termin / Name</th>
<th>Notiz</th>
<th>Eingetragen von</th>
<th>Aktion</th>
</tr>
</thead>
<tbody>
<?php if (empty($termine)): ?>
<tr>
<td colspan="6" style="text-align: center; padding: 20px;">Keine Termine eingetragen.</td>
</tr>
<?php else: ?>
<?php foreach ($termine as $termin): ?>
<?php
$dateFormatted = date('d.m.Y', strtotime($termin['DATUM']));
$timeFormatted = date('H:i', strtotime($termin['ZEIT']));
$istSerie = !empty($termin['SERIEN_ID']);
?>
<tr>
<td data-label="Datum:">
<strong><?= htmlspecialchars($dateFormatted) ?></strong>
<?php if ($istSerie): ?>
<br>
<small style="color: var(--primary-color, #007bff); font-style: italic;">
(<?= $intervallNamen[$termin['INTERVALL']] ?? '' ?>
bis <?= date('d.m.Y', strtotime($termin['SERIEN_ENDE'])) ?>)
</small>
<?php endif; ?>
</td>
<td data-label="Uhrzeit:"><?= htmlspecialchars($timeFormatted) ?></td>
<td data-label="Termin / Name:">
<strong><?= htmlspecialchars($termin['NAME']) ?></strong>
<?php if ($istSerie): ?>
<span
style="font-size: 0.8em; background: #e1f5fe; color: #0288d1; padding: 2px 6px; border-radius: 10px; margin-left: 5px;">Wiederholend</span>
<?php endif; ?>
</td>
<td data-label="Notiz:"><?= htmlspecialchars($termin['NOTIZ'] ?: '-') ?></td>
<td data-label="Von:"><span class="badge"><?= htmlspecialchars($termin['ERSTELLT_VON']) ?></span>
</td>
<td data-label="Aktion:">
<?php if ($istSerie): ?>
<a href="?service=termine&action=delete&serien_id=<?= urlencode($termin['SERIEN_ID']) ?>"
onclick="return confirm('Möchtest du die GESAMTE Terminserie löschen?');">
<button class="btn-delete" style="background-color: #d32f2f;">löschen</button>
</a>
<?php else: ?>
<a href="?service=termine&action=delete&id=<?= $termin['ID'] ?>"
onclick="return confirm('Möchtest du diesen Termin wirklich löschen?');">
<button class="btn-delete">Löschen</button>
</a>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<script>
function toggleEnddatum() {
var wiederholung = document.getElementById('wiederholung').value;
var enddatumGroup = document.getElementById('enddatum-group');
var enddatumInput = document.getElementById('enddatum');
if (wiederholung !== 'none') {
enddatumGroup.style.display = 'block';
enddatumInput.required = true;
} else {
enddatumGroup.style.display = 'none';
enddatumInput.required = false;
enddatumInput.value = '';
}
}
</script>
@@ -1,10 +1,78 @@
<?php
// 1. Logik zum Verarbeiten des Formulars (bevor die Liste geladen wird)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_user'])) {
// Nur Admins dürfen Benutzer hinzufügen (Sicherheitscheck)
if ((new getSpecificData())->getIsAdmin()) {
$controller = new UserController();
$controller->setUser(
$_POST['name'],
$_POST['nachname'],
$_POST['email'],
$_POST['passwort'],
$_POST['rolle'],
$_POST['admin'] ?? 0, // Falls Checkbox nicht gesetzt, 0 senden
$_POST['home_ip']
);
// Optional: Seite neu laden, um die Liste zu aktualisieren
header("Location: " . (new getSpecificData())->getReferer());
exit;
}
}
if (isset($_POST['password'])) {
if (isset($superuser['admin']['password']) && $_POST['password'] === $superuser['admin']['password']) {
$_SESSION['logged_in'] = true;
} else {
$error = "Falsches Passwort!";
}
}
// 2. Daten für die Tabelle holen (dein bestehender Code)
$specificData = new getSpecificData();
$isAdmin = $specificData->getIsAdmin();
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()
);
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_user'])) {
if ((new getSpecificData())->getIsAdmin()) {
$controller = new UserController();
$controller->DeleteUser(
$_POST['delete_name'],
$_POST['delete_nachname'],
$_POST['delete_email']
);
header("Location: " . (new getSpecificData())->getReferer());
exit;
}
}
if (isset($_POST['logout'])) {
unset($_SESSION['logged_in']);
header("Location: " . (new getSpecificData())->getReferer());
exit;
}
?>
<div class="content">
<?php if (!$loggedIn): ?>
<?php if (!($_SESSION['logged_in'] ?? false)): ?>
<div class="login-container">
<div class="login-box">
<form method="post">
<h2>🔐 Login</h2>
<?php if($error): ?>
<?php if(isset($error)): ?>
<div class="status-msg" style="background:#dc3545; color:#fff;"><?= htmlspecialchars($error) ?>
</div>
<?php endif; ?>
@@ -88,3 +156,7 @@
</div>
</div>
<?php endif; ?>
<script>
</script>
@@ -1,3 +1,29 @@
<?php
if (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"
);
?>
<div class="admin-grid">
<?php foreach ($variablen as $var): ?>
@@ -26,4 +52,4 @@
</div>
<?php endforeach; ?>
</div>
</div>
+299
View File
@@ -0,0 +1,299 @@
<?php
include_once __DIR__ . '/../controller/DatabaseController.php';
include_once __DIR__ . '/../controller/RoutesController.php';
include_once __DIR__ . '/../modules/ConsoleLog.php';
$conn = db_connect();
$view = $_GET['view'] ?? 'show';
$selectedService = $_GET['service'] ?? null;
// --- Suchfilter auslesen ---
$searchWer = $_GET['search_wer'] ?? '';
$searchWas = $_GET['search_was'] ?? '';
// --- LOGIK: Überstunden-Eintrag (Abbau) speichern ---
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_entry'])) {
$was = $_POST['was'];
$wer = $_POST['wer'];
$wann = $_POST['wann'];
$rueckgabe = $_POST['rueckgabe'];
DatabaseInsert(
"Vergabe",
"WAS, WER, WANN, RUECKGABE",
$was,
$wer,
$wann,
$rueckgabe
);
redirectToWebsite((new getSpecificData())->getReferer());
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_entry'])) {
$id = $_POST['id'];
ConsoleLog("$id");
DatabaseUpdate(
"Vergabe",
"STATUS = ?",
"ID = ?",
1,
$id
);
redirectToWebsite((new getSpecificData())->getReferer());
exit;
}
// --- Dynamic Query mit kombiniertem Suchfilter ---
$whereClause = "STATUS IS NULL";
if (!empty($searchWer)) {
$whereClause .= " AND WER LIKE '%" . $conn->real_escape_string($searchWer) . "%'";
}
if (!empty($searchWas)) {
$whereClause .= " AND WAS LIKE '%" . $conn->real_escape_string($searchWas) . "%'";
}
$entries = DatabaseShow(
"Vergabe",
"ID, WAS, WER, WANN, RUECKGABE",
$whereClause
);
$searchEntries = DatabaseShow(
"Vergabe",
);
$existingWas = [];
$existingWer = [];
foreach ($searchEntries as $entry) {
if (!empty($entry['WAS'])) $existingWas[$entry['WAS']] = true;
if (!empty($entry['WER'])) $existingWer[$entry['WER']] = true;
}
$existingWas = array_keys($existingWas);
$existingWer = array_keys($existingWer);
?>
<div class="arbeitsberichte-wrapper">
<div class="vergabe-form">
<form method="POST" class="vergabe-grid">
<div class="form-group">
<label>Wann ausgeliehen</label>
<input type="date" name="wann" value="<?= date('Y-m-d') ?>" required>
</div>
<div class="form-group">
<label>Rückgabe</label>
<input type="date" name="rueckgabe" value="<?= date('Y-m-d') ?>" required>
</div>
<div class="form-group autocomplete-wrapper">
<label>An wen</label>
<input type="text" name="wer" id="wer-input" placeholder="Name eingeben" required autocomplete="off">
<div class="autocomplete-suggestions" id="wer-suggestions"></div>
</div>
<div class="form-group autocomplete-wrapper">
<label>Was</label>
<input type="text" name="was" id="was-input" placeholder="Gegenstand eingeben" required autocomplete="off">
<div class="autocomplete-suggestions" id="was-suggestions"></div>
</div>
<button type="submit" name="add_entry" class="btn-submit-vergabe">
Speichern
</button>
</form>
</div>
<div class="vergabe-form" style="margin-top: 30px;">
<form method="GET" action="" class="vergabe-grid" id="search-form">
<input type="hidden" name="view" value="<?= htmlspecialchars($view) ?>">
<?php if ($selectedService): ?>
<input type="hidden" name="service" value="<?= htmlspecialchars($selectedService) ?>">
<?php endif; ?>
<div class="form-group autocomplete-wrapper">
<label for="search-wer-input">Nach Person filtern</label>
<input type="text" name="search_wer" id="search-wer-input" value="<?= htmlspecialchars($searchWer) ?>" placeholder="Name eingeben..." autocomplete="off">
<div class="autocomplete-suggestions" id="search-wer-suggestions"></div>
</div>
<div class="form-group autocomplete-wrapper">
<label for="search-was-input">Nach Gegenstand filtern</label>
<input type="text" name="search_was" id="search-was-input" value="<?= htmlspecialchars($searchWas) ?>" placeholder="Gegenstand eingeben..." autocomplete="off">
<div class="autocomplete-suggestions" id="search-was-suggestions"></div>
</div>
<div style="display: flex; gap: 10px; align-items: flex-end;">
<button type="submit" class="btn-submit-vergabe" style="margin: 0; flex-grow: 1;">
Suchen
</button>
<?php if (!empty($searchWer) || !empty($searchWas)): ?>
<a href="?view=<?= urlencode($view) ?><?= $selectedService ? '&service=' . urlencode($selectedService) : '' ?>" class="btn-delete" style="text-decoration: none; padding: 10px; display: inline-block; text-align: center; white-space: nowrap; margin: 0;">
Zurücksetzen
</a>
<?php endif; ?>
</div>
</form>
</div>
<div class="table-container" style="margin-top: 30px;">
<h3>
<?php
if (!empty($searchWer) && !empty($searchWas)) {
echo "Gefiltert nach: " . htmlspecialchars($searchWer) . " ➔ " . htmlspecialchars($searchWas);
} elseif (!empty($searchWer)) {
echo "Vergaben für: " . htmlspecialchars($searchWer);
} elseif (!empty($searchWas)) {
echo "Ausgeliehene Gegenstände: " . htmlspecialchars($searchWas);
} else {
echo "Aktuelle Vergaben";
}
?>
</h3>
<?php if (!empty($entries)): ?>
<table class="db-table mobile-responsive-table">
<thead>
<tr>
<th>Was</th>
<th>Wer</th>
<th>Wann ausgeliehen</th>
<th>Rückgabe</th>
<th style="width: 100px;">Aktion</th>
</tr>
</thead>
<tbody>
<?php foreach ($entries as $entry): ?>
<tr>
<td data-label="Was"><?= htmlspecialchars($entry['WAS'] ?? '') ?></td>
<td data-label="Wer"><?= htmlspecialchars($entry['WER'] ?? '') ?></td>
<td data-label="Wann"><?= htmlspecialchars($entry['WANN'] ?? '') ?></td>
<td data-label="Rückgabe"><?= htmlspecialchars($entry['RUECKGABE'] ?? '') ?></td>
<td data-label="Aktion">
<form method="POST" onsubmit="return confirm('Möchtest du diesen Eintrag wirklich löschen?');" style="margin: 0;">
<input type="hidden" name="id" value="<?= $entry['ID'] ?? '' ?>">
<button type="submit" name="delete_entry" class="btn-delete">
Löschen
</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php else: ?>
<p>Keine Einträge vorhanden.</p>
<?php endif; ?>
</div>
</div>
<script>
// Parameter `submitOnSelect` hinzugefügt (Standard: false)
function setupAutocomplete(inputId, suggestionsId, data, submitOnSelect = false) {
const input = document.getElementById(inputId);
const box = document.getElementById(suggestionsId);
if (!input || !box) return;
let items = [];
let activeIndex = -1;
function render(list) {
box.innerHTML = '';
items = list;
activeIndex = -1;
if (list.length === 0) {
box.style.display = 'none';
return;
}
list.forEach((val, i) => {
const div = document.createElement('div');
div.textContent = val;
div.addEventListener('mousedown', (e) => {
e.preventDefault();
selectItem(i);
});
box.appendChild(div);
});
box.style.display = 'block';
}
function updateActive() {
[...box.children].forEach((el, i) => {
el.classList.toggle('active', i === activeIndex);
});
if (activeIndex >= 0) {
box.children[activeIndex].scrollIntoView({
block: 'nearest'
});
}
}
function selectItem(i) {
if (items[i] !== undefined) {
input.value = items[i];
}
box.style.display = 'none';
activeIndex = -1;
// NEU: Wenn gewünscht, schicke das übergeordnete Formular sofort ab
if (submitOnSelect) {
const form = input.closest('form');
if (form) form.submit();
}
}
input.addEventListener('input', () => {
const val = input.value.toLowerCase();
if (val.length === 0) {
box.style.display = 'none';
return;
}
const filtered = data.filter(d => d.toLowerCase().includes(val));
render(filtered);
});
input.addEventListener('keydown', (e) => {
if (box.style.display !== 'block') return;
if (e.key === 'ArrowDown') {
e.preventDefault();
activeIndex = Math.min(activeIndex + 1, items.length - 1);
updateActive();
} else if (e.key === 'ArrowUp') {
e.preventDefault();
activeIndex = Math.max(activeIndex - 1, 0);
updateActive();
} else if (e.key === 'Tab' || e.key === 'Enter') {
if (activeIndex >= 0) {
e.preventDefault();
selectItem(activeIndex);
} else {
box.style.display = 'none';
}
} else if (e.key === 'Escape') {
box.style.display = 'none';
}
});
document.addEventListener('click', (e) => {
if (e.target !== input) {
box.style.display = 'none';
}
});
}
// 1. Normales Autocomplete für das Eintragungs-Formular (schickt nicht automatisch ab)
setupAutocomplete('was-input', 'was-suggestions', <?= json_encode($existingWas) ?>);
setupAutocomplete('wer-input', 'wer-suggestions', <?= json_encode($existingWer) ?>);
// 2. Such-Autocomplete: Hier wird als 4. Parameter `true` übergeben, damit sofort abgesendet wird!
setupAutocomplete('search-wer-input', 'search-wer-suggestions', <?= json_encode($existingWer) ?>, true);
setupAutocomplete('search-was-input', 'search-was-suggestions', <?= json_encode($existingWas) ?>, true);
</script>
+103
View File
@@ -0,0 +1,103 @@
<?php
$conn = db_connect();
$sql = "SELECT ZEIT, AKTUELL FROM Wetter";
$result = $conn->query($sql);
$labels = [];
$data = [];
if ($result->num_rows > 0) {
while($row = $result->fetch_assoc()) {
$labels[] = $row['ZEIT'];
$data[] = (float)$row['AKTUELL'];
}
}
$conn->close();
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-zoom"></script>
</head>
<body>
<canvas id="logChart" width="600" height="400"></canvas>
<script>
const ctx = document.getElementById('logChart').getContext('2d');
const logChart = new Chart(ctx, {
type: 'line',
data: {
labels: <?php echo json_encode($labels); ?>,
datasets: [{
label: 'Wetter',
data: <?php echo json_encode($data); ?>,
borderColor: 'rgba(75, 192, 192, 1)',
backgroundColor: 'rgba(75, 192, 192, 0.2)',
fill: false,
tension: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: true,
},
legend: {
display: true,
position: 'top'
},
zoom: {
pan: {
enabled: true,
mode: 'x',
},
zoom: {
wheel: {
enabled: true,
},
mode: 'x',
}
}
},
interaction: {
mode: 'index',
intersect: false
},
scales: {
x: {
title: {
display: true,
text: 'Zeitraum'
}
},
y: {
min: -10,
max: 40,
title: {
display: true,
text: 'Temperatur in °C'
},
ticks: {
stepSize: 1
},
beginAtZero: true
}
}
}
});
</script>
</body>
</html>
@@ -1,3 +1,45 @@
<?php
include_once __DIR__ . '/../controller/DatabaseController.php';
include_once __DIR__ . '/../controller/RoutesController.php';
$conn = db_connect();
$view = $_GET['view'] ?? 'show';
$selectedService = $_GET['service'];
// --- LOGIK: Überstunden-Eintrag (Abbau) speichern ---
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
);
redirectToWebsite((new getSpecificData())->getReferer());
exit;
}
// --- Gesamtsumme berechnen ---
$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;
$res2 = $conn->query("SELECT SUM(MINUTEN) AS summe FROM Ueberstunden WHERE GRUND = 'Aufbau'");
if ($row = $res2->fetch_object()) { $minuten_aufbau = intval($row->summe); }
$gesamt_minuten = $minuten_berichte - $minuten_abbau + $minuten_aufbau;
?>
<div class="arbeitsberichte-wrapper">
<div
style="background: rgba(150,150,150,0.05); padding: 25px; border-radius: 10px; margin-bottom: 25px; border: 1px solid rgba(150,150,150,0.2); border-left: 6px solid #007bff;">
@@ -8,7 +50,7 @@
<?= intdiv($gesamt_minuten, 60) ?>h <?= abs($gesamt_minuten % 60) ?>min
</h2>
</div>
<?php if (strtolower($username) == "bruno"): ?>
<?php if ($_SESSION['username'] == "bruno" || $_SESSION['username'] == "Bruno"): ?>
<form method="POST" style="display: flex; gap: 15px; flex-wrap: wrap; align-items: flex-end;">
<input type="hidden" name="add_overtime" value="1">
<div style="display: flex; flex-direction: column; gap: 5px;">
@@ -23,7 +65,9 @@
</div>
<div class="form-group">
<label style="font-size: 0.85rem; font-weight: 600; color: inherit;">Grund</label>
<select name="ot_reason" required>
<option value="">Bitte wählen</option>
<option value="Abbau">Abbau</option>
<option value="Aufbau">Aufbau</option>
@@ -37,4 +81,4 @@
</form>
<?php endif ?>
</div>
</div>
</div>
@@ -1,227 +0,0 @@
<?php
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();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['add_user']) && $isAdmin) {
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) {
User::delete("NAME = ? AND NACHNAME = ? AND EMAIL = ?", $_POST['delete_name'], $_POST['delete_nachname'], $_POST['delete_email']);
header("Location: " . $specificData->getReferer());
exit;
}
if (isset($_POST['password'])) {
if (isset($superuser['admin']['password']) && $_POST['password'] === $superuser['admin']['password']) {
$_SESSION['logged_in'] = true;
} else {
$error = "Falsches Passwort!";
}
}
if (isset($_POST['logout'])) {
unset($_SESSION['logged_in']);
header("Location: " . $specificData->getReferer());
exit;
}
}
$users = $isAdmin ? User::all() : User::getByName($specificData->getUsername());
return $this->render('user_control', [
'users' => $users,
'isAdmin' => $isAdmin,
'error' => $error ?? null,
'loggedIn' => $_SESSION['logged_in'] ?? false
]);
}
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";
$configFile = 'config.php';
$backupDir = 'config_backups';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['password'])) {
if (isset($superuser['admin']['password']) && $_POST['password'] === $superuser['admin']['password']) {
$_SESSION['logged_in'] = true;
} else {
$error = "Falsches Passwort!";
}
}
if (isset($_POST['logout'])) {
unset($_SESSION['logged_in']);
header("Location: " . (new \getSpecificData())->getReferer());
exit;
}
if (isset($_POST['save']) && ($_SESSION['logged_in'] ?? false)) {
if (!is_dir($backupDir)) mkdir($backupDir, 0755, true);
copy($configFile, $backupDir . '/config_' . date('Y-m-d_H-i-s') . '.php');
$newServices = [];
$serviceToGroupMapping = [];
if(isset($_POST['services'])){
foreach ($_POST['services'] as $id => $s) {
$finalId = !empty($s['new_id']) ? trim($s['new_id']) : $id;
if (empty($finalId)) continue;
$newServices[$finalId] = [
'name' => $s['name'],
'active' => isset($s['active']),
'adminOnly' => isset($s['adminOnly'])
];
if(!empty($s['targetGroup'])) {
$serviceToGroupMapping[$s['targetGroup']][] = $finalId;
}
}
(new \RightsController())->RightsHandler($s['name']);
}
$newServiceGroups = [];
if(isset($_POST['serviceGroups'])){
foreach ($_POST['serviceGroups'] as $id => $groupData) {
$finalId = !empty($groupData['new_id']) ? trim($groupData['new_id']) : $id;
if (empty($finalId)) continue;
$currentServices = $serviceToGroupMapping[$finalId] ?? ($serviceGroups[$id]['services'] ?? []);
$newServiceGroups[$finalId] = [
'label' => $groupData['label'],
'defaultOpen' => isset($groupData['defaultOpen']),
'services' => array_values(array_unique($currentServices))
];
}
}
$newLinkGroups = [];
if(isset($_POST['linkGroups'])){
foreach ($_POST['linkGroups'] as $id => $lg) {
$finalId = !empty($lg['new_id']) ? trim($lg['new_id']) : $id;
if (empty($finalId)) continue;
$newLinkGroups[$finalId] = [
'label' => $lg['label'],
'color' => $lg['color'],
'defaultOpen' => isset($lg['defaultOpen'])
];
}
}
$newLinkButtons = [];
if(isset($_POST['linkButtons'])){
foreach ($_POST['linkButtons'] as $btn) {
if (empty($btn['label'])) continue;
$newLinkButtons[] = [
'label' => $btn['label'],
'url' => $btn['url'],
'newTab' => isset($btn['newTab']),
'adminOnly' => isset($btn['adminOnly']),
'group' => $btn['group']
];
}
}
$content = "<?php\n\n";
$content .= '$superuser = ' . var_export($superuser, true) . ";\n\n";
$content .= '$serviceGroups = ' . var_export($newServiceGroups, true) . ";\n\n";
$content .= '$services = ' . var_export($newServices, true) . ";\n\n";
$content .= '$linkGroups = ' . var_export($newLinkGroups, true) . ";\n\n";
$content .= '$linkButtons = ' . var_export($newLinkButtons, true) . ";\n";
if (file_put_contents($configFile, $content)) {
if (function_exists('opcache_invalidate')) { @opcache_invalidate($configFile, true); }
$_SESSION['status_message'] = "✅ Konfiguration erfolgreich aktualisiert!";
header("Location: " . (new \getSpecificData())->getReferer());
exit;
}
}
}
$message = $_SESSION['status_message'] ?? null;
unset($_SESSION['status_message']);
return $this->render('services_control', [
'serviceGroups' => $serviceGroups,
'services' => $services,
'linkGroups' => $linkGroups,
'linkButtons' => $linkButtons,
'loggedIn' => $_SESSION['logged_in'] ?? false,
'error' => $error ?? null,
'message' => $message
]);
}
public function data_info() {
global $isAdmin;
if (!$isAdmin) return "Access Denied";
$data = new \getSpecificData();
$allData = $data->getAllData();
$groupedData = [];
foreach ($allData as $key => $value) {
if (strpos($key, ' - ') !== false) {
[$group, $field] = explode(' - ', $key, 2);
} else {
$group = 'Other';
$field = $key;
}
$groupedData[$group][$field] = $value;
}
return $this->render('data_info', ['groupedData' => $groupedData]);
}
public function tabellen() {
global $isAdmin;
if (!$isAdmin) return "Access Denied";
$tables = \DatabaseShowTables();
$selectedTable = $_GET['table'] ?? null;
$structure = [];
if ($selectedTable && in_array($selectedTable, $tables)) {
$structure = \DatabaseShowTableStructure($selectedTable);
}
return $this->render('tabellen', [
'tables' => $tables,
'selectedTable' => $selectedTable,
'structure' => $structure
]);
}
}
@@ -1,31 +0,0 @@
<?php
namespace MVC\Controller;
use MVC\Core\Controller;
class ApiController extends Controller {
public function api_tester() {
$apiDir = realpath(__DIR__ . '/../../../api');
$apiOptions = [];
if ($apiDir !== false) {
$iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($apiDir, \RecursiveDirectoryIterator::SKIP_DOTS)
);
foreach ($iterator as $file) {
if (!$file->isFile() || $file->getExtension() !== 'php') continue;
$relativePath = str_replace($apiDir . DIRECTORY_SEPARATOR, '', $file->getPathname());
$relativePath = str_replace('\\', '/', $relativePath);
$apiOptions[] = 'api/' . $relativePath;
}
}
sort($apiOptions);
return $this->render('api_tester', [
'apiOptions' => $apiOptions,
'defaultToken' => "15a437d128c918633e57dc7d18118a073b807644f21731ed5a7ebcf3eca9e242"
]);
}
}
@@ -1,24 +0,0 @@
<?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
]);
}
}
@@ -1,35 +0,0 @@
<?php
namespace MVC\Controller;
use MVC\Core\Controller;
use MVC\Model\Domain;
class DomainController extends Controller {
public function domains() {
$statusMsg = "";
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST["add_domain"])) {
$subdomain = trim($_POST["subdomain"]);
if ($subdomain != "") {
Domain::insert(['SUBDOMAIN' => $subdomain]);
$statusMsg = "Domain wurde hinzugefügt.";
}
}
if (isset($_POST["delete_domain"])) {
Domain::delete("ID = ?", intval($_POST["id"]));
$statusMsg = "Domain wurde gelöscht.";
}
if (isset($_POST["save_domain"])) {
Domain::update(['SUBDOMAIN' => trim($_POST["subdomain"])], "ID = ?", intval($_POST["id"]));
$statusMsg = "Domain wurde gespeichert.";
}
}
return $this->render('domains', [
'domains' => Domain::all(),
'statusMsg' => $statusMsg
]);
}
}
@@ -1,93 +0,0 @@
<?php
namespace MVC\Controller;
use MVC\Core\Controller;
use MVC\Model\Log;
class LogController extends Controller {
protected function getDevIpsSql() {
$devips = (new \getSpecificData())->getWhitelistedIPs();
return "'" . implode("','", $devips) . "'";
}
protected function handleLogs($websiteFilter, $serviceName) {
$devips_sql = $this->getDevIpsSql();
$filter = $_GET['filter'] ?? 'today';
$daten = Log::getFiltered($websiteFilter, $devips_sql, $filter);
return $this->render('logs', [
'daten' => $daten,
'filter' => $filter,
'serviceName' => $serviceName
]);
}
public function public_request_logs() {
return $this->handleLogs("(WEBSITE != 'backend.blackpi.ipv64.net' OR WEBSITE IS NULL)", 'public_request_logs');
}
public function backend_request_logs() {
return $this->handleLogs("(WEBSITE = 'backend.blackpi.ipv64.net')", 'backend_request_logs');
}
public function personal_request_logs() {
return $this->handleLogs("1=1", 'personal_request_logs');
}
public function public_request_chart() {
$totalData = Log::getChartData();
$totalDataAssoc = [];
$allLabels = [];
foreach ($totalData as $row) {
$totalDataAssoc[$row['tag']] = (int)$row['anzahl'];
$allLabels[] = $row['tag'];
}
$nodes = ['node1','node2','node3','node4'];
$finalNodeData = [];
foreach ($nodes as $node) {
$nodeData = Log::getNodeChartData($node);
$nodeAssoc = [];
foreach ($nodeData as $row) {
$nodeAssoc[$row['tag']] = (int)$row['anzahl'];
if (!in_array($row['tag'], $allLabels)) $allLabels[] = $row['tag'];
}
// Re-map to all labels
$finalNodeData[$node] = $nodeAssoc;
}
sort($allLabels);
$finalTotalData = [];
$mappedNodeData = [];
foreach ($allLabels as $label) {
$finalTotalData[] = $totalDataAssoc[$label] ?? 0;
foreach ($nodes as $node) {
$mappedNodeData[$node][] = $finalNodeData[$node][$label] ?? 0;
}
}
return $this->render('public_chart', [
'allLabels' => $allLabels,
'finalTotalData' => $finalTotalData,
'finalNodeData' => $mappedNodeData
]);
}
public function backend_request_chart() {
$data = Log::getChartData("WEBSITE = 'backend.blackpi.ipv64.net'");
$labels = [];
$counts = [];
foreach ($data as $row) {
$labels[] = $row['tag'];
$counts[] = (int)$row['anzahl'];
}
return $this->render('backend_chart', [
'labels' => $labels,
'data' => $counts
]);
}
}
@@ -1,129 +0,0 @@
<?php
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';
if ($mode === 'normal') {
$data = CompanyReport::all();
} else {
$data = SchoolReport::allWithFK();
}
$data = array_reverse($data);
$displayData = (is_array($data) && isset($data['rows'])) ? $data['rows'] : $data;
$count = is_array($displayData) ? count($displayData) : 0;
return $this->render('berichte', [
'mode' => $mode,
'displayData' => $displayData,
'count' => $count,
'selectedService' => $_GET['service'] ?? 'berichte'
]);
}
public function berichte_arbeit_gen() {
if (empty($_SESSION['user_logged_in'])) return "Access Denied";
$wochenArray = CompanyReport::getWochen();
$startDate = $_POST['start_date'] ?? date('Y-m-d', strtotime('-5 days'));
$endDate = $_POST['end_date'] ?? date('Y-m-d');
$berichteArray = CompanyReport::getByDateRange($startDate, $endDate);
$pdfFile = null;
if (isset($_POST['generate_pdf']) && count($berichteArray) > 0) {
$pdfFile = $this->generatePDF($berichteArray);
}
return $this->render('berichte_arbeit_gen', [
'startDate' => $startDate,
'endDate' => $endDate,
'wochenArray' => $wochenArray,
'pdfFile' => $pdfFile,
'berichteArray' => $berichteArray
]);
}
protected function generatePDF($berichteArray) {
$Einzelstunden = "2";
$Gesamtstunden = "8";
$data = [
'Text1' => '', // Nr
'Text2' => $berichteArray[0]['DATUM'] ?? '',
'Text3' => end($berichteArray)['DATUM'] ?? '',
'Text4' => 'Eckhardt',
'Text5' => 'Bruno Renato',
'Text6' => '1',
];
$fieldIndex = 7;
foreach ($berichteArray as $bericht) {
for ($i = 1; $i <= 4; $i++) {
$data["Text$fieldIndex"] = $bericht["PROZESS_$i"] ?? '';
$fieldIndex++;
$data["Text$fieldIndex"] = $Einzelstunden;
$fieldIndex++;
}
$data["Text$fieldIndex"] = $Gesamtstunden;
$fieldIndex++;
}
$fdfFields = array_map(function($field, $value){
$value = str_replace(['(', ')'], ['\(', '\)'], $value);
return "<< /T ($field) /V ($value) >>";
}, array_keys($data), $data);
$fdf = "%FDF-1.2\n1 0 obj\n<<\n/FDF << /Fields [\n" . implode("\n", $fdfFields) . "\n] >> >>\nendobj\ntrailer\n<< /Root 1 0 R >>\n%%EOF";
$fdfFile = tempnam(sys_get_temp_dir(), 'fdf_') . '.fdf';
file_put_contents($fdfFile, $fdf);
$specificData = new \getSpecificData();
$inputPdf = $specificData->getDocumentRoot() . '/templates/ausbildungsnachweis.pdf';
$outputPdf = $specificData->getDocumentRoot() . '/static/ausbildungsnachweis_' . $berichteArray[0]['DATUM'] . '-' . end($berichteArray)['DATUM'] . '.pdf';
exec("pdftk $inputPdf fill_form $fdfFile output $outputPdf flatten");
unlink($fdfFile);
return $outputPdf;
}
public function zeiterfassung() {
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_overtime'])) {
Overtime::insert([
'DATUM' => $_POST['ot_date'],
'MINUTEN' => (int)$_POST['ot_minutes'],
'GRUND' => $_POST['ot_reason']
]);
\redirectToWebsite((new \getSpecificData())->getReferer());
exit;
}
$minuten_berichte = CompanyReport::getTotalOvertime();
$minuten_abbau = Overtime::getSumByReason('Abbau');
$minuten_aufbau = Overtime::getSumByReason('Aufbau');
$gesamt_minuten = $minuten_berichte - $minuten_abbau + $minuten_aufbau;
return $this->render('zeiterfassung', [
'gesamt_minuten' => $gesamt_minuten,
'username' => $_SESSION['username']
]);
}
public function print_static() {
$dir = (new \getSpecificData())->getDocumentRoot() . "/static";
$files = is_dir($dir) ? array_diff(scandir($dir), ['.', '..']) : [];
return $this->render('print_static', ['files' => $files]);
}
}
@@ -1,48 +0,0 @@
<?php
namespace MVC\Controller;
use MVC\Core\Controller;
class ServiceController extends Controller {
public function home_info() {
$username = $_SESSION['username'] ?? 'Gast';
$is_superuser = $_SESSION['is_superuser'] ?? false;
$is_admin = $_SESSION['is_admin'] ?? false;
$db_res = \DatabaseShow(
"Benutzer",
"NACHNAME, EMAIL, ROLLE, HOME_IP",
"NAME = ?",
$username
);
return $this->render('home_info', [
'username' => $username,
'is_superuser' => $is_superuser,
'is_admin' => $is_admin,
'db_res' => $db_res
]);
}
public function links() {
global $linkButtons, $linkGroups, $isAdmin;
return $this->render('links', [
'linkButtons' => $linkButtons,
'linkGroups' => $linkGroups,
'isAdmin' => $isAdmin
]);
}
public function logout() {
session_destroy();
header('Location: login.php');
exit;
}
// Placeholder for other services
public function __call($name, $arguments) {
return "Service $name logic not implemented yet in Controller.";
}
}
@@ -1,24 +0,0 @@
<?php
namespace MVC\Controller;
use MVC\Core\Controller;
use MVC\Model\Domain;
class StatsController extends Controller {
public function weather_chart() {
// Since Weather is not yet a dedicated model, we use the Domain query helper or raw query
$res = Domain::query("SELECT ZEIT, AKTUELL FROM Wetter");
$labels = [];
$data = [];
while($row = $res->fetch_assoc()) {
$labels[] = $row['ZEIT'];
$data[] = (float)$row['AKTUELL'];
}
return $this->render('weather_chart', [
'labels' => $labels,
'data' => $data
]);
}
}
-25
View File
@@ -1,25 +0,0 @@
<?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;
}
}
-61
View File
@@ -1,61 +0,0 @@
<?php
namespace MVC\Core;
abstract class Model {
protected static $table;
protected static $conn;
protected static function getConnection() {
if (!self::$conn) {
self::$conn = \db_connect();
}
return self::$conn;
}
public static function all() {
return \DatabaseShow(static::$table, "*");
}
public static function find($id, $column = 'ID') {
$result = \DatabaseShow(static::$table, "*", "$column = ?", $id);
return $result[0] ?? null;
}
public static function where($where, ...$params) {
return \DatabaseShow(static::$table, "*", $where, ...$params);
}
public static function insert($data) {
$columns = implode(", ", array_keys($data));
$values = array_values($data);
return \DatabaseInsert(static::$table, $columns, ...$values);
}
public static function update($data, $where, ...$whereParams) {
$sets = [];
$values = [];
foreach ($data as $column => $value) {
$sets[] = "$column = ?";
$values[] = $value;
}
$setString = implode(", ", $sets);
return \DatabaseUpdate(static::$table, $setString, $where, ...array_merge($values, $whereParams));
}
public static function delete($where, ...$params) {
return \DatabaseDelete(static::$table, $where, ...$params);
}
public static function query($sql, ...$params) {
$conn = self::getConnection();
if (empty($params)) {
return $conn->query($sql);
}
$stmt = $conn->prepare($sql);
$types = str_repeat('s', count($params));
$stmt->bind_param($types, ...$params);
$stmt->execute();
return $stmt->get_result();
}
}
-59
View File
@@ -1,59 +0,0 @@
<?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;
}
}
}
}
-27
View File
@@ -1,27 +0,0 @@
<?php
namespace MVC\Core;
class View {
protected $data = [];
protected $viewPath;
public function __construct($viewPath) {
$this->viewPath = $viewPath;
}
public function assign($key, $value) {
$this->data[$key] = $value;
}
public function render($template) {
extract($this->data);
$file = $this->viewPath . '/' . $template . '.php';
if (file_exists($file)) {
ob_start();
include $file;
return ob_get_clean();
}
return "View $template not found at $file";
}
}
-30
View File
@@ -1,30 +0,0 @@
<?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 {$this->template} not found.";
}
}
-25
View File
@@ -1,25 +0,0 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= $this->getBlock('title') ?></title>
<link rel="stylesheet" href="style.css">
<?= $this->getBlock('head') ?>
</head>
<body>
<header class="main-header">
<?= $this->getBlock('header') ?>
</header>
<div class="main">
<div class="sidebar">
<?= $this->getBlock('sidebar') ?>
</div>
<div class="content">
<?= $this->getBlock('content') ?>
</div>
</div>
<script src="main.js"></script>
<?= $this->getBlock('scripts') ?>
</body>
</html>
-121
View File
@@ -1,121 +0,0 @@
# 🏗️ Backend MVC - Umfassende Dokumentation
Diese Dokumentation beschreibt die Architektur, die Funktionsweise und die Besonderheiten des neuen Backend-Systems.
---
## 🏛️ 1. Architektur-Übersicht
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.
---
## 🚦 2. Routing & Middleware (Der Verkehrspolizist)
Das Routing wird in der `index.php` über den `Router` konfiguriert.
### Routen registrieren
```php
$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']);
```
### 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><?= htmlspecialchars($user_name) ?></p>
```
---
## 🔍 7. Besonderheiten & Tipps
- **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/`.
---
## 🚀 Modul-Generator
Nutze das Script im Hauptverzeichnis für schnelle Ergebnisse:
```bash
php create_module.php mein_name
```
Dieses erstellt automatisch Controller und View nach den oben genannten Standards.
-25
View File
@@ -1,25 +0,0 @@
<?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
@@ -1,29 +0,0 @@
<?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
@@ -1,9 +0,0 @@
<?php
namespace MVC\Model;
use MVC\Core\Model;
class Domain extends Model {
protected static $table = "Domains";
}
-43
View File
@@ -1,43 +0,0 @@
<?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
@@ -1,14 +0,0 @@
<?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
@@ -1,14 +0,0 @@
<?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
@@ -1,18 +0,0 @@
<?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
@@ -1,22 +0,0 @@
<?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;
}
}
-72
View File
@@ -1,72 +0,0 @@
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-zoom"></script>
<canvas id="logChart" width="600" height="400"></canvas>
<script>
const ctx = document.getElementById('logChart').getContext('2d');
const logChart = new Chart(ctx, {
type: 'line',
data: {
labels: <?php echo json_encode($labels); ?>,
datasets: [{
label: 'Logs pro Tag',
data: <?php echo json_encode($data); ?>,
borderColor: 'rgba(75, 192, 192, 1)',
backgroundColor: 'rgba(75, 192, 192, 0.2)',
fill: true,
tension: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: true,
text: 'Logs pro Tag'
},
legend: {
display: true,
position: 'top'
},
zoom: {
pan: {
enabled: true,
mode: 'x',
},
zoom: {
wheel: {
enabled: true,
},
mode: 'x',
}
}
},
interaction: {
mode: 'index',
intersect: false
},
scales: {
x: {
title: {
display: true,
text: 'Datum'
}
},
y: {
min: 0,
max: 100,
title: {
display: true,
text: 'Anzahl Logs'
},
ticks: {
stepSize: 20
},
beginAtZero: true
}
}
}
});
</script>
-115
View File
@@ -1,115 +0,0 @@
<style>
.debug-table-container {
background: #252525;
border-radius: 8px;
padding: 0;
margin-top: 20px;
overflow-x: auto;
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
}
table {
width: 100%;
border-collapse: collapse;
color: #ccc;
white-space: nowrap;
}
th {
background: #333;
text-align: left;
padding: 15px 12px;
border-bottom: 2px solid #3498db;
color: #3498db;
text-transform: uppercase;
font-size: 11px;
letter-spacing: 1px;
position: sticky;
top: 0;
z-index: 10;
}
td {
padding: 10px 12px;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
font-size: 13px;
}
tr:nth-child(even) {
background: rgba(255, 255, 255, 0.02);
}
tr:hover {
background: rgba(52, 152, 219, 0.1);
color: #fff;
}
td:first-child,
th:first-child {
position: sticky;
left: 0;
background: #252525;
z-index: 5;
border-right: 1px solid rgba(255, 255, 255, 0.1);
}
th:first-child {
z-index: 11;
background: #333;
}
.empty-note {
padding: 40px;
text-align: center;
color: #777;
font-style: italic;
}
</style>
<h2 style="display: flex; justify-content: space-between; align-items: center;">
<span>📊 Berichtte-Vorschau:</span>
<div>
<a href="?service=<?= htmlspecialchars($selectedService) ?>&mode=fk">
<button <?= $mode === 'fk' ? 'style="background:#3498db;color:white;"' : '' ?>>
Berufsschulberichte
</button>
</a>
<a href="?service=<?= htmlspecialchars($selectedService) ?>&mode=normal">
<button <?= $mode === 'normal' ? 'style="background:#3498db;color:white;"' : '' ?>>
Arbeitsberichte
</button>
</a>
</div>
</h2>
<?php if ($count > 0 && is_array($displayData[0])): ?>
<div class="debug-table-container">
<table>
<thead>
<tr>
<?php foreach (array_keys($displayData[0]) as $column): ?>
<th><?= htmlspecialchars($column) ?></th>
<?php endforeach; ?>
</tr>
</thead>
<tbody>
<?php foreach ($displayData as $row): ?>
<tr>
<?php foreach ($row as $key => $cell): ?>
<td><?= htmlspecialchars($cell ?? '-') ?></td>
<?php endforeach; ?>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php else: ?>
<div class="debug-table-container">
<div class="empty-note">
Keine tabellarischen Daten gefunden.
</div>
</div>
<?php endif; ?>
@@ -1,56 +0,0 @@
<div class="content-box">
<h2>Ausbildungsberichte PDF erstellen</h2>
<form method="post" class="overtime-container">
<label for="start_date">Von:</label>
<input type="date" name="start_date" id="start_date" value="<?= htmlspecialchars($startDate) ?>">
<label for="end_date">Bis:</label>
<input type="date" name="end_date" id="end_date" value="<?= htmlspecialchars($endDate) ?>">
<button type="submit" name="generate_pdf">PDF erstellen</button>
</form>
<?php if ($pdfFile): ?>
<div class="overtime-result">
<?= htmlspecialchars(basename($pdfFile)) ?> erfolgreich erstellt!
</div>
<?php elseif(isset($_POST['generate_pdf'])): ?>
<div class="overtime-result negative">
Keine Berichte im gewählten Zeitraum gefunden.
</div>
<?php endif; ?>
<?php if(!empty($wochenArray)): ?>
<div class="wochen-liste">
<h3>Verfügbare Wochenabschnitte:</h3>
<ul>
<?php
foreach($wochenArray as $woche):
$pdfName = 'ausbildungsnachweis_' . $woche['start_date'] . '-' . $woche['end_date'] . '.pdf';
$pdfPath = (new \getSpecificData())->getDocumentRoot() . '/static/' . $pdfName;
$exists = file_exists($pdfPath);
?>
<li>
<code><?= htmlspecialchars($woche['start_date']) ?></code> bis
<code><?= htmlspecialchars($woche['end_date']) ?></code>
<?php if($exists): ?>
<span style="color:green;">(PDF vorhanden)</span>
<a href="<?= htmlspecialchars('/static/' . $pdfName) ?>" download class="btn btn-success">
Download
</a>
<?php else: ?>
<span style="color:red;">(noch keine PDF)</span>
<form method="post" style="display:inline;">
<input type="hidden" name="start_date" value="<?= htmlspecialchars($woche['start_date']) ?>">
<input type="hidden" name="end_date" value="<?= htmlspecialchars($woche['end_date']) ?>">
<button type="submit" name="generate_pdf" class="btn btn-primary">
PDF erzeugen
</button>
</form>
<?php endif; ?>
</li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
</div>
-115
View File
@@ -1,115 +0,0 @@
<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>
-26
View File
@@ -1,26 +0,0 @@
<h2>Domain Verwaltung</h2>
<?php if ($statusMsg): ?>
<div class="status-msg"><?= htmlspecialchars($statusMsg) ?></div>
<?php endif; ?>
<div class="admin-grid">
<?php foreach ($domains as $domain): ?>
<form method="post" class="admin-card">
<input type="hidden" name="id" value="<?= $domain["ID"] ?>">
<label>Subdomain</label>
<input type="text" name="subdomain" value="<?= htmlspecialchars($domain["SUBDOMAIN"]) ?>">
<div style="margin-top:10px; display:flex; gap:10px;">
<button type="submit" name="save_domain" class="btn-save">Speichern</button>
<button type="submit" name="delete_domain" class="btn-delete">Löschen</button>
</div>
</form>
<?php endforeach; ?>
<form method="post" class="admin-card new-item">
<h3>Neu</h3>
<label>Subdomain</label>
<input type="text" name="subdomain" placeholder="z.B. subdomain.domain.net">
<button type="submit" name="add_domain" class="btn-add">Domain hinzufügen</button>
</form>
</div>
-41
View File
@@ -1,41 +0,0 @@
<h1>📊 Logs</h1>
<form method="GET" action="index.php">
<input type="hidden" name="service" value="<?= htmlspecialchars($serviceName) ?>">
<select name="filter" onchange="this.form.submit()">
<?php
$options = ['alle','today','node1','node2','node3','node4','counterperip'];
foreach ($options as $opt) {
$sel = ($filter === $opt) ? 'selected' : '';
echo "<option value='$opt' $sel>$opt</option>";
}
?>
</select>
</form>
<br>
<?php if (!$daten): ?>
<b>Keine Daten gefunden</b>
<?php else: ?>
<div style="overflow-x:auto;">
<table border="1" cellpadding="5" cellspacing="0" style="border-collapse:collapse;width:100%;">
<tr>
<?php foreach (array_keys($daten[0]) as $key): ?>
<th><?= htmlspecialchars($key) ?></th>
<?php endforeach; ?>
</tr>
<?php foreach ($daten as $row): ?>
<tr>
<?php foreach ($row as $val): ?>
<td><?= htmlspecialchars($val ?? '') ?></td>
<?php endforeach; ?>
</tr>
<?php endforeach; ?>
</table>
</div>
<?php endif; ?>
-57
View File
@@ -1,57 +0,0 @@
<button onclick="printFiles()">Alle Dateien drucken</button>
<div class="print-container">
<?php foreach ($files as $file):
$url = "static/" . rawurlencode($file);
$ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
?>
<div class="print-page">
<?php if (in_array($ext, ['png','jpg','jpeg','webp'])): ?>
<img src='<?= $url ?>'>
<?php elseif ($ext === "pdf"): ?>
<embed src='<?= $url ?>' type='application/pdf'>
<?php else: ?>
<p><?= htmlspecialchars($file) ?> kann nicht direkt gedruckt werden.</p>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
<script>
function printFiles() {
window.print();
}
</script>
<style>
.print-page {
width: 100%;
height: 100vh;
page-break-after: always;
}
.print-page img {
max-width: 100%;
}
.print-page embed {
width: 100%;
height: 100vh;
}
@media print {
body * {
visibility: hidden;
}
.print-container,
.print-container * {
visibility: visible;
}
.print-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
}
}
</style>
-105
View File
@@ -1,105 +0,0 @@
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-zoom"></script>
<canvas id="logChart" width="900" height="500"></canvas>
<script>
const ctx = document.getElementById('logChart').getContext('2d');
const logChart = new Chart(ctx, {
type: 'line',
data: {
labels: <?php echo json_encode($allLabels); ?>,
datasets: [{
label: 'Gesamt',
data: <?php echo json_encode($finalTotalData); ?>,
borderColor: 'rgb(255, 0, 0)',
backgroundColor: 'rgba(0, 0, 0, 0.2)',
fill: false,
tension: 0
},
{
label: 'Node1',
data: <?php echo json_encode($finalNodeData['node1']); ?>,
borderColor: 'rgba(75, 192, 192, 1)',
backgroundColor: 'rgba(75, 192, 192, 0.2)',
fill: false,
tension: 0,
hidden: true,
},
{
label: 'Node2',
data: <?php echo json_encode($finalNodeData['node2']); ?>,
borderColor: 'rgba(255, 99, 132, 1)',
backgroundColor: 'rgba(255, 99, 132, 0.2)',
fill: false,
tension: 0,
hidden: true,
},
{
label: 'Node3',
data: <?php echo json_encode($finalNodeData['node3']); ?>,
borderColor: 'rgba(255, 206, 86, 1)',
backgroundColor: 'rgba(255, 206, 86, 0.2)',
fill: false,
tension: 0,
hidden: true,
},
{
label: 'Node4',
data: <?php echo json_encode($finalNodeData['node4']); ?>,
borderColor: 'rgba(54, 162, 235, 1)',
backgroundColor: 'rgba(54, 162, 235, 0.2)',
fill: false,
tension: 0,
hidden: true,
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: true,
text: 'Logs pro Tag nach Node und Gesamt'
},
legend: {
display: true,
position: 'top'
},
zoom: {
pan: {
enabled: true,
mode: 'x'
},
zoom: {
wheel: {
enabled: true
},
mode: 'x'
}
}
},
interaction: {
mode: 'index',
intersect: false
},
scales: {
x: {
title: {
display: true,
text: 'Datum'
}
},
y: {
beginAtZero: true,
title: {
display: true,
text: 'Anzahl Logs'
}
}
}
}
});
</script>
-193
View File
@@ -1,193 +0,0 @@
<div class="content">
<?php if (!$loggedIn): ?>
<div class="login-container">
<div class="login-box">
<form method="post">
<h2>🔐 Login</h2>
<?php if($error): ?>
<div class="status-msg" style="background:#dc3545; color:#fff;"><?= htmlspecialchars($error) ?>
</div>
<?php endif; ?>
<input type="password" name="password" placeholder="Passwort" required>
<button type="submit">Einloggen</button>
</form>
</div>
</div>
<?php else: ?>
<div class="content">
<h2>Seitenverwaltung</h2>
<form method="post">
<button type="submit" name="logout" id="logoutSiteButton">Logout</button>
</form>
<?php if($message): ?><div class="status-msg"><?= htmlspecialchars($message) ?></div><?php endif; ?>
<form method="post">
<!-- Sidebar Gruppen -->
<h2>📦 Sidebar Gruppen
<button type="button" class="btn-add" onclick="addNew('serviceGroup')">+ Neue Gruppe</button>
</h2>
<div class="admin-grid" id="container-serviceGroup">
<?php foreach ($serviceGroups as $id => $group): ?>
<div class="admin-card">
<span class="badge">ID: <?= htmlspecialchars($id) ?></span>
<input type="text" name="serviceGroups[<?= $id ?>][label]"
value="<?= htmlspecialchars($group['label']) ?>" placeholder="Anzeigename">
<label>
<input type="checkbox" name="serviceGroups[<?= $id ?>][defaultOpen]"
<?= ($group['defaultOpen'] ?? false) ? 'checked' : '' ?>>
Offen
</label>
</div>
<?php endforeach; ?>
</div>
<!-- Sidebar Services -->
<h2>🖥 Sidebar Services
<button type="button" class="btn-add" onclick="addNew('service')">+ Neuer Service</button>
</h2>
<div class="admin-grid" id="container-service">
<?php foreach ($services as $id => $s): ?>
<div class="admin-card">
<span class="badge">Datei: <?= htmlspecialchars($id) ?>.php</span>
<input type="text" name="services[<?= $id ?>][name]" value="<?= htmlspecialchars($s['name']) ?>"
placeholder="Name">
<label>Sidebar-Gruppe:</label>
<select name="services[<?= $id ?>][targetGroup]">
<?php foreach ($serviceGroups as $gid => $g): ?>
<option value="<?= $gid ?>"
<?= (isset($g['services']) && in_array($id, $g['services'])) ? 'selected' : '' ?>>
<?= htmlspecialchars($g['label']) ?>
</option>
<?php endforeach; ?>
</select>
<label>
<input type="checkbox" name="services[<?= $id ?>][active]"
<?= ($s['active'] ?? false) ? 'checked' : '' ?>>
Aktiv
</label>
|
<label>
<input type="checkbox" name="services[<?= $id ?>][adminOnly]"
<?= ($s['adminOnly'] ?? false) ? 'checked' : '' ?>>
Nur Admins
</label>
</div>
<?php endforeach; ?>
</div>
<!-- Dashboard Kategorien -->
<h2>🎨 Dashboard Kategorien
<button type="button" class="btn-add" onclick="addNew('linkGroup')">+ Neue Kategorie</button>
</h2>
<div class="admin-grid" id="container-linkGroup">
<?php foreach ($linkGroups as $id => $lg): ?>
<div class="admin-card">
<span class="badge">ID: <?= htmlspecialchars($id) ?></span>
<input type="text" name="linkGroups[<?= $id ?>][label]"
value="<?= htmlspecialchars($lg['label']) ?>" placeholder="Gruppen-Label">
<label>Farbe:</label>
<input type="color" name="linkGroups[<?= $id ?>][color]"
value="<?= $lg['color'] ?? '#3498db' ?>">
<label>
<input type="checkbox" name="linkGroups[<?= $id ?>][defaultOpen]"
<?= ($lg['defaultOpen'] ?? false) ? 'checked' : '' ?>>
Offen
</label>
</div>
<?php endforeach; ?>
</div>
<!-- Dashboard Kacheln -->
<h2>🔘 Dashboard Kacheln
<button type="button" class="btn-add" onclick="addNew('linkButton')">+ Neue Kachel</button>
</h2>
<div class="admin-grid" id="container-linkButton">
<?php foreach ($linkButtons as $key => $btn): ?>
<div class="admin-card">
<input type="text" name="linkButtons[<?= $key ?>][label]"
value="<?= htmlspecialchars($btn['label']) ?>" placeholder="Name">
<input type="text" name="linkButtons[<?= $key ?>][url]"
value="<?= htmlspecialchars($btn['url']) ?>" placeholder="URL">
<label>Dashboard Kategorie:</label>
<select name="linkButtons[<?= $key ?>][group]">
<?php foreach ($linkGroups as $id => $lg): ?>
<option value="<?= $id ?>" <?= ($btn['group'] == $id) ? 'selected' : '' ?>>
<?= htmlspecialchars($lg['label']) ?>
</option>
<?php endforeach; ?>
</select>
<div style="margin-top:5px;">
<label>
<input type="checkbox" name="linkButtons[<?= $key ?>][newTab]"
<?= ($btn['newTab'] ?? false) ? 'checked' : '' ?>>
Neuer Tab
</label>
|
<label>
<input type="checkbox" name="linkButtons[<?= $key ?>][adminOnly]"
<?= ($btn['adminOnly'] ?? false) ? 'checked' : '' ?>>
Nur Admins
</label>
</div>
</div>
<?php endforeach; ?>
</div>
<div class="save-bar">
<button type="submit" name="save" class="btn-save">💾 Gesamte Konfiguration speichern</button>
</div>
</form>
</div>
<?php endif; ?>
</div>
<script>
function addNew(type) {
const container = document.getElementById('container-' + type);
const tempId = 'new_' + Date.now();
const card = document.createElement('div');
card.className = 'admin-card new-item';
let html = `<div><span class="badge">Neu hinzufügen</span></div>`;
if (type === 'serviceGroup') {
html +=
`<input type="text" name="serviceGroups[${tempId}][new_id]" placeholder="ID">
<input type="text" name="serviceGroups[${tempId}][label]" placeholder="Name">
<label><input type="checkbox" name="serviceGroups[${tempId}][defaultOpen]" checked> Ausgeklappt</label>`;
} else if (type === 'service') {
html += `<input type="text" name="services[${tempId}][new_id]" placeholder="Datei ohne .php">
<input type="text" name="services[${tempId}][name]" placeholder="Anzeigename">
<label>Gruppe:</label>
<select name="services[${tempId}][targetGroup]">
<?php foreach ($serviceGroups as $gid => $gl): ?>
<option value="<?= $gid ?>"><?= $gl['label'] ?></option>
<?php endforeach; ?>
</select>
<label><input type="checkbox" name="services[${tempId}][active]" checked> Aktiv</label>`;
} else if (type === 'linkGroup') {
html += `<input type="text" name="linkGroups[${tempId}][new_id]" placeholder="ID">
<input type="text" name="linkGroups[${tempId}][label]" placeholder="Label">
<input type="color" name="linkGroups[${tempId}][color]" value="#3498db">`;
} else if (type === 'linkButton') {
html += `<input type="text" name="linkButtons[${tempId}][label]" placeholder="Anzeigename">
<input type="text" name="linkButtons[${tempId}][url]" placeholder="https://...">
<label>Kategorie:</label>
<select name="linkButtons[${tempId}][group]">
<?php foreach ($linkGroups as $gid => $gl): ?>
<option value="<?= $gid ?>"><?= $gl['label'] ?></option>
<?php endforeach; ?>
</select>
<label><input type="checkbox" name="linkButtons[${tempId}][newTab]" checked> Tab</label>
| <label><input type="checkbox" name="linkButtons[${tempId}][adminOnly]"> Nur Admins</label>`;
}
card.innerHTML = html;
container.appendChild(card);
card.scrollIntoView({
behavior: 'smooth'
});
}
</script>
-40
View File
@@ -1,40 +0,0 @@
<h2>Datenbank Struktur</h2>
<form method="GET">
<input type="hidden" name="service" value="tabellen">
<label for="table">Tabelle auswählen:</label>
<select name="table" id="table" onchange="this.form.submit()">
<option value="">-- Bitte wählen --</option>
<?php foreach ($tables as $table): ?>
<option value="<?= htmlspecialchars($table) ?>" <?= ($selectedTable === $table) ? 'selected' : '' ?>>
<?= htmlspecialchars($table) ?>
</option>
<?php endforeach; ?>
</select>
</form>
<?php if ($selectedTable && !empty($structure)): ?>
<div class='table-container'>
<table class='db-table'>
<tr>
<th>Field</th>
<th>Type</th>
<th>Null</th>
<th>Key</th>
<th>Default</th>
<th>Extra</th>
</tr>
<?php foreach ($structure as $column): ?>
<tr>
<td><?= htmlspecialchars($column['Field']) ?></td>
<td><?= htmlspecialchars($column['Type']) ?></td>
<td><?= htmlspecialchars($column['Null']) ?></td>
<td><?= htmlspecialchars($column['Key']) ?></td>
<td><?= htmlspecialchars($column['Default'] ?? '') ?></td>
<td><?= htmlspecialchars($column['Extra']) ?></td>
</tr>
<?php endforeach; ?>
</table>
</div>
<?php endif; ?>
-45
View File
@@ -1,45 +0,0 @@
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-zoom"></script>
<canvas id="logChart" width="600" height="400"></canvas>
<script>
const ctx = document.getElementById('logChart').getContext('2d');
const logChart = new Chart(ctx, {
type: 'line',
data: {
labels: <?php echo json_encode($labels); ?>,
datasets: [{
label: 'Wetter',
data: <?php echo json_encode($data); ?>,
borderColor: 'rgba(75, 192, 192, 1)',
backgroundColor: 'rgba(75, 192, 192, 0.2)',
fill: false,
tension: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: { display: true },
legend: { display: true, position: 'top' },
zoom: {
pan: { enabled: true, mode: 'x' },
zoom: { wheel: { enabled: true }, mode: 'x' }
}
},
interaction: { mode: 'index', intersect: false },
scales: {
x: { title: { display: true, text: 'Zeitraum' } },
y: {
min: -10,
max: 40,
title: { display: true, text: 'Temperatur in °C' },
ticks: { stepSize: 1 },
beginAtZero: true
}
}
}
});
</script>
-18
View File
@@ -1,18 +0,0 @@
<?php
spl_autoload_register(function ($class) {
$prefix = 'MVC\\';
$base_dir = __DIR__ . '/';
$len = strlen($prefix);
if (strncmp($prefix, $class, $len) !== 0) {
return;
}
$relative_class = substr($class, $len);
$file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
if (file_exists($file)) {
require $file;
}
});
-262
View File
@@ -1,262 +0,0 @@
<?php
class getSpecificData {
public function getWeather() {
$url = "https://api.openweathermap.org/data/2.5/weather?q=Jena,DE&APPID=5bc3e1a438c58fca444c4370ac0517b2&units=metric";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$response = curl_exec($ch);
if(curl_errno($ch)) {
echo "cURL Fehler: " . curl_error($ch);
exit;
}
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200) {
$data = json_decode($response, true);
return [
'aktuell' => $data['main']['temp'],
'min' => $data['main']['temp_min'],
'max' => $data['main']['temp_max'],
'gefuehlt' => $data['main']['feels_like']
];
} else {
return "API Fehler. HTTP Code: " . $httpCode;
}
}
public function getUserAgent(): string {
return $_SERVER['HTTP_USER_AGENT'] ?? 'Unbekannt';
}
public function getRequestURL(): string {
return $_SERVER['REQUEST_URI'] ?? 'Unbekannt';
}
public function getCookie(string $name): ?string {
return $_COOKIE[$name] ?? 'Unbekannt';
}
public function getSessionID(): string {
return $session_id ?? 'Unbekannt ';
}
public function getServerPort(): string {
return $_SERVER['SERVER_PORT'] ?? 'Unbekannt';
}
public function getClientPort(): string {
return $_SERVER['REMOTE_PORT'] ?? 'Unbekannt';
}
public function getDocumentRoot(): string {
return $_SERVER['DOCUMENT_ROOT'] ?? 'Unbekannt';
}
public function getQuery(): string {
return $_SERVER['QUERY_STRING'] ?? 'Unbekannt';
}
public function getRequestMethod(): string {
return $_SERVER['REQUEST_METHOD'] ?? 'Unbekannt';
}
public function getCurrentDir(): string {
return __DIR__ ;
}
public function getReferer(): string {
return $_SERVER['HTTP_REFERER'] ?? 'Unbekannt';
}
public function getWebsite(): string {
return $_SERVER['HTTP_HOST'] ?? 'Unbekannt';
}
public function getDatabase(): string {
$conn = db_connect();
return $conn->host_info;
}
public function getLoggingStatus($variable) {
$status = DatabaseShow(
"Variablen",
"WERT",
"VARIABLENNAME = ?",
$variable
);
return $status;
}
public function getServerName(): string {
$Servername = $_SERVER['SERVER_NAME'] ?? 'Unbekannt';
if (in_array($Servername, ['Unbekannt', '0.0.0.0', '127.0.0.1'])) {
return "Lokal";
} else {
return $Servername;
}
}
public function getServerIP(): string {
$ServerIP = $_SERVER['SERVER_ADDR'] ?? 'Unbekannt';
if (in_array($ServerIP, ['Unbekannt', '0.0.0.0', '127.0.0.1'])) {
return "Lokal";
} else {
return $ServerIP;
}
}
public function getClientIP(): string {
$xff = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? null;
if ($xff) {
$ips = array_map('trim', explode(',', $xff));
$clientIp = $ips[0];
return $clientIp;
} else {
return $_SERVER['REMOTE_ADDR'];
}
}
public function getRequestTime(): string {
date_default_timezone_set('Europe/Berlin');
$timestamp = $_SERVER['REQUEST_TIME'] ?? time();
return date('Y-m-d H:i:s', $timestamp);
}
function getGeoIP($ClientIP) {
if (in_array($ClientIP, ['0.0.0.0', '127.0.0.1', '172.30.0.1'])) {
return "Lokal";
} elseif (in_array($ClientIP, ['Unbekannt', '0.0.0.0', '127.0.0.1'])) {
return "Lokal";
} else {
$response = file_get_contents("http://ip-api.com/json/{$ClientIP}?fields=country,countryCode");
$data = json_decode($response);
return $data->country;
}
}
function getWhitelistedIPs() {
include_once(__DIR__ . '/../utils/databaseMethods.php');
$data = DatabaseShow(
"Whitelisted",
"`IP-ADDRESSE`"
);
$results = [];
foreach ($data as $row) {
$results[] =$row['IP-ADDRESSE'];
}
return $results;
}
function getServerNode($ServerIP) {
$nodes = [
'node1' => '172.30.0.11',
'node1' => '172.30.0.21',
'node2' => '172.30.0.12',
'node2' => '172.30.0.22',
'node3' => '172.30.0.13',
'node3' => '172.30.0.23',
'node4' => '172.30.0.14',
'node4' => '172.30.0.24',
'Lokal' => 'Lokal',
];
foreach ($nodes as $node => $ip) {
if ($ServerIP == $ip) {
return $node;
break;
}
}
}
function getClientStatus($ClientIP) {
if (in_array($ClientIP, $this->getWhitelistedIPs())) {
return "Trusted";
}
return "Untrusted";
}
function getIsAdmin(){
return $_SESSION['is_admin'] ?? false;
}
function getIsSuperuser(){
return $_SESSION['is_superuser'] ?? false;
}
function getUsername(){
return $_SESSION['username'] ?? 'Gast';
}
public function getAllData(): array {
return [
'Server - Document Root' => $this->getDocumentRoot(),
'Server - Current Dir' => $this->getCurrentDir(),
'Server - Name' => $this->getServerName(),
'Server - IP' => $this->getServerIP(),
'Server - Port' => $this->getServerPort(),
'Server - Node' => $this->getServerNode($this->getServerIP()),
'Server - Database' => $this->getDatabase(),
'Request - URL' => $this->getRequestURL(),
'Request - Method' => $this->getRequestMethod(),
'Request - Query' => $this->getQuery(),
'Request - Referer' => $this->getReferer(),
'Request - Time' => $this->getRequestTime(),
'Client - IP' => $this->getClientIP(),
'Client - Port' => $this->getClientPort(),
'Client - Country' => $this->getGeoIP($this->getClientIP()),
'Client - User Agent' => $this->getUserAgent(),
'Client - Cookie USER' => $this->getCookie('USER'),
'Client - Cookie SESSSRV' => $this->getCookie('SESSSRV'),
'Client - Session ID' => $this->getSessionID(),
'Access - Whitelisted' => $this->getClientStatus($this->getClientIP()),
'Access - Whitelisted IPs' => $this->getWhitelistedIPs(),
'User - Username' => $this->getUsername(),
'User - Is Admin' => $this->getIsAdmin(),
'User - Is Superuser' => $this->getIsSuperuser(),
'System - Website' => $this->getWebsite(),
'Logging - Public' => $this->getLoggingStatus('GENERAL_PUBLIC_LOGGING')[0]['WERT'],
'Logging - Public DEV' => $this->getLoggingStatus('DEV_PUBLIC_LOGGING')[0]['WERT'],
'Logging - Backend' => $this->getLoggingStatus('GENERAL_BACKEND_LOGGING')[0]['WERT'],
'Logging - Backend DEV' => $this->getLoggingStatus('DEV_BACKEND_LOGGING')[0]['WERT'],
];
}
public function ShowAllData(): void {
$SpecificData = (new getSpecificData())->getAllData();
echo "<pre>";
foreach ($SpecificData as $key => $value) {
if (is_array($value)) {
$value = implode(', ', $value);
}
printf("%-22s : %s\n", $key, $value);
}
echo "</pre>";
}
}
-31
View File
@@ -1,31 +0,0 @@
APP_ENV = "vpn"
DB_LOCAL_HOST = "127.0.0.1"
DB_LOCAL_USERNAME = "worker"
DB_LOCAL_PASSWORD = "bBHofurUJWKzPE8Z"
DB_LOCAL_DATABASE = "DEV"
DB_VPN_HOST = "100.123.136.86"
DB_VPN_USERNAME = "worker"
DB_VPN_PASSWORD = "bBHofurUJWKzPE8Z"
DB_VPN_DATABASE = "DEV"
DB_BACKUP_HOST = "10.233.1.241"
DB_BACKUP_USERNAME = "worker"
DB_BACKUP_PASSWORD = "bBHofurUJWKzPE8Z"
DB_BACKUP_DATABASE = "DEV"
DB_DEV_HOST = "10.233.1.243"
DB_DEV_USERNAME = "worker"
DB_DEV_PASSWORD = "bBHofurUJWKzPE8Z"
DB_DEV_DATABASE = "DEV"
DB_LIVE_HOST = "10.233.1.244"
DB_LIVE_USERNAME = "worker"
DB_LIVE_PASSWORD = "bBHofurUJWKzPE8Z"
DB_LIVE_DATABASE = "DEV"
DB_ERROR_HOST = "10.233.1.244"
DB_ERROR_USERNAME = "error"
DB_ERROR_PASSWORD = "bBHofurUJWKzPE8Z"
DB_ERROR_DATABASE = "DEV"
-40
View File
@@ -1,40 +0,0 @@
<?php
$_ENV = parse_ini_file(__DIR__ . '/../config/env.ini');
include_once( __DIR__ . '/../utils/mailer.php');
include_once( __DIR__ . '/../utils/router.php');
include_once( __DIR__ . '/../classes/getData.php');
function db_connect() {
$environment = $_ENV['APP_ENV'];
if (function_exists('mysqli_report')) {
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
}
try {
$conn = new mysqli(
$_ENV['DB_' . strtoupper($environment) . '_HOST'],
$_ENV['DB_' . strtoupper($environment) . '_USERNAME'],
$_ENV['DB_' . strtoupper($environment) . '_PASSWORD'],
$_ENV['DB_' . strtoupper($environment) . '_DATABASE']
);
$conn->set_charset("utf8mb4");
return $conn;
} catch (mysqli_sql_exception $e) {
errorMail(
"Database Connection failed",
$e->getMessage(),
false
);
redirectToWebsite("https://blackpi.ipv64.net");
throw $e;
}
}
db_connect();
?>
-31
View File
@@ -1,31 +0,0 @@
<?php
trait ReponseFormatter {
/**
* json pretty print method
*
* @param string $status
* @param mixed $data
* @param string $field
* @param string $extra
* @return bool
*/
public function formatJson($status, $data, $field = 'extra', $extra = '') {
try {
return json_encode(
[
"status" => $status,
"data" => $data,
$field => $extra
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE
);
} catch (Exception $error) {
return json_encode(
[
"status" => "error",
"data" => $error->getMessage(),
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE
);
}
}
}
+369 -3
View File
@@ -981,7 +981,7 @@ body.dark-mode .info-value {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
table-layout: fixed;
/* table-layout: fixed; */
/* sorgt für feste Spaltenbreite */
}
@@ -997,9 +997,9 @@ body.dark-mode .info-value {
border-bottom: 1px solid var(--card-border);
/* Wichtig für gleiche Größe */
overflow: hidden;
/* overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
white-space: nowrap; */
}
.db-table tr:hover {
@@ -1070,4 +1070,370 @@ select {
/* Dark Mode Pfeil */
body.dark-mode select {
background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg fill='%23ccc' height='20' viewBox='0 0 20 20' width='20' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M5 7l5 5 5-5z'/%3E%3C/svg%3E");
}
/* ===== Vergabe Formular ===== */
.vergabe-form {
background: var(--card-bg);
border: 1px solid var(--card-border);
border-left: 5px solid var(--btn-bg);
border-radius: 10px;
padding: 20px;
margin-bottom: 25px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05);
}
/* Desktop-First Grid Layout */
.vergabe-grid {
display: grid;
/* Nutzt minmax, damit die Datumsfelder auf kleineren Desktop-Monitoren nicht zerquetschen */
grid-template-columns: minmax(140px, 1fr) minmax(140px, 1fr) 1.3fr 2fr auto;
gap: 15px;
align-items: end;
}
.vergabe-grid .form-group {
margin: 0;
}
.vergabe-grid input,
.btn-submit-vergabe {
width: 100%;
/* Wichtig für Responsiveness */
padding: 10px 16px;
font-size: 15px;
border-radius: 5px;
box-sizing: border-box;
}
body.dark-mode input[type="date"] {
color-scheme: dark;
}
/* Dedizierter Speicher-Button Style */
.btn-submit-vergabe {
background: var(--btn-add-bg);
color: white;
border: none;
font-weight: 600;
cursor: pointer;
height: 43px;
/* Gleicht die Höhe der Inputs inklusive Padding/Border perfekt aus */
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.2s ease, transform 0.1s ease;
}
.btn-submit-vergabe:hover {
opacity: 0.9;
transform: translateY(-1px);
}
.btn-submit-vergabe:active {
transform: scale(0.98);
}
/* ===== Mobile Optimierung (Tablets & Smartphones) ===== */
@media (max-width: 1024px) {
.vergabe-grid {
/* Schaltet auf Mobilgeräten in eine saubere 1-spaltige Liste um */
grid-template-columns: 1fr;
gap: 12px;
/* Schafft vertikalen Abstand zwischen den Feldern */
}
.btn-submit-vergabe {
margin-top: 5px;
height: 48px;
/* Größere Touch-Fläche für Smartphones */
font-size: 1rem;
}
}
/* ===== Globale Mobile Anpassungen ===== */
@media (max-width: 768px) {
/* 1. Haupt-Layout untereinander brechen lassen */
.main {
flex-direction: column;
height: calc(100vh - 60px);
/* Behält die feste Gesamthöhe bei */
overflow-y: auto;
/* Erlaubt das Scrollen der gesamten Seite falls nötig */
}
/* 2. Sidebar nimmt volle Breite ein und passt sich dem Inhalt an */
.sidebar {
width: 100%;
height: auto;
/* Keine feste Höhe mehr auf Mobile */
border-right: none;
border-bottom: 1px solid #333;
/* Trennlinie nach unten statt nach rechts */
overflow-y: visible;
/* Scrollbar hier deaktivieren */
}
/* 3. Inhaltsbereich nimmt den Rest des Platzes ein */
.content {
width: 100%;
height: auto;
overflow-y: visible;
/* Verhindert doppelte Scrollbalken */
padding: 15px 10px;
}
/* 4. Das Vergabe-Formular bricht sauber in eine Spalte um */
.vergabe-grid {
grid-template-columns: 1fr !important;
/* Erzwingt eine Spalte */
gap: 12px;
}
/* 5. Inputs und Buttons nehmen die volle Breite der Box ein */
.vergabe-grid input,
.btn-submit-vergabe,
.vergabe-grid button {
width: 100% !important;
box-sizing: border-box;
}
/* Optional: Header-Buttons (Logout / Light Mode) für Mobile optimieren, falls sie überlappen */
#darkModeToggle,
#logoutButton {
position: static;
/* Entfernt absolute Positionierung im mobilen Header */
margin: 5px;
padding: 6px 12px;
font-size: 0.9rem;
}
header {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
padding: 10px;
height: auto;
}
}
/* ===== Mobile Optimierung für die Vergabe-Tabelle ===== */
@media (max-width: 768px) {
/* Verstecke die normale Tabellen-Überschrift */
.mobile-responsive-table thead {
display: none;
}
/* Mache aus der Tabelle ein Block-Layout */
.mobile-responsive-table,
.mobile-responsive-table tbody,
.mobile-responsive-table tr,
.mobile-responsive-table td {
display: block;
width: 100% !important;
}
/* Jede Zeile wird zu einer schicken Card */
.mobile-responsive-table tr {
background: var(--card-bg);
border: 1px solid var(--card-border);
border-radius: 8px;
margin-bottom: 15px;
padding: 10px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
}
/* Einzelne Zellen nebeneinander/untereinander stylen */
.mobile-responsive-table td {
text-align: right !important;
padding: 8px 10px !important;
border-bottom: 1px solid var(--card-border);
position: relative;
white-space: normal !important;
/* Erlaubt Textumbruch auf Handys */
overflow: visible !important;
}
.mobile-responsive-table td:last-child {
border-bottom: none;
padding-top: 12px !important;
}
/* Die Beschriftung (z.B. "Was:") links einblenden */
.mobile-responsive-table td::before {
content: attr(data-label);
position: absolute;
left: 10px;
top: 50%;
transform: translateY(-50%);
font-weight: bold;
font-size: 0.9rem;
color: var(--text);
}
/* Lösch-Button auf Mobile über die gesamte Breite strecken für leichtere Bedienung */
.mobile-responsive-table .btn-delete {
width: 100%;
padding: 10px;
font-size: 1rem;
}
}
/* ===== Optimiertes Firmenberichte Erfassungsformular ===== */
.berichte-container {
background: var(--card-bg);
border: 1px solid var(--card-border);
border-top: 4px solid #007bff;
/* Eleganter blauer Akzentstreifen oben statt links */
border-radius: 12px;
padding: 30px;
max-width: 500px;
/* Kompakt und perfekt lesbar auf Monitoren */
margin: 40px auto;
/* Schön zentriert mit Luft nach oben/unten */
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
}
/* Tiefenwirkung für den Dark Mode */
body.dark-mode .berichte-container {
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
background: #161616;
/* Passt sich perfekt deiner CSS-Variable an */
}
.berichte-form {
display: flex;
flex-direction: column;
gap: 20px;
}
.berichte-form .form-group {
display: flex;
flex-direction: column;
gap: 8px;
width: 100%;
}
.berichte-form .form-group label {
font-size: 0.85rem;
font-weight: 700;
color: var(--text);
text-transform: uppercase;
letter-spacing: 0.8px;
opacity: 0.85;
}
/* Einheitlicher, moderner Look für ALLE Input-Typen */
.berichte-form input[type="text"],
.berichte-form input[type="time"],
.berichte-form input[type="date"] {
width: 100%;
padding: 12px 16px;
background: var(--input-bg);
border: 1px solid var(--input-border);
color: var(--input-text);
border-radius: 8px;
font-size: 0.95rem;
box-sizing: border-box;
transition: border-color 0.2s, box-shadow 0.2s;
}
/* Behebt das Problem mit den grellweißen Zeit/Datumsfeldern im Dark Mode */
body.dark-mode .berichte-form input[type="time"],
body.dark-mode .berichte-form input[type="date"] {
color-scheme: dark;
/* Zwingt den Browser, das native Dropdown dunkel zu machen */
background: #222;
border-color: #444;
color: #fff;
}
/* Fokus-Effekt (weicher blauer Schein) */
.berichte-form input:focus {
outline: none;
border-color: #007bff;
box-shadow: 0 0 0 4px rgba(0, 123, 255, 0.15);
}
body.dark-mode .berichte-form input:focus {
box-shadow: 0 0 0 4px rgba(0, 123, 255, 0.3);
}
/* Vollbreiter, moderner Absende-Button */
.btn-submit-bericht {
width: 100%;
background: #28a745;
/* Kräftiges Success-Grün */
color: white;
border: none;
padding: 14px;
font-size: 1rem;
font-weight: 600;
border-radius: 8px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
margin-top: 10px;
box-shadow: 0 4px 12px rgba(40, 167, 69, 0.2);
transition: all 0.2s ease;
}
.btn-submit-bericht:hover {
background: #218838;
transform: translateY(-1px);
box-shadow: 0 6px 15px rgba(40, 167, 69, 0.3);
}
.btn-submit-bericht:active {
transform: scale(0.98);
}
/* Kleine Bildschirme (Smartphones) */
@media (max-width: 600px) {
.berichte-container {
margin: 15px;
padding: 20px;
}
}
.autocomplete-wrapper {
position: relative;
}
.autocomplete-suggestions {
display: none;
position: absolute;
top: 100%;
left: 0;
right: 0;
background: var(--card-bg);
color: var(--text);
border: 1px solid var(--input-border);
border-top: none;
max-height: 180px;
overflow-y: auto;
z-index: 1000;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
.autocomplete-suggestions div {
padding: 8px 10px;
cursor: pointer;
}
.autocomplete-suggestions div:hover,
.autocomplete-suggestions div.active {
background: rgba(0, 123, 255, 0.15);
}
body.dark-mode .autocomplete-suggestions div:hover,
body.dark-mode .autocomplete-suggestions div.active {
background: rgba(0, 123, 255, 0.25);
}
+22
View File
@@ -0,0 +1,22 @@
<?php
// autoload.php @generated by Composer
if (PHP_VERSION_ID < 50600) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, $err);
} elseif (!headers_sent()) {
echo $err;
}
}
throw new RuntimeException($err);
}
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit77f94e6fc6e485edf380978f04d2c8cd::getLoader();
+579
View File
@@ -0,0 +1,579 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see https://www.php-fig.org/psr/psr-0/
* @see https://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
/** @var \Closure(string):void */
private static $includeFile;
/** @var string|null */
private $vendorDir;
// PSR-4
/**
* @var array<string, array<string, int>>
*/
private $prefixLengthsPsr4 = array();
/**
* @var array<string, list<string>>
*/
private $prefixDirsPsr4 = array();
/**
* @var list<string>
*/
private $fallbackDirsPsr4 = array();
// PSR-0
/**
* List of PSR-0 prefixes
*
* Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
*
* @var array<string, array<string, list<string>>>
*/
private $prefixesPsr0 = array();
/**
* @var list<string>
*/
private $fallbackDirsPsr0 = array();
/** @var bool */
private $useIncludePath = false;
/**
* @var array<string, string>
*/
private $classMap = array();
/** @var bool */
private $classMapAuthoritative = false;
/**
* @var array<string, bool>
*/
private $missingClasses = array();
/** @var string|null */
private $apcuPrefix;
/**
* @var array<string, self>
*/
private static $registeredLoaders = array();
/**
* @param string|null $vendorDir
*/
public function __construct($vendorDir = null)
{
$this->vendorDir = $vendorDir;
self::initializeIncludeClosure();
}
/**
* @return array<string, list<string>>
*/
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
}
return array();
}
/**
* @return array<string, list<string>>
*/
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
/**
* @return list<string>
*/
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
/**
* @return list<string>
*/
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
/**
* @return array<string, string> Array of classname => path
*/
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array<string, string> $classMap Class to filename map
*
* @return void
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param list<string>|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*
* @return void
*/
public function add($prefix, $paths, $prepend = false)
{
$paths = (array) $paths;
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
$paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
$paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
$paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param list<string>|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
$paths = (array) $paths;
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
$paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
$paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
$paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param list<string>|string $paths The PSR-0 base directories
*
* @return void
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param list<string>|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*
* @return void
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*
* @return void
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*
* @return void
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*
* @return void
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
if (null === $this->vendorDir) {
return;
}
if ($prepend) {
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
} else {
unset(self::$registeredLoaders[$this->vendorDir]);
self::$registeredLoaders[$this->vendorDir] = $this;
}
}
/**
* Unregisters this instance as an autoloader.
*
* @return void
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
if (null !== $this->vendorDir) {
unset(self::$registeredLoaders[$this->vendorDir]);
}
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return true|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
$includeFile = self::$includeFile;
$includeFile($file);
return true;
}
return null;
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
/**
* Returns the currently registered loaders keyed by their corresponding vendor directories.
*
* @return array<string, self>
*/
public static function getRegisteredLoaders()
{
return self::$registeredLoaders;
}
/**
* @param string $class
* @param string $ext
* @return string|false
*/
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
/**
* @return void
*/
private static function initializeIncludeClosure()
{
if (self::$includeFile !== null) {
return;
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*
* @param string $file
* @return void
*/
self::$includeFile = \Closure::bind(static function($file) {
include $file;
}, null, null);
}
}
+396
View File
@@ -0,0 +1,396 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer;
use Composer\Autoload\ClassLoader;
use Composer\Semver\VersionParser;
/**
* This class is copied in every Composer installed project and available to all
*
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
*
* To require its presence, you can require `composer-runtime-api ^2.0`
*
* @final
*/
class InstalledVersions
{
/**
* @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to
* @internal
*/
private static $selfDir = null;
/**
* @var mixed[]|null
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
*/
private static $installed;
/**
* @var bool
*/
private static $installedIsLocalDir;
/**
* @var bool|null
*/
private static $canGetVendors;
/**
* @var array[]
* @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static $installedByVendor = array();
/**
* Returns a list of all package names which are present, either by being installed, replaced or provided
*
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackages()
{
$packages = array();
foreach (self::getInstalled() as $installed) {
$packages[] = array_keys($installed['versions']);
}
if (1 === \count($packages)) {
return $packages[0];
}
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
}
/**
* Returns a list of all package names with a specific type e.g. 'library'
*
* @param string $type
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackagesByType($type)
{
$packagesByType = array();
foreach (self::getInstalled() as $installed) {
foreach ($installed['versions'] as $name => $package) {
if (isset($package['type']) && $package['type'] === $type) {
$packagesByType[] = $name;
}
}
}
return $packagesByType;
}
/**
* Checks whether the given package is installed
*
* This also returns true if the package name is provided or replaced by another package
*
* @param string $packageName
* @param bool $includeDevRequirements
* @return bool
*/
public static function isInstalled($packageName, $includeDevRequirements = true)
{
foreach (self::getInstalled() as $installed) {
if (isset($installed['versions'][$packageName])) {
return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
}
}
return false;
}
/**
* Checks whether the given package satisfies a version constraint
*
* e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
*
* Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
*
* @param VersionParser $parser Install composer/semver to have access to this class and functionality
* @param string $packageName
* @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
* @return bool
*/
public static function satisfies(VersionParser $parser, $packageName, $constraint)
{
$constraint = $parser->parseConstraints((string) $constraint);
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
return $provided->matches($constraint);
}
/**
* Returns a version constraint representing all the range(s) which are installed for a given package
*
* It is easier to use this via isInstalled() with the $constraint argument if you need to check
* whether a given version of a package is installed, and not just whether it exists
*
* @param string $packageName
* @return string Version constraint usable with composer/semver
*/
public static function getVersionRanges($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
$ranges = array();
if (isset($installed['versions'][$packageName]['pretty_version'])) {
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
}
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
}
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
}
if (array_key_exists('provided', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
}
return implode(' || ', $ranges);
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['version'])) {
return null;
}
return $installed['versions'][$packageName]['version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getPrettyVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
return null;
}
return $installed['versions'][$packageName]['pretty_version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
*/
public static function getReference($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['reference'])) {
return null;
}
return $installed['versions'][$packageName]['reference'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
*/
public static function getInstallPath($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @return array
* @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
*/
public static function getRootPackage()
{
$installed = self::getInstalled();
return $installed[0]['root'];
}
/**
* Returns the raw installed.php data for custom implementations
*
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
* @return array[]
* @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
*/
public static function getRawData()
{
@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = include __DIR__ . '/installed.php';
} else {
self::$installed = array();
}
}
return self::$installed;
}
/**
* Returns the raw data of all installed.php which are currently loaded for custom implementations
*
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
public static function getAllRawData()
{
return self::getInstalled();
}
/**
* Lets you reload the static array from another file
*
* This is only useful for complex integrations in which a project needs to use
* this class but then also needs to execute another project's autoloader in process,
* and wants to ensure both projects have access to their version of installed.php.
*
* A typical case would be PHPUnit, where it would need to make sure it reads all
* the data it needs from this class, then call reload() with
* `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
* the project in which it runs can then also use this class safely, without
* interference between PHPUnit's dependencies and the project's dependencies.
*
* @param array[] $data A vendor/composer/installed.php data set
* @return void
*
* @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
*/
public static function reload($data)
{
self::$installed = $data;
self::$installedByVendor = array();
// when using reload, we disable the duplicate protection to ensure that self::$installed data is
// always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not,
// so we have to assume it does not, and that may result in duplicate data being returned when listing
// all installed packages for example
self::$installedIsLocalDir = false;
}
/**
* @return string
*/
private static function getSelfDir()
{
if (self::$selfDir === null) {
self::$selfDir = strtr(__DIR__, '\\', '/');
}
return self::$selfDir;
}
/**
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static function getInstalled()
{
if (null === self::$canGetVendors) {
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
}
$installed = array();
$copiedLocalDir = false;
if (self::$canGetVendors) {
$selfDir = self::getSelfDir();
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
$vendorDir = strtr($vendorDir, '\\', '/');
if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) {
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require $vendorDir.'/composer/installed.php';
self::$installedByVendor[$vendorDir] = $required;
$installed[] = $required;
if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
self::$installed = $required;
self::$installedIsLocalDir = true;
}
}
if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
$copiedLocalDir = true;
}
}
}
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require __DIR__ . '/installed.php';
self::$installed = $required;
} else {
self::$installed = array();
}
}
if (self::$installed !== array() && !$copiedLocalDir) {
$installed[] = self::$installed;
}
return $installed;
}
}
+21
View File
@@ -0,0 +1,21 @@
Copyright (c) Nils Adermann, Jordi Boggiano
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+10
View File
@@ -0,0 +1,10 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
);
+9
View File
@@ -0,0 +1,9 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
);
+11
View File
@@ -0,0 +1,11 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'MaxMind\\Db\\' => array($vendorDir . '/maxmind-db/reader/src/MaxMind/Db'),
'Hydrax\\Webroot\\' => array($baseDir . '/src'),
);
Loaded 100 of 127 files, more files were not shown because too many files have changed in this diff. Show more