Compare commits
23
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
52c11cc1b9 | ||
|
|
37fa819e97 | ||
|
|
6922bfa99b | ||
|
|
ce9fe75e9c | ||
|
|
8bccdddebe | ||
|
|
1228eccf52 | ||
|
|
973aa67153 | ||
|
|
a84b13a5c2 | ||
|
|
5ebed74e12 | ||
|
|
b80e44fd32 | ||
|
|
5d027879ba | ||
|
|
854e6d03cb | ||
|
|
66eb55d264 | ||
|
|
26f5519888 | ||
|
|
405b9c27e7 | ||
|
|
6ad068c940 | ||
|
|
ecea3fe741 | ||
|
|
e570dcce25 | ||
|
|
951253c559 | ||
|
|
8f84260186 | ||
|
|
448bffd2f5 | ||
|
|
db392d915d | ||
|
|
584f168074 |
No files matched your search
@@ -5,7 +5,7 @@
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"workbench.colorTheme": "Default Dark Modern",
|
||||
"workbench.colorTheme": "Dark Modern",
|
||||
"workbench.colorCustomizations": {
|
||||
"activityBar.background": "#ff000075",
|
||||
"activityBar.foreground": "#000000",
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
// reset_einkaufsliste.php
|
||||
// Aufruf z.B. per Cron: php /pfad/zu/reset_einkaufsliste.php
|
||||
|
||||
include_once(__DIR__ . '/../controller/DatabaseController.php');
|
||||
|
||||
$result = DatabaseUpdate(
|
||||
'Einkaufsliste',
|
||||
'`STATUS` = ?',
|
||||
'`TYP` = ? AND `STATUS` = ?',
|
||||
'aktiv', 'vorlage', 'erledigt'
|
||||
);
|
||||
|
||||
echo $result
|
||||
? "Vorlagen-Artikel wurden zurückgesetzt.\n"
|
||||
: "Fehler beim Zurücksetzen.\n";
|
||||
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
include_once(__DIR__ . '/../../controller/DatabaseController.php');
|
||||
|
||||
header('Content-Type: application/json');
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', '0');
|
||||
|
||||
$deniedTables = [];
|
||||
|
||||
$validOperations = ['=', '!=', '<', '>', '<=', '>=', 'LIKE'];
|
||||
|
||||
function isValidEntry(string $name): bool {
|
||||
return (bool) preg_match('/^[a-zA-Z0-9_]+$/', $name);
|
||||
}
|
||||
|
||||
function PermittedSchemas(array $deniedTables): array {
|
||||
static $cache = null;
|
||||
if ($cache !== null) {
|
||||
return $cache;
|
||||
}
|
||||
|
||||
$alleSchemas = DatabaseShowAllSchemas(); // 1 Query statt N+1
|
||||
$schema = [];
|
||||
|
||||
foreach ($alleSchemas as $table => $columns) {
|
||||
if (!isValidEntry($table)) {
|
||||
continue;
|
||||
}
|
||||
if (in_array($table, $deniedTables, true)) {
|
||||
continue;
|
||||
}
|
||||
$schema[$table] = $columns;
|
||||
}
|
||||
|
||||
$cache = $schema;
|
||||
return $schema;
|
||||
}
|
||||
|
||||
$erlaubteSchema = PermittedSchemas($deniedTables);
|
||||
|
||||
function isValidTable(string $table, array $schema): void {
|
||||
if (!isValidEntry($table) || !isset($schema[$table])) {
|
||||
throw new InvalidArgumentException("Tabelle nicht erlaubt: $table");
|
||||
}
|
||||
}
|
||||
|
||||
function isValidColumn(string $table, array $columns, array $schema): void {
|
||||
foreach ($columns as $col) {
|
||||
if (!isValidEntry($col) || !in_array($col, $schema[$table], true)) {
|
||||
throw new InvalidArgumentException("Spalte nicht erlaubt: $col");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function buildWhere(array $conditions, string $table, array $schema, array $validOperations): array {
|
||||
$parts = [];
|
||||
$values = [];
|
||||
|
||||
foreach ($conditions as $cond) {
|
||||
$col = $cond['column'] ?? '';
|
||||
$op = $cond['operator'] ?? '=';
|
||||
$val = $cond['value'] ?? null;
|
||||
|
||||
isValidColumn($table, [$col], $schema);
|
||||
|
||||
if (!in_array($op, $validOperations, true)) {
|
||||
throw new InvalidArgumentException("Ungültiger Operator: $op");
|
||||
}
|
||||
|
||||
$parts[] = "`$col` $op ?";
|
||||
$values[] = $val;
|
||||
}
|
||||
|
||||
return [implode(' AND ', $parts), $values];
|
||||
}
|
||||
|
||||
// Request einlesen
|
||||
$daten = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||
$action = $daten['action'] ?? '';
|
||||
$table = $daten['table'] ?? '';
|
||||
|
||||
try {
|
||||
isValidTable($table, $erlaubteSchema);
|
||||
|
||||
switch ($action) {
|
||||
|
||||
case 'insert': {
|
||||
$columns = $daten['columns'] ?? [];
|
||||
$values = $daten['values'] ?? [];
|
||||
|
||||
isValidColumn($table, $columns, $erlaubteSchema);
|
||||
|
||||
if (count($columns) !== count($values)) {
|
||||
throw new InvalidArgumentException('Anzahl der Werte passt nicht zu den Spalten');
|
||||
}
|
||||
|
||||
$columnsSql = implode(', ', array_map(fn($c) => "`$c`", $columns));
|
||||
$result = DatabaseInsert($table, $columnsSql, ...$values);
|
||||
echo json_encode(['success' => $result]);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'show': {
|
||||
$columns = $daten['columns'] ?? [];
|
||||
$conditions = $daten['conditions'] ?? [];
|
||||
|
||||
if (empty($columns)) {
|
||||
$columns = $erlaubteSchema[$table];
|
||||
} else {
|
||||
isValidColumn($table, $columns, $erlaubteSchema);
|
||||
}
|
||||
|
||||
$columnsSql = implode(', ', array_map(fn($c) => "`$c`", $columns));
|
||||
[$whereSql, $values] = buildWhere($conditions, $table, $erlaubteSchema, $validOperations);
|
||||
|
||||
$result = DatabaseShow($table, $columnsSql, $whereSql, ...$values);
|
||||
echo json_encode(['success' => true, 'entries' => array_values($result ?? [])]);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'update': {
|
||||
$updates = $daten['updates'] ?? [];
|
||||
$conditions = $daten['conditions'] ?? [];
|
||||
|
||||
if (empty($conditions)) {
|
||||
throw new InvalidArgumentException('WHERE-Bedingung ist bei UPDATE Pflicht');
|
||||
}
|
||||
|
||||
isValidColumn($table, array_keys($updates), $erlaubteSchema);
|
||||
|
||||
$setParts = [];
|
||||
$setValues = [];
|
||||
foreach ($updates as $col => $val) {
|
||||
$setParts[] = "`$col` = ?";
|
||||
$setValues[] = $val;
|
||||
}
|
||||
$setSql = implode(', ', $setParts);
|
||||
|
||||
[$whereSql, $whereValues] = buildWhere($conditions, $table, $erlaubteSchema, $validOperations);
|
||||
|
||||
$result = DatabaseUpdate($table, $setSql, $whereSql, ...array_merge($setValues, $whereValues));
|
||||
echo json_encode(['success' => $result]);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'delete': {
|
||||
$conditions = $daten['conditions'] ?? [];
|
||||
|
||||
if (empty($conditions)) {
|
||||
throw new InvalidArgumentException('WHERE-Bedingung ist bei DELETE Pflicht');
|
||||
}
|
||||
|
||||
[$whereSql, $values] = buildWhere($conditions, $table, $erlaubteSchema, $validOperations);
|
||||
|
||||
$result = DatabaseDelete($table, $whereSql, ...$values);
|
||||
echo json_encode(['success' => $result]);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new InvalidArgumentException("Unbekannte Aktion: $action");
|
||||
}
|
||||
|
||||
} catch (InvalidArgumentException $e) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => $e->getMessage()]);
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(500);
|
||||
error_log($e->getMessage());
|
||||
echo json_encode(['error' => 'Serverfehler']);
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
session_start();
|
||||
include_once(__DIR__ . '/../../../controller/DatabaseController.php');
|
||||
|
||||
header('Content-Type: application/json');
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', '0');
|
||||
|
||||
$daten = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||
$action = $daten['action'] ?? '';
|
||||
$username = $_SESSION['username'] ?? 'Gast';
|
||||
|
||||
$erlaubteIntervalle = ['daily' => '+1 day', 'weekly' => '+1 week', 'monthly' => '+1 month'];
|
||||
|
||||
try {
|
||||
switch ($action) {
|
||||
|
||||
// ---------- Termine laden (nur die eigenen, serienweise gruppiert) ----------
|
||||
case 'get_entries': {
|
||||
$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
|
||||
);
|
||||
|
||||
echo json_encode(['success' => true, 'entries' => array_values($termine ?? [])]);
|
||||
break;
|
||||
}
|
||||
|
||||
// ---------- Neuen Termin (einzeln oder als Serie) anlegen ----------
|
||||
case 'add_termin': {
|
||||
$datum = trim($daten['datum'] ?? '');
|
||||
$zeit = trim($daten['zeit'] ?? '');
|
||||
$name = trim($daten['name'] ?? '');
|
||||
$notiz = trim($daten['notiz'] ?? '');
|
||||
$wiederholung = $daten['wiederholung'] ?? 'none';
|
||||
$enddatum = trim($daten['enddatum'] ?? '');
|
||||
|
||||
if ($datum === '' || $zeit === '' || $name === '') {
|
||||
throw new InvalidArgumentException('Bitte alle Pflichtfelder ausfüllen.');
|
||||
}
|
||||
|
||||
// ----- Serientermin -----
|
||||
if ($wiederholung !== 'none' && $enddatum !== '') {
|
||||
|
||||
if (!isset($erlaubteIntervalle[$wiederholung])) {
|
||||
throw new InvalidArgumentException('Ungültiges Wiederholungsintervall.');
|
||||
}
|
||||
|
||||
$currentDate = new DateTime($datum);
|
||||
$endDate = new DateTime($enddatum);
|
||||
|
||||
if ($endDate < $currentDate) {
|
||||
throw new InvalidArgumentException('Das Enddatum darf nicht vor dem Startdatum liegen.');
|
||||
}
|
||||
|
||||
$modifier = $erlaubteIntervalle[$wiederholung];
|
||||
$serienId = uniqid('serie_', true);
|
||||
$insertedCount = 0;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true, 'message' => "Serientermin ($insertedCount Termine) erfolgreich eingetragen!"]);
|
||||
break;
|
||||
}
|
||||
|
||||
// ----- Einzeltermin -----
|
||||
DatabaseInsert(
|
||||
"Termine",
|
||||
"DATUM, ZEIT, NAME, NOTIZ, ERSTELLT_VON",
|
||||
$datum,
|
||||
$zeit,
|
||||
$name,
|
||||
$notiz,
|
||||
$username
|
||||
);
|
||||
|
||||
echo json_encode(['success' => true, 'message' => 'Termin erfolgreich eingetragen!']);
|
||||
break;
|
||||
}
|
||||
|
||||
// ---------- Einzelnen Termin löschen ----------
|
||||
case 'delete_termin': {
|
||||
$id = $daten['id'] ?? null;
|
||||
|
||||
if (!is_numeric($id)) {
|
||||
throw new InvalidArgumentException('Ungültige ID');
|
||||
}
|
||||
|
||||
// Zusätzlich ERSTELLT_VON prüfen, damit niemand fremde Termine per ID löschen kann
|
||||
DatabaseDelete("Termine", "ID = ? AND ERSTELLT_VON = ?", (int)$id, $username);
|
||||
|
||||
echo json_encode(['success' => true, 'message' => 'Termin erfolgreich gelöscht!']);
|
||||
break;
|
||||
}
|
||||
|
||||
// ---------- Komplette Serie löschen ----------
|
||||
case 'delete_serie': {
|
||||
$serienId = trim($daten['serienId'] ?? '');
|
||||
|
||||
if ($serienId === '') {
|
||||
throw new InvalidArgumentException('Ungültige Serien-ID');
|
||||
}
|
||||
|
||||
DatabaseDelete("Termine", "SERIEN_ID = ? AND ERSTELLT_VON = ?", $serienId, $username);
|
||||
|
||||
echo json_encode(['success' => true, 'message' => 'Komplette Terminserie erfolgreich gelöscht!']);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new InvalidArgumentException("Unbekannte Aktion: $action");
|
||||
}
|
||||
|
||||
} catch (InvalidArgumentException $e) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => $e->getMessage()]);
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(500);
|
||||
error_log($e->getMessage());
|
||||
echo json_encode(['error' => 'Serverfehler']);
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
header('Content-Type: application/json');
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', '0');
|
||||
|
||||
include_once(__DIR__ . '/../../../controller/DatabaseController.php');
|
||||
include_once(__DIR__ . '/../../../controller/RoutesController.php');
|
||||
include_once(__DIR__ . '/../../../modules/Mail.php');
|
||||
|
||||
$daten = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||
$action = $daten['action'] ?? '';
|
||||
|
||||
try {
|
||||
switch ($action) {
|
||||
|
||||
// ---------- Liste laden (nur eigene Tickets) ----------
|
||||
case 'get_entries': {
|
||||
$entries = DatabaseShow(
|
||||
"Tickets",
|
||||
"ID, TITEL, BESCHREIBUNG, STATUS",
|
||||
"ERSTELLER = ?",
|
||||
((new getSpecificData())->getUsername()),
|
||||
);
|
||||
|
||||
echo json_encode(['success' => true, 'entries' => array_values($entries ?? [])]);
|
||||
break;
|
||||
}
|
||||
|
||||
// ---------- Neues Ticket anlegen ----------
|
||||
case 'add_entry': {
|
||||
foreach (['titel', 'beschreibung'] as $feld) {
|
||||
if (empty($daten[$feld])) {
|
||||
throw new InvalidArgumentException("Pflichtfeld fehlt: $feld");
|
||||
}
|
||||
}
|
||||
|
||||
$titel = $daten['titel'];
|
||||
$beschreibung = $daten['beschreibung'];
|
||||
$user = ucfirst(strtolower((new getSpecificData())->getUsername()));
|
||||
|
||||
DatabaseInsert(
|
||||
"Tickets",
|
||||
"TITEL, BESCHREIBUNG, STATUS, ERSTELLER",
|
||||
$titel,
|
||||
$beschreibung,
|
||||
"OFFEN",
|
||||
$user,
|
||||
);
|
||||
|
||||
sendMail(
|
||||
"Neues Ticket von $user",
|
||||
"Titel: $titel\nBeschreibung: $beschreibung",
|
||||
(new getSpecificData())->getRequestURL(),
|
||||
true,
|
||||
);
|
||||
|
||||
echo json_encode(['success' => true]);
|
||||
break;
|
||||
}
|
||||
|
||||
// ---------- Ticket löschen ----------
|
||||
case 'delete_entry': {
|
||||
$id = $daten['id'] ?? null;
|
||||
|
||||
if (!is_numeric($id)) {
|
||||
throw new InvalidArgumentException('Ungültige ID');
|
||||
}
|
||||
|
||||
DatabaseDelete("Tickets", "ID = ?", (int)$id);
|
||||
|
||||
echo json_encode(['success' => true]);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new InvalidArgumentException("Unbekannte Aktion: $action");
|
||||
}
|
||||
|
||||
} catch (InvalidArgumentException $e) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => $e->getMessage()]);
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(500);
|
||||
error_log($e->getMessage());
|
||||
echo json_encode(['error' => 'Serverfehler']);
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
session_set_cookie_params([
|
||||
'lifetime' => 0,
|
||||
'path' => '/',
|
||||
'domain' => '',
|
||||
'secure' => false,
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax'
|
||||
]);
|
||||
session_start();
|
||||
|
||||
header('Content-Type: application/json');
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', '0');
|
||||
|
||||
if (empty($_SESSION['user_logged_in'])) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['error' => 'Nicht eingeloggt']);
|
||||
exit;
|
||||
}
|
||||
|
||||
include_once(__DIR__ . '/../../../controller/DatabaseController.php');
|
||||
include_once(__DIR__ . '/../../../controller/RoutesController.php');
|
||||
include_once(__DIR__ . '/../../../modules/Mail.php');
|
||||
|
||||
$allowedStatuses = ['OFFEN', 'DEPLOYED', 'ABGELEHNT', 'ANGENOMMEN'];
|
||||
|
||||
$daten = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||
$action = $daten['action'] ?? '';
|
||||
|
||||
try {
|
||||
switch ($action) {
|
||||
|
||||
// ---------- Alle Tickets laden (Verwaltungsansicht) ----------
|
||||
case 'get_entries': {
|
||||
$entries = DatabaseShow(
|
||||
"Tickets",
|
||||
"ID, TITEL, BESCHREIBUNG, STATUS, ERSTELLER",
|
||||
);
|
||||
|
||||
echo json_encode(['success' => true, 'entries' => array_values($entries ?? [])]);
|
||||
break;
|
||||
}
|
||||
|
||||
// ---------- Status ändern ----------
|
||||
case 'edit_entry': {
|
||||
$id = $daten['id'] ?? null;
|
||||
$status = $daten['status'] ?? '';
|
||||
|
||||
if (!is_numeric($id)) {
|
||||
throw new InvalidArgumentException('Ungültige ID');
|
||||
}
|
||||
if (!in_array($status, $allowedStatuses, true)) {
|
||||
throw new InvalidArgumentException('Ungültiger Status');
|
||||
}
|
||||
|
||||
DatabaseUpdate(
|
||||
"Tickets",
|
||||
"STATUS = ?",
|
||||
"ID = ?",
|
||||
$status,
|
||||
(int)$id
|
||||
);
|
||||
|
||||
echo json_encode(['success' => true]);
|
||||
break;
|
||||
}
|
||||
|
||||
// ---------- Ticket löschen ----------
|
||||
case 'delete_entry': {
|
||||
$id = $daten['id'] ?? null;
|
||||
|
||||
if (!is_numeric($id)) {
|
||||
throw new InvalidArgumentException('Ungültige ID');
|
||||
}
|
||||
|
||||
DatabaseDelete("Tickets", "ID = ?", (int)$id);
|
||||
|
||||
echo json_encode(['success' => true]);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new InvalidArgumentException("Unbekannte Aktion: $action");
|
||||
}
|
||||
|
||||
} catch (InvalidArgumentException $e) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => $e->getMessage()]);
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(500);
|
||||
error_log($e->getMessage());
|
||||
echo json_encode(['error' => 'Serverfehler']);
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
include_once(__DIR__ . '/../../../controller/DatabaseController.php');
|
||||
|
||||
header('Content-Type: application/json');
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', '0');
|
||||
|
||||
$daten = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||
$action = $daten['action'] ?? '';
|
||||
|
||||
/**
|
||||
* Baut die WHERE-Klausel serverseitig. STATUS IS NULL ist FEST verdrahtet
|
||||
* und niemals durch den Client beeinflussbar -- das ist die eigentliche
|
||||
* Geschäftsregel dieser Seite (nur "aktive" Vergaben werden angezeigt).
|
||||
*/
|
||||
function baueVergabeWhere(string $searchWer, string $searchWas, array &$values): string {
|
||||
$parts = ["STATUS IS NULL"];
|
||||
|
||||
if ($searchWer !== '') {
|
||||
$parts[] = "WER LIKE ?";
|
||||
$values[] = '%' . $searchWer . '%';
|
||||
}
|
||||
if ($searchWas !== '') {
|
||||
$parts[] = "WAS LIKE ?";
|
||||
$values[] = '%' . $searchWas . '%';
|
||||
}
|
||||
|
||||
return implode(' AND ', $parts);
|
||||
}
|
||||
|
||||
try {
|
||||
switch ($action) {
|
||||
|
||||
// ---------- Liste laden (mit optionalem Filter) ----------
|
||||
case 'get_entries': {
|
||||
$searchWer = trim($daten['searchWer'] ?? '');
|
||||
$searchWas = trim($daten['searchWas'] ?? '');
|
||||
|
||||
$values = [];
|
||||
$where = baueVergabeWhere($searchWer, $searchWas, $values);
|
||||
|
||||
$entries = DatabaseShow("Vergabe", "ID, WAS, WER, WANN, RUECKGABE", $where, ...$values);
|
||||
|
||||
echo json_encode(['success' => true, 'entries' => array_values($entries ?? [])]);
|
||||
break;
|
||||
}
|
||||
|
||||
// ---------- Autocomplete-Vorschläge (alle bekannten WAS/WER, auch zurückgegebene) ----------
|
||||
case 'get_suggestions': {
|
||||
$alle = DatabaseShow("Vergabe");
|
||||
|
||||
$was = [];
|
||||
$wer = [];
|
||||
foreach ($alle as $entry) {
|
||||
if (!empty($entry['WAS'])) $was[$entry['WAS']] = true;
|
||||
if (!empty($entry['WER'])) $wer[$entry['WER']] = true;
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'was' => array_keys($was),
|
||||
'wer' => array_keys($wer),
|
||||
]);
|
||||
break;
|
||||
}
|
||||
|
||||
// ---------- Neue Vergabe anlegen ----------
|
||||
case 'add_entry': {
|
||||
foreach (['was', 'wer', 'wann', 'rueckgabe'] as $feld) {
|
||||
if (empty($daten[$feld])) {
|
||||
throw new InvalidArgumentException("Pflichtfeld fehlt: $feld");
|
||||
}
|
||||
}
|
||||
|
||||
DatabaseInsert(
|
||||
"Vergabe",
|
||||
"WAS, WER, WANN, RUECKGABE",
|
||||
$daten['was'],
|
||||
$daten['wer'],
|
||||
$daten['wann'],
|
||||
$daten['rueckgabe']
|
||||
);
|
||||
|
||||
echo json_encode(['success' => true]);
|
||||
break;
|
||||
}
|
||||
|
||||
// ---------- Vergabe als zurückgegeben markieren (Soft-Delete) ----------
|
||||
case 'return_entry': {
|
||||
$id = $daten['id'] ?? null;
|
||||
|
||||
if (!is_numeric($id)) {
|
||||
throw new InvalidArgumentException('Ungültige ID');
|
||||
}
|
||||
|
||||
DatabaseUpdate("Vergabe", "STATUS = ?", "ID = ?", 1, (int)$id);
|
||||
|
||||
echo json_encode(['success' => true]);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new InvalidArgumentException("Unbekannte Aktion: $action");
|
||||
}
|
||||
|
||||
} catch (InvalidArgumentException $e) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => $e->getMessage()]);
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(500);
|
||||
error_log($e->getMessage());
|
||||
echo json_encode(['error' => 'Serverfehler']);
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
session_start();
|
||||
include_once(__DIR__ . '/../../../controller/DatabaseController.php');
|
||||
|
||||
header('Content-Type: application/json');
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', '0');
|
||||
|
||||
$daten = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||
$action = $daten['action'] ?? '';
|
||||
|
||||
$erlaubteGruende = ['Abbau', 'Aufbau', 'Anderes'];
|
||||
|
||||
function istBerechtigt(): bool {
|
||||
$username = $_SESSION['username'] ?? '';
|
||||
return strtolower($username) === 'bruno';
|
||||
}
|
||||
|
||||
try {
|
||||
switch ($action) {
|
||||
|
||||
case 'get_stats': {
|
||||
include_once(__DIR__ . '/../../../controller/DatabaseConnect.php');
|
||||
$conn = db_connect();
|
||||
|
||||
$minuten_berichte = 0;
|
||||
$res1 = $conn->query("SELECT SUM(OVERTIME) AS summe FROM Firmenberichte");
|
||||
if ($row = $res1->fetch_object()) { $minuten_berichte = intval($row->summe); }
|
||||
|
||||
$minuten_abbau = 0;
|
||||
$res2 = $conn->query("SELECT SUM(MINUTEN) AS summe FROM Ueberstunden WHERE GRUND = 'Abbau'");
|
||||
if ($row = $res2->fetch_object()) { $minuten_abbau = intval($row->summe); }
|
||||
|
||||
$minuten_aufbau = 0;
|
||||
$res3 = $conn->query("SELECT SUM(MINUTEN) AS summe FROM Ueberstunden WHERE GRUND = 'Aufbau'");
|
||||
if ($row = $res3->fetch_object()) { $minuten_aufbau = intval($row->summe); }
|
||||
|
||||
$gesamt_minuten = $minuten_berichte - $minuten_abbau + $minuten_aufbau;
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'gesamtMinuten' => $gesamt_minuten,
|
||||
'istBerechtigt' => istBerechtigt(),
|
||||
]);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'add_overtime': {
|
||||
if (!istBerechtigt()) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'Keine Berechtigung für diese Aktion']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$datum = trim($daten['datum'] ?? '');
|
||||
$minuten = $daten['minuten'] ?? null;
|
||||
$grund = trim($daten['grund'] ?? '');
|
||||
|
||||
if ($datum === '' || !is_numeric($minuten) || $grund === '') {
|
||||
throw new InvalidArgumentException('Bitte alle Pflichtfelder korrekt ausfüllen.');
|
||||
}
|
||||
|
||||
if (!in_array($grund, $erlaubteGruende, true)) {
|
||||
throw new InvalidArgumentException('Ungültiger Grund.');
|
||||
}
|
||||
|
||||
DatabaseInsert(
|
||||
"Ueberstunden",
|
||||
"DATUM, MINUTEN, GRUND",
|
||||
$datum,
|
||||
intval($minuten),
|
||||
$grund
|
||||
);
|
||||
|
||||
echo json_encode(['success' => true, 'message' => 'Eintrag erfolgreich gespeichert!']);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new InvalidArgumentException("Unbekannte Aktion: $action");
|
||||
}
|
||||
|
||||
} catch (InvalidArgumentException $e) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => $e->getMessage()]);
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(500);
|
||||
error_log($e->getMessage());
|
||||
echo json_encode(['error' => 'Serverfehler']);
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"name": "hydrax/webroot",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Hydrax\\Webroot\\": "src/"
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"maxmind-db/reader": "^1.13"
|
||||
}
|
||||
}
|
||||
Generated
-83
@@ -1,83 +0,0 @@
|
||||
{
|
||||
"_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"
|
||||
}
|
||||
+4
-408
@@ -1,413 +1,9 @@
|
||||
<?php
|
||||
|
||||
$superuser = array(
|
||||
'admin' =>
|
||||
array(
|
||||
$superuser = array (
|
||||
'admin' =>
|
||||
array (
|
||||
'password' => 'bBHofurUJWKzPE8Z:',
|
||||
'active' => true,
|
||||
),
|
||||
);
|
||||
|
||||
$serviceGroups = array(
|
||||
'Info' =>
|
||||
array(
|
||||
'label' => 'Home',
|
||||
'defaultOpen' => true,
|
||||
'services' =>
|
||||
array(
|
||||
0 => 'home_info',
|
||||
1 => 'vergabe',
|
||||
2 => 'links',
|
||||
3 => 'einkaufsliste',
|
||||
4 => 'termine',
|
||||
),
|
||||
),
|
||||
'Ausbildung' =>
|
||||
array(
|
||||
'label' => 'Ausbildung',
|
||||
'defaultOpen' => true,
|
||||
'services' =>
|
||||
array(
|
||||
0 => 'print_static',
|
||||
1 => 'zeiterfassung',
|
||||
2 => 'berichte_arbeit_gen',
|
||||
3 => 'berichte',
|
||||
4 => 'firmenberichte',
|
||||
5 => 'berufsschulberichte',
|
||||
),
|
||||
),
|
||||
'Logs' =>
|
||||
array(
|
||||
'label' => 'Webserver',
|
||||
'defaultOpen' => false,
|
||||
'services' =>
|
||||
array(
|
||||
0 => 'public_request_logs',
|
||||
1 => 'public_request_chart',
|
||||
2 => 'backend_request_logs',
|
||||
3 => 'backend_request_chart',
|
||||
4 => 'personal_request_logs',
|
||||
),
|
||||
),
|
||||
'System' =>
|
||||
array(
|
||||
'label' => 'Verwaltung',
|
||||
'defaultOpen' => false,
|
||||
'services' =>
|
||||
array(
|
||||
0 => 'data_info',
|
||||
1 => 'services_control',
|
||||
2 => 'user_control',
|
||||
3 => 'variablen_control',
|
||||
),
|
||||
),
|
||||
'Test' =>
|
||||
array(
|
||||
'label' => 'Dev / Test',
|
||||
'defaultOpen' => false,
|
||||
'services' =>
|
||||
array(
|
||||
0 => 'api_tester',
|
||||
1 => 'weather_chart',
|
||||
2 => 'tabellen',
|
||||
3 => 'domains',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$services = array(
|
||||
'home_info' =>
|
||||
array(
|
||||
'name' => 'Home',
|
||||
'active' => true,
|
||||
'adminOnly' => false,
|
||||
),
|
||||
'vergabe' =>
|
||||
array(
|
||||
'name' => 'Vergabe',
|
||||
'active' => true,
|
||||
'adminOnly' => false,
|
||||
),
|
||||
'print_static' =>
|
||||
array(
|
||||
'name' => 'Berichte Drucken',
|
||||
'active' => true,
|
||||
'adminOnly' => false,
|
||||
),
|
||||
'api_tester' =>
|
||||
array(
|
||||
'name' => 'API Testing',
|
||||
'active' => true,
|
||||
'adminOnly' => true,
|
||||
),
|
||||
'weather_chart' =>
|
||||
array(
|
||||
'name' => 'Wetter Statistik',
|
||||
'active' => true,
|
||||
'adminOnly' => true,
|
||||
),
|
||||
'data_info' =>
|
||||
array(
|
||||
'name' => 'Data Info',
|
||||
'active' => true,
|
||||
'adminOnly' => true,
|
||||
),
|
||||
'links' =>
|
||||
array(
|
||||
'name' => 'Links',
|
||||
'active' => true,
|
||||
'adminOnly' => false,
|
||||
),
|
||||
'public_request_logs' =>
|
||||
array(
|
||||
'name' => 'Public Logs',
|
||||
'active' => true,
|
||||
'adminOnly' => false,
|
||||
),
|
||||
'public_request_chart' =>
|
||||
array(
|
||||
'name' => 'Public Statistik',
|
||||
'active' => true,
|
||||
'adminOnly' => false,
|
||||
),
|
||||
'backend_request_logs' =>
|
||||
array(
|
||||
'name' => 'Backend Logs',
|
||||
'active' => true,
|
||||
'adminOnly' => false,
|
||||
),
|
||||
'backend_request_chart' =>
|
||||
array(
|
||||
'name' => 'Backend Statistik',
|
||||
'active' => true,
|
||||
'adminOnly' => false,
|
||||
),
|
||||
'personal_request_logs' =>
|
||||
array(
|
||||
'name' => 'Persönliche Logs',
|
||||
'active' => true,
|
||||
'adminOnly' => true,
|
||||
),
|
||||
'services_control' =>
|
||||
array(
|
||||
'name' => 'Seiten',
|
||||
'active' => true,
|
||||
'adminOnly' => true,
|
||||
),
|
||||
'zeiterfassung' =>
|
||||
array(
|
||||
'name' => 'Zeit Erfassung',
|
||||
'active' => true,
|
||||
'adminOnly' => false,
|
||||
),
|
||||
'berichte_arbeit_gen' =>
|
||||
array(
|
||||
'name' => 'Berichte PDF',
|
||||
'active' => true,
|
||||
'adminOnly' => false,
|
||||
),
|
||||
'user_control' =>
|
||||
array(
|
||||
'name' => 'Benutzer',
|
||||
'active' => true,
|
||||
'adminOnly' => true,
|
||||
),
|
||||
'variablen_control' =>
|
||||
array(
|
||||
'name' => 'Variablen',
|
||||
'active' => true,
|
||||
'adminOnly' => true,
|
||||
),
|
||||
'tabellen' =>
|
||||
array(
|
||||
'name' => 'Tabellen',
|
||||
'active' => true,
|
||||
'adminOnly' => true,
|
||||
),
|
||||
'berichte' =>
|
||||
array(
|
||||
'name' => 'Berichte Einsehen',
|
||||
'active' => true,
|
||||
'adminOnly' => false,
|
||||
),
|
||||
'domains' =>
|
||||
array(
|
||||
'name' => 'Domains',
|
||||
'active' => true,
|
||||
'adminOnly' => false,
|
||||
),
|
||||
'einkaufsliste' =>
|
||||
array(
|
||||
'name' => 'Einkaufsliste',
|
||||
'active' => true,
|
||||
'adminOnly' => false,
|
||||
),
|
||||
'termine' =>
|
||||
array(
|
||||
'name' => 'Termine',
|
||||
'active' => true,
|
||||
'adminOnly' => false,
|
||||
),
|
||||
'firmenberichte' =>
|
||||
array(
|
||||
'name' => 'Firmenberichte',
|
||||
'active' => true,
|
||||
'adminOnly' => false,
|
||||
),
|
||||
'berufsschulberichte' =>
|
||||
array(
|
||||
'name' => 'Berufsschulberichte',
|
||||
'active' => true,
|
||||
'adminOnly' => false,
|
||||
),
|
||||
);
|
||||
|
||||
$linkGroups = array(
|
||||
'info' =>
|
||||
array(
|
||||
'label' => 'Informationen',
|
||||
'color' => '#3498db',
|
||||
'defaultOpen' => false,
|
||||
),
|
||||
'tools' =>
|
||||
array(
|
||||
'label' => 'IoT',
|
||||
'color' => '#2ecc71',
|
||||
'defaultOpen' => false,
|
||||
),
|
||||
'admin' =>
|
||||
array(
|
||||
'label' => 'Administration',
|
||||
'color' => '#e74c3c',
|
||||
'defaultOpen' => false,
|
||||
),
|
||||
'devices' =>
|
||||
array(
|
||||
'label' => 'Geräte',
|
||||
'color' => '#b7db33',
|
||||
'defaultOpen' => false,
|
||||
),
|
||||
);
|
||||
|
||||
$linkButtons = array(
|
||||
0 =>
|
||||
array(
|
||||
'label' => 'Gitea',
|
||||
'url' => 'http://gitea.mrhossa.de',
|
||||
'newTab' => true,
|
||||
'adminOnly' => false,
|
||||
'group' => 'admin',
|
||||
),
|
||||
1 =>
|
||||
array(
|
||||
'label' => 'HAProxy',
|
||||
'url' => 'http://stats.mrhossa.de',
|
||||
'newTab' => true,
|
||||
'adminOnly' => false,
|
||||
'group' => 'admin',
|
||||
),
|
||||
2 =>
|
||||
array(
|
||||
'label' => 'Public',
|
||||
'url' => 'https://mrhossa.de',
|
||||
'newTab' => true,
|
||||
'adminOnly' => false,
|
||||
'group' => 'info',
|
||||
),
|
||||
3 =>
|
||||
array(
|
||||
'label' => 'NAS-1',
|
||||
'url' => 'http://nas-1.blackpi.ipv64.net',
|
||||
'newTab' => true,
|
||||
'adminOnly' => false,
|
||||
'group' => 'devices',
|
||||
),
|
||||
4 =>
|
||||
array(
|
||||
'label' => 'NAS-2',
|
||||
'url' => 'http://nas-2.blackpi.ipv64.net',
|
||||
'newTab' => true,
|
||||
'adminOnly' => false,
|
||||
'group' => 'devices',
|
||||
),
|
||||
5 =>
|
||||
array(
|
||||
'label' => 'FritzBox-1',
|
||||
'url' => 'http://fritzbox-1.blackpi.ipv64.net',
|
||||
'newTab' => true,
|
||||
'adminOnly' => false,
|
||||
'group' => 'devices',
|
||||
),
|
||||
6 =>
|
||||
array(
|
||||
'label' => 'FritzBox-2',
|
||||
'url' => 'http://fritzbox-2.blackpi.ipv64.net',
|
||||
'newTab' => true,
|
||||
'adminOnly' => false,
|
||||
'group' => 'devices',
|
||||
),
|
||||
7 =>
|
||||
array(
|
||||
'label' => 'GS1910-24',
|
||||
'url' => 'http://switch.blackpi.ipv64.net',
|
||||
'newTab' => true,
|
||||
'adminOnly' => false,
|
||||
'group' => 'admin',
|
||||
),
|
||||
8 =>
|
||||
array(
|
||||
'label' => 'NWA5121-NI Innen',
|
||||
'url' => 'http://ap-1.blackpi.ipv64.net',
|
||||
'newTab' => true,
|
||||
'adminOnly' => false,
|
||||
'group' => 'devices',
|
||||
),
|
||||
9 =>
|
||||
array(
|
||||
'label' => 'NWA5121-NI Aussen',
|
||||
'url' => 'http://ap-2.blackpi.ipv64.net',
|
||||
'newTab' => true,
|
||||
'adminOnly' => false,
|
||||
'group' => 'devices',
|
||||
),
|
||||
10 =>
|
||||
array(
|
||||
'label' => 'ZyWALL 110',
|
||||
'url' => 'http://10.233.1248',
|
||||
'newTab' => true,
|
||||
'adminOnly' => false,
|
||||
'group' => 'admin',
|
||||
),
|
||||
11 =>
|
||||
array(
|
||||
'label' => 'Ender 3 V3 KE',
|
||||
'url' => 'http://10.233.1.52',
|
||||
'newTab' => true,
|
||||
'adminOnly' => false,
|
||||
'group' => 'tools',
|
||||
),
|
||||
12 =>
|
||||
array(
|
||||
'label' => 'Ubuntu NAS',
|
||||
'url' => 'http://10.233.1.32:9000',
|
||||
'newTab' => true,
|
||||
'adminOnly' => false,
|
||||
'group' => 'tools',
|
||||
),
|
||||
13 =>
|
||||
array(
|
||||
'label' => 'Backend',
|
||||
'url' => 'https://backend.blackpi.ipv64.net',
|
||||
'newTab' => true,
|
||||
'adminOnly' => false,
|
||||
'group' => 'info',
|
||||
),
|
||||
14 =>
|
||||
array(
|
||||
'label' => 'Dev Backend',
|
||||
'url' => 'https://dev-backend.blackpi.ipv64.net',
|
||||
'newTab' => true,
|
||||
'adminOnly' => false,
|
||||
'group' => 'info',
|
||||
),
|
||||
15 =>
|
||||
array(
|
||||
'label' => 'Dev Pubic',
|
||||
'url' => 'https://dev-public.blackpi.ipv64.net',
|
||||
'newTab' => true,
|
||||
'adminOnly' => false,
|
||||
'group' => 'info',
|
||||
),
|
||||
16 =>
|
||||
array(
|
||||
'label' => 'Local Backend',
|
||||
'url' => 'http://backend.local.ipv64.net',
|
||||
'newTab' => true,
|
||||
'adminOnly' => false,
|
||||
'group' => 'info',
|
||||
),
|
||||
17 =>
|
||||
array(
|
||||
'label' => 'Local Pubic',
|
||||
'url' => 'http://public.local.ipv64.net',
|
||||
'newTab' => true,
|
||||
'adminOnly' => false,
|
||||
'group' => 'info',
|
||||
),
|
||||
18 =>
|
||||
array(
|
||||
'label' => 'Proxmox',
|
||||
'url' => 'https://100.88.208.86:8006',
|
||||
'newTab' => true,
|
||||
'adminOnly' => true,
|
||||
'group' => 'devices',
|
||||
),
|
||||
19 =>
|
||||
array(
|
||||
'label' => 'Flatnotes',
|
||||
'url' => 'http://10.233.1.130:8080',
|
||||
'newTab' => true,
|
||||
'adminOnly' => false,
|
||||
'group' => 'tools',
|
||||
),
|
||||
);
|
||||
);
|
||||
@@ -1,14 +1,8 @@
|
||||
<?php
|
||||
|
||||
require_once(__DIR__ . '/../vendor/autoload.php');
|
||||
class getSpecificData {
|
||||
|
||||
use MaxMind\Db\Reader;
|
||||
|
||||
class getSpecificData
|
||||
{
|
||||
|
||||
public function getWeather()
|
||||
{
|
||||
public function getWeather() {
|
||||
$url = "https://api.openweathermap.org/data/2.5/weather?q=Jena,DE&APPID=5bc3e1a438c58fca444c4370ac0517b2&units=metric";
|
||||
|
||||
$ch = curl_init();
|
||||
@@ -18,7 +12,7 @@ class getSpecificData
|
||||
|
||||
$response = curl_exec($ch);
|
||||
|
||||
if (curl_errno($ch)) {
|
||||
if(curl_errno($ch)) {
|
||||
echo "cURL Fehler: " . curl_error($ch);
|
||||
exit;
|
||||
}
|
||||
@@ -40,74 +34,60 @@ class getSpecificData
|
||||
}
|
||||
}
|
||||
|
||||
public function getUserAgent(): string
|
||||
{
|
||||
public function getUserAgent(): string {
|
||||
return $_SERVER['HTTP_USER_AGENT'] ?? 'Unbekannt';
|
||||
}
|
||||
|
||||
public function getRequestURL(): string
|
||||
{
|
||||
public function getRequestURL(): string {
|
||||
return $_SERVER['REQUEST_URI'] ?? 'Unbekannt';
|
||||
}
|
||||
|
||||
public function getCookie(string $name): ?string
|
||||
{
|
||||
public function getCookie(string $name): ?string {
|
||||
return $_COOKIE[$name] ?? 'Unbekannt';
|
||||
}
|
||||
|
||||
public function getSessionID(): string
|
||||
{
|
||||
public function getSessionID(): string {
|
||||
return $session_id ?? 'Unbekannt ';
|
||||
}
|
||||
|
||||
public function getServerPort(): string
|
||||
{
|
||||
public function getServerPort(): string {
|
||||
return $_SERVER['SERVER_PORT'] ?? 'Unbekannt';
|
||||
}
|
||||
|
||||
public function getClientPort(): string
|
||||
{
|
||||
public function getClientPort(): string {
|
||||
return $_SERVER['REMOTE_PORT'] ?? 'Unbekannt';
|
||||
}
|
||||
|
||||
public function getDocumentRoot(): string
|
||||
{
|
||||
public function getDocumentRoot(): string {
|
||||
return $_SERVER['DOCUMENT_ROOT'] ?? 'Unbekannt';
|
||||
}
|
||||
|
||||
public function getQuery(): string
|
||||
{
|
||||
|
||||
public function getQuery(): string {
|
||||
return $_SERVER['QUERY_STRING'] ?? 'Unbekannt';
|
||||
}
|
||||
|
||||
public function getRequestMethod(): string
|
||||
{
|
||||
|
||||
public function getRequestMethod(): string {
|
||||
return $_SERVER['REQUEST_METHOD'] ?? 'Unbekannt';
|
||||
}
|
||||
|
||||
public function getCurrentDir(): string
|
||||
{
|
||||
return __DIR__;
|
||||
public function getCurrentDir(): string {
|
||||
return __DIR__ ;
|
||||
}
|
||||
|
||||
public function getReferer(): string
|
||||
{
|
||||
public function getReferer(): string {
|
||||
return $_SERVER['HTTP_REFERER'] ?? 'Unbekannt';
|
||||
}
|
||||
|
||||
public function getWebsite(): string
|
||||
{
|
||||
public function getWebsite(): string {
|
||||
return $_SERVER['HTTP_HOST'] ?? 'Unbekannt';
|
||||
}
|
||||
|
||||
public function getDatabase(): string
|
||||
{
|
||||
public function getDatabase(): string {
|
||||
$conn = db_connect();
|
||||
return $conn->host_info;
|
||||
}
|
||||
|
||||
public function getLoggingStatus($variable)
|
||||
{
|
||||
public function getLoggingStatus($variable) {
|
||||
$status = DatabaseShow(
|
||||
"Variablen",
|
||||
"WERT",
|
||||
@@ -116,9 +96,8 @@ class getSpecificData
|
||||
);
|
||||
return $status;
|
||||
}
|
||||
|
||||
public function getServerName(): string
|
||||
{
|
||||
|
||||
public function getServerName(): string {
|
||||
$Servername = $_SERVER['SERVER_NAME'] ?? 'Unbekannt';
|
||||
|
||||
if (in_array($Servername, ['Unbekannt', '0.0.0.0', '127.0.0.1'])) {
|
||||
@@ -128,17 +107,10 @@ class getSpecificData
|
||||
}
|
||||
}
|
||||
|
||||
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 getServerIP(): string {
|
||||
return $_SERVER['SERVER_ADDR'] ?? 'Unbekannt';
|
||||
}
|
||||
|
||||
|
||||
public function getClientIP(): string
|
||||
{
|
||||
if (PHP_SAPI === 'cli') {
|
||||
@@ -155,15 +127,13 @@ class getSpecificData
|
||||
return $_SERVER['REMOTE_ADDR'] ?? 'UNKNOWN';
|
||||
}
|
||||
|
||||
public function getRequestTime(): string
|
||||
{
|
||||
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)
|
||||
{
|
||||
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'])) {
|
||||
@@ -171,26 +141,11 @@ class getSpecificData
|
||||
} else {
|
||||
$response = file_get_contents("http://ip-api.com/json/{$ClientIP}?fields=country,countryCode");
|
||||
$data = json_decode($response);
|
||||
return $data->country;
|
||||
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()
|
||||
{
|
||||
function getWhitelistedIPs() {
|
||||
include_once 'DatabaseController.php';
|
||||
$data = DatabaseShow(
|
||||
"Whitelisted",
|
||||
@@ -199,105 +154,78 @@ class getSpecificData
|
||||
|
||||
$results = [];
|
||||
foreach ($data as $row) {
|
||||
$results[] = $row['IP-ADDRESSE'];
|
||||
$results[] =$row['IP-ADDRESSE'];
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
function getServerNode($ServerIP)
|
||||
{
|
||||
$nodes = [
|
||||
'node1' => '172.30.0.11',
|
||||
'node1' => '172.30.0.21',
|
||||
function getServerNode($ServerIP) {
|
||||
return $ServerIP;
|
||||
}
|
||||
|
||||
'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)
|
||||
{
|
||||
function getClientStatus($ClientIP) {
|
||||
if (in_array($ClientIP, $this->getWhitelistedIPs())) {
|
||||
return "Trusted";
|
||||
}
|
||||
}
|
||||
return "Untrusted";
|
||||
}
|
||||
|
||||
function getIsAdmin()
|
||||
{
|
||||
function getIsAdmin(){
|
||||
return $_SESSION['is_admin'] ?? false;
|
||||
}
|
||||
|
||||
function getIsSuperuser()
|
||||
{
|
||||
|
||||
function getIsSuperuser(){
|
||||
return $_SESSION['is_superuser'] ?? false;
|
||||
}
|
||||
|
||||
function getUsername()
|
||||
{
|
||||
|
||||
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(),
|
||||
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(),
|
||||
'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(),
|
||||
'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(),
|
||||
'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(),
|
||||
'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'],
|
||||
];
|
||||
}
|
||||
'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
|
||||
{
|
||||
public function ShowAllData(): void {
|
||||
$SpecificData = (new getSpecificData())->getAllData();
|
||||
|
||||
echo "<pre>";
|
||||
@@ -305,8 +233,8 @@ class getSpecificData
|
||||
if (is_array($value)) {
|
||||
$value = implode(', ', $value);
|
||||
}
|
||||
printf("%-22s : %s\n", $key, $value);
|
||||
printf("%-22s : %s\n", $key, $value);
|
||||
}
|
||||
echo "</pre>";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,15 +25,9 @@ function db_connect()
|
||||
'password' => 'bBHofurUJWKzPE8Z',
|
||||
'database' => 'DEV',
|
||||
],
|
||||
'error' => [
|
||||
'hostaddr' => '10.233.1.244',
|
||||
'username' => 'error',
|
||||
'password' => 'bBHofurUJWKzPE8Z',
|
||||
'database' => 'DEV',
|
||||
]
|
||||
];
|
||||
|
||||
$enviroment = 'vpn';
|
||||
$enviroment = 'dev';
|
||||
|
||||
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
|
||||
|
||||
|
||||
@@ -222,6 +222,29 @@ function DatabaseShowWithFK($table, $fkMap = [], $columns = "*", $where = "", ..
|
||||
];
|
||||
}
|
||||
|
||||
function DatabaseShowAllSchemas() {
|
||||
include_once 'DatabaseConnect.php';
|
||||
$conn = db_connect();
|
||||
|
||||
$result = $conn->query("
|
||||
SELECT TABLE_NAME, COLUMN_NAME
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
ORDER BY TABLE_NAME, ORDINAL_POSITION
|
||||
");
|
||||
|
||||
if (!$result) {
|
||||
die("Fehler beim Laden des Schemas: " . $conn->error);
|
||||
}
|
||||
|
||||
$schema = [];
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
$schema[$row['TABLE_NAME']][] = $row['COLUMN_NAME'];
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
$Berufsschulberichte = [
|
||||
"FACH_S1" => ["table" => "Fach", "key" => "ID", "value" => "Fach"],
|
||||
"LEHRER_S1" => ["table" => "Lehrer", "key" => "ID", "value" => "NAME"],
|
||||
|
||||
@@ -61,8 +61,6 @@ class RightsController {
|
||||
*/
|
||||
public function RightsHandler($sitename, $name = 'Bruno', $lastname = 'Eckhardt') {
|
||||
try {
|
||||
$sitename = strtolower($sitename);
|
||||
$this->saveSite($sitename);
|
||||
|
||||
DatabaseInsert(
|
||||
"Benutzer_Berechtigungen",
|
||||
|
||||
@@ -23,13 +23,6 @@ class UserController {
|
||||
"NAME = ?",
|
||||
$name
|
||||
);
|
||||
|
||||
DatabaseInsert(
|
||||
"Benutzer_Berechtigungen",
|
||||
"BENUTZER_ID, SEITEN_ID",
|
||||
$userID[0]['ID'],
|
||||
"8"
|
||||
);
|
||||
}
|
||||
|
||||
public function DeleteUser($name, $nachname, $email) {
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
include_once(__DIR__ . '/controller/DatabaseController.php');
|
||||
include_once(__DIR__ . '/controller/RoutesController.php');
|
||||
include_once(__DIR__ . '/controller/DataController.php');
|
||||
include_once(__DIR__ . '/modules/Mail.php');
|
||||
|
||||
$message = null;
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$email = trim($_POST['email'] ?? '');
|
||||
|
||||
if ($email !== '') {
|
||||
$benutzerRows = DatabaseShow("Benutzer", "ID, NAME", "EMAIL = ?", $email);
|
||||
|
||||
if (!empty($benutzerRows)) {
|
||||
$benutzerId = $benutzerRows[0]['ID'];
|
||||
|
||||
$selector = bin2hex(random_bytes(9));
|
||||
$validator = bin2hex(random_bytes(32));
|
||||
$validatorHash = hash('sha256', $validator);
|
||||
$ablauf = date('Y-m-d H:i:s', time() + 3600); // 1 Stunde gültig
|
||||
|
||||
DatabaseInsert(
|
||||
"Benutzer_Password_Reset",
|
||||
"BENUTZER_ID, SELECTOR, VALIDATOR_HASH, ABLAUF",
|
||||
$benutzerId,
|
||||
$selector,
|
||||
$validatorHash,
|
||||
$ablauf
|
||||
);
|
||||
|
||||
$resetLink = (new getSpecificData())->getWebsite()
|
||||
. "/reset_password.php?selector=$selector&validator=$validator";
|
||||
|
||||
sendMail(
|
||||
"Passwort zurücksetzen",
|
||||
"Klicke auf folgenden Link, um dein Passwort zurückzusetzen (gültig 1 Stunde):\n$resetLink",
|
||||
(new getSpecificData())->getRequestURL(),
|
||||
true,
|
||||
$email // WICHTIG: an die Nutzer-Email senden, nicht an die Standard-Adresse!
|
||||
);
|
||||
}
|
||||
// Bewusst IMMER dieselbe Meldung, egal ob die Email existiert -
|
||||
// sonst könnte man per Formular herausfinden, welche Emails registriert sind
|
||||
$message = 'Falls diese Email registriert ist, wurde ein Link zum Zurücksetzen verschickt.';
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Passwort vergessen</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="login-container">
|
||||
<div class="login-box">
|
||||
<h2>Passwort vergessen</h2>
|
||||
|
||||
<?php if ($message): ?>
|
||||
<p class="login-error" style="color:#28a745;"><?= htmlspecialchars($message) ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="post" class="register-form">
|
||||
<input type="email" name="email" placeholder="Deine Email" required>
|
||||
<button type="submit">Link anfordern</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
+61
-30
@@ -8,14 +8,15 @@ session_set_cookie_params([
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax'
|
||||
]);
|
||||
|
||||
session_start();
|
||||
|
||||
if (empty($_SESSION['user_logged_in'])) {
|
||||
header('Location: login.php');
|
||||
header('Location: login');
|
||||
exit;
|
||||
}
|
||||
|
||||
include_once 'config.php';
|
||||
include_once 'config.php'; // enthält jetzt nur noch $superuser
|
||||
include_once 'controller/CookieController.php';
|
||||
include_once 'controller/DatabaseConnect.php';
|
||||
include_once 'controller/DatabaseController.php';
|
||||
@@ -23,7 +24,6 @@ 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",
|
||||
@@ -32,6 +32,7 @@ $user_id = DatabaseShow(
|
||||
$_SESSION['username'],
|
||||
);
|
||||
|
||||
// Berechtigte Seiten-IDs des Nutzers (bleibt unverändert)
|
||||
$rightsArray = DatabaseShowWithFK(
|
||||
"Benutzer_Berechtigungen",
|
||||
$Berechtigungen,
|
||||
@@ -39,9 +40,47 @@ $rightsArray = DatabaseShowWithFK(
|
||||
"BENUTZER_ID = ?",
|
||||
$user_id[0]['ID'],
|
||||
);
|
||||
|
||||
$userRights = array_map('trim', array_column($rightsArray['rows'], 'SEITEN_ID'));
|
||||
|
||||
$isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
|
||||
// --- Seiten & Gruppen aus der DB laden ---
|
||||
$seitenGruppenRows = DatabaseShow(
|
||||
"Backend_Seiten_Gruppen",
|
||||
"ID, LABEL, DEFAULT_OPEN, SORTIERUNG"
|
||||
);
|
||||
usort($seitenGruppenRows, fn($a, $b) => ($a['SORTIERUNG'] ?? 0) <=> ($b['SORTIERUNG'] ?? 0));
|
||||
|
||||
$seitenRows = DatabaseShow(
|
||||
"Backend_Seiten",
|
||||
"ID, DATEINAME, NAME, AKTIV, ADMIN_ONLY, GRUPPE_ID, SORTIERUNG"
|
||||
);
|
||||
usort($seitenRows, fn($a, $b) => ($a['SORTIERUNG'] ?? 0) <=> ($b['SORTIERUNG'] ?? 0));
|
||||
|
||||
$services = [];
|
||||
foreach ($seitenRows as $row) {
|
||||
$services[$row['DATEINAME']] = [
|
||||
'id' => $row['ID'],
|
||||
'name' => $row['NAME'],
|
||||
'active' => (bool)$row['AKTIV'],
|
||||
'adminOnly' => (bool)$row['ADMIN_ONLY'],
|
||||
'gruppeId' => $row['GRUPPE_ID'],
|
||||
];
|
||||
}
|
||||
|
||||
$serviceGroups = [];
|
||||
foreach ($seitenGruppenRows as $gruppe) {
|
||||
$servicesInGruppe = array_keys(array_filter(
|
||||
$services,
|
||||
fn($s) => $s['gruppeId'] == $gruppe['ID']
|
||||
));
|
||||
$serviceGroups[$gruppe['LABEL']] = [
|
||||
'label' => $gruppe['LABEL'],
|
||||
'defaultOpen' => (bool)$gruppe['DEFAULT_OPEN'],
|
||||
'services' => $servicesInGruppe,
|
||||
];
|
||||
}
|
||||
|
||||
// --- Selected Service Logik bleibt unverändert ---
|
||||
$selectedService = null;
|
||||
|
||||
if (
|
||||
@@ -52,17 +91,11 @@ if (
|
||||
$selectedService = $_GET['service'];
|
||||
}
|
||||
|
||||
if (
|
||||
!$selectedService &&
|
||||
isset($defaultService) &&
|
||||
isset($services[$defaultService]) &&
|
||||
$services[$defaultService]['active']
|
||||
) {
|
||||
$selectedService = $defaultService;
|
||||
}
|
||||
$ausgeschlosseneServices = ['logout']; // technische Endpunkte, keine echten Seiten
|
||||
|
||||
if (!$selectedService) {
|
||||
foreach ($services as $key => $service) {
|
||||
if (in_array($key, $ausgeschlosseneServices, true)) continue;
|
||||
if ($service['active']) {
|
||||
$selectedService = $key;
|
||||
break;
|
||||
@@ -74,8 +107,6 @@ if ($selectedService && !in_array($selectedService, $userRights, true)) {
|
||||
$selectedService = null;
|
||||
}
|
||||
|
||||
$isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
|
||||
|
||||
if ((new getSpecificData())->getWebsite() != "backend.mrhossa.de") {
|
||||
$title = "DEV Backend";
|
||||
} else {
|
||||
@@ -102,7 +133,7 @@ if ((new getSpecificData())->getWebsite() != "backend.mrhossa.de") {
|
||||
<div class="main">
|
||||
<div class="sidebar">
|
||||
<?php foreach ($serviceGroups as $groupKey => $group): ?>
|
||||
<?php
|
||||
<?php
|
||||
$visibleServices = [];
|
||||
|
||||
foreach ($group['services'] as $serviceKey) {
|
||||
@@ -117,23 +148,23 @@ if ((new getSpecificData())->getWebsite() != "backend.mrhossa.de") {
|
||||
|
||||
$isOpen = !empty($group['defaultOpen']) || in_array($selectedService, $visibleServices, true);
|
||||
?>
|
||||
<div class="service-group <?= $isOpen ? 'open' : '' ?>" data-group="<?= $groupKey ?>">
|
||||
<div class="service-group <?= $isOpen ? 'open' : '' ?>" data-group="<?= htmlspecialchars($groupKey) ?>">
|
||||
|
||||
<div class="group-title">
|
||||
<?= htmlspecialchars($group['label']) ?>
|
||||
<span class="arrow">▸</span>
|
||||
</div>
|
||||
|
||||
<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 class="group-title">
|
||||
<?= htmlspecialchars($group['label']) ?>
|
||||
<span class="arrow">▸</span>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
const AJAX_HANDLER_URL = '/api/js/AjaxHandler';
|
||||
|
||||
function escapeHtml(str) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = str ?? '';
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
async function parseJsonResponse(response) {
|
||||
const rawText = await response.text();
|
||||
try {
|
||||
return JSON.parse(rawText);
|
||||
} catch (e) {
|
||||
console.error('Antwort war kein gültiges JSON:', rawText);
|
||||
throw new Error('Server hat kein gültiges JSON zurückgegeben (siehe Konsole)');
|
||||
}
|
||||
}
|
||||
|
||||
async function ajaxRequest(payload) {
|
||||
try {
|
||||
const response = await fetch(AJAX_HANDLER_URL, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
const data = await parseJsonResponse(response);
|
||||
|
||||
if (data.error) {
|
||||
throw new Error(data.error);
|
||||
}
|
||||
return data;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Ajax-Fehler:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function DatabaseInsert(table, columns, values) {
|
||||
return ajaxRequest({
|
||||
action: 'insert',
|
||||
table,
|
||||
columns,
|
||||
values
|
||||
});
|
||||
}
|
||||
|
||||
function DatabaseShow(table, columns = [], conditions = []) {
|
||||
return ajaxRequest({
|
||||
action: 'show',
|
||||
table,
|
||||
columns,
|
||||
conditions
|
||||
});
|
||||
}
|
||||
|
||||
function DatabaseUpdate(table, updates, conditions) {
|
||||
return ajaxRequest({
|
||||
action: 'update',
|
||||
table,
|
||||
updates,
|
||||
conditions
|
||||
});
|
||||
}
|
||||
|
||||
function DatabaseDelete(table, conditions) {
|
||||
return ajaxRequest({
|
||||
action: 'delete',
|
||||
table,
|
||||
conditions
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
|
||||
function renderTable(wrapperEl, entries, spaltenKonfig, idSpalte, onDelete) {
|
||||
|
||||
if (entries && !Array.isArray(entries)) {
|
||||
console.warn('entries war kein Array:', entries);
|
||||
entries = Object.values(entries);
|
||||
}
|
||||
|
||||
if (!entries || entries.length === 0) {
|
||||
wrapperEl.innerHTML = '<p>Keine Einträge vorhanden.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = `
|
||||
<table class="db-table mobile-responsive-table">
|
||||
<thead>
|
||||
<tr>
|
||||
${spaltenKonfig.map(s => `<th>${escapeHtml(s.label)}</th>`).join('')}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
`;
|
||||
|
||||
for (const entry of entries) {
|
||||
html += `<tr>`;
|
||||
for (const s of spaltenKonfig) {
|
||||
html += `<td data-label="${escapeHtml(s.label)}">${escapeHtml(entry[s.key])}</td>`;
|
||||
}
|
||||
}
|
||||
|
||||
html += `</tbody>
|
||||
</table>`;
|
||||
wrapperEl.innerHTML = html;
|
||||
|
||||
wrapperEl.querySelectorAll('.btn-delete').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
if (!confirm('Möchtest du diesen Eintrag wirklich löschen?')) return;
|
||||
await onDelete(btn.dataset.id);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function renderTableWithDelete(wrapperEl, entries, spaltenKonfig, idSpalte, onDelete) {
|
||||
|
||||
if (entries && !Array.isArray(entries)) {
|
||||
console.warn('entries war kein Array:', entries);
|
||||
entries = Object.values(entries);
|
||||
}
|
||||
|
||||
if (!entries || entries.length === 0) {
|
||||
wrapperEl.innerHTML = '<p>Keine Einträge vorhanden.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = `
|
||||
<table class="db-table mobile-responsive-table">
|
||||
<thead>
|
||||
<tr>
|
||||
${spaltenKonfig.map(s => `<th>${escapeHtml(s.label)}</th>`).join('')}
|
||||
<th style="width: 100px;">Aktion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
`;
|
||||
|
||||
for (const entry of entries) {
|
||||
html += `<tr>`;
|
||||
for (const s of spaltenKonfig) {
|
||||
html += `<td data-label="${escapeHtml(s.label)}">${escapeHtml(entry[s.key])}</td>`;
|
||||
}
|
||||
html += `
|
||||
<td data-label="Aktion">
|
||||
<button type="button" class="btn-delete" data-id="${escapeHtml(entry[idSpalte])}">
|
||||
Löschen
|
||||
</button>
|
||||
</td>
|
||||
</tr>`;
|
||||
}
|
||||
|
||||
html += `</tbody>
|
||||
</table>`;
|
||||
wrapperEl.innerHTML = html;
|
||||
|
||||
wrapperEl.querySelectorAll('.btn-delete').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
if (!confirm('Möchtest du diesen Eintrag wirklich löschen?')) return;
|
||||
await onDelete(btn.dataset.id);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// [Erweiterung für /js/AjaxMethods.js]
|
||||
function renderTableWithEdit(wrapperEl, entries, spaltenKonfig, idSpalte, onEdit, onDelete) {
|
||||
|
||||
if (entries && !Array.isArray(entries)) {
|
||||
console.warn('entries war kein Array:', entries);
|
||||
entries = Object.values(entries);
|
||||
}
|
||||
|
||||
if (!entries || entries.length === 0) {
|
||||
wrapperEl.innerHTML = '<p>Keine Einträge vorhanden.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = `
|
||||
<table class="db-table mobile-responsive-table">
|
||||
<thead>
|
||||
<tr>
|
||||
${spaltenKonfig.map(s => `<th>${escapeHtml(s.label)}</th>`).join('')}
|
||||
<th style="width: 180px;">Aktion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
`;
|
||||
|
||||
for (const entry of entries) {
|
||||
html += `<tr>`;
|
||||
for (const s of spaltenKonfig) {
|
||||
html += `<td data-label="${escapeHtml(s.label)}">${escapeHtml(entry[s.key])}</td>`;
|
||||
}
|
||||
html += `
|
||||
<td data-label="Aktion">
|
||||
<button type="button" class="btn-edit" data-id="${escapeHtml(entry[idSpalte])}" style="margin-right: 5px; padding: 6px 12px;">
|
||||
Bearbeiten
|
||||
</button>
|
||||
|
||||
</td>
|
||||
</tr>`;
|
||||
}
|
||||
|
||||
html += `</tbody>
|
||||
</table>`;
|
||||
wrapperEl.innerHTML = html;
|
||||
|
||||
// Event-Listener für Edit
|
||||
wrapperEl.querySelectorAll('.btn-edit').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
await onEdit(btn.dataset.id);
|
||||
});
|
||||
});
|
||||
|
||||
// Event-Listener für Löschen
|
||||
wrapperEl.querySelectorAll('.btn-delete').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
if (!confirm('Möchtest du diesen Eintrag wirklich löschen?')) return;
|
||||
await onDelete(btn.dataset.id);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function renderTableWithEditAndDelete(wrapperEl, entries, spaltenKonfig, idSpalte, onEdit, onDelete) {
|
||||
|
||||
if (entries && !Array.isArray(entries)) {
|
||||
console.warn('entries war kein Array:', entries);
|
||||
entries = Object.values(entries);
|
||||
}
|
||||
|
||||
if (!entries || entries.length === 0) {
|
||||
wrapperEl.innerHTML = '<p>Keine Einträge vorhanden.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = `
|
||||
<table class="db-table mobile-responsive-table">
|
||||
<thead>
|
||||
<tr>
|
||||
${spaltenKonfig.map(s => `<th>${escapeHtml(s.label)}</th>`).join('')}
|
||||
<th style="width: 180px;">Aktion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
`;
|
||||
|
||||
for (const entry of entries) {
|
||||
html += `<tr>`;
|
||||
for (const s of spaltenKonfig) {
|
||||
html += `<td data-label="${escapeHtml(s.label)}">${escapeHtml(entry[s.key])}</td>`;
|
||||
}
|
||||
html += `
|
||||
<td data-label="Aktion">
|
||||
<button type="button" class="btn-edit" data-id="${escapeHtml(entry[idSpalte])}" style="margin-right: 5px; padding: 6px 12px;">
|
||||
Bearbeiten
|
||||
</button>
|
||||
<button type="button" class="btn-delete" data-id="${escapeHtml(entry[idSpalte])}" style="margin-right: 5px; padding: 6px 12px;">
|
||||
Löschen
|
||||
</button>
|
||||
|
||||
</td>
|
||||
</tr>`;
|
||||
}
|
||||
|
||||
html += `</tbody>
|
||||
</table>`;
|
||||
wrapperEl.innerHTML = html;
|
||||
|
||||
// Event-Listener für Edit
|
||||
wrapperEl.querySelectorAll('.btn-edit').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
await onEdit(btn.dataset.id);
|
||||
});
|
||||
});
|
||||
|
||||
// Event-Listener für Löschen
|
||||
wrapperEl.querySelectorAll('.btn-delete').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
if (!confirm('Möchtest du diesen Eintrag wirklich löschen?')) return;
|
||||
await onDelete(btn.dataset.id);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
const TERMINE_HANDLER_URL = '/api/js/handler/TermineAjaxHandler';
|
||||
|
||||
async function termineAjaxRequest(payload) {
|
||||
try {
|
||||
const response = await fetch(TERMINE_HANDLER_URL, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
const data = await parseJsonResponse(response);
|
||||
|
||||
if (data.error) {
|
||||
throw new Error(data.error);
|
||||
}
|
||||
return data;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Termine-Ajax-Fehler:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function getTermine() {
|
||||
return termineAjaxRequest({ action: 'get_entries' });
|
||||
}
|
||||
|
||||
function addTermin(datum, zeit, name, notiz, wiederholung, enddatum) {
|
||||
return termineAjaxRequest({ action: 'add_termin', datum, zeit, name, notiz, wiederholung, enddatum });
|
||||
}
|
||||
|
||||
function deleteTermin(id) {
|
||||
return termineAjaxRequest({ action: 'delete_termin', id });
|
||||
}
|
||||
|
||||
function deleteTerminSerie(serienId) {
|
||||
return termineAjaxRequest({ action: 'delete_serie', serienId });
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
const TICKETS_HANDLER_URL = '/api/js/handler/TicketBoardAjaxHandler';
|
||||
|
||||
async function ticketsAjaxRequest(payload) {
|
||||
try {
|
||||
const response = await fetch(TICKETS_HANDLER_URL, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
const data = await parseJsonResponse(response);
|
||||
|
||||
if (data.error) {
|
||||
throw new Error(data.error);
|
||||
}
|
||||
return data;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Tickets-Ajax-Fehler:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function getTicketEntries() {
|
||||
return ticketsAjaxRequest({ action: 'get_entries' });
|
||||
}
|
||||
|
||||
function addTicketEntry(titel, beschreibung) {
|
||||
return ticketsAjaxRequest({ action: 'add_entry', titel, beschreibung });
|
||||
}
|
||||
|
||||
function deleteTicketEntry(id) {
|
||||
return ticketsAjaxRequest({ action: 'delete_entry', id });
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
const TICKET_CONTRO_HANDLER_URL = '/api/js/handler/TicketControlAjaxHandler';
|
||||
|
||||
async function ticketControAjaxRequest(payload) {
|
||||
try {
|
||||
const response = await fetch(TICKET_CONTRO_HANDLER_URL, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
const data = await parseJsonResponse(response);
|
||||
|
||||
if (data.error) {
|
||||
throw new Error(data.error);
|
||||
}
|
||||
return data;
|
||||
|
||||
} catch (error) {
|
||||
console.error('TicketContro-Ajax-Fehler:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function getTicketControEntries() {
|
||||
return ticketControAjaxRequest({ action: 'get_entries' });
|
||||
}
|
||||
|
||||
function editTicketControEntry(id, status) {
|
||||
return ticketControAjaxRequest({ action: 'edit_entry', id, status });
|
||||
}
|
||||
|
||||
function deleteTicketControEntry(id) {
|
||||
return ticketControAjaxRequest({ action: 'delete_entry', id });
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
const VERGABE_HANDLER_URL = '/api/js/handler/VergabeAjaxHandler';
|
||||
|
||||
async function vergabeAjaxRequest(payload) {
|
||||
try {
|
||||
const response = await fetch(VERGABE_HANDLER_URL, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
const data = await parseJsonResponse(response);
|
||||
|
||||
if (data.error) {
|
||||
throw new Error(data.error);
|
||||
}
|
||||
return data;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Vergabe-Ajax-Fehler:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function getVergabeEntries(searchWer = '', searchWas = '') {
|
||||
return vergabeAjaxRequest({ action: 'get_entries', searchWer, searchWas });
|
||||
}
|
||||
|
||||
function getVergabeSuggestions() {
|
||||
return vergabeAjaxRequest({ action: 'get_suggestions' });
|
||||
}
|
||||
|
||||
function addVergabeEntry(was, wer, wann, rueckgabe) {
|
||||
return vergabeAjaxRequest({ action: 'add_entry', was, wer, wann, rueckgabe });
|
||||
}
|
||||
|
||||
function returnVergabeEntry(id) {
|
||||
return vergabeAjaxRequest({ action: 'return_entry', id });
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
const OVERTIME_HANDLER_URL = '/api/js/handler/ZeiterfassungAjaxHandler';
|
||||
|
||||
async function overtimeAjaxRequest(payload) {
|
||||
try {
|
||||
const response = await fetch(OVERTIME_HANDLER_URL, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
const data = await parseJsonResponse(response);
|
||||
|
||||
if (data.error) {
|
||||
throw new Error(data.error);
|
||||
}
|
||||
return data;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Overtime-Ajax-Fehler:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function getOvertimeStats() {
|
||||
return overtimeAjaxRequest({ action: 'get_stats' });
|
||||
}
|
||||
|
||||
function addOvertimeEntry(datum, minuten, grund) {
|
||||
return overtimeAjaxRequest({ action: 'add_overtime', datum, minuten, grund });
|
||||
}
|
||||
+71
-43
@@ -5,11 +5,13 @@ include_once 'controller/LogController.php';
|
||||
include_once 'modules/Mail.php';
|
||||
include_once 'config.php';
|
||||
|
||||
ob_start();
|
||||
|
||||
session_set_cookie_params([
|
||||
'lifetime' => 0,
|
||||
'path' => '/',
|
||||
'domain' => '',
|
||||
'secure' => false,
|
||||
'secure' => false,
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax'
|
||||
]);
|
||||
@@ -19,6 +21,22 @@ logging();
|
||||
|
||||
$error = null;
|
||||
|
||||
function schreibeSessionUndPruefe(): void
|
||||
{
|
||||
$sid = session_id();
|
||||
$writeOk = session_write_close();
|
||||
$sessionFile = session_save_path() . '/sess_' . $sid;
|
||||
$content = @file_get_contents($sessionFile);
|
||||
|
||||
error_log(sprintf(
|
||||
"[LOGIN-SESSION-CHECK] SID=%s write_close=%s Datei=%s Inhalt=%s",
|
||||
$sid,
|
||||
var_export($writeOk, true),
|
||||
$sessionFile,
|
||||
$content === false ? '(Datei nicht lesbar/vorhanden)' : var_export($content, true)
|
||||
));
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$username = $_POST['username'] ?? '';
|
||||
$password = $_POST['password'] ?? '';
|
||||
@@ -30,9 +48,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$_SESSION['is_admin'] = false;
|
||||
$_SESSION['is_superuser'] = false;
|
||||
|
||||
schreibeSessionUndPruefe();
|
||||
|
||||
logging();
|
||||
sendMail(
|
||||
"Gast Login erfolgreich",
|
||||
"Gast Login Successful",
|
||||
"Benutzer: gast",
|
||||
(new getSpecificData())->getRequestURL(),
|
||||
true,
|
||||
@@ -44,9 +64,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
|
||||
// Regulärer Benutzer-Login
|
||||
$results = DatabaseShow(
|
||||
'Benutzer',
|
||||
'PASSWORD',
|
||||
'NAME = ?',
|
||||
'Benutzer',
|
||||
'PASSWORD',
|
||||
'NAME = ?',
|
||||
$username
|
||||
);
|
||||
|
||||
@@ -58,13 +78,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
);
|
||||
|
||||
if (!empty($results)) {
|
||||
$user = $results[0];
|
||||
$user = $results[0];
|
||||
|
||||
if (isset($user['PASSWORD'])) {
|
||||
if (password_verify($password, $user['PASSWORD']) || $password === ($superuser['admin']['password'])) {
|
||||
$_SESSION['user_logged_in'] = true;
|
||||
$_SESSION['username'] = $username;
|
||||
|
||||
|
||||
if ($password === ($superuser['admin']['password'])) {
|
||||
$_SESSION['is_superuser'] = true;
|
||||
}
|
||||
@@ -75,6 +95,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$_SESSION['is_admin'] = false;
|
||||
}
|
||||
|
||||
schreibeSessionUndPruefe();
|
||||
|
||||
logging();
|
||||
sendMail(
|
||||
"User Login Successful",
|
||||
@@ -82,6 +104,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
(new getSpecificData())->getRequestURL(),
|
||||
true,
|
||||
);
|
||||
|
||||
redirectToWebsite("index.php");
|
||||
exit;
|
||||
} else {
|
||||
@@ -119,56 +142,61 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
|
||||
<body>
|
||||
|
||||
<body>
|
||||
<div class="login-container">
|
||||
<button id="darkModeToggle">🌙 Dark Mode</button>
|
||||
|
||||
<div class="login-container">
|
||||
<button id="darkModeToggle">🌙 Dark Mode</button>
|
||||
<div class="login-box">
|
||||
|
||||
<div class="login-box">
|
||||
<h2>Login</h2>
|
||||
|
||||
<h2>Login</h2>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<?php if ($error): ?>
|
||||
<p class="login-error"><?= htmlspecialchars($error) ?></p>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="post">
|
||||
<input type="text" name="username" placeholder="Benutzername" required>
|
||||
<input type="password" name="password" placeholder="Passwort" required>
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
<form method="post">
|
||||
<input type="text" name="username" placeholder="Benutzername" required>
|
||||
<input type="password" name="password" placeholder="Passwort" required>
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
|
||||
<button style="margin-top: 9px" type="button"
|
||||
onclick="document.querySelector('input[name=username]').value='gast'; document.querySelector('form').submit();">
|
||||
Als Gast einloggen
|
||||
</button>
|
||||
<button style="margin-top: 9px" type="button"
|
||||
onclick="document.querySelector('input[name=username]').value='gast'; document.querySelector('form').submit();">
|
||||
Als Gast einloggen
|
||||
</button>
|
||||
<button style="margin-top: 9px" type="button" id="registryButton">
|
||||
Registrieren
|
||||
</button>
|
||||
<button style="margin-top: 9px" type="button" id="resetpassword">
|
||||
Passwort vergessen
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const toggle = document.getElementById('darkModeToggle');
|
||||
document.getElementById('registryButton').addEventListener('click', () => {
|
||||
window.location.href = 'register.php';
|
||||
});
|
||||
document.getElementById('resetpassword').addEventListener('click', () => {
|
||||
window.location.href = 'forgot_password.php';
|
||||
});
|
||||
const toggle = document.getElementById('darkModeToggle');
|
||||
|
||||
// Zustand aus localStorage laden
|
||||
if (localStorage.getItem('darkMode') === 'enabled') {
|
||||
document.body.classList.add('dark-mode');
|
||||
}
|
||||
|
||||
toggle.addEventListener('click', () => {
|
||||
document.body.classList.toggle('dark-mode');
|
||||
|
||||
if (document.body.classList.contains('dark-mode')) {
|
||||
localStorage.setItem('darkMode', 'enabled');
|
||||
} else {
|
||||
localStorage.setItem('darkMode', 'disabled');
|
||||
if (localStorage.getItem('darkMode') === 'enabled') {
|
||||
document.body.classList.add('dark-mode');
|
||||
}
|
||||
});
|
||||
|
||||
toggle.addEventListener('click', () => {
|
||||
document.body.classList.toggle('dark-mode');
|
||||
|
||||
if (document.body.classList.contains('dark-mode')) {
|
||||
localStorage.setItem('darkMode', 'enabled');
|
||||
} else {
|
||||
localStorage.setItem('darkMode', 'disabled');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
+9
-2
@@ -1,10 +1,17 @@
|
||||
<?php
|
||||
include_once(__DIR__ . '/modules/Mail.php');
|
||||
include_once(__DIR__ . '/controller/DataController.php');
|
||||
include_once(__DIR__ . '/controller/LogController.php');
|
||||
|
||||
try {
|
||||
session_start();
|
||||
|
||||
logging();
|
||||
sendMail(
|
||||
"User Logged Out Successful",
|
||||
(new getSpecificData())->getUsername(),
|
||||
(new getSpecificData())->getRequestURL(),
|
||||
true
|
||||
);
|
||||
$_SESSION = array();
|
||||
|
||||
session_destroy();
|
||||
@@ -22,4 +29,4 @@ try {
|
||||
true,
|
||||
);
|
||||
exit();
|
||||
}
|
||||
}
|
||||
@@ -29,9 +29,12 @@ function sendMail($subject, $message, $url, bool $log, $to = "bruno.eckhardt07@g
|
||||
function errorMail($title, $error, bool $log)
|
||||
{
|
||||
$headers = "From: bruno.eckhardt07@gmail.com\r\n";
|
||||
$headers .= "MIME-Version: 1.0\r\n";
|
||||
$headers .= "Content-Type: text/html; charset=UTF-8\r\n";
|
||||
|
||||
mail(
|
||||
"bruno.eckhardt07@gmail.com",
|
||||
"ERROR: $title",
|
||||
"[ERROR] $title",
|
||||
$error,
|
||||
$headers,
|
||||
);
|
||||
@@ -40,9 +43,9 @@ function errorMail($title, $error, bool $log)
|
||||
"Mailing",
|
||||
"DATUM, SUBJECT, MESSAGE, URL, CLIENT_IP",
|
||||
(new getSpecificData())->getRequestTime(),
|
||||
"ERROR: $title",
|
||||
"[ERROR] $title",
|
||||
$error,
|
||||
(new getSpecificData())->getClientIP(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
include_once(__DIR__ . '/controller/UserController.php');
|
||||
include_once(__DIR__ . '/controller/DataController.php');
|
||||
include_once(__DIR__ . '/controller/RightsController.php');
|
||||
include_once(__DIR__ . '/controller/RoutesController.php');
|
||||
|
||||
ob_start();
|
||||
|
||||
session_set_cookie_params([
|
||||
'lifetime' => 0,
|
||||
'path' => '/',
|
||||
'domain' => '',
|
||||
'secure' => false,
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax'
|
||||
]);
|
||||
session_start();
|
||||
|
||||
$error = null;
|
||||
|
||||
function loginAfterRegister($username): void
|
||||
{
|
||||
$_SESSION['user_logged_in'] = true;
|
||||
$_SESSION['username'] = $username;
|
||||
$_SESSION['is_admin'] = false;
|
||||
$_SESSION['is_superuser'] = false;
|
||||
|
||||
session_write_close();
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$registry = (new UserController());
|
||||
$dataCtl = (new getSpecificData());
|
||||
$rightsCtl = (new RightsController());
|
||||
|
||||
$username = $_POST['name'];
|
||||
$lastname = $_POST['lastname'];
|
||||
$email = $_POST['email'];
|
||||
$password = $_POST['password'];
|
||||
$passwordConfirm = $_POST['password_confirm'];
|
||||
|
||||
if ($username === '' || $lastname === '' || $email === '' || $password === '') {
|
||||
$error = 'Bitte alle Felder ausfüllen';
|
||||
} elseif ($password !== $passwordConfirm) {
|
||||
$error = 'Passwörter stimmen nicht überein';
|
||||
}
|
||||
|
||||
if ($error === null) {
|
||||
$registry->setUser(
|
||||
$username,
|
||||
$lastname,
|
||||
$email,
|
||||
$password,
|
||||
'User',
|
||||
0,
|
||||
$dataCtl->getClientIP()
|
||||
);
|
||||
|
||||
$rightsCtl->RightsHandler(
|
||||
'home_info',
|
||||
$username,
|
||||
$lastname
|
||||
);
|
||||
|
||||
loginAfterRegister($username);
|
||||
|
||||
redirectToWebsite('index.php');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Registrieren</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="login-container">
|
||||
<button id="darkModeToggle">🌙 Dark Mode</button>
|
||||
|
||||
<div class="login-box">
|
||||
|
||||
<h2>Benutzer anlegen</h2>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<p class="login-error"><?= htmlspecialchars($error) ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="post" class="register-form">
|
||||
<input type="text" name="name" placeholder="Vorname" required>
|
||||
<input type="text" name="lastname" placeholder="Nachname" required>
|
||||
<input type="email" name="email" placeholder="Email" required>
|
||||
<input type="password" name="password" placeholder="Passwort" required>
|
||||
<input type="password" name="password_confirm" placeholder="Passwort wiederholen" required>
|
||||
<button type="submit">Registrieren</button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const toggle = document.getElementById('darkModeToggle');
|
||||
|
||||
if (localStorage.getItem('darkMode') === 'enabled') {
|
||||
document.body.classList.add('dark-mode');
|
||||
}
|
||||
|
||||
toggle.addEventListener('click', () => {
|
||||
document.body.classList.toggle('dark-mode');
|
||||
|
||||
if (document.body.classList.contains('dark-mode')) {
|
||||
localStorage.setItem('darkMode', 'enabled');
|
||||
} else {
|
||||
localStorage.setItem('darkMode', 'disabled');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
include_once(__DIR__ . '/controller/DatabaseController.php');
|
||||
include_once(__DIR__ . '/controller/RoutesController.php');
|
||||
|
||||
$error = null;
|
||||
$success = false;
|
||||
|
||||
$selector = $_GET['selector'] ?? $_POST['selector'] ?? '';
|
||||
$validator = $_GET['validator'] ?? $_POST['validator'] ?? '';
|
||||
|
||||
$tokenGueltig = false;
|
||||
$tokenRow = null;
|
||||
|
||||
if ($selector !== '' && $validator !== '') {
|
||||
$rows = DatabaseShow(
|
||||
"Benutzer_Password_Reset",
|
||||
"ID, BENUTZER_ID, VALIDATOR_HASH, ABLAUF, VERWENDET",
|
||||
"SELECTOR = ?",
|
||||
$selector
|
||||
);
|
||||
|
||||
if (!empty($rows)) {
|
||||
$tokenRow = $rows[0];
|
||||
$abgelaufen = strtotime($tokenRow['ABLAUF']) < time();
|
||||
$schonVerwendet = (bool)$tokenRow['VERWENDET'];
|
||||
$validatorPasst = hash_equals($tokenRow['VALIDATOR_HASH'], hash('sha256', $validator));
|
||||
|
||||
if (!$abgelaufen && !$schonVerwendet && $validatorPasst) {
|
||||
$tokenGueltig = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$tokenGueltig) {
|
||||
$error = 'Dieser Link ist ungültig oder abgelaufen.';
|
||||
} elseif ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['password'])) {
|
||||
$password = $_POST['password'] ?? '';
|
||||
$passwordConfirm = $_POST['password_confirm'] ?? '';
|
||||
|
||||
if ($password === '' || $password !== $passwordConfirm) {
|
||||
$error = 'Passwörter stimmen nicht überein.';
|
||||
} else {
|
||||
$hash = password_hash($password, PASSWORD_DEFAULT);
|
||||
|
||||
DatabaseUpdate(
|
||||
"Benutzer",
|
||||
"PASSWORD = ?",
|
||||
"ID = ?",
|
||||
$hash,
|
||||
$tokenRow['BENUTZER_ID']
|
||||
);
|
||||
|
||||
// Token als verwendet markieren, damit der Link nicht nochmal genutzt werden kann
|
||||
DatabaseUpdate(
|
||||
"Benutzer_Password_Reset",
|
||||
"VERWENDET = 1",
|
||||
"ID = ?",
|
||||
$tokenRow['ID']
|
||||
);
|
||||
|
||||
$success = true;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Passwort zurücksetzen</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="login-container">
|
||||
<div class="login-box">
|
||||
<h2>Neues Passwort setzen</h2>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<p class="login-error"><?= htmlspecialchars($error) ?></p>
|
||||
<?php elseif ($success): ?>
|
||||
<p class="login-error" style="color:#28a745;">
|
||||
Passwort erfolgreich geändert. <a href="login.php">Jetzt einloggen</a>
|
||||
</p>
|
||||
<?php else: ?>
|
||||
<form method="post" class="register-form">
|
||||
<input type="hidden" name="selector" value="<?= htmlspecialchars($selector) ?>">
|
||||
<input type="hidden" name="validator" value="<?= htmlspecialchars($validator) ?>">
|
||||
<input type="password" name="password" placeholder="Neues Passwort" required>
|
||||
<input type="password" name="password_confirm" placeholder="Passwort wiederholen" required>
|
||||
<button type="submit">Passwort ändern</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
if (!isset($selectedService)) {
|
||||
exit('Direkter Zugriff nicht erlaubt.');
|
||||
}
|
||||
?>
|
||||
|
||||
<div>
|
||||
<!-- Formular für INSERT -->
|
||||
<div class="vergabe-form">
|
||||
<h3>1. Eintrag hinzufügen (DatabaseInsert)</h3>
|
||||
<form id="insertForm" class="vergabe-grid">
|
||||
<div class="form-group">
|
||||
<label>Artikelname</label>
|
||||
<input type="text" id="insertProdukt" placeholder="z.B. Apfel" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Menge</label>
|
||||
<input type="text" id="insertMenge" placeholder="z.B. 5 Stück" required>
|
||||
</div>
|
||||
<button type="submit" class="btn-submit-vergabe">💾 Hinzufügen</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Suchfeld für SHOW mit Filtern -->
|
||||
<div class="form-container" style="margin-top: 20px;">
|
||||
<h3>2. Liste filtern (DatabaseShow mit Conditions)</h3>
|
||||
<div class="user-form-grid">
|
||||
<div class="form-group">
|
||||
<label>Suche nach Produkt (LIKE)</label>
|
||||
<input type="text" id="searchProdukt" placeholder="Leer lassen für alle Einträge...">
|
||||
</div>
|
||||
<button type="button" id="btnFilter" class="btn btn-primary" style="margin: 0; width: 100%; height: 43px;">
|
||||
🔍 Filtern
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tabellen-Container -->
|
||||
<div class="table-container" style="margin-top: 30px;">
|
||||
<h3>3 & 4. Daten verwalten (Tabelle via JS gerendert)</h3>
|
||||
<div id="showcaseTableWrapper">
|
||||
<p>Lade daten...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/js/AjaxMethods.js"></script>
|
||||
<script src="/js/AjaxRenderTables.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const tableWrapper = document.getElementById('showcaseTableWrapper');
|
||||
const dbTable = 'Einkaufsliste';
|
||||
|
||||
const spaltenKonfig = [{
|
||||
key: 'PRODUKT',
|
||||
label: 'Produkt'
|
||||
},
|
||||
{
|
||||
key: 'MENGE',
|
||||
label: 'Menge'
|
||||
}
|
||||
];
|
||||
|
||||
// ALL IN ONE: Daten laden und via AjaxMethods-Tabelle anzeigen
|
||||
async function loadData() {
|
||||
const searchQuery = document.getElementById('searchProdukt').value.trim();
|
||||
let conditions = [];
|
||||
|
||||
if (searchQuery !== '') {
|
||||
conditions.push({
|
||||
column: 'PRODUKT',
|
||||
operator: 'LIKE',
|
||||
value: `%${searchQuery}%`
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. SHOW ausführen
|
||||
const data = await DatabaseShow(dbTable, ['ID', 'PRODUKT', 'MENGE'], conditions);
|
||||
|
||||
// 2. TABELLE RENDERN(Nutzt die neue Methode aus AjaxMethods.js)
|
||||
// renderTableWithEdit(
|
||||
// tableWrapper,
|
||||
// data.entries,
|
||||
// spaltenKonfig,
|
||||
// 'ID',
|
||||
// async (id) => {
|
||||
// await editWorkflow(id);
|
||||
// }, // Edit Callback
|
||||
// );
|
||||
|
||||
// renderTableWithDelete(
|
||||
// tableWrapper,
|
||||
// data.entries,
|
||||
// spaltenKonfig,
|
||||
// 'ID',
|
||||
// async (id) => {
|
||||
// await deleteWorkflow(id);
|
||||
// }, // Edit Callback
|
||||
// )
|
||||
|
||||
// renderTableWithEditAndDelete(
|
||||
// tableWrapper,
|
||||
// data.entries,
|
||||
// spaltenKonfig,
|
||||
// 'ID',
|
||||
// async (id) => {
|
||||
// await editWorkflow(id);
|
||||
// }, // Edit Callback
|
||||
// async (id) => {
|
||||
// await deleteWorkflow(id);
|
||||
// } // Delete Callback
|
||||
// )
|
||||
|
||||
renderTable(
|
||||
tableWrapper,
|
||||
data.entries,
|
||||
spaltenKonfig,
|
||||
)
|
||||
|
||||
} catch (error) {
|
||||
tableWrapper.innerHTML = `<p style="color:red;">❌ Fehler: ${escapeHtml(error.message)}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
// WORKFLOW: INSERT
|
||||
document.getElementById('insertForm').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const produkt = document.getElementById('insertProdukt').value.trim();
|
||||
const menge = document.getElementById('insertMenge').value.trim();
|
||||
|
||||
try {
|
||||
await DatabaseInsert(dbTable, ['PRODUKT', 'MENGE'], [produkt, menge]);
|
||||
e.target.reset();
|
||||
await loadData();
|
||||
} catch (error) {
|
||||
alert('Fehler beim Einfügen: ' + error.message);
|
||||
}
|
||||
});
|
||||
|
||||
// WORKFLOW: UPDATE
|
||||
async function editWorkflow(id) {
|
||||
|
||||
const neueMenge = prompt('Neue Menge (Abbrechen für keine Änderung):');
|
||||
if (neueMenge === null) return; // Abgebrochen
|
||||
|
||||
// Daten-Objekt für den UPDATE-Query vorbereiten
|
||||
let updates = {};
|
||||
if (neueMenge.trim() !== '') updates.MENGE = neueMenge.trim();
|
||||
|
||||
// Falls beide Felder leer gelassen wurden, abbrechen
|
||||
if (Object.keys(updates).length === 0) return;
|
||||
|
||||
try {
|
||||
await DatabaseUpdate(
|
||||
dbTable,
|
||||
updates,
|
||||
[{
|
||||
column: 'ID',
|
||||
operator: '=',
|
||||
value: id
|
||||
}]
|
||||
);
|
||||
await loadData();
|
||||
} catch (error) {
|
||||
alert('Fehler beim Aktualisieren: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// WORKFLOW: DELETE
|
||||
async function deleteWorkflow(id) {
|
||||
try {
|
||||
await DatabaseDelete(dbTable, [{
|
||||
column: 'ID',
|
||||
operator: '=',
|
||||
value: id
|
||||
}]);
|
||||
await loadData();
|
||||
} catch (error) {
|
||||
alert('Fehler beim Löschen: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Event Listener für Filter-Button
|
||||
document.getElementById('btnFilter').addEventListener('click', loadData);
|
||||
|
||||
// Erster App-Start
|
||||
loadData();
|
||||
});
|
||||
</script>
|
||||
@@ -1,91 +1,178 @@
|
||||
<?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">
|
||||
|
||||
<form id="addForm" class="vergabe-grid">
|
||||
<div class="form-group">
|
||||
<label>Artikel</label>
|
||||
<input type="text" name="artikel" required>
|
||||
<input type="text" name="artikel" id="artikel" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Menge</label>
|
||||
<input type="text" name="menge" required>
|
||||
<input type="text" name="menge" id="menge" required>
|
||||
</div>
|
||||
|
||||
<button type="submit" name="add_entry" class="btn-submit-vergabe">
|
||||
💾 Speichern
|
||||
</button>
|
||||
<div class="form-group">
|
||||
<label>Typ</label>
|
||||
<select name="typ" id="typ">
|
||||
<option value="einmalig">Einmalig</option>
|
||||
<option value="vorlage">Vorlage (wiederkehrend)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button type="submit" 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 id="tableWrapper">
|
||||
<p>Lade Daten...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/js/AjaxMethods.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const tableWrapper = document.getElementById('tableWrapper');
|
||||
const table = 'Einkaufsliste';
|
||||
|
||||
const spaltenKonfig = [{
|
||||
key: 'PRODUKT',
|
||||
label: 'Artikel'
|
||||
},
|
||||
{
|
||||
key: 'MENGE',
|
||||
label: 'Menge'
|
||||
},
|
||||
{
|
||||
key: 'TYP',
|
||||
label: 'Typ'
|
||||
},
|
||||
];
|
||||
|
||||
// Eigene Render-Funktion, da hier je nach TYP unterschiedlich
|
||||
// gehandelt werden muss (Löschen vs. Erledigt-Markieren).
|
||||
function renderEinkaufsliste(entries) {
|
||||
if (entries && !Array.isArray(entries)) {
|
||||
entries = Object.values(entries);
|
||||
}
|
||||
|
||||
if (!entries || entries.length === 0) {
|
||||
tableWrapper.innerHTML = '<p>Keine Einträge vorhanden.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = `
|
||||
<table class="db-table mobile-responsive-table">
|
||||
<thead>
|
||||
<tr>
|
||||
${spaltenKonfig.map(s => `<th>${escapeHtml(s.label)}</th>`).join('')}
|
||||
<th style="width: 100px;">Aktion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
`;
|
||||
|
||||
for (const entry of entries) {
|
||||
const istVorlage = entry.TYP === 'vorlage';
|
||||
const label = istVorlage ? 'Erledigt' : 'Löschen';
|
||||
|
||||
html += `<tr>`;
|
||||
for (const s of spaltenKonfig) {
|
||||
let wert = entry[s.key];
|
||||
if (s.key === 'TYP') {
|
||||
wert = istVorlage ? 'Vorlage' : 'Einmalig';
|
||||
}
|
||||
html += `<td data-label="${escapeHtml(s.label)}">${escapeHtml(wert)}</td>`;
|
||||
}
|
||||
html += `
|
||||
<td data-label="Aktion">
|
||||
<button type="button" class="btn-delete" data-id="${escapeHtml(entry.ID)}" data-typ="${escapeHtml(entry.TYP)}">
|
||||
${label}
|
||||
</button>
|
||||
</td>
|
||||
</tr>`;
|
||||
}
|
||||
|
||||
html += `</tbody></table>`;
|
||||
tableWrapper.innerHTML = html;
|
||||
|
||||
tableWrapper.querySelectorAll('.btn-delete').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
const id = btn.dataset.id;
|
||||
const istVorlage = btn.dataset.typ === 'vorlage';
|
||||
|
||||
const frage = istVorlage ?
|
||||
'Diesen Vorlagen-Artikel als erledigt markieren?' :
|
||||
'Diesen Artikel wirklich endgültig löschen?';
|
||||
if (!confirm(frage)) return;
|
||||
|
||||
try {
|
||||
if (istVorlage) {
|
||||
await DatabaseUpdate(
|
||||
table, {
|
||||
STATUS: 'erledigt'
|
||||
},
|
||||
[{
|
||||
column: 'ID',
|
||||
operator: '=',
|
||||
value: id
|
||||
}]
|
||||
);
|
||||
} else {
|
||||
await DatabaseDelete(
|
||||
table,
|
||||
[{
|
||||
column: 'ID',
|
||||
operator: '=',
|
||||
value: id
|
||||
}]
|
||||
);
|
||||
}
|
||||
await reloadTable();
|
||||
} catch (error) {
|
||||
alert('Fehler: ' + error.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function reloadTable() {
|
||||
try {
|
||||
const data = await DatabaseShow(table, [], [{
|
||||
column: 'STATUS',
|
||||
operator: '=',
|
||||
value: 'aktiv'
|
||||
}]);
|
||||
renderEinkaufsliste(data.entries);
|
||||
} catch (error) {
|
||||
tableWrapper.innerHTML = `<p>${escapeHtml(error.message)}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('addForm').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const artikel = document.getElementById('artikel').value.trim();
|
||||
const menge = document.getElementById('menge').value.trim();
|
||||
const typ = document.getElementById('typ').value;
|
||||
if (!artikel || !menge) return;
|
||||
|
||||
try {
|
||||
await DatabaseInsert(
|
||||
table,
|
||||
['PRODUKT', 'MENGE', 'TYP', 'STATUS'],
|
||||
[artikel, menge, typ, 'aktiv']
|
||||
);
|
||||
e.target.reset();
|
||||
await reloadTable();
|
||||
} catch (error) {
|
||||
alert('Fehler beim Speichern: ' + error.message);
|
||||
}
|
||||
});
|
||||
|
||||
reloadTable();
|
||||
});
|
||||
</script>
|
||||
+159
-179
@@ -1,108 +1,8 @@
|
||||
<?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 id="statusMsg" class="status-msg" style="display:none;"></div>
|
||||
|
||||
<div class="form-container">
|
||||
<h3>📅 Neuen Termin vergeben</h3>
|
||||
<form method="POST" class="vergabe-form">
|
||||
<form id="addTerminForm" class="vergabe-form">
|
||||
<div class="vergabe-grid">
|
||||
<div class="form-group">
|
||||
<label for="datum">Start-Datum *</label>
|
||||
@@ -123,7 +23,7 @@ $intervallNamen = [
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="wiederholung">Wiederholung</label>
|
||||
<select id="wiederholung" name="wiederholung" onchange="toggleEnddatum()"
|
||||
<select id="wiederholung" name="wiederholung"
|
||||
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>
|
||||
@@ -135,7 +35,7 @@ $intervallNamen = [
|
||||
<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>
|
||||
<button type="submit" class="btn-submit-vergabe">Speichern</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -143,85 +43,165 @@ $intervallNamen = [
|
||||
<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 id="termineTableWrapper">Lade Daten...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/js/AjaxMethods.js"></script>
|
||||
<script src="/js/AjaxRenderTables.js"></script>
|
||||
<script src="/js/ajaxApi/TermineAjaxMethods.js"></script>
|
||||
<script>
|
||||
function toggleEnddatum() {
|
||||
var wiederholung = document.getElementById('wiederholung').value;
|
||||
var enddatumGroup = document.getElementById('enddatum-group');
|
||||
var enddatumInput = document.getElementById('enddatum');
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const statusMsg = document.getElementById('statusMsg');
|
||||
const tableWrapper = document.getElementById('termineTableWrapper');
|
||||
|
||||
if (wiederholung !== 'none') {
|
||||
enddatumGroup.style.display = 'block';
|
||||
enddatumInput.required = true;
|
||||
} else {
|
||||
enddatumGroup.style.display = 'none';
|
||||
enddatumInput.required = false;
|
||||
enddatumInput.value = '';
|
||||
const intervallNamen = {
|
||||
daily: 'Täglich',
|
||||
weekly: 'Wöchentlich',
|
||||
monthly: 'Monatlich'
|
||||
};
|
||||
|
||||
function zeigeStatus(text, istFehler = false) {
|
||||
statusMsg.textContent = text;
|
||||
statusMsg.style.display = 'block';
|
||||
statusMsg.style.background = istFehler ? '#f8d7da' : '';
|
||||
statusMsg.style.color = istFehler ? '#721c24' : '';
|
||||
setTimeout(() => {
|
||||
statusMsg.style.display = 'none';
|
||||
}, 4000);
|
||||
}
|
||||
}
|
||||
|
||||
function formatDatum(iso) {
|
||||
const d = new Date(iso);
|
||||
return d.toLocaleDateString('de-DE');
|
||||
}
|
||||
|
||||
function formatZeit(zeit) {
|
||||
return zeit?.slice(0, 5) ?? '';
|
||||
}
|
||||
|
||||
function renderTermine(termine) {
|
||||
if (!termine || termine.length === 0) {
|
||||
tableWrapper.innerHTML = `
|
||||
<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><tr><td colspan="6" style="text-align:center; padding:20px;">Keine Termine eingetragen.</td></tr></tbody>
|
||||
</table>`;
|
||||
return;
|
||||
}
|
||||
|
||||
let html = `
|
||||
<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>
|
||||
`;
|
||||
|
||||
for (const t of termine) {
|
||||
const istSerie = !!t.SERIEN_ID;
|
||||
html += `
|
||||
<tr>
|
||||
<td data-label="Datum:">
|
||||
<strong>${escapeHtml(formatDatum(t.DATUM))}</strong>
|
||||
${istSerie ? `<br><small style="color:#007bff; font-style:italic;">(${escapeHtml(intervallNamen[t.INTERVALL] ?? '')} bis ${escapeHtml(formatDatum(t.SERIEN_ENDE))})</small>` : ''}
|
||||
</td>
|
||||
<td data-label="Uhrzeit:">${escapeHtml(formatZeit(t.ZEIT))}</td>
|
||||
<td data-label="Termin / Name:">
|
||||
<strong>${escapeHtml(t.NAME)}</strong>
|
||||
${istSerie ? `<span style="font-size:0.8em; background:#e1f5fe; color:#0288d1; padding:2px 6px; border-radius:10px; margin-left:5px;">Wiederholend</span>` : ''}
|
||||
</td>
|
||||
<td data-label="Notiz:">${escapeHtml(t.NOTIZ || '-')}</td>
|
||||
<td data-label="Von:"><span class="badge">${escapeHtml(t.ERSTELLT_VON)}</span></td>
|
||||
<td data-label="Aktion:">
|
||||
${istSerie
|
||||
? `<button type="button" class="btn-delete btn-delete-serie" data-serien-id="${escapeHtml(t.SERIEN_ID)}" style="background-color:#d32f2f;">löschen</button>`
|
||||
: `<button type="button" class="btn-delete btn-delete-einzeln" data-id="${escapeHtml(t.ID)}">Löschen</button>`
|
||||
}
|
||||
</td>
|
||||
</tr>`;
|
||||
}
|
||||
|
||||
html += `</tbody></table>`;
|
||||
tableWrapper.innerHTML = html;
|
||||
|
||||
tableWrapper.querySelectorAll('.btn-delete-einzeln').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
if (!confirm('Möchtest du diesen Termin wirklich löschen?')) return;
|
||||
try {
|
||||
const data = await deleteTermin(btn.dataset.id);
|
||||
zeigeStatus(data.message);
|
||||
await ladeTermine();
|
||||
} catch (err) {
|
||||
zeigeStatus('Fehler: ' + err.message, true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
tableWrapper.querySelectorAll('.btn-delete-serie').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
if (!confirm('Möchtest du die GESAMTE Terminserie löschen?')) return;
|
||||
try {
|
||||
const data = await deleteTerminSerie(btn.dataset.serienId);
|
||||
zeigeStatus(data.message);
|
||||
await ladeTermine();
|
||||
} catch (err) {
|
||||
zeigeStatus('Fehler: ' + err.message, true);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function ladeTermine() {
|
||||
try {
|
||||
const data = await getTermine();
|
||||
renderTermine(data.entries);
|
||||
} catch (err) {
|
||||
tableWrapper.innerHTML = `<p>❌ ${escapeHtml(err.message)}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------
|
||||
// Wiederholung: Enddatum-Feld ein-/ausblenden
|
||||
// -----------------------------------------------------------
|
||||
document.getElementById('wiederholung').addEventListener('change', function() {
|
||||
const enddatumGroup = document.getElementById('enddatum-group');
|
||||
const enddatumInput = document.getElementById('enddatum');
|
||||
|
||||
if (this.value !== 'none') {
|
||||
enddatumGroup.style.display = 'block';
|
||||
enddatumInput.required = true;
|
||||
} else {
|
||||
enddatumGroup.style.display = 'none';
|
||||
enddatumInput.required = false;
|
||||
enddatumInput.value = '';
|
||||
}
|
||||
});
|
||||
|
||||
// -----------------------------------------------------------
|
||||
// Formular absenden
|
||||
// -----------------------------------------------------------
|
||||
document.getElementById('addTerminForm').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const datum = document.getElementById('datum').value;
|
||||
const zeit = document.getElementById('zeit').value;
|
||||
const name = document.getElementById('name').value.trim();
|
||||
const notiz = document.getElementById('notiz').value.trim();
|
||||
const wiederholung = document.getElementById('wiederholung').value;
|
||||
const enddatum = document.getElementById('enddatum').value;
|
||||
|
||||
try {
|
||||
const data = await addTermin(datum, zeit, name, notiz, wiederholung, enddatum);
|
||||
zeigeStatus(data.message);
|
||||
e.target.reset();
|
||||
document.getElementById('enddatum-group').style.display = 'none';
|
||||
await ladeTermine();
|
||||
} catch (err) {
|
||||
zeigeStatus('Fehler: ' + err.message, true);
|
||||
}
|
||||
});
|
||||
|
||||
ladeTermine();
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,144 @@
|
||||
<div class="arbeitsberichte-wrapper">
|
||||
<div class="vergabe-form">
|
||||
<h3>Neues Ticket anlegen</h3>
|
||||
<form id="addTicketForm" class="vergabe-grid">
|
||||
|
||||
<div class="form-group">
|
||||
<label>Titel</label>
|
||||
<input type="text" name="titel" id="in-titel" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Beschreibung</label>
|
||||
<input type="text" name="beschreibung" id="in-beschreibung" required>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn-submit-vergabe">💾 Speichern</button>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="table-container" style="margin-top: 30px;">
|
||||
<h3>Tickets</h3>
|
||||
<div id="tableWrapper">
|
||||
<p>Lade Daten...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/js/AjaxMethods.js"></script>
|
||||
<script src="/js/ajaxApi/TicketBoardAjaxMethods.js"></script>
|
||||
<script src="/js/AjaxRenderTables.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const tableWrapper = document.getElementById('tableWrapper');
|
||||
const POLL_INTERVAL_MS = 5000;
|
||||
let lastStatusById = {};
|
||||
|
||||
// -----------------------------------------------------------
|
||||
// Tabelle rendern (eigene Render-Funktion wg. Status-Highlight)
|
||||
// -----------------------------------------------------------
|
||||
function renderTicketsTabelle(entries) {
|
||||
if (!entries || entries.length === 0) {
|
||||
tableWrapper.innerHTML = '<p>Keine Tickets vorhanden.</p>';
|
||||
lastStatusById = {};
|
||||
return;
|
||||
}
|
||||
|
||||
let html = `
|
||||
<table class="db-table mobile-responsive-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Titel</th><th>Beschreibung</th><th>Status</th>
|
||||
<th style="width: 100px;">Aktion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
`;
|
||||
for (const e of entries) {
|
||||
const changed = lastStatusById[e.ID] !== undefined &&
|
||||
lastStatusById[e.ID] !== e.STATUS;
|
||||
|
||||
html += `
|
||||
<tr data-id="${escapeHtml(e.ID)}" class="${changed ? 'status-row-changed' : ''}">
|
||||
<td data-label="Titel">${escapeHtml(e.TITEL)}</td>
|
||||
<td data-label="Beschreibung">${escapeHtml(e.BESCHREIBUNG)}</td>
|
||||
<td data-label="Status"><span class="status-badge">${escapeHtml(e.STATUS)}</span></td>
|
||||
<td data-label="Aktion">
|
||||
<button type="button" class="btn-delete" data-id="${escapeHtml(e.ID)}">Löschen</button>
|
||||
</td>
|
||||
</tr>`;
|
||||
}
|
||||
html += `</tbody></table>`;
|
||||
tableWrapper.innerHTML = html;
|
||||
|
||||
const neueStatus = {};
|
||||
entries.forEach(e => {
|
||||
neueStatus[e.ID] = e.STATUS;
|
||||
});
|
||||
lastStatusById = neueStatus;
|
||||
|
||||
tableWrapper.querySelectorAll('.btn-delete').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
if (!confirm('Möchtest du dieses Ticket wirklich löschen?')) return;
|
||||
try {
|
||||
await deleteTicketEntry(btn.dataset.id);
|
||||
await ladeListe();
|
||||
} catch (err) {
|
||||
alert('Fehler beim Löschen: ' + err.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function ladeListe() {
|
||||
try {
|
||||
const data = await getTicketEntries();
|
||||
renderTicketsTabelle(data.entries);
|
||||
} catch (err) {
|
||||
tableWrapper.innerHTML = `<p>❌ ${escapeHtml(err.message)}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------
|
||||
// Ticket anlegen
|
||||
// -----------------------------------------------------------
|
||||
document.getElementById('addTicketForm').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const titel = document.getElementById('in-titel').value.trim();
|
||||
const beschreibung = document.getElementById('in-beschreibung').value.trim();
|
||||
|
||||
if (!titel || !beschreibung) return;
|
||||
|
||||
try {
|
||||
await addTicketEntry(titel, beschreibung);
|
||||
e.target.reset();
|
||||
await ladeListe();
|
||||
} catch (err) {
|
||||
alert('Fehler beim Speichern: ' + err.message);
|
||||
}
|
||||
});
|
||||
|
||||
// -----------------------------------------------------------
|
||||
// Initialisierung + zyklische Aktualisierung
|
||||
// -----------------------------------------------------------
|
||||
ladeListe();
|
||||
setInterval(ladeListe, POLL_INTERVAL_MS);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@keyframes statusChanged {
|
||||
0% {
|
||||
background-color: #fff3cd;
|
||||
}
|
||||
|
||||
100% {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.status-row-changed {
|
||||
animation: statusChanged 2.5s ease-out;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,97 @@
|
||||
<div class="arbeitsberichte-wrapper">
|
||||
<div class="table-container" style="margin-top: 30px;">
|
||||
<h3>Ticket Verwaltung</h3>
|
||||
<div id="tableWrapper">
|
||||
<p>Lade Daten...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/js/AjaxMethods.js"></script>
|
||||
<script src="/js/AjaxRenderTables.js"></script>
|
||||
<script src="/js/ajaxApi/TicketControlAjaxMethods.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const tableWrapper = document.getElementById('tableWrapper');
|
||||
const allowedStatuses = ['OFFEN', 'DEPLOYED', 'ABGELEHNT', 'ANGENOMMEN'];
|
||||
|
||||
function renderTicketControTabelle(entries) {
|
||||
if (!entries || entries.length === 0) {
|
||||
tableWrapper.innerHTML = '<p>Keine Tickets vorhanden.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = `
|
||||
<table class="db-table mobile-responsive-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Titel</th><th>Beschreibung</th><th>Status</th><th>Ersteller</th>
|
||||
<th style="width: 220px;">Aktion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
`;
|
||||
for (const e of entries) {
|
||||
const options = allowedStatuses.map(s =>
|
||||
`<option value="${escapeHtml(s)}" ${e.STATUS === s ? 'selected' : ''}>${escapeHtml(s)}</option>`
|
||||
).join('');
|
||||
|
||||
html += `
|
||||
<tr data-id="${escapeHtml(e.ID)}">
|
||||
<td data-label="Titel">${escapeHtml(e.TITEL)}</td>
|
||||
<td data-label="Beschreibung">${escapeHtml(e.BESCHREIBUNG)}</td>
|
||||
<td data-label="Status">${escapeHtml(e.STATUS)}</td>
|
||||
<td data-label="Ersteller">${escapeHtml(e.ERSTELLER)}</td>
|
||||
<td data-label="Aktion">
|
||||
<div style="display:flex; flex-direction:column; gap:6px; align-items:stretch;">
|
||||
<select class="status-select" data-id="${escapeHtml(e.ID)}">
|
||||
${options}
|
||||
</select>
|
||||
<button type="button" class="btn-edit" data-id="${escapeHtml(e.ID)}">Speichern</button>
|
||||
<button type="button" class="btn-delete" data-id="${escapeHtml(e.ID)}" style="margin-top:3pt;">Löschen</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>`;
|
||||
}
|
||||
html += `</tbody></table>`;
|
||||
tableWrapper.innerHTML = html;
|
||||
|
||||
tableWrapper.querySelectorAll('.btn-edit').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
const id = btn.dataset.id;
|
||||
const select = tableWrapper.querySelector(
|
||||
`.status-select[data-id="${id}"]`);
|
||||
try {
|
||||
await editTicketControEntry(id, select.value);
|
||||
await ladeListe();
|
||||
} catch (err) {
|
||||
alert('Fehler beim Speichern: ' + err.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
tableWrapper.querySelectorAll('.btn-delete').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
if (!confirm('Möchtest du dieses Ticket wirklich löschen?')) return;
|
||||
try {
|
||||
await deleteTicketControEntry(btn.dataset.id);
|
||||
await ladeListe();
|
||||
} catch (err) {
|
||||
alert('Fehler beim Löschen: ' + err.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function ladeListe() {
|
||||
try {
|
||||
const data = await getTicketControEntries();
|
||||
renderTicketControTabelle(data.entries);
|
||||
} catch (err) {
|
||||
tableWrapper.innerHTML = `<p>❌ ${escapeHtml(err.message)}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
ladeListe();
|
||||
});
|
||||
</script>
|
||||
+163
-178
@@ -1,201 +1,191 @@
|
||||
<?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">
|
||||
<form id="addForm" class="vergabe-grid">
|
||||
|
||||
<div class="form-group">
|
||||
<label>Wann ausgeliehen</label>
|
||||
<input type="date" name="wann" value="<?= date('Y-m-d') ?>" required>
|
||||
<input type="date" name="wann" id="in-wann" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Rückgabe</label>
|
||||
<input type="date" name="rueckgabe" value="<?= date('Y-m-d') ?>" required>
|
||||
<input type="date" name="rueckgabe" id="in-rueckgabe" 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">
|
||||
<input type="text" 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">
|
||||
<input type="text" 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>
|
||||
<button type="submit" 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; ?>
|
||||
<form id="searchForm" class="vergabe-grid">
|
||||
|
||||
<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">
|
||||
<input type="text" id="search-wer-input" 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">
|
||||
<input type="text" id="search-was-input" 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; ?>
|
||||
<button type="submit" class="btn-submit-vergabe" style="margin: 0; flex-grow: 1;">Suchen</button>
|
||||
<button type="button" id="resetSearch" class="btn-delete" style="margin: 0;">Zurücksetzen</button>
|
||||
</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>
|
||||
<h3 id="tableHeading">Aktuelle Vergaben</h3>
|
||||
<div id="tableWrapper">
|
||||
<p>Lade Daten...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($entries)): ?>
|
||||
<script src="/js/AjaxMethods.js"></script>
|
||||
<script src="/js/ajaxApi/VergabeAjaxMethods.js"></script>
|
||||
<script src="/js/AjaxRenderTables.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const tableWrapper = document.getElementById('tableWrapper');
|
||||
const tableHeading = document.getElementById('tableHeading');
|
||||
|
||||
let aktuellSearchWer = '';
|
||||
let aktuellSearchWas = '';
|
||||
|
||||
// -----------------------------------------------------------
|
||||
// Tabelle rendern (eigene Render-Funktion, da "Zurückgeben"
|
||||
// eine andere Aktion ist als das generische Löschen)
|
||||
// -----------------------------------------------------------
|
||||
function renderVergabeTabelle(entries) {
|
||||
if (!entries || entries.length === 0) {
|
||||
tableWrapper.innerHTML = '<p>Keine Einträge vorhanden.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = `
|
||||
<table class="db-table mobile-responsive-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Was</th>
|
||||
<th>Wer</th>
|
||||
<th>Wann ausgeliehen</th>
|
||||
<th>Rückgabe</th>
|
||||
<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>
|
||||
`;
|
||||
for (const e of entries) {
|
||||
html += `
|
||||
<tr data-id="${escapeHtml(e.ID)}">
|
||||
<td data-label="Was">${escapeHtml(e.WAS)}</td>
|
||||
<td data-label="Wer">${escapeHtml(e.WER)}</td>
|
||||
<td data-label="Wann">${escapeHtml(e.WANN)}</td>
|
||||
<td data-label="Rückgabe">${escapeHtml(e.RUECKGABE)}</td>
|
||||
<td data-label="Aktion">
|
||||
<button type="button" class="btn-delete" data-id="${escapeHtml(e.ID)}">Löschen</button>
|
||||
</td>
|
||||
</tr>`;
|
||||
}
|
||||
html += `</tbody></table>`;
|
||||
tableWrapper.innerHTML = html;
|
||||
|
||||
<script>
|
||||
// Parameter `submitOnSelect` hinzugefügt (Standard: false)
|
||||
tableWrapper.querySelectorAll('.btn-delete').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
if (!confirm('Möchtest du diesen Eintrag wirklich löschen?')) return;
|
||||
try {
|
||||
await returnVergabeEntry(btn.dataset.id);
|
||||
await ladeListe();
|
||||
} catch (err) {
|
||||
alert('Fehler beim Löschen: ' + err.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function aktualisiereUeberschrift() {
|
||||
if (aktuellSearchWer && aktuellSearchWas) {
|
||||
tableHeading.textContent = `Gefiltert nach: ${aktuellSearchWer} ➔ ${aktuellSearchWas}`;
|
||||
} else if (aktuellSearchWer) {
|
||||
tableHeading.textContent = `Vergaben für: ${aktuellSearchWer}`;
|
||||
} else if (aktuellSearchWas) {
|
||||
tableHeading.textContent = `Ausgeliehene Gegenstände: ${aktuellSearchWas}`;
|
||||
} else {
|
||||
tableHeading.textContent = 'Aktuelle Vergaben';
|
||||
}
|
||||
}
|
||||
|
||||
async function ladeListe() {
|
||||
try {
|
||||
const data = await getVergabeEntries(aktuellSearchWer, aktuellSearchWas);
|
||||
aktualisiereUeberschrift();
|
||||
renderVergabeTabelle(data.entries);
|
||||
} catch (err) {
|
||||
tableWrapper.innerHTML = `<p>❌ ${escapeHtml(err.message)}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------
|
||||
// Eintrag hinzufügen
|
||||
// -----------------------------------------------------------
|
||||
document.getElementById('addForm').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const wann = document.getElementById('in-wann').value;
|
||||
const rueckgabe = document.getElementById('in-rueckgabe').value;
|
||||
const wer = document.getElementById('wer-input').value.trim();
|
||||
const was = document.getElementById('was-input').value.trim();
|
||||
|
||||
if (!wann || !rueckgabe || !wer || !was) return;
|
||||
|
||||
try {
|
||||
await addVergabeEntry(was, wer, wann, rueckgabe);
|
||||
e.target.reset();
|
||||
document.getElementById('in-wann').value = new Date().toISOString().slice(0, 10);
|
||||
document.getElementById('in-rueckgabe').value = new Date().toISOString().slice(0, 10);
|
||||
await ladeVorschlaege(); // neue WAS/WER-Werte könnten dazugekommen sein
|
||||
await ladeListe();
|
||||
} catch (err) {
|
||||
alert('Fehler beim Speichern: ' + err.message);
|
||||
}
|
||||
});
|
||||
|
||||
// -----------------------------------------------------------
|
||||
// Suche
|
||||
// -----------------------------------------------------------
|
||||
document.getElementById('searchForm').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
aktuellSearchWer = document.getElementById('search-wer-input').value.trim();
|
||||
aktuellSearchWas = document.getElementById('search-was-input').value.trim();
|
||||
await ladeListe();
|
||||
});
|
||||
|
||||
document.getElementById('resetSearch').addEventListener('click', async () => {
|
||||
aktuellSearchWer = '';
|
||||
aktuellSearchWas = '';
|
||||
document.getElementById('search-wer-input').value = '';
|
||||
document.getElementById('search-was-input').value = '';
|
||||
await ladeListe();
|
||||
});
|
||||
|
||||
// -----------------------------------------------------------
|
||||
// Autocomplete (unverändert zu deiner Logik, nur Datenquelle
|
||||
// kommt jetzt per AJAX statt server-seitig eingebettet)
|
||||
// -----------------------------------------------------------
|
||||
function setupAutocomplete(inputId, suggestionsId, data, submitOnSelect = false) {
|
||||
const input = document.getElementById(inputId);
|
||||
const box = document.getElementById(suggestionsId);
|
||||
@@ -225,27 +215,20 @@ $existingWer = array_keys($existingWer);
|
||||
}
|
||||
|
||||
function updateActive() {
|
||||
[...box.children].forEach((el, i) => {
|
||||
el.classList.toggle('active', i === activeIndex);
|
||||
[...box.children].forEach((el, i) => el.classList.toggle('active', i === activeIndex));
|
||||
if (activeIndex >= 0) box.children[activeIndex].scrollIntoView({
|
||||
block: 'nearest'
|
||||
});
|
||||
if (activeIndex >= 0) {
|
||||
box.children[activeIndex].scrollIntoView({
|
||||
block: 'nearest'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function selectItem(i) {
|
||||
if (items[i] !== undefined) {
|
||||
input.value = items[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.closest('form').dispatchEvent(new Event('submit', {
|
||||
cancelable: true
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,13 +238,11 @@ $existingWer = array_keys($existingWer);
|
||||
box.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
const filtered = data.filter(d => d.toLowerCase().includes(val));
|
||||
render(filtered);
|
||||
render(data.filter(d => d.toLowerCase().includes(val)));
|
||||
});
|
||||
|
||||
input.addEventListener('keydown', (e) => {
|
||||
if (box.style.display !== 'block') return;
|
||||
|
||||
if (e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
activeIndex = Math.min(activeIndex + 1, items.length - 1);
|
||||
@@ -274,26 +255,30 @@ $existingWer = array_keys($existingWer);
|
||||
if (activeIndex >= 0) {
|
||||
e.preventDefault();
|
||||
selectItem(activeIndex);
|
||||
} else {
|
||||
box.style.display = 'none';
|
||||
}
|
||||
} else if (e.key === 'Escape') {
|
||||
box.style.display = 'none';
|
||||
}
|
||||
} 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';
|
||||
}
|
||||
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) ?>);
|
||||
async function ladeVorschlaege() {
|
||||
const data = await getVergabeSuggestions();
|
||||
setupAutocomplete('was-input', 'was-suggestions', data.was);
|
||||
setupAutocomplete('wer-input', 'wer-suggestions', data.wer);
|
||||
setupAutocomplete('search-wer-input', 'search-wer-suggestions', data.wer, true);
|
||||
setupAutocomplete('search-was-input', 'search-was-suggestions', data.was, true);
|
||||
}
|
||||
|
||||
// 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);
|
||||
// -----------------------------------------------------------
|
||||
// Initialisierung
|
||||
// -----------------------------------------------------------
|
||||
document.getElementById('in-wann').value = new Date().toISOString().slice(0, 10);
|
||||
document.getElementById('in-rueckgabe').value = new Date().toISOString().slice(0, 10);
|
||||
|
||||
ladeVorschlaege();
|
||||
ladeListe();
|
||||
});
|
||||
</script>
|
||||
@@ -1,73 +1,27 @@
|
||||
<?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
|
||||
<div id="overtimeCard"
|
||||
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;">
|
||||
|
||||
<div style="margin-bottom: 20px;">
|
||||
<span style="color: #888; font-size: 0.8rem; font-weight: bold; text-transform: uppercase;">Aktueller
|
||||
Stand</span>
|
||||
<h2 style="margin: 5px 0 0 0; font-size: 2rem; color: <?= $gesamt_minuten >= 0 ? '#28a745' : '#dc3545' ?>;">
|
||||
<?= intdiv($gesamt_minuten, 60) ?>h <?= abs($gesamt_minuten % 60) ?>min
|
||||
</h2>
|
||||
<h2 id="standAnzeige" style="margin: 5px 0 0 0; font-size: 2rem;">Lädt...</h2>
|
||||
</div>
|
||||
<?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">
|
||||
|
||||
<form id="overtimeForm" style="display: none; gap: 15px; flex-wrap: wrap; align-items: flex-end;">
|
||||
<div style="display: flex; flex-direction: column; gap: 5px;">
|
||||
<label style="font-size: 0.85rem; font-weight: 600; color: inherit;">Datum</label>
|
||||
<input type="date" name="ot_date" value="<?= date('Y-m-d') ?>" required
|
||||
<input type="date" id="ot-date" required
|
||||
style="padding: 8px; border: 1px solid rgba(150,150,150,0.3); border-radius: 4px; background: rgba(150,150,150,0.1); color: inherit;">
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; gap: 5px;">
|
||||
<label style="font-size: 0.85rem; font-weight: 600; color: inherit;">Minuten Abbau</label>
|
||||
<input type="number" name="ot_minutes" placeholder="480" required
|
||||
<label style="font-size: 0.85rem; font-weight: 600; color: inherit;">Minuten</label>
|
||||
<input type="number" id="ot-minutes" placeholder="480" required
|
||||
style="width: 100px; padding: 8px; border: 1px solid rgba(150,150,150,0.3); border-radius: 4px; background: rgba(150,150,150,0.1); color: inherit;">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label style="font-size: 0.85rem; font-weight: 600; color: inherit;">Grund</label>
|
||||
|
||||
<select name="ot_reason" required>
|
||||
|
||||
<select id="ot-reason" required>
|
||||
<option value="">Bitte wählen</option>
|
||||
<option value="Abbau">Abbau</option>
|
||||
<option value="Aufbau">Aufbau</option>
|
||||
@@ -79,6 +33,56 @@ $gesamt_minuten = $minuten_berichte - $minuten_abbau + $minuten_aufbau;
|
||||
Eintrag speichern
|
||||
</button>
|
||||
</form>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/js/AjaxMethods.js"></script>
|
||||
<script src="/js/AjaxRenderTables.js"></script>
|
||||
<script src="/js/ajaxApi/ZeiterfassungAjaxMethods.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const standAnzeige = document.getElementById('standAnzeige');
|
||||
const overtimeForm = document.getElementById('overtimeForm');
|
||||
|
||||
function formatStand(gesamtMinuten) {
|
||||
const stunden = Math.trunc(gesamtMinuten / 60);
|
||||
const minuten = Math.abs(gesamtMinuten % 60);
|
||||
return `${stunden}h ${minuten}min`;
|
||||
}
|
||||
|
||||
async function ladeStand() {
|
||||
try {
|
||||
const data = await getOvertimeStats();
|
||||
|
||||
standAnzeige.textContent = formatStand(data.gesamtMinuten);
|
||||
standAnzeige.style.color = data.gesamtMinuten >= 0 ? '#28a745' : '#dc3545';
|
||||
|
||||
overtimeForm.style.display = data.istBerechtigt ? 'flex' : 'none';
|
||||
|
||||
} catch (err) {
|
||||
standAnzeige.textContent = 'Fehler beim Laden';
|
||||
console.error(err.message);
|
||||
}
|
||||
}
|
||||
|
||||
overtimeForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const datum = document.getElementById('ot-date').value;
|
||||
const minuten = document.getElementById('ot-minutes').value;
|
||||
const grund = document.getElementById('ot-reason').value;
|
||||
|
||||
try {
|
||||
await addOvertimeEntry(datum, minuten, grund);
|
||||
e.target.reset();
|
||||
document.getElementById('ot-date').value = new Date().toISOString().slice(0, 10);
|
||||
await ladeStand();
|
||||
} catch (err) {
|
||||
alert('Fehler beim Speichern: ' + err.message);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('ot-date').value = new Date().toISOString().slice(0, 10);
|
||||
ladeStand();
|
||||
});
|
||||
</script>
|
||||
+30
-6
@@ -880,6 +880,17 @@ body.dark-mode .user-table .btn-primary:hover {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.btn-edit {
|
||||
background-color: #32b844;
|
||||
color: white;
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
|
||||
/* ===== Toggle Switch ===== */
|
||||
|
||||
.toggle-card {
|
||||
@@ -981,8 +992,7 @@ body.dark-mode .info-value {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
/* table-layout: fixed; */
|
||||
/* sorgt für feste Spaltenbreite */
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
.db-table thead {
|
||||
@@ -996,10 +1006,13 @@ body.dark-mode .info-value {
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--card-border);
|
||||
|
||||
/* Wichtig für gleiche Größe */
|
||||
/* overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap; */
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.db-table td:nth-child(2) {
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.db-table tr:hover {
|
||||
@@ -1436,4 +1449,15 @@ body.dark-mode .berichte-form input:focus {
|
||||
body.dark-mode .autocomplete-suggestions div:hover,
|
||||
body.dark-mode .autocomplete-suggestions div.active {
|
||||
background: rgba(0, 123, 255, 0.25);
|
||||
}
|
||||
|
||||
.register-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.register-form input {
|
||||
margin-bottom: 0;
|
||||
/* gap übernimmt jetzt den Abstand, kein doppeltes Spacing mehr */
|
||||
}
|
||||
Vendored
-22
@@ -1,22 +0,0 @@
|
||||
<?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
@@ -1,579 +0,0 @@
|
||||
<?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
@@ -1,396 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
Vendored
-21
@@ -1,21 +0,0 @@
|
||||
|
||||
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
@@ -1,10 +0,0 @@
|
||||
<?php
|
||||
|
||||
// autoload_classmap.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
||||
);
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
// autoload_namespaces.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
);
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
<?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'),
|
||||
);
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
<?php
|
||||
|
||||
// autoload_real.php @generated by Composer
|
||||
|
||||
class ComposerAutoloaderInit77f94e6fc6e485edf380978f04d2c8cd
|
||||
{
|
||||
private static $loader;
|
||||
|
||||
public static function loadClassLoader($class)
|
||||
{
|
||||
if ('Composer\Autoload\ClassLoader' === $class) {
|
||||
require __DIR__ . '/ClassLoader.php';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Composer\Autoload\ClassLoader
|
||||
*/
|
||||
public static function getLoader()
|
||||
{
|
||||
if (null !== self::$loader) {
|
||||
return self::$loader;
|
||||
}
|
||||
|
||||
require __DIR__ . '/platform_check.php';
|
||||
|
||||
spl_autoload_register(array('ComposerAutoloaderInit77f94e6fc6e485edf380978f04d2c8cd', 'loadClassLoader'), true, true);
|
||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInit77f94e6fc6e485edf380978f04d2c8cd', 'loadClassLoader'));
|
||||
|
||||
require __DIR__ . '/autoload_static.php';
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInit77f94e6fc6e485edf380978f04d2c8cd::getInitializer($loader));
|
||||
|
||||
$loader->register(true);
|
||||
|
||||
return $loader;
|
||||
}
|
||||
}
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
<?php
|
||||
|
||||
// autoload_static.php @generated by Composer
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
class ComposerStaticInit77f94e6fc6e485edf380978f04d2c8cd
|
||||
{
|
||||
public static $prefixLengthsPsr4 = array (
|
||||
'M' =>
|
||||
array (
|
||||
'MaxMind\\Db\\' => 11,
|
||||
),
|
||||
'H' =>
|
||||
array (
|
||||
'Hydrax\\Webroot\\' => 15,
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixDirsPsr4 = array (
|
||||
'MaxMind\\Db\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/maxmind-db/reader/src/MaxMind/Db',
|
||||
),
|
||||
'Hydrax\\Webroot\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/../..' . '/src',
|
||||
),
|
||||
);
|
||||
|
||||
public static $classMap = array (
|
||||
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
||||
);
|
||||
|
||||
public static function getInitializer(ClassLoader $loader)
|
||||
{
|
||||
return \Closure::bind(function () use ($loader) {
|
||||
$loader->prefixLengthsPsr4 = ComposerStaticInit77f94e6fc6e485edf380978f04d2c8cd::$prefixLengthsPsr4;
|
||||
$loader->prefixDirsPsr4 = ComposerStaticInit77f94e6fc6e485edf380978f04d2c8cd::$prefixDirsPsr4;
|
||||
$loader->classMap = ComposerStaticInit77f94e6fc6e485edf380978f04d2c8cd::$classMap;
|
||||
|
||||
}, null, ClassLoader::class);
|
||||
}
|
||||
}
|
||||
-73
@@ -1,73 +0,0 @@
|
||||
{
|
||||
"packages": [
|
||||
{
|
||||
"name": "maxmind-db/reader",
|
||||
"version": "v1.13.1",
|
||||
"version_normalized": "1.13.1.0",
|
||||
"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)"
|
||||
},
|
||||
"time": "2025-11-21T22:24:26+00:00",
|
||||
"type": "library",
|
||||
"installation-source": "dist",
|
||||
"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"
|
||||
},
|
||||
"install-path": "../maxmind-db/reader"
|
||||
}
|
||||
],
|
||||
"dev": true,
|
||||
"dev-package-names": []
|
||||
}
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
<?php return array(
|
||||
'root' => array(
|
||||
'name' => 'hydrax/webroot',
|
||||
'pretty_version' => 'dev-main',
|
||||
'version' => 'dev-main',
|
||||
'reference' => '75802de1424185c86220a17a734d7a4fa457b5f2',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
'dev' => true,
|
||||
),
|
||||
'versions' => array(
|
||||
'hydrax/webroot' => array(
|
||||
'pretty_version' => 'dev-main',
|
||||
'version' => 'dev-main',
|
||||
'reference' => '75802de1424185c86220a17a734d7a4fa457b5f2',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'maxmind-db/reader' => array(
|
||||
'pretty_version' => 'v1.13.1',
|
||||
'version' => '1.13.1.0',
|
||||
'reference' => '2194f58d0f024ce923e685cdf92af3daf9951908',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../maxmind-db/reader',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
),
|
||||
);
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
// platform_check.php @generated by Composer
|
||||
|
||||
$issues = array();
|
||||
|
||||
if (!(PHP_VERSION_ID >= 70200)) {
|
||||
$issues[] = 'Your Composer dependencies require a PHP version ">= 7.2.0". You are running ' . PHP_VERSION . '.';
|
||||
}
|
||||
|
||||
if ($issues) {
|
||||
if (!headers_sent()) {
|
||||
header('HTTP/1.1 500 Internal Server Error');
|
||||
}
|
||||
if (!ini_get('display_errors')) {
|
||||
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
|
||||
} elseif (!headers_sent()) {
|
||||
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
|
||||
}
|
||||
}
|
||||
throw new \RuntimeException(
|
||||
'Composer detected issues in your platform: ' . implode(' ', $issues)
|
||||
);
|
||||
}
|
||||
-261
@@ -1,261 +0,0 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
1.13.1 (2025-11-21)
|
||||
-------------------
|
||||
|
||||
* First PIE release. No other changes.
|
||||
|
||||
1.13.0 (2025-11-20)
|
||||
-------------------
|
||||
|
||||
* A redundant `filesize()` call in the reader's constructor was removed.
|
||||
Pull request by Pavel Djundik. GitHub #189.
|
||||
|
||||
1.12.1 (2025-05-05)
|
||||
-------------------
|
||||
|
||||
* The C extension now checks that the database metadata lookup was
|
||||
successful.
|
||||
|
||||
1.12.0 (2024-11-14)
|
||||
-------------------
|
||||
|
||||
* Improve the error handling when the user tries to open a directory
|
||||
with the pure PHP reader.
|
||||
* Improve the typehints on arrays in the PHPDocs.
|
||||
|
||||
1.11.1 (2023-12-01)
|
||||
-------------------
|
||||
|
||||
* Resolve warnings when compiling the C extension.
|
||||
* Fix various type issues detected by PHPStan level. Pull request by
|
||||
LauraTaylorUK. GitHub #160.
|
||||
|
||||
1.11.0 (2021-10-18)
|
||||
-------------------
|
||||
|
||||
* Replace runtime define of a constant to facilitate opcache preloading.
|
||||
Reported by vedadkajtaz. GitHub #134.
|
||||
* Resolve minor issue found by the Clang static analyzer in the C
|
||||
extension.
|
||||
|
||||
1.10.1 (2021-04-14)
|
||||
-------------------
|
||||
|
||||
* Fix a `TypeError` exception in the pure PHP reader when using large
|
||||
databases on 32-bit PHP builds with the `bcmath` extension. Reported
|
||||
by dodo1708. GitHub #124.
|
||||
|
||||
1.10.0 (2021-02-09)
|
||||
-------------------
|
||||
|
||||
* When using the pure PHP reader, unsigned integers up to PHP_MAX_INT
|
||||
will now be integers in PHP rather than strings. Previously integers
|
||||
greater than 2^24 on 32-bit platforms and 2^56 on 64-bit platforms
|
||||
would be strings due to the use of `gmp` or `bcmath` to decode them.
|
||||
Reported by Alejandro Celaya. GitHub #119.
|
||||
|
||||
1.9.0 (2021-01-07)
|
||||
------------------
|
||||
|
||||
* The `maxminddb` extension is now buildable on Windows. Pull request
|
||||
by Jan Ehrhardt. GitHub #115.
|
||||
|
||||
1.8.0 (2020-10-01)
|
||||
------------------
|
||||
|
||||
* Fixes for PHP 8.0. Pull Request by Remi Collet. GitHub #108.
|
||||
|
||||
1.7.0 (2020-08-07)
|
||||
------------------
|
||||
|
||||
* IMPORTANT: PHP 7.2 or greater is now required.
|
||||
* The extension no longer depends on the pure PHP classes in
|
||||
`maxmind-db/reader`. You can use it independently.
|
||||
* Type hints have been added to both the pure PHP implementation
|
||||
and the extension.
|
||||
* The `metadata` method on the reader now returns a new copy of the
|
||||
metadata object rather than the actual object used by the reader.
|
||||
* Work around PHP `is_readable()` bug. Reported by Ben Roberts. GitHub
|
||||
#92.
|
||||
* This is the first release of the extension as a PECL package.
|
||||
GitHub #34.
|
||||
|
||||
1.6.0 (2019-12-19)
|
||||
------------------
|
||||
|
||||
* 1.5.0 and 1.5.1 contained a possible memory corruptions when using
|
||||
`getWithPrefixLen`. This has been fixed. Reported by proton-ab.
|
||||
GitHub #96.
|
||||
* The `composer.json` file now conflicts with all versions of the
|
||||
`maxminddb` C extension less than the Composer version. This is to
|
||||
reduce the chance of having an older, conflicting version of the
|
||||
extension installed. You will need to upgrade the extension before
|
||||
running `composer update`. Pull request by Benoît Burnichon. GitHub
|
||||
#97.
|
||||
|
||||
1.5.1 (2019-12-12)
|
||||
------------------
|
||||
|
||||
* Minor performance improvements.
|
||||
* Make tests pass with older versions of libmaxminddb. PR by Remi
|
||||
Collet. GitHub #90.
|
||||
* Test enhancements. PR by Chun-Sheng, Li. GitHub #91.
|
||||
|
||||
1.5.0 (2019-09-30)
|
||||
------------------
|
||||
|
||||
* PHP 5.6 or greater is now required.
|
||||
* The C extension now supports PHP 8. Pull request by John Boehr.
|
||||
GitHub #87.
|
||||
* A new method, `getWithPrefixLen`, was added to the `Reader` class.
|
||||
This method returns an array containing the record and the prefix
|
||||
length for that record. GitHub #89.
|
||||
|
||||
1.4.1 (2019-01-04)
|
||||
------------------
|
||||
|
||||
* The `maxminddb` extension now returns a string when a `uint32`
|
||||
value is greater than `LONG_MAX`. Previously, the value would
|
||||
overflow. This generally only affects 32-bit machines. Reported
|
||||
by Remi Collet. GitHub #79.
|
||||
* For `uint64` values, the `maxminddb` extension now returns an
|
||||
integer rather than a string when the value is less than or equal
|
||||
to `LONG_MAX`. This more closely matches the behavior of the pure
|
||||
PHP reader.
|
||||
|
||||
1.4.0 (2018-11-20)
|
||||
------------------
|
||||
|
||||
* The `maxminddb` extension now has the arginfo when using reflection.
|
||||
PR by Remi Collet. GitHub #75.
|
||||
* The `maxminddb` extension now provides `MINFO()` function that
|
||||
displays the extension version and the libmaxminddb version. PR by
|
||||
Remi Collet. GitHub #74.
|
||||
* The `maxminddb` `configure` script now uses `pkg-config` when
|
||||
available to get libmaxmindb build info. PR by Remi Collet.
|
||||
GitHub #73.
|
||||
* The pure PHP reader now correctly decodes integers on 32-bit platforms.
|
||||
Previously, large integers would overflow. Reported by Remi Collet.
|
||||
GitHub #77.
|
||||
* There are small performance improvements for the pure PHP reader.
|
||||
|
||||
1.3.0 (2018-02-21)
|
||||
------------------
|
||||
|
||||
* IMPORTANT: The `maxminddb` extension now obeys `open_basedir`. If
|
||||
`open_basedir` is set, you _must_ store the database within the
|
||||
specified directory. Placing the file outside of this directory
|
||||
will result in an exception. Please test your integration before
|
||||
upgrading the extension. This does not affect the pure PHP
|
||||
implementation, which has always had this restriction. Reported
|
||||
by Benoît Burnichon. GitHub #61.
|
||||
* A custom `autoload.php` file is provided for installations without
|
||||
Composer. GitHub #56.
|
||||
|
||||
1.2.0 (2017-10-27)
|
||||
------------------
|
||||
|
||||
* PHP 5.4 or greater is now required.
|
||||
* The `Reader` class for the `maxminddb` extension is no longer final.
|
||||
This was change to match the behavior of the pure PHP class.
|
||||
Reported and fixed by venyii. GitHub #52 & #54.
|
||||
|
||||
1.1.3 (2017-01-19)
|
||||
------------------
|
||||
|
||||
* Fix incorrect version in `ext/php_maxminddb.h`. GitHub #48.
|
||||
|
||||
1.1.2 (2016-11-22)
|
||||
------------------
|
||||
|
||||
* Searching for database metadata only occurs within the last 128KB
|
||||
(128 * 1024 bytes) of the file, speeding detection of corrupt
|
||||
datafiles. Reported by Eric Teubert. GitHub #42.
|
||||
* Suggest relevant extensions when installing with Composer. GitHub #37.
|
||||
|
||||
1.1.1 (2016-09-15)
|
||||
------------------
|
||||
|
||||
* Development files were added to the `.gitattributes` as `export-ignore` so
|
||||
that they are not part of the Composer release. Pull request by Michele
|
||||
Locati. GitHub #39.
|
||||
|
||||
1.1.0 (2016-01-04)
|
||||
------------------
|
||||
|
||||
* The MaxMind DB extension now supports PHP 7. Pull request by John Boehr.
|
||||
GitHub #27.
|
||||
|
||||
1.0.3 (2015-03-13)
|
||||
------------------
|
||||
|
||||
* All uses of `strlen` were removed. This should prevent issues in situations
|
||||
where the function is overloaded or otherwise broken.
|
||||
|
||||
1.0.2 (2015-01-19)
|
||||
------------------
|
||||
|
||||
* Previously the MaxMind DB extension would cause a segfault if the Reader
|
||||
object's destructor was called without first having called the constructor.
|
||||
(Reported by Matthias Saou & Juan Peri. GitHub #20.)
|
||||
|
||||
1.0.1 (2015-01-12)
|
||||
------------------
|
||||
|
||||
* In the last several releases, the version number in the extension was
|
||||
incorrect. This release is being done to correct it. No other code changes
|
||||
are included.
|
||||
|
||||
1.0.0 (2014-09-22)
|
||||
------------------
|
||||
|
||||
* First production release.
|
||||
* In the pure PHP reader, a string length test after `fread()` was replaced
|
||||
with the difference between the start pointer and the end pointer. This
|
||||
provided a 15% speed increase.
|
||||
|
||||
0.3.3 (2014-09-15)
|
||||
------------------
|
||||
|
||||
* Clarified behavior of 128-bit type in documentation.
|
||||
* Updated phpunit and fixed some test breakage from the newer version.
|
||||
|
||||
0.3.2 (2014-09-10)
|
||||
------------------
|
||||
|
||||
* Fixed invalid reference to global class RuntimeException from namespaced
|
||||
code. Fixed by Steven Don. GitHub issue #15.
|
||||
* Additional documentation of `Metadata` class as well as misc. documentation
|
||||
cleanup.
|
||||
|
||||
0.3.1 (2014-05-01)
|
||||
------------------
|
||||
|
||||
* The API now works when `mbstring.func_overload` is set.
|
||||
* BCMath is no longer required. If the decoder encounters a big integer,
|
||||
it will try to use GMP and then BCMath. If both of those fail, it will
|
||||
throw an exception. No databases released by MaxMind currently use big
|
||||
integers.
|
||||
* The API now officially supports HHVM when using the pure PHP reader.
|
||||
|
||||
0.3.0 (2014-02-19)
|
||||
------------------
|
||||
|
||||
* This API is now licensed under the Apache License, Version 2.0.
|
||||
* The code for the C extension was cleaned up, fixing several potential
|
||||
issues.
|
||||
|
||||
0.2.0 (2013-10-21)
|
||||
------------------
|
||||
|
||||
* Added optional C extension for using libmaxminddb in place of the pure PHP
|
||||
reader.
|
||||
* Significantly improved error handling in pure PHP reader.
|
||||
* Improved performance for IPv4 lookups in an IPv6 database.
|
||||
|
||||
0.1.0 (2013-07-16)
|
||||
------------------
|
||||
|
||||
* Initial release
|
||||
-202
@@ -1,202 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-214
@@ -1,214 +0,0 @@
|
||||
# MaxMind DB Reader PHP API #
|
||||
|
||||
## Description ##
|
||||
|
||||
This is the PHP API for reading MaxMind DB files. MaxMind DB is a binary file
|
||||
format that stores data indexed by IP address subnets (IPv4 or IPv6).
|
||||
|
||||
## Installation ##
|
||||
|
||||
### C Extension (Recommended for Performance) ###
|
||||
|
||||
For significantly faster IP lookups, we recommend installing the C extension via
|
||||
[PIE](https://github.com/php/pie):
|
||||
|
||||
```bash
|
||||
pie install maxmind-db/reader-ext
|
||||
```
|
||||
|
||||
The C extension requires the [libmaxminddb](https://github.com/maxmind/libmaxminddb)
|
||||
C library. See the [installation instructions](https://github.com/maxmind/MaxMind-DB-Reader-php-ext#prerequisites)
|
||||
for your platform.
|
||||
|
||||
### Pure PHP (No Compilation Required) ###
|
||||
|
||||
If you prefer not to compile a C extension or need maximum portability, you can
|
||||
install the pure PHP implementation with [Composer](https://getcomposer.org/).
|
||||
|
||||
### Download Composer ###
|
||||
|
||||
To download Composer, run in the root directory of your project:
|
||||
|
||||
```bash
|
||||
curl -sS https://getcomposer.org/installer | php
|
||||
```
|
||||
|
||||
You should now have the file `composer.phar` in your project directory.
|
||||
|
||||
### Install Dependencies ###
|
||||
|
||||
Run in your project root:
|
||||
|
||||
```
|
||||
php composer.phar require maxmind-db/reader:^1.13.1
|
||||
```
|
||||
|
||||
You should now have the files `composer.json` and `composer.lock` as well as
|
||||
the directory `vendor` in your project directory. If you use a version control
|
||||
system, `composer.json` should be added to it.
|
||||
|
||||
### Require Autoloader ###
|
||||
|
||||
After installing the dependencies, you need to require the Composer autoloader
|
||||
from your code:
|
||||
|
||||
```php
|
||||
require 'vendor/autoload.php';
|
||||
```
|
||||
|
||||
## Installation (Standalone) ##
|
||||
|
||||
If you don't want to use Composer for some reason, a custom
|
||||
`autoload.php` is provided for you in the project root. To use the
|
||||
library, simply include that file,
|
||||
|
||||
```php
|
||||
require('/path/to/MaxMind-DB-Reader-php/autoload.php');
|
||||
```
|
||||
|
||||
and then instantiate the reader class normally:
|
||||
|
||||
```php
|
||||
use MaxMind\Db\Reader;
|
||||
$reader = new Reader('example.mmdb');
|
||||
```
|
||||
|
||||
## Installation (RPM)
|
||||
|
||||
RPMs are available in the [official Fedora repository](https://apps.fedoraproject.org/packages/php-maxminddb).
|
||||
|
||||
To install on Fedora, run:
|
||||
|
||||
```bash
|
||||
dnf install php-maxminddb
|
||||
```
|
||||
|
||||
To install on CentOS or RHEL 7, first [enable the EPEL repository](https://fedoraproject.org/wiki/EPEL)
|
||||
and then run:
|
||||
|
||||
```bash
|
||||
yum install php-maxminddb
|
||||
```
|
||||
|
||||
Please note that these packages are *not* maintained by MaxMind.
|
||||
|
||||
## Usage ##
|
||||
|
||||
## Example ##
|
||||
|
||||
```php
|
||||
<?php
|
||||
require_once 'vendor/autoload.php';
|
||||
|
||||
use MaxMind\Db\Reader;
|
||||
|
||||
$ipAddress = '24.24.24.24';
|
||||
$databaseFile = 'GeoIP2-City.mmdb';
|
||||
|
||||
$reader = new Reader($databaseFile);
|
||||
|
||||
// get returns just the record for the IP address
|
||||
print_r($reader->get($ipAddress));
|
||||
|
||||
// getWithPrefixLen returns an array containing the record and the
|
||||
// associated prefix length for that record.
|
||||
print_r($reader->getWithPrefixLen($ipAddress));
|
||||
|
||||
$reader->close();
|
||||
```
|
||||
|
||||
## Optional PHP C Extension ##
|
||||
|
||||
MaxMind provides an optional C extension that is a drop-in replacement for
|
||||
`MaxMind\Db\Reader`. In order to use this extension, you must install the
|
||||
Reader API as described above and install the extension as described below. If
|
||||
you are using an autoloader, no changes to your code should be necessary.
|
||||
|
||||
### Installing Extension via PIE (Recommended) ###
|
||||
|
||||
We recommend installing the extension via [PIE](https://github.com/php/pie):
|
||||
|
||||
```bash
|
||||
pie install maxmind-db/reader-ext
|
||||
```
|
||||
|
||||
See the [extension repository](https://github.com/maxmind/MaxMind-DB-Reader-php-ext#prerequisites)
|
||||
for prerequisites including libmaxminddb installation instructions.
|
||||
|
||||
### Installing Extension via PECL (Legacy) ###
|
||||
|
||||
First install [libmaxminddb](https://github.com/maxmind/libmaxminddb) as
|
||||
described in its [README.md
|
||||
file](https://github.com/maxmind/libmaxminddb/blob/main/README.md#installing-from-a-tarball).
|
||||
After successfully installing libmaxmindb, you may install the extension
|
||||
from [PECL](https://pecl.php.net/package/maxminddb):
|
||||
|
||||
```
|
||||
pecl install maxminddb
|
||||
```
|
||||
|
||||
### Installing Extension from Source ###
|
||||
|
||||
Alternatively, you may install it from the source. To do so, run the following
|
||||
commands from the top-level directory of this distribution:
|
||||
|
||||
```
|
||||
cd ext
|
||||
phpize
|
||||
./configure
|
||||
make
|
||||
make test
|
||||
sudo make install
|
||||
```
|
||||
|
||||
You then must load your extension. The recommended method is to add the
|
||||
following to your `php.ini` file:
|
||||
|
||||
```
|
||||
extension=maxminddb.so
|
||||
```
|
||||
|
||||
Note: You may need to install the PHP development package on your OS such as
|
||||
php5-dev for Debian-based systems or php-devel for RedHat/Fedora-based ones.
|
||||
|
||||
## 128-bit Integer Support ##
|
||||
|
||||
The MaxMind DB format includes 128-bit unsigned integer as a type. Although
|
||||
no MaxMind-distributed database currently makes use of this type, both the
|
||||
pure PHP reader and the C extension support this type. The pure PHP reader
|
||||
requires gmp or bcmath to read databases with 128-bit unsigned integers.
|
||||
|
||||
The integer is currently returned as a hexadecimal string (prefixed with "0x")
|
||||
by the C extension and a decimal string (no prefix) by the pure PHP reader.
|
||||
Any change to make the reader implementations always return either a
|
||||
hexadecimal or decimal representation of the integer will NOT be considered a
|
||||
breaking change.
|
||||
|
||||
## Support ##
|
||||
|
||||
Please report all issues with this code using the [GitHub issue tracker](https://github.com/maxmind/MaxMind-DB-Reader-php/issues).
|
||||
|
||||
If you are having an issue with a MaxMind service that is not specific to the
|
||||
client API, please see [our support page](https://www.maxmind.com/en/support).
|
||||
|
||||
## Requirements ##
|
||||
|
||||
This library requires PHP 7.2 or greater.
|
||||
|
||||
The GMP or BCMath extension may be required to read some databases
|
||||
using the pure PHP API.
|
||||
|
||||
## Contributing ##
|
||||
|
||||
Patches and pull requests are encouraged. All code should follow the PSR-1 and
|
||||
PSR-2 style guidelines. Please include unit tests whenever possible.
|
||||
|
||||
## Versioning ##
|
||||
|
||||
The MaxMind DB Reader PHP API uses [Semantic Versioning](https://semver.org/).
|
||||
|
||||
## Copyright and License ##
|
||||
|
||||
This software is Copyright (c) 2014-2025 by MaxMind, Inc.
|
||||
|
||||
This is free software, licensed under the Apache License, Version 2.0.
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* PSR-4 autoloader implementation for the MaxMind\DB namespace.
|
||||
* First we define the 'mmdb_autoload' function, and then we register
|
||||
* it with 'spl_autoload_register' so that PHP knows to use it.
|
||||
*
|
||||
* @param mixed $class
|
||||
*/
|
||||
|
||||
/**
|
||||
* Automatically include the file that defines <code>class</code>.
|
||||
*
|
||||
* @param string $class
|
||||
* the name of the class to load
|
||||
*/
|
||||
function mmdb_autoload($class): void
|
||||
{
|
||||
/*
|
||||
* A project-specific mapping between the namespaces and where
|
||||
* they're located. By convention, we include the trailing
|
||||
* slashes. The one-element array here simply makes things easy
|
||||
* to extend in the future if (for example) the test classes
|
||||
* begin to use one another.
|
||||
*/
|
||||
$namespace_map = ['MaxMind\Db\\' => __DIR__ . '/src/MaxMind/Db/'];
|
||||
|
||||
foreach ($namespace_map as $prefix => $dir) {
|
||||
// First swap out the namespace prefix with a directory...
|
||||
$path = str_replace($prefix, $dir, $class);
|
||||
|
||||
// replace the namespace separator with a directory separator...
|
||||
$path = str_replace('\\', '/', $path);
|
||||
|
||||
// and finally, add the PHP file extension to the result.
|
||||
$path .= '.php';
|
||||
|
||||
// $path should now contain the path to a PHP file defining $class
|
||||
if (file_exists($path)) {
|
||||
include $path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
spl_autoload_register('mmdb_autoload');
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
{
|
||||
"name": "maxmind-db/reader",
|
||||
"description": "MaxMind DB Reader API",
|
||||
"keywords": ["database", "geoip", "geoip2", "geolocation", "maxmind"],
|
||||
"homepage": "https://github.com/maxmind/MaxMind-DB-Reader-php",
|
||||
"type": "library",
|
||||
"license": "Apache-2.0",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Gregory J. Oschwald",
|
||||
"email": "goschwald@maxmind.com",
|
||||
"homepage": "https://www.maxmind.com/"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"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)"
|
||||
},
|
||||
"conflict": {
|
||||
"ext-maxminddb": "<1.11.1 || >=2.0.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "3.*",
|
||||
"phpunit/phpunit": ">=8.0.0,<10.0.0",
|
||||
"squizlabs/php_codesniffer": "4.*",
|
||||
"phpstan/phpstan": "*"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"MaxMind\\Db\\": "src/MaxMind/Db"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"MaxMind\\Db\\Test\\Reader\\": "tests/MaxMind/Db/Test/Reader"
|
||||
}
|
||||
}
|
||||
}
|
||||
-40
@@ -1,40 +0,0 @@
|
||||
PHP_ARG_WITH(maxminddb,
|
||||
[Whether to enable the MaxMind DB Reader extension],
|
||||
[ --with-maxminddb Enable MaxMind DB Reader extension support])
|
||||
|
||||
PHP_ARG_ENABLE(maxminddb-debug, for MaxMind DB debug support,
|
||||
[ --enable-maxminddb-debug Enable MaxMind DB debug support], no, no)
|
||||
|
||||
if test $PHP_MAXMINDDB != "no"; then
|
||||
|
||||
AC_PATH_PROG(PKG_CONFIG, pkg-config, no)
|
||||
|
||||
AC_MSG_CHECKING(for libmaxminddb)
|
||||
if test -x "$PKG_CONFIG" && $PKG_CONFIG --exists libmaxminddb; then
|
||||
dnl retrieve build options from pkg-config
|
||||
if $PKG_CONFIG libmaxminddb --atleast-version 1.0.0; then
|
||||
LIBMAXMINDDB_INC=`$PKG_CONFIG libmaxminddb --cflags`
|
||||
LIBMAXMINDDB_LIB=`$PKG_CONFIG libmaxminddb --libs`
|
||||
LIBMAXMINDDB_VER=`$PKG_CONFIG libmaxminddb --modversion`
|
||||
AC_MSG_RESULT(found version $LIBMAXMINDDB_VER)
|
||||
else
|
||||
AC_MSG_ERROR(system libmaxminddb must be upgraded to version >= 1.0.0)
|
||||
fi
|
||||
PHP_EVAL_LIBLINE($LIBMAXMINDDB_LIB, MAXMINDDB_SHARED_LIBADD)
|
||||
PHP_EVAL_INCLINE($LIBMAXMINDDB_INC)
|
||||
else
|
||||
AC_MSG_RESULT(pkg-config information missing)
|
||||
AC_MSG_WARN(will use libmaxmxinddb from compiler default path)
|
||||
|
||||
PHP_CHECK_LIBRARY(maxminddb, MMDB_open)
|
||||
PHP_ADD_LIBRARY(maxminddb, 1, MAXMINDDB_SHARED_LIBADD)
|
||||
fi
|
||||
|
||||
if test $PHP_MAXMINDDB_DEBUG != "no"; then
|
||||
CFLAGS="$CFLAGS -Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -Werror"
|
||||
fi
|
||||
|
||||
PHP_SUBST(MAXMINDDB_SHARED_LIBADD)
|
||||
|
||||
PHP_NEW_EXTENSION(maxminddb, maxminddb.c, $ext_shared)
|
||||
fi
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
ARG_WITH("maxminddb", "Enable MaxMind DB Reader extension support", "no");
|
||||
|
||||
if (PHP_MAXMINDDB == "yes") {
|
||||
if (CHECK_HEADER_ADD_INCLUDE("maxminddb.h", "CFLAGS_MAXMINDDB", PHP_MAXMINDDB + ";" + PHP_PHP_BUILD + "\\include\\maxminddb") &&
|
||||
CHECK_LIB("libmaxminddb.lib", "maxminddb", PHP_MAXMINDDB)) {
|
||||
EXTENSION("maxminddb", "maxminddb.c");
|
||||
} else {
|
||||
WARNING('Could not find maxminddb.h or libmaxminddb.lib; skipping');
|
||||
}
|
||||
}
|
||||
-819
@@ -1,819 +0,0 @@
|
||||
/* MaxMind, Inc., licenses this file to you under the Apache License, Version
|
||||
* 2.0 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "php_maxminddb.h"
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <php.h>
|
||||
#include <zend.h>
|
||||
|
||||
#include "Zend/zend_exceptions.h"
|
||||
#include "Zend/zend_types.h"
|
||||
#include "ext/spl/spl_exceptions.h"
|
||||
#include "ext/standard/info.h"
|
||||
#include <maxminddb.h>
|
||||
|
||||
#ifdef ZTS
|
||||
#include <TSRM.h>
|
||||
#endif
|
||||
|
||||
#define __STDC_FORMAT_MACROS
|
||||
#include <inttypes.h>
|
||||
|
||||
#define PHP_MAXMINDDB_NS ZEND_NS_NAME("MaxMind", "Db")
|
||||
#define PHP_MAXMINDDB_READER_NS ZEND_NS_NAME(PHP_MAXMINDDB_NS, "Reader")
|
||||
#define PHP_MAXMINDDB_METADATA_NS \
|
||||
ZEND_NS_NAME(PHP_MAXMINDDB_READER_NS, "Metadata")
|
||||
#define PHP_MAXMINDDB_READER_EX_NS \
|
||||
ZEND_NS_NAME(PHP_MAXMINDDB_READER_NS, "InvalidDatabaseException")
|
||||
|
||||
#define Z_MAXMINDDB_P(zv) php_maxminddb_fetch_object(Z_OBJ_P(zv))
|
||||
typedef size_t strsize_t;
|
||||
typedef zend_object free_obj_t;
|
||||
|
||||
/* For PHP 8 compatibility */
|
||||
#if PHP_VERSION_ID < 80000
|
||||
|
||||
#define PROP_OBJ(zv) (zv)
|
||||
|
||||
#else
|
||||
|
||||
#define PROP_OBJ(zv) Z_OBJ_P(zv)
|
||||
|
||||
#define TSRMLS_C
|
||||
#define TSRMLS_CC
|
||||
#define TSRMLS_DC
|
||||
|
||||
/* End PHP 8 compatibility */
|
||||
#endif
|
||||
|
||||
#ifndef ZEND_ACC_CTOR
|
||||
#define ZEND_ACC_CTOR 0
|
||||
#endif
|
||||
|
||||
/* IS_MIXED was added in 2020 */
|
||||
#ifndef IS_MIXED
|
||||
#define IS_MIXED IS_UNDEF
|
||||
#endif
|
||||
|
||||
/* ZEND_THIS was added in 7.4 */
|
||||
#ifndef ZEND_THIS
|
||||
#define ZEND_THIS (&EX(This))
|
||||
#endif
|
||||
|
||||
typedef struct _maxminddb_obj {
|
||||
MMDB_s *mmdb;
|
||||
zend_object std;
|
||||
} maxminddb_obj;
|
||||
|
||||
PHP_FUNCTION(maxminddb);
|
||||
|
||||
static int
|
||||
get_record(INTERNAL_FUNCTION_PARAMETERS, zval *record, int *prefix_len);
|
||||
static const MMDB_entry_data_list_s *
|
||||
handle_entry_data_list(const MMDB_entry_data_list_s *entry_data_list,
|
||||
zval *z_value TSRMLS_DC);
|
||||
static const MMDB_entry_data_list_s *
|
||||
handle_array(const MMDB_entry_data_list_s *entry_data_list,
|
||||
zval *z_value TSRMLS_DC);
|
||||
static const MMDB_entry_data_list_s *
|
||||
handle_map(const MMDB_entry_data_list_s *entry_data_list,
|
||||
zval *z_value TSRMLS_DC);
|
||||
static void handle_uint128(const MMDB_entry_data_list_s *entry_data_list,
|
||||
zval *z_value TSRMLS_DC);
|
||||
static void handle_uint64(const MMDB_entry_data_list_s *entry_data_list,
|
||||
zval *z_value TSRMLS_DC);
|
||||
static void handle_uint32(const MMDB_entry_data_list_s *entry_data_list,
|
||||
zval *z_value TSRMLS_DC);
|
||||
|
||||
#define CHECK_ALLOCATED(val) \
|
||||
if (!val) { \
|
||||
zend_error(E_ERROR, "Out of memory"); \
|
||||
return; \
|
||||
}
|
||||
|
||||
static zend_object_handlers maxminddb_obj_handlers;
|
||||
static zend_class_entry *maxminddb_ce, *maxminddb_exception_ce, *metadata_ce;
|
||||
|
||||
static inline maxminddb_obj *
|
||||
php_maxminddb_fetch_object(zend_object *obj TSRMLS_DC) {
|
||||
return (maxminddb_obj *)((char *)(obj)-XtOffsetOf(maxminddb_obj, std));
|
||||
}
|
||||
|
||||
ZEND_BEGIN_ARG_INFO_EX(arginfo_maxminddbreader_construct, 0, 0, 1)
|
||||
ZEND_ARG_TYPE_INFO(0, db_file, IS_STRING, 0)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
PHP_METHOD(MaxMind_Db_Reader, __construct) {
|
||||
char *db_file = NULL;
|
||||
strsize_t name_len;
|
||||
zval *_this_zval = NULL;
|
||||
|
||||
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
|
||||
getThis(),
|
||||
"Os",
|
||||
&_this_zval,
|
||||
maxminddb_ce,
|
||||
&db_file,
|
||||
&name_len) == FAILURE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (0 != php_check_open_basedir(db_file TSRMLS_CC) ||
|
||||
0 != access(db_file, R_OK)) {
|
||||
zend_throw_exception_ex(
|
||||
spl_ce_InvalidArgumentException,
|
||||
0 TSRMLS_CC,
|
||||
"The file \"%s\" does not exist or is not readable.",
|
||||
db_file);
|
||||
return;
|
||||
}
|
||||
|
||||
MMDB_s *mmdb = (MMDB_s *)ecalloc(1, sizeof(MMDB_s));
|
||||
int const status = MMDB_open(db_file, MMDB_MODE_MMAP, mmdb);
|
||||
|
||||
if (MMDB_SUCCESS != status) {
|
||||
zend_throw_exception_ex(
|
||||
maxminddb_exception_ce,
|
||||
0 TSRMLS_CC,
|
||||
"Error opening database file (%s). Is this a valid "
|
||||
"MaxMind DB file?",
|
||||
db_file);
|
||||
efree(mmdb);
|
||||
return;
|
||||
}
|
||||
|
||||
maxminddb_obj *mmdb_obj = Z_MAXMINDDB_P(ZEND_THIS);
|
||||
mmdb_obj->mmdb = mmdb;
|
||||
}
|
||||
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(
|
||||
arginfo_maxminddbreader_get, 0, 1, IS_MIXED, 1)
|
||||
ZEND_ARG_TYPE_INFO(0, ip_address, IS_STRING, 0)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
PHP_METHOD(MaxMind_Db_Reader, get) {
|
||||
int prefix_len = 0;
|
||||
get_record(INTERNAL_FUNCTION_PARAM_PASSTHRU, return_value, &prefix_len);
|
||||
}
|
||||
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(
|
||||
arginfo_maxminddbreader_getWithPrefixLen, 0, 1, IS_ARRAY, 1)
|
||||
ZEND_ARG_TYPE_INFO(0, ip_address, IS_STRING, 0)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
PHP_METHOD(MaxMind_Db_Reader, getWithPrefixLen) {
|
||||
zval record, z_prefix_len;
|
||||
|
||||
int prefix_len = 0;
|
||||
if (get_record(INTERNAL_FUNCTION_PARAM_PASSTHRU, &record, &prefix_len) ==
|
||||
FAILURE) {
|
||||
return;
|
||||
}
|
||||
|
||||
array_init(return_value);
|
||||
add_next_index_zval(return_value, &record);
|
||||
|
||||
ZVAL_LONG(&z_prefix_len, prefix_len);
|
||||
add_next_index_zval(return_value, &z_prefix_len);
|
||||
}
|
||||
|
||||
static int
|
||||
get_record(INTERNAL_FUNCTION_PARAMETERS, zval *record, int *prefix_len) {
|
||||
char *ip_address = NULL;
|
||||
strsize_t name_len;
|
||||
zval *this_zval = NULL;
|
||||
|
||||
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
|
||||
getThis(),
|
||||
"Os",
|
||||
&this_zval,
|
||||
maxminddb_ce,
|
||||
&ip_address,
|
||||
&name_len) == FAILURE) {
|
||||
return FAILURE;
|
||||
}
|
||||
|
||||
const maxminddb_obj *mmdb_obj = (maxminddb_obj *)Z_MAXMINDDB_P(ZEND_THIS);
|
||||
|
||||
MMDB_s *mmdb = mmdb_obj->mmdb;
|
||||
|
||||
if (NULL == mmdb) {
|
||||
zend_throw_exception_ex(spl_ce_BadMethodCallException,
|
||||
0 TSRMLS_CC,
|
||||
"Attempt to read from a closed MaxMind DB.");
|
||||
return FAILURE;
|
||||
}
|
||||
|
||||
struct addrinfo hints = {
|
||||
.ai_family = AF_UNSPEC,
|
||||
.ai_flags = AI_NUMERICHOST,
|
||||
/* We set ai_socktype so that we only get one result back */
|
||||
.ai_socktype = SOCK_STREAM};
|
||||
|
||||
struct addrinfo *addresses = NULL;
|
||||
int gai_status = getaddrinfo(ip_address, NULL, &hints, &addresses);
|
||||
if (gai_status) {
|
||||
zend_throw_exception_ex(spl_ce_InvalidArgumentException,
|
||||
0 TSRMLS_CC,
|
||||
"The value \"%s\" is not a valid IP address.",
|
||||
ip_address);
|
||||
return FAILURE;
|
||||
}
|
||||
if (!addresses || !addresses->ai_addr) {
|
||||
zend_throw_exception_ex(
|
||||
spl_ce_InvalidArgumentException,
|
||||
0 TSRMLS_CC,
|
||||
"getaddrinfo was successful but failed to set the addrinfo");
|
||||
return FAILURE;
|
||||
}
|
||||
|
||||
int sa_family = addresses->ai_addr->sa_family;
|
||||
|
||||
int mmdb_error = MMDB_SUCCESS;
|
||||
MMDB_lookup_result_s result =
|
||||
MMDB_lookup_sockaddr(mmdb, addresses->ai_addr, &mmdb_error);
|
||||
|
||||
freeaddrinfo(addresses);
|
||||
|
||||
if (MMDB_SUCCESS != mmdb_error) {
|
||||
zend_class_entry *ex;
|
||||
if (MMDB_IPV6_LOOKUP_IN_IPV4_DATABASE_ERROR == mmdb_error) {
|
||||
ex = spl_ce_InvalidArgumentException;
|
||||
} else {
|
||||
ex = maxminddb_exception_ce;
|
||||
}
|
||||
zend_throw_exception_ex(ex,
|
||||
0 TSRMLS_CC,
|
||||
"Error looking up %s. %s",
|
||||
ip_address,
|
||||
MMDB_strerror(mmdb_error));
|
||||
return FAILURE;
|
||||
}
|
||||
|
||||
*prefix_len = result.netmask;
|
||||
|
||||
if (sa_family == AF_INET && mmdb->metadata.ip_version == 6) {
|
||||
/* We return the prefix length given the IPv4 address. If there is
|
||||
no IPv4 subtree, we return a prefix length of 0. */
|
||||
*prefix_len = *prefix_len >= 96 ? *prefix_len - 96 : 0;
|
||||
}
|
||||
|
||||
if (!result.found_entry) {
|
||||
ZVAL_NULL(record);
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
MMDB_entry_data_list_s *entry_data_list = NULL;
|
||||
int status = MMDB_get_entry_data_list(&result.entry, &entry_data_list);
|
||||
|
||||
if (MMDB_SUCCESS != status) {
|
||||
zend_throw_exception_ex(maxminddb_exception_ce,
|
||||
0 TSRMLS_CC,
|
||||
"Error while looking up data for %s. %s",
|
||||
ip_address,
|
||||
MMDB_strerror(status));
|
||||
MMDB_free_entry_data_list(entry_data_list);
|
||||
return FAILURE;
|
||||
} else if (NULL == entry_data_list) {
|
||||
zend_throw_exception_ex(
|
||||
maxminddb_exception_ce,
|
||||
0 TSRMLS_CC,
|
||||
"Error while looking up data for %s. Your database may "
|
||||
"be corrupt or you have found a bug in libmaxminddb.",
|
||||
ip_address);
|
||||
return FAILURE;
|
||||
}
|
||||
|
||||
const MMDB_entry_data_list_s *rv =
|
||||
handle_entry_data_list(entry_data_list, record TSRMLS_CC);
|
||||
if (rv == NULL) {
|
||||
/* We should have already thrown the exception in handle_entry_data_list
|
||||
*/
|
||||
return FAILURE;
|
||||
}
|
||||
MMDB_free_entry_data_list(entry_data_list);
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
ZEND_BEGIN_ARG_INFO_EX(arginfo_maxminddbreader_void, 0, 0, 0)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
PHP_METHOD(MaxMind_Db_Reader, metadata) {
|
||||
zval *this_zval = NULL;
|
||||
|
||||
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
|
||||
getThis(),
|
||||
"O",
|
||||
&this_zval,
|
||||
maxminddb_ce) == FAILURE) {
|
||||
return;
|
||||
}
|
||||
|
||||
const maxminddb_obj *const mmdb_obj =
|
||||
(maxminddb_obj *)Z_MAXMINDDB_P(this_zval);
|
||||
|
||||
if (NULL == mmdb_obj->mmdb) {
|
||||
zend_throw_exception_ex(spl_ce_BadMethodCallException,
|
||||
0 TSRMLS_CC,
|
||||
"Attempt to read from a closed MaxMind DB.");
|
||||
return;
|
||||
}
|
||||
|
||||
object_init_ex(return_value, metadata_ce);
|
||||
|
||||
MMDB_entry_data_list_s *entry_data_list;
|
||||
int status =
|
||||
MMDB_get_metadata_as_entry_data_list(mmdb_obj->mmdb, &entry_data_list);
|
||||
if (status != MMDB_SUCCESS) {
|
||||
zend_throw_exception_ex(maxminddb_exception_ce,
|
||||
0 TSRMLS_CC,
|
||||
"Error while decoding metadata. %s",
|
||||
MMDB_strerror(status));
|
||||
return;
|
||||
}
|
||||
|
||||
zval metadata_array;
|
||||
const MMDB_entry_data_list_s *rv =
|
||||
handle_entry_data_list(entry_data_list, &metadata_array TSRMLS_CC);
|
||||
if (rv == NULL) {
|
||||
return;
|
||||
}
|
||||
MMDB_free_entry_data_list(entry_data_list);
|
||||
zend_call_method_with_1_params(PROP_OBJ(return_value),
|
||||
metadata_ce,
|
||||
&metadata_ce->constructor,
|
||||
ZEND_CONSTRUCTOR_FUNC_NAME,
|
||||
NULL,
|
||||
&metadata_array);
|
||||
zval_ptr_dtor(&metadata_array);
|
||||
}
|
||||
|
||||
PHP_METHOD(MaxMind_Db_Reader, close) {
|
||||
zval *this_zval = NULL;
|
||||
|
||||
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
|
||||
getThis(),
|
||||
"O",
|
||||
&this_zval,
|
||||
maxminddb_ce) == FAILURE) {
|
||||
return;
|
||||
}
|
||||
|
||||
maxminddb_obj *mmdb_obj = (maxminddb_obj *)Z_MAXMINDDB_P(this_zval);
|
||||
|
||||
if (NULL == mmdb_obj->mmdb) {
|
||||
zend_throw_exception_ex(spl_ce_BadMethodCallException,
|
||||
0 TSRMLS_CC,
|
||||
"Attempt to close a closed MaxMind DB.");
|
||||
return;
|
||||
}
|
||||
MMDB_close(mmdb_obj->mmdb);
|
||||
efree(mmdb_obj->mmdb);
|
||||
mmdb_obj->mmdb = NULL;
|
||||
}
|
||||
|
||||
static const MMDB_entry_data_list_s *
|
||||
handle_entry_data_list(const MMDB_entry_data_list_s *entry_data_list,
|
||||
zval *z_value TSRMLS_DC) {
|
||||
switch (entry_data_list->entry_data.type) {
|
||||
case MMDB_DATA_TYPE_MAP:
|
||||
return handle_map(entry_data_list, z_value TSRMLS_CC);
|
||||
case MMDB_DATA_TYPE_ARRAY:
|
||||
return handle_array(entry_data_list, z_value TSRMLS_CC);
|
||||
case MMDB_DATA_TYPE_UTF8_STRING:
|
||||
ZVAL_STRINGL(z_value,
|
||||
entry_data_list->entry_data.utf8_string,
|
||||
entry_data_list->entry_data.data_size);
|
||||
break;
|
||||
case MMDB_DATA_TYPE_BYTES:
|
||||
ZVAL_STRINGL(z_value,
|
||||
(char const *)entry_data_list->entry_data.bytes,
|
||||
entry_data_list->entry_data.data_size);
|
||||
break;
|
||||
case MMDB_DATA_TYPE_DOUBLE:
|
||||
ZVAL_DOUBLE(z_value, entry_data_list->entry_data.double_value);
|
||||
break;
|
||||
case MMDB_DATA_TYPE_FLOAT:
|
||||
ZVAL_DOUBLE(z_value, entry_data_list->entry_data.float_value);
|
||||
break;
|
||||
case MMDB_DATA_TYPE_UINT16:
|
||||
ZVAL_LONG(z_value, entry_data_list->entry_data.uint16);
|
||||
break;
|
||||
case MMDB_DATA_TYPE_UINT32:
|
||||
handle_uint32(entry_data_list, z_value TSRMLS_CC);
|
||||
break;
|
||||
case MMDB_DATA_TYPE_BOOLEAN:
|
||||
ZVAL_BOOL(z_value, entry_data_list->entry_data.boolean);
|
||||
break;
|
||||
case MMDB_DATA_TYPE_UINT64:
|
||||
handle_uint64(entry_data_list, z_value TSRMLS_CC);
|
||||
break;
|
||||
case MMDB_DATA_TYPE_UINT128:
|
||||
handle_uint128(entry_data_list, z_value TSRMLS_CC);
|
||||
break;
|
||||
case MMDB_DATA_TYPE_INT32:
|
||||
ZVAL_LONG(z_value, entry_data_list->entry_data.int32);
|
||||
break;
|
||||
default:
|
||||
zend_throw_exception_ex(maxminddb_exception_ce,
|
||||
0 TSRMLS_CC,
|
||||
"Invalid data type arguments: %d",
|
||||
entry_data_list->entry_data.type);
|
||||
return NULL;
|
||||
}
|
||||
return entry_data_list;
|
||||
}
|
||||
|
||||
static const MMDB_entry_data_list_s *
|
||||
handle_map(const MMDB_entry_data_list_s *entry_data_list,
|
||||
zval *z_value TSRMLS_DC) {
|
||||
array_init(z_value);
|
||||
const uint32_t map_size = entry_data_list->entry_data.data_size;
|
||||
|
||||
uint32_t i;
|
||||
for (i = 0; i < map_size && entry_data_list; i++) {
|
||||
entry_data_list = entry_data_list->next;
|
||||
|
||||
char *key = estrndup(entry_data_list->entry_data.utf8_string,
|
||||
entry_data_list->entry_data.data_size);
|
||||
if (NULL == key) {
|
||||
zend_throw_exception_ex(maxminddb_exception_ce,
|
||||
0 TSRMLS_CC,
|
||||
"Invalid data type arguments");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
entry_data_list = entry_data_list->next;
|
||||
zval new_value;
|
||||
entry_data_list =
|
||||
handle_entry_data_list(entry_data_list, &new_value TSRMLS_CC);
|
||||
if (entry_data_list != NULL) {
|
||||
add_assoc_zval(z_value, key, &new_value);
|
||||
}
|
||||
efree(key);
|
||||
}
|
||||
return entry_data_list;
|
||||
}
|
||||
|
||||
static const MMDB_entry_data_list_s *
|
||||
handle_array(const MMDB_entry_data_list_s *entry_data_list,
|
||||
zval *z_value TSRMLS_DC) {
|
||||
const uint32_t size = entry_data_list->entry_data.data_size;
|
||||
|
||||
array_init(z_value);
|
||||
|
||||
uint32_t i;
|
||||
for (i = 0; i < size && entry_data_list; i++) {
|
||||
entry_data_list = entry_data_list->next;
|
||||
zval new_value;
|
||||
entry_data_list =
|
||||
handle_entry_data_list(entry_data_list, &new_value TSRMLS_CC);
|
||||
if (entry_data_list != NULL) {
|
||||
add_next_index_zval(z_value, &new_value);
|
||||
}
|
||||
}
|
||||
return entry_data_list;
|
||||
}
|
||||
|
||||
static void handle_uint128(const MMDB_entry_data_list_s *entry_data_list,
|
||||
zval *z_value TSRMLS_DC) {
|
||||
uint64_t high = 0;
|
||||
uint64_t low = 0;
|
||||
#if MMDB_UINT128_IS_BYTE_ARRAY
|
||||
int i;
|
||||
for (i = 0; i < 8; i++) {
|
||||
high = (high << 8) | entry_data_list->entry_data.uint128[i];
|
||||
}
|
||||
|
||||
for (i = 8; i < 16; i++) {
|
||||
low = (low << 8) | entry_data_list->entry_data.uint128[i];
|
||||
}
|
||||
#else
|
||||
high = entry_data_list->entry_data.uint128 >> 64;
|
||||
low = (uint64_t)entry_data_list->entry_data.uint128;
|
||||
#endif
|
||||
|
||||
char *num_str;
|
||||
spprintf(&num_str, 0, "0x%016" PRIX64 "%016" PRIX64, high, low);
|
||||
CHECK_ALLOCATED(num_str);
|
||||
|
||||
ZVAL_STRING(z_value, num_str);
|
||||
efree(num_str);
|
||||
}
|
||||
|
||||
static void handle_uint32(const MMDB_entry_data_list_s *entry_data_list,
|
||||
zval *z_value TSRMLS_DC) {
|
||||
uint32_t val = entry_data_list->entry_data.uint32;
|
||||
|
||||
#if LONG_MAX >= UINT32_MAX
|
||||
ZVAL_LONG(z_value, val);
|
||||
return;
|
||||
#else
|
||||
if (val <= LONG_MAX) {
|
||||
ZVAL_LONG(z_value, val);
|
||||
return;
|
||||
}
|
||||
|
||||
char *int_str;
|
||||
spprintf(&int_str, 0, "%" PRIu32, val);
|
||||
CHECK_ALLOCATED(int_str);
|
||||
|
||||
ZVAL_STRING(z_value, int_str);
|
||||
efree(int_str);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void handle_uint64(const MMDB_entry_data_list_s *entry_data_list,
|
||||
zval *z_value TSRMLS_DC) {
|
||||
uint64_t val = entry_data_list->entry_data.uint64;
|
||||
|
||||
#if LONG_MAX >= UINT64_MAX
|
||||
ZVAL_LONG(z_value, val);
|
||||
return;
|
||||
#else
|
||||
if (val <= LONG_MAX) {
|
||||
ZVAL_LONG(z_value, val);
|
||||
return;
|
||||
}
|
||||
|
||||
char *int_str;
|
||||
spprintf(&int_str, 0, "%" PRIu64, val);
|
||||
CHECK_ALLOCATED(int_str);
|
||||
|
||||
ZVAL_STRING(z_value, int_str);
|
||||
efree(int_str);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void maxminddb_free_storage(free_obj_t *object TSRMLS_DC) {
|
||||
maxminddb_obj *obj =
|
||||
php_maxminddb_fetch_object((zend_object *)object TSRMLS_CC);
|
||||
if (obj->mmdb != NULL) {
|
||||
MMDB_close(obj->mmdb);
|
||||
efree(obj->mmdb);
|
||||
}
|
||||
|
||||
zend_object_std_dtor(&obj->std TSRMLS_CC);
|
||||
}
|
||||
|
||||
static zend_object *maxminddb_create_handler(zend_class_entry *type TSRMLS_DC) {
|
||||
maxminddb_obj *obj = (maxminddb_obj *)ecalloc(1, sizeof(maxminddb_obj));
|
||||
zend_object_std_init(&obj->std, type TSRMLS_CC);
|
||||
object_properties_init(&(obj->std), type);
|
||||
|
||||
obj->std.handlers = &maxminddb_obj_handlers;
|
||||
|
||||
return &obj->std;
|
||||
}
|
||||
|
||||
/* clang-format off */
|
||||
static zend_function_entry maxminddb_methods[] = {
|
||||
PHP_ME(MaxMind_Db_Reader, __construct, arginfo_maxminddbreader_construct,
|
||||
ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
|
||||
PHP_ME(MaxMind_Db_Reader, close, arginfo_maxminddbreader_void, ZEND_ACC_PUBLIC)
|
||||
PHP_ME(MaxMind_Db_Reader, get, arginfo_maxminddbreader_get, ZEND_ACC_PUBLIC)
|
||||
PHP_ME(MaxMind_Db_Reader, getWithPrefixLen, arginfo_maxminddbreader_getWithPrefixLen, ZEND_ACC_PUBLIC)
|
||||
PHP_ME(MaxMind_Db_Reader, metadata, arginfo_maxminddbreader_void, ZEND_ACC_PUBLIC)
|
||||
{ NULL, NULL, NULL }
|
||||
};
|
||||
/* clang-format on */
|
||||
|
||||
ZEND_BEGIN_ARG_INFO_EX(arginfo_metadata_construct, 0, 0, 1)
|
||||
ZEND_ARG_TYPE_INFO(0, metadata, IS_ARRAY, 0)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
PHP_METHOD(MaxMind_Db_Reader_Metadata, __construct) {
|
||||
zval *object = NULL;
|
||||
zval *metadata_array = NULL;
|
||||
zend_long node_count = 0;
|
||||
zend_long record_size = 0;
|
||||
|
||||
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
|
||||
getThis(),
|
||||
"Oa",
|
||||
&object,
|
||||
metadata_ce,
|
||||
&metadata_array) == FAILURE) {
|
||||
return;
|
||||
}
|
||||
|
||||
zval *tmp = NULL;
|
||||
if ((tmp = zend_hash_str_find(HASH_OF(metadata_array),
|
||||
"binary_format_major_version",
|
||||
sizeof("binary_format_major_version") - 1))) {
|
||||
zend_update_property(metadata_ce,
|
||||
PROP_OBJ(object),
|
||||
"binaryFormatMajorVersion",
|
||||
sizeof("binaryFormatMajorVersion") - 1,
|
||||
tmp);
|
||||
}
|
||||
|
||||
if ((tmp = zend_hash_str_find(HASH_OF(metadata_array),
|
||||
"binary_format_minor_version",
|
||||
sizeof("binary_format_minor_version") - 1))) {
|
||||
zend_update_property(metadata_ce,
|
||||
PROP_OBJ(object),
|
||||
"binaryFormatMinorVersion",
|
||||
sizeof("binaryFormatMinorVersion") - 1,
|
||||
tmp);
|
||||
}
|
||||
|
||||
if ((tmp = zend_hash_str_find(HASH_OF(metadata_array),
|
||||
"build_epoch",
|
||||
sizeof("build_epoch") - 1))) {
|
||||
zend_update_property(metadata_ce,
|
||||
PROP_OBJ(object),
|
||||
"buildEpoch",
|
||||
sizeof("buildEpoch") - 1,
|
||||
tmp);
|
||||
}
|
||||
|
||||
if ((tmp = zend_hash_str_find(HASH_OF(metadata_array),
|
||||
"database_type",
|
||||
sizeof("database_type") - 1))) {
|
||||
zend_update_property(metadata_ce,
|
||||
PROP_OBJ(object),
|
||||
"databaseType",
|
||||
sizeof("databaseType") - 1,
|
||||
tmp);
|
||||
}
|
||||
|
||||
if ((tmp = zend_hash_str_find(HASH_OF(metadata_array),
|
||||
"description",
|
||||
sizeof("description") - 1))) {
|
||||
zend_update_property(metadata_ce,
|
||||
PROP_OBJ(object),
|
||||
"description",
|
||||
sizeof("description") - 1,
|
||||
tmp);
|
||||
}
|
||||
|
||||
if ((tmp = zend_hash_str_find(HASH_OF(metadata_array),
|
||||
"ip_version",
|
||||
sizeof("ip_version") - 1))) {
|
||||
zend_update_property(metadata_ce,
|
||||
PROP_OBJ(object),
|
||||
"ipVersion",
|
||||
sizeof("ipVersion") - 1,
|
||||
tmp);
|
||||
}
|
||||
|
||||
if ((tmp = zend_hash_str_find(
|
||||
HASH_OF(metadata_array), "languages", sizeof("languages") - 1))) {
|
||||
zend_update_property(metadata_ce,
|
||||
PROP_OBJ(object),
|
||||
"languages",
|
||||
sizeof("languages") - 1,
|
||||
tmp);
|
||||
}
|
||||
|
||||
if ((tmp = zend_hash_str_find(HASH_OF(metadata_array),
|
||||
"record_size",
|
||||
sizeof("record_size") - 1))) {
|
||||
zend_update_property(metadata_ce,
|
||||
PROP_OBJ(object),
|
||||
"recordSize",
|
||||
sizeof("recordSize") - 1,
|
||||
tmp);
|
||||
if (Z_TYPE_P(tmp) == IS_LONG) {
|
||||
record_size = Z_LVAL_P(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
if (record_size != 0) {
|
||||
zend_update_property_long(metadata_ce,
|
||||
PROP_OBJ(object),
|
||||
"nodeByteSize",
|
||||
sizeof("nodeByteSize") - 1,
|
||||
record_size / 4);
|
||||
}
|
||||
|
||||
if ((tmp = zend_hash_str_find(HASH_OF(metadata_array),
|
||||
"node_count",
|
||||
sizeof("node_count") - 1))) {
|
||||
zend_update_property(metadata_ce,
|
||||
PROP_OBJ(object),
|
||||
"nodeCount",
|
||||
sizeof("nodeCount") - 1,
|
||||
tmp);
|
||||
if (Z_TYPE_P(tmp) == IS_LONG) {
|
||||
node_count = Z_LVAL_P(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
if (record_size != 0) {
|
||||
zend_update_property_long(metadata_ce,
|
||||
PROP_OBJ(object),
|
||||
"searchTreeSize",
|
||||
sizeof("searchTreeSize") - 1,
|
||||
record_size * node_count / 4);
|
||||
}
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
static zend_function_entry metadata_methods[] = {
|
||||
PHP_ME(MaxMind_Db_Reader_Metadata, __construct, arginfo_metadata_construct, ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
|
||||
{NULL, NULL, NULL}
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
PHP_MINIT_FUNCTION(maxminddb) {
|
||||
zend_class_entry ce;
|
||||
|
||||
INIT_CLASS_ENTRY(ce, PHP_MAXMINDDB_READER_EX_NS, NULL);
|
||||
maxminddb_exception_ce =
|
||||
zend_register_internal_class_ex(&ce, zend_ce_exception);
|
||||
|
||||
INIT_CLASS_ENTRY(ce, PHP_MAXMINDDB_READER_NS, maxminddb_methods);
|
||||
maxminddb_ce = zend_register_internal_class(&ce TSRMLS_CC);
|
||||
maxminddb_ce->create_object = maxminddb_create_handler;
|
||||
|
||||
INIT_CLASS_ENTRY(ce, PHP_MAXMINDDB_METADATA_NS, metadata_methods);
|
||||
metadata_ce = zend_register_internal_class(&ce TSRMLS_CC);
|
||||
zend_declare_property_null(metadata_ce,
|
||||
"binaryFormatMajorVersion",
|
||||
sizeof("binaryFormatMajorVersion") - 1,
|
||||
ZEND_ACC_PUBLIC);
|
||||
zend_declare_property_null(metadata_ce,
|
||||
"binaryFormatMinorVersion",
|
||||
sizeof("binaryFormatMinorVersion") - 1,
|
||||
ZEND_ACC_PUBLIC);
|
||||
zend_declare_property_null(
|
||||
metadata_ce, "buildEpoch", sizeof("buildEpoch") - 1, ZEND_ACC_PUBLIC);
|
||||
zend_declare_property_null(metadata_ce,
|
||||
"databaseType",
|
||||
sizeof("databaseType") - 1,
|
||||
ZEND_ACC_PUBLIC);
|
||||
zend_declare_property_null(
|
||||
metadata_ce, "description", sizeof("description") - 1, ZEND_ACC_PUBLIC);
|
||||
zend_declare_property_null(
|
||||
metadata_ce, "ipVersion", sizeof("ipVersion") - 1, ZEND_ACC_PUBLIC);
|
||||
zend_declare_property_null(
|
||||
metadata_ce, "languages", sizeof("languages") - 1, ZEND_ACC_PUBLIC);
|
||||
zend_declare_property_null(metadata_ce,
|
||||
"nodeByteSize",
|
||||
sizeof("nodeByteSize") - 1,
|
||||
ZEND_ACC_PUBLIC);
|
||||
zend_declare_property_null(
|
||||
metadata_ce, "nodeCount", sizeof("nodeCount") - 1, ZEND_ACC_PUBLIC);
|
||||
zend_declare_property_null(
|
||||
metadata_ce, "recordSize", sizeof("recordSize") - 1, ZEND_ACC_PUBLIC);
|
||||
zend_declare_property_null(metadata_ce,
|
||||
"searchTreeSize",
|
||||
sizeof("searchTreeSize") - 1,
|
||||
ZEND_ACC_PUBLIC);
|
||||
|
||||
memcpy(&maxminddb_obj_handlers,
|
||||
zend_get_std_object_handlers(),
|
||||
sizeof(zend_object_handlers));
|
||||
maxminddb_obj_handlers.clone_obj = NULL;
|
||||
maxminddb_obj_handlers.offset = XtOffsetOf(maxminddb_obj, std);
|
||||
maxminddb_obj_handlers.free_obj = maxminddb_free_storage;
|
||||
zend_declare_class_constant_string(maxminddb_ce,
|
||||
"MMDB_LIB_VERSION",
|
||||
sizeof("MMDB_LIB_VERSION") - 1,
|
||||
MMDB_lib_version() TSRMLS_CC);
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
static PHP_MINFO_FUNCTION(maxminddb) {
|
||||
php_info_print_table_start();
|
||||
|
||||
php_info_print_table_row(2, "MaxMind DB Reader", "enabled");
|
||||
php_info_print_table_row(
|
||||
2, "maxminddb extension version", PHP_MAXMINDDB_VERSION);
|
||||
php_info_print_table_row(
|
||||
2, "libmaxminddb library version", MMDB_lib_version());
|
||||
|
||||
php_info_print_table_end();
|
||||
}
|
||||
|
||||
zend_module_entry maxminddb_module_entry = {STANDARD_MODULE_HEADER,
|
||||
PHP_MAXMINDDB_EXTNAME,
|
||||
NULL,
|
||||
PHP_MINIT(maxminddb),
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
PHP_MINFO(maxminddb),
|
||||
PHP_MAXMINDDB_VERSION,
|
||||
STANDARD_MODULE_PROPERTIES};
|
||||
|
||||
#ifdef COMPILE_DL_MAXMINDDB
|
||||
ZEND_GET_MODULE(maxminddb)
|
||||
#endif
|
||||
@@ -1,24 +0,0 @@
|
||||
/* MaxMind, Inc., licenses this file to you under the Apache License, Version
|
||||
* 2.0 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include <zend_interfaces.h>
|
||||
|
||||
#ifndef PHP_MAXMINDDB_H
|
||||
#define PHP_MAXMINDDB_H 1
|
||||
#define PHP_MAXMINDDB_VERSION "1.13.1"
|
||||
#define PHP_MAXMINDDB_EXTNAME "maxminddb"
|
||||
|
||||
extern zend_module_entry maxminddb_module_entry;
|
||||
#define phpext_maxminddb_ptr &maxminddb_module_entry
|
||||
|
||||
#endif
|
||||
@@ -1,12 +0,0 @@
|
||||
--TEST--
|
||||
Check for maxminddb presence
|
||||
--SKIPIF--
|
||||
<?php if (!extension_loaded('maxminddb')) {
|
||||
echo 'skip';
|
||||
} ?>
|
||||
--FILE--
|
||||
<?php
|
||||
echo 'maxminddb extension is available';
|
||||
?>
|
||||
--EXPECT--
|
||||
maxminddb extension is available
|
||||
@@ -1,13 +0,0 @@
|
||||
--TEST--
|
||||
Check that Reader class is not final
|
||||
--SKIPIF--
|
||||
<?php if (!extension_loaded('maxminddb')) {
|
||||
echo 'skip';
|
||||
} ?>
|
||||
--FILE--
|
||||
<?php
|
||||
$reflectionClass = new \ReflectionClass('MaxMind\Db\Reader');
|
||||
var_dump($reflectionClass->isFinal());
|
||||
?>
|
||||
--EXPECT--
|
||||
bool(false)
|
||||
@@ -1,12 +0,0 @@
|
||||
--TEST--
|
||||
openbase_dir is followed
|
||||
--INI--
|
||||
open_basedir=/--dne--
|
||||
--FILE--
|
||||
<?php
|
||||
use MaxMind\Db\Reader;
|
||||
|
||||
$reader = new Reader('/usr/local/share/GeoIP/GeoIP2-City.mmdb');
|
||||
?>
|
||||
--EXPECTREGEX--
|
||||
.*open_basedir restriction in effect.*
|
||||
-61
@@ -1,61 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<package version="2.0" xmlns="http://pear.php.net/dtd/package-2.0"
|
||||
xmlns:tasks="http://pear.php.net/dtd/tasks-1.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0 http://pear.php.net/dtd/tasks-1.0.xsd http://pear.php.net/dtd/package-2.0 http://pear.php.net/dtd/package-2.0.xsd">
|
||||
|
||||
<name>maxminddb</name>
|
||||
<channel>pecl.php.net</channel>
|
||||
<summary>Reader for the MaxMind DB file format</summary>
|
||||
<description>This is the PHP extension for reading MaxMind DB files. MaxMind DB is a binary file format that stores data indexed by IP address subnets (IPv4 or IPv6).</description>
|
||||
<lead>
|
||||
<name>Greg Oschwald</name>
|
||||
<user>oschwald</user>
|
||||
<email>goschwald@maxmind.com</email>
|
||||
<active>yes</active>
|
||||
</lead>
|
||||
<date>2025-11-21</date>
|
||||
<version>
|
||||
<release>1.13.1</release>
|
||||
<api>1.13.1</api>
|
||||
</version>
|
||||
<stability>
|
||||
<release>stable</release>
|
||||
<api>stable</api>
|
||||
</stability>
|
||||
<license uri="https://github.com/maxmind/MaxMind-DB-Reader-php/blob/main/LICENSE">Apache License 2.0</license>
|
||||
<notes>* First PIE release. No other changes.</notes>
|
||||
<contents>
|
||||
<dir name="/">
|
||||
<file role="doc" name="LICENSE"/>
|
||||
<file role="doc" name="CHANGELOG.md"/>
|
||||
<file role="doc" name="README.md"/>
|
||||
|
||||
<dir name="ext">
|
||||
<file role="src" name="config.m4"/>
|
||||
<file role="src" name="config.w32"/>
|
||||
|
||||
<file role="src" name="maxminddb.c"/>
|
||||
<file role="src" name="php_maxminddb.h"/>
|
||||
|
||||
<dir name="tests">
|
||||
<file role="test" name="001-load.phpt"/>
|
||||
<file role="test" name="002-final.phpt"/>
|
||||
<file role="test" name="003-open-basedir.phpt"/>
|
||||
</dir>
|
||||
</dir>
|
||||
</dir>
|
||||
</contents>
|
||||
<dependencies>
|
||||
<required>
|
||||
<php>
|
||||
<min>7.2.0</min>
|
||||
</php>
|
||||
<pearinstaller>
|
||||
<min>1.10.0</min>
|
||||
</pearinstaller>
|
||||
</required>
|
||||
</dependencies>
|
||||
<providesextension>maxminddb</providesextension>
|
||||
<extsrcrelease />
|
||||
</package>
|
||||
@@ -1,404 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace MaxMind\Db;
|
||||
|
||||
use MaxMind\Db\Reader\Decoder;
|
||||
use MaxMind\Db\Reader\InvalidDatabaseException;
|
||||
use MaxMind\Db\Reader\Metadata;
|
||||
use MaxMind\Db\Reader\Util;
|
||||
|
||||
/**
|
||||
* Instances of this class provide a reader for the MaxMind DB format. IP
|
||||
* addresses can be looked up using the get method.
|
||||
*/
|
||||
class Reader
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private static $DATA_SECTION_SEPARATOR_SIZE = 16;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private static $METADATA_START_MARKER = "\xAB\xCD\xEFMaxMind.com";
|
||||
|
||||
/**
|
||||
* @var int<0, max>
|
||||
*/
|
||||
private static $METADATA_START_MARKER_LENGTH = 14;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private static $METADATA_MAX_SIZE = 131072; // 128 * 1024 = 128KiB
|
||||
|
||||
/**
|
||||
* @var Decoder
|
||||
*/
|
||||
private $decoder;
|
||||
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
private $fileHandle;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $fileSize;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $ipV4Start;
|
||||
|
||||
/**
|
||||
* @var Metadata
|
||||
*/
|
||||
private $metadata;
|
||||
|
||||
/**
|
||||
* Constructs a Reader for the MaxMind DB format. The file passed to it must
|
||||
* be a valid MaxMind DB file such as a GeoIp2 database file.
|
||||
*
|
||||
* @param string $database the MaxMind DB file to use
|
||||
*
|
||||
* @throws \InvalidArgumentException for invalid database path or unknown arguments
|
||||
* @throws InvalidDatabaseException
|
||||
* if the database is invalid or there is an error reading
|
||||
* from it
|
||||
*/
|
||||
public function __construct(string $database)
|
||||
{
|
||||
if (\func_num_args() !== 1) {
|
||||
throw new \ArgumentCountError(
|
||||
\sprintf('%s() expects exactly 1 parameter, %d given', __METHOD__, \func_num_args())
|
||||
);
|
||||
}
|
||||
|
||||
if (is_dir($database)) {
|
||||
// This matches the error that the C extension throws.
|
||||
throw new InvalidDatabaseException(
|
||||
"Error opening database file ($database). Is this a valid MaxMind DB file?"
|
||||
);
|
||||
}
|
||||
|
||||
$fileHandle = @fopen($database, 'rb');
|
||||
if ($fileHandle === false) {
|
||||
throw new \InvalidArgumentException(
|
||||
"The file \"$database\" does not exist or is not readable."
|
||||
);
|
||||
}
|
||||
$this->fileHandle = $fileHandle;
|
||||
|
||||
$fstat = fstat($fileHandle);
|
||||
if ($fstat === false) {
|
||||
throw new \UnexpectedValueException(
|
||||
"Error determining the size of \"$database\"."
|
||||
);
|
||||
}
|
||||
$this->fileSize = $fstat['size'];
|
||||
|
||||
$start = $this->findMetadataStart($database);
|
||||
$metadataDecoder = new Decoder($this->fileHandle, $start);
|
||||
[$metadataArray] = $metadataDecoder->decode($start);
|
||||
$this->metadata = new Metadata($metadataArray);
|
||||
$this->decoder = new Decoder(
|
||||
$this->fileHandle,
|
||||
$this->metadata->searchTreeSize + self::$DATA_SECTION_SEPARATOR_SIZE
|
||||
);
|
||||
$this->ipV4Start = $this->ipV4StartNode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the record for the IP address.
|
||||
*
|
||||
* @param string $ipAddress the IP address to look up
|
||||
*
|
||||
* @throws \BadMethodCallException if this method is called on a closed database
|
||||
* @throws \InvalidArgumentException if something other than a single IP address is passed to the method
|
||||
* @throws InvalidDatabaseException
|
||||
* if the database is invalid or there is an error reading
|
||||
* from it
|
||||
*
|
||||
* @return mixed the record for the IP address
|
||||
*/
|
||||
public function get(string $ipAddress)
|
||||
{
|
||||
if (\func_num_args() !== 1) {
|
||||
throw new \ArgumentCountError(
|
||||
\sprintf('%s() expects exactly 1 parameter, %d given', __METHOD__, \func_num_args())
|
||||
);
|
||||
}
|
||||
[$record] = $this->getWithPrefixLen($ipAddress);
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the record for the IP address and its associated network prefix length.
|
||||
*
|
||||
* @param string $ipAddress the IP address to look up
|
||||
*
|
||||
* @throws \BadMethodCallException if this method is called on a closed database
|
||||
* @throws \InvalidArgumentException if something other than a single IP address is passed to the method
|
||||
* @throws InvalidDatabaseException
|
||||
* if the database is invalid or there is an error reading
|
||||
* from it
|
||||
*
|
||||
* @return array{0:mixed, 1:int} an array where the first element is the record and the
|
||||
* second the network prefix length for the record
|
||||
*/
|
||||
public function getWithPrefixLen(string $ipAddress): array
|
||||
{
|
||||
if (\func_num_args() !== 1) {
|
||||
throw new \ArgumentCountError(
|
||||
\sprintf('%s() expects exactly 1 parameter, %d given', __METHOD__, \func_num_args())
|
||||
);
|
||||
}
|
||||
|
||||
if (!\is_resource($this->fileHandle)) {
|
||||
throw new \BadMethodCallException(
|
||||
'Attempt to read from a closed MaxMind DB.'
|
||||
);
|
||||
}
|
||||
|
||||
[$pointer, $prefixLen] = $this->findAddressInTree($ipAddress);
|
||||
if ($pointer === 0) {
|
||||
return [null, $prefixLen];
|
||||
}
|
||||
|
||||
return [$this->resolveDataPointer($pointer), $prefixLen];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0:int, 1:int}
|
||||
*/
|
||||
private function findAddressInTree(string $ipAddress): array
|
||||
{
|
||||
$packedAddr = @inet_pton($ipAddress);
|
||||
if ($packedAddr === false) {
|
||||
throw new \InvalidArgumentException(
|
||||
"The value \"$ipAddress\" is not a valid IP address."
|
||||
);
|
||||
}
|
||||
|
||||
$rawAddress = unpack('C*', $packedAddr);
|
||||
if ($rawAddress === false) {
|
||||
throw new InvalidDatabaseException(
|
||||
'Could not unpack the unsigned char of the packed in_addr representation.'
|
||||
);
|
||||
}
|
||||
|
||||
$bitCount = \count($rawAddress) * 8;
|
||||
|
||||
// The first node of the tree is always node 0, at the beginning of the
|
||||
// value
|
||||
$node = 0;
|
||||
|
||||
$metadata = $this->metadata;
|
||||
|
||||
// Check if we are looking up an IPv4 address in an IPv6 tree. If this
|
||||
// is the case, we can skip over the first 96 nodes.
|
||||
if ($metadata->ipVersion === 6) {
|
||||
if ($bitCount === 32) {
|
||||
$node = $this->ipV4Start;
|
||||
}
|
||||
} elseif ($metadata->ipVersion === 4 && $bitCount === 128) {
|
||||
throw new \InvalidArgumentException(
|
||||
"Error looking up $ipAddress. You attempted to look up an"
|
||||
. ' IPv6 address in an IPv4-only database.'
|
||||
);
|
||||
}
|
||||
|
||||
$nodeCount = $metadata->nodeCount;
|
||||
|
||||
for ($i = 0; $i < $bitCount && $node < $nodeCount; ++$i) {
|
||||
$tempBit = 0xFF & $rawAddress[($i >> 3) + 1];
|
||||
$bit = 1 & ($tempBit >> 7 - ($i % 8));
|
||||
|
||||
$node = $this->readNode($node, $bit);
|
||||
}
|
||||
if ($node === $nodeCount) {
|
||||
// Record is empty
|
||||
return [0, $i];
|
||||
}
|
||||
if ($node > $nodeCount) {
|
||||
// Record is a data pointer
|
||||
return [$node, $i];
|
||||
}
|
||||
|
||||
throw new InvalidDatabaseException(
|
||||
'Invalid or corrupt database. Maximum search depth reached without finding a leaf node'
|
||||
);
|
||||
}
|
||||
|
||||
private function ipV4StartNode(): int
|
||||
{
|
||||
// If we have an IPv4 database, the start node is the first node
|
||||
if ($this->metadata->ipVersion === 4) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$node = 0;
|
||||
|
||||
for ($i = 0; $i < 96 && $node < $this->metadata->nodeCount; ++$i) {
|
||||
$node = $this->readNode($node, 0);
|
||||
}
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
private function readNode(int $nodeNumber, int $index): int
|
||||
{
|
||||
$baseOffset = $nodeNumber * $this->metadata->nodeByteSize;
|
||||
|
||||
switch ($this->metadata->recordSize) {
|
||||
case 24:
|
||||
$bytes = Util::read($this->fileHandle, $baseOffset + $index * 3, 3);
|
||||
$rc = unpack('N', "\x00" . $bytes);
|
||||
if ($rc === false) {
|
||||
throw new InvalidDatabaseException(
|
||||
'Could not unpack the unsigned long of the node.'
|
||||
);
|
||||
}
|
||||
[, $node] = $rc;
|
||||
|
||||
return $node;
|
||||
|
||||
case 28:
|
||||
$bytes = Util::read($this->fileHandle, $baseOffset + 3 * $index, 4);
|
||||
if ($index === 0) {
|
||||
$middle = (0xF0 & \ord($bytes[3])) >> 4;
|
||||
} else {
|
||||
$middle = 0x0F & \ord($bytes[0]);
|
||||
}
|
||||
$rc = unpack('N', \chr($middle) . substr($bytes, $index, 3));
|
||||
if ($rc === false) {
|
||||
throw new InvalidDatabaseException(
|
||||
'Could not unpack the unsigned long of the node.'
|
||||
);
|
||||
}
|
||||
[, $node] = $rc;
|
||||
|
||||
return $node;
|
||||
|
||||
case 32:
|
||||
$bytes = Util::read($this->fileHandle, $baseOffset + $index * 4, 4);
|
||||
$rc = unpack('N', $bytes);
|
||||
if ($rc === false) {
|
||||
throw new InvalidDatabaseException(
|
||||
'Could not unpack the unsigned long of the node.'
|
||||
);
|
||||
}
|
||||
[, $node] = $rc;
|
||||
|
||||
return $node;
|
||||
|
||||
default:
|
||||
throw new InvalidDatabaseException(
|
||||
'Unknown record size: '
|
||||
. $this->metadata->recordSize
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
private function resolveDataPointer(int $pointer)
|
||||
{
|
||||
$resolved = $pointer - $this->metadata->nodeCount
|
||||
+ $this->metadata->searchTreeSize;
|
||||
if ($resolved >= $this->fileSize) {
|
||||
throw new InvalidDatabaseException(
|
||||
"The MaxMind DB file's search tree is corrupt"
|
||||
);
|
||||
}
|
||||
|
||||
[$data] = $this->decoder->decode($resolved);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is an extremely naive but reasonably readable implementation. There
|
||||
* are much faster algorithms (e.g., Boyer-Moore) for this if speed is ever
|
||||
* an issue, but I suspect it won't be.
|
||||
*/
|
||||
private function findMetadataStart(string $filename): int
|
||||
{
|
||||
$handle = $this->fileHandle;
|
||||
$fileSize = $this->fileSize;
|
||||
$marker = self::$METADATA_START_MARKER;
|
||||
$markerLength = self::$METADATA_START_MARKER_LENGTH;
|
||||
|
||||
$minStart = $fileSize - min(self::$METADATA_MAX_SIZE, $fileSize);
|
||||
|
||||
for ($offset = $fileSize - $markerLength; $offset >= $minStart; --$offset) {
|
||||
if (fseek($handle, $offset) !== 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
$value = fread($handle, $markerLength);
|
||||
if ($value === $marker) {
|
||||
return $offset + $markerLength;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidDatabaseException(
|
||||
"Error opening database file ($filename). "
|
||||
. 'Is this a valid MaxMind DB file?'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \InvalidArgumentException if arguments are passed to the method
|
||||
* @throws \BadMethodCallException if the database has been closed
|
||||
*
|
||||
* @return Metadata object for the database
|
||||
*/
|
||||
public function metadata(): Metadata
|
||||
{
|
||||
if (\func_num_args()) {
|
||||
throw new \ArgumentCountError(
|
||||
\sprintf('%s() expects exactly 0 parameters, %d given', __METHOD__, \func_num_args())
|
||||
);
|
||||
}
|
||||
|
||||
// Not technically required, but this makes it consistent with
|
||||
// C extension and it allows us to change our implementation later.
|
||||
if (!\is_resource($this->fileHandle)) {
|
||||
throw new \BadMethodCallException(
|
||||
'Attempt to read from a closed MaxMind DB.'
|
||||
);
|
||||
}
|
||||
|
||||
return clone $this->metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the MaxMind DB and returns resources to the system.
|
||||
*
|
||||
* @throws \Exception
|
||||
* if an I/O error occurs
|
||||
*/
|
||||
public function close(): void
|
||||
{
|
||||
if (\func_num_args()) {
|
||||
throw new \ArgumentCountError(
|
||||
\sprintf('%s() expects exactly 0 parameters, %d given', __METHOD__, \func_num_args())
|
||||
);
|
||||
}
|
||||
|
||||
if (!\is_resource($this->fileHandle)) {
|
||||
throw new \BadMethodCallException(
|
||||
'Attempt to close a closed MaxMind DB.'
|
||||
);
|
||||
}
|
||||
fclose($this->fileHandle);
|
||||
}
|
||||
}
|
||||
@@ -1,452 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace MaxMind\Db\Reader;
|
||||
|
||||
// @codingStandardsIgnoreLine
|
||||
|
||||
class Decoder
|
||||
{
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
private $fileStream;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $pointerBase;
|
||||
|
||||
/**
|
||||
* This is only used for unit testing.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $pointerTestHack;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $switchByteOrder;
|
||||
|
||||
private const _EXTENDED = 0;
|
||||
private const _POINTER = 1;
|
||||
private const _UTF8_STRING = 2;
|
||||
private const _DOUBLE = 3;
|
||||
private const _BYTES = 4;
|
||||
private const _UINT16 = 5;
|
||||
private const _UINT32 = 6;
|
||||
private const _MAP = 7;
|
||||
private const _INT32 = 8;
|
||||
private const _UINT64 = 9;
|
||||
private const _UINT128 = 10;
|
||||
private const _ARRAY = 11;
|
||||
// 12 is the container type
|
||||
// 13 is the end marker type
|
||||
private const _BOOLEAN = 14;
|
||||
private const _FLOAT = 15;
|
||||
|
||||
/**
|
||||
* @param resource $fileStream
|
||||
*/
|
||||
public function __construct(
|
||||
$fileStream,
|
||||
int $pointerBase = 0,
|
||||
bool $pointerTestHack = false
|
||||
) {
|
||||
$this->fileStream = $fileStream;
|
||||
$this->pointerBase = $pointerBase;
|
||||
|
||||
$this->pointerTestHack = $pointerTestHack;
|
||||
|
||||
$this->switchByteOrder = $this->isPlatformLittleEndian();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public function decode(int $offset): array
|
||||
{
|
||||
$ctrlByte = \ord(Util::read($this->fileStream, $offset, 1));
|
||||
++$offset;
|
||||
|
||||
$type = $ctrlByte >> 5;
|
||||
|
||||
// Pointers are a special case, we don't read the next $size bytes, we
|
||||
// use the size to determine the length of the pointer and then follow
|
||||
// it.
|
||||
if ($type === self::_POINTER) {
|
||||
[$pointer, $offset] = $this->decodePointer($ctrlByte, $offset);
|
||||
|
||||
// for unit testing
|
||||
if ($this->pointerTestHack) {
|
||||
return [$pointer];
|
||||
}
|
||||
|
||||
[$result] = $this->decode($pointer);
|
||||
|
||||
return [$result, $offset];
|
||||
}
|
||||
|
||||
if ($type === self::_EXTENDED) {
|
||||
$nextByte = \ord(Util::read($this->fileStream, $offset, 1));
|
||||
|
||||
$type = $nextByte + 7;
|
||||
|
||||
if ($type < 8) {
|
||||
throw new InvalidDatabaseException(
|
||||
'Something went horribly wrong in the decoder. An extended type '
|
||||
. 'resolved to a type number < 8 ('
|
||||
. $type
|
||||
. ')'
|
||||
);
|
||||
}
|
||||
|
||||
++$offset;
|
||||
}
|
||||
|
||||
[$size, $offset] = $this->sizeFromCtrlByte($ctrlByte, $offset);
|
||||
|
||||
return $this->decodeByType($type, $offset, $size);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int<0, max> $size
|
||||
*
|
||||
* @return array{0:mixed, 1:int}
|
||||
*/
|
||||
private function decodeByType(int $type, int $offset, int $size): array
|
||||
{
|
||||
switch ($type) {
|
||||
case self::_MAP:
|
||||
return $this->decodeMap($size, $offset);
|
||||
|
||||
case self::_ARRAY:
|
||||
return $this->decodeArray($size, $offset);
|
||||
|
||||
case self::_BOOLEAN:
|
||||
return [$this->decodeBoolean($size), $offset];
|
||||
}
|
||||
|
||||
$newOffset = $offset + $size;
|
||||
$bytes = Util::read($this->fileStream, $offset, $size);
|
||||
|
||||
switch ($type) {
|
||||
case self::_BYTES:
|
||||
case self::_UTF8_STRING:
|
||||
return [$bytes, $newOffset];
|
||||
|
||||
case self::_DOUBLE:
|
||||
$this->verifySize(8, $size);
|
||||
|
||||
return [$this->decodeDouble($bytes), $newOffset];
|
||||
|
||||
case self::_FLOAT:
|
||||
$this->verifySize(4, $size);
|
||||
|
||||
return [$this->decodeFloat($bytes), $newOffset];
|
||||
|
||||
case self::_INT32:
|
||||
return [$this->decodeInt32($bytes, $size), $newOffset];
|
||||
|
||||
case self::_UINT16:
|
||||
case self::_UINT32:
|
||||
case self::_UINT64:
|
||||
case self::_UINT128:
|
||||
return [$this->decodeUint($bytes, $size), $newOffset];
|
||||
|
||||
default:
|
||||
throw new InvalidDatabaseException(
|
||||
'Unknown or unexpected type: ' . $type
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function verifySize(int $expected, int $actual): void
|
||||
{
|
||||
if ($expected !== $actual) {
|
||||
throw new InvalidDatabaseException(
|
||||
"The MaxMind DB file's data section contains bad data (unknown data type or corrupt data)"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0:array<mixed>, 1:int}
|
||||
*/
|
||||
private function decodeArray(int $size, int $offset): array
|
||||
{
|
||||
$array = [];
|
||||
|
||||
for ($i = 0; $i < $size; ++$i) {
|
||||
[$value, $offset] = $this->decode($offset);
|
||||
$array[] = $value;
|
||||
}
|
||||
|
||||
return [$array, $offset];
|
||||
}
|
||||
|
||||
private function decodeBoolean(int $size): bool
|
||||
{
|
||||
return $size !== 0;
|
||||
}
|
||||
|
||||
private function decodeDouble(string $bytes): float
|
||||
{
|
||||
// This assumes IEEE 754 doubles, but most (all?) modern platforms
|
||||
// use them.
|
||||
$rc = unpack('E', $bytes);
|
||||
if ($rc === false) {
|
||||
throw new InvalidDatabaseException(
|
||||
'Could not unpack a double value from the given bytes.'
|
||||
);
|
||||
}
|
||||
[, $double] = $rc;
|
||||
|
||||
return $double;
|
||||
}
|
||||
|
||||
private function decodeFloat(string $bytes): float
|
||||
{
|
||||
// This assumes IEEE 754 floats, but most (all?) modern platforms
|
||||
// use them.
|
||||
$rc = unpack('G', $bytes);
|
||||
if ($rc === false) {
|
||||
throw new InvalidDatabaseException(
|
||||
'Could not unpack a float value from the given bytes.'
|
||||
);
|
||||
}
|
||||
[, $float] = $rc;
|
||||
|
||||
return $float;
|
||||
}
|
||||
|
||||
private function decodeInt32(string $bytes, int $size): int
|
||||
{
|
||||
switch ($size) {
|
||||
case 0:
|
||||
return 0;
|
||||
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
$bytes = str_pad($bytes, 4, "\x00", \STR_PAD_LEFT);
|
||||
|
||||
break;
|
||||
|
||||
case 4:
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidDatabaseException(
|
||||
"The MaxMind DB file's data section contains bad data (unknown data type or corrupt data)"
|
||||
);
|
||||
}
|
||||
|
||||
$rc = unpack('l', $this->maybeSwitchByteOrder($bytes));
|
||||
if ($rc === false) {
|
||||
throw new InvalidDatabaseException(
|
||||
'Could not unpack a 32bit integer value from the given bytes.'
|
||||
);
|
||||
}
|
||||
[, $int] = $rc;
|
||||
|
||||
return $int;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0:array<string, mixed>, 1:int}
|
||||
*/
|
||||
private function decodeMap(int $size, int $offset): array
|
||||
{
|
||||
$map = [];
|
||||
|
||||
for ($i = 0; $i < $size; ++$i) {
|
||||
[$key, $offset] = $this->decode($offset);
|
||||
[$value, $offset] = $this->decode($offset);
|
||||
$map[$key] = $value;
|
||||
}
|
||||
|
||||
return [$map, $offset];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0:int, 1:int}
|
||||
*/
|
||||
private function decodePointer(int $ctrlByte, int $offset): array
|
||||
{
|
||||
$pointerSize = (($ctrlByte >> 3) & 0x3) + 1;
|
||||
|
||||
$buffer = Util::read($this->fileStream, $offset, $pointerSize);
|
||||
$offset += $pointerSize;
|
||||
|
||||
switch ($pointerSize) {
|
||||
case 1:
|
||||
$packed = \chr($ctrlByte & 0x7) . $buffer;
|
||||
$rc = unpack('n', $packed);
|
||||
if ($rc === false) {
|
||||
throw new InvalidDatabaseException(
|
||||
'Could not unpack an unsigned short value from the given bytes (pointerSize is 1).'
|
||||
);
|
||||
}
|
||||
[, $pointer] = $rc;
|
||||
$pointer += $this->pointerBase;
|
||||
|
||||
break;
|
||||
|
||||
case 2:
|
||||
$packed = "\x00" . \chr($ctrlByte & 0x7) . $buffer;
|
||||
$rc = unpack('N', $packed);
|
||||
if ($rc === false) {
|
||||
throw new InvalidDatabaseException(
|
||||
'Could not unpack an unsigned long value from the given bytes (pointerSize is 2).'
|
||||
);
|
||||
}
|
||||
[, $pointer] = $rc;
|
||||
$pointer += $this->pointerBase + 2048;
|
||||
|
||||
break;
|
||||
|
||||
case 3:
|
||||
$packed = \chr($ctrlByte & 0x7) . $buffer;
|
||||
|
||||
// It is safe to use 'N' here, even on 32 bit machines as the
|
||||
// first bit is 0.
|
||||
$rc = unpack('N', $packed);
|
||||
if ($rc === false) {
|
||||
throw new InvalidDatabaseException(
|
||||
'Could not unpack an unsigned long value from the given bytes (pointerSize is 3).'
|
||||
);
|
||||
}
|
||||
[, $pointer] = $rc;
|
||||
$pointer += $this->pointerBase + 526336;
|
||||
|
||||
break;
|
||||
|
||||
case 4:
|
||||
// We cannot use unpack here as we might overflow on 32 bit
|
||||
// machines
|
||||
$pointerOffset = $this->decodeUint($buffer, $pointerSize);
|
||||
|
||||
$pointerBase = $this->pointerBase;
|
||||
|
||||
if (\PHP_INT_MAX - $pointerBase >= $pointerOffset) {
|
||||
$pointer = $pointerOffset + $pointerBase;
|
||||
} else {
|
||||
throw new \RuntimeException(
|
||||
'The database offset is too large to be represented on your platform.'
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidDatabaseException(
|
||||
'Unexpected pointer size ' . $pointerSize
|
||||
);
|
||||
}
|
||||
|
||||
return [$pointer, $offset];
|
||||
}
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
private function decodeUint(string $bytes, int $byteLength)
|
||||
{
|
||||
if ($byteLength === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// PHP integers are signed. PHP_INT_SIZE - 1 is the number of
|
||||
// complete bytes that can be converted to an integer. However,
|
||||
// we can convert another byte if the leading bit is zero.
|
||||
$useRealInts = $byteLength <= \PHP_INT_SIZE - 1
|
||||
|| ($byteLength === \PHP_INT_SIZE && (\ord($bytes[0]) & 0x80) === 0);
|
||||
|
||||
if ($useRealInts) {
|
||||
$integer = 0;
|
||||
for ($i = 0; $i < $byteLength; ++$i) {
|
||||
$part = \ord($bytes[$i]);
|
||||
$integer = ($integer << 8) + $part;
|
||||
}
|
||||
|
||||
return $integer;
|
||||
}
|
||||
|
||||
// We only use gmp or bcmath if the final value is too big
|
||||
$integerAsString = '0';
|
||||
for ($i = 0; $i < $byteLength; ++$i) {
|
||||
$part = \ord($bytes[$i]);
|
||||
|
||||
if (\extension_loaded('gmp')) {
|
||||
$integerAsString = gmp_strval(gmp_add(gmp_mul($integerAsString, '256'), $part));
|
||||
} elseif (\extension_loaded('bcmath')) {
|
||||
$integerAsString = bcadd(bcmul($integerAsString, '256'), (string) $part);
|
||||
} else {
|
||||
throw new \RuntimeException(
|
||||
'The gmp or bcmath extension must be installed to read this database.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $integerAsString;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0:int, 1:int}
|
||||
*/
|
||||
private function sizeFromCtrlByte(int $ctrlByte, int $offset): array
|
||||
{
|
||||
$size = $ctrlByte & 0x1F;
|
||||
|
||||
if ($size < 29) {
|
||||
return [$size, $offset];
|
||||
}
|
||||
|
||||
$bytesToRead = $size - 28;
|
||||
$bytes = Util::read($this->fileStream, $offset, $bytesToRead);
|
||||
|
||||
if ($size === 29) {
|
||||
$size = 29 + \ord($bytes);
|
||||
} elseif ($size === 30) {
|
||||
$rc = unpack('n', $bytes);
|
||||
if ($rc === false) {
|
||||
throw new InvalidDatabaseException(
|
||||
'Could not unpack an unsigned short value from the given bytes.'
|
||||
);
|
||||
}
|
||||
[, $adjust] = $rc;
|
||||
$size = 285 + $adjust;
|
||||
} else {
|
||||
$rc = unpack('N', "\x00" . $bytes);
|
||||
if ($rc === false) {
|
||||
throw new InvalidDatabaseException(
|
||||
'Could not unpack an unsigned long value from the given bytes.'
|
||||
);
|
||||
}
|
||||
[, $adjust] = $rc;
|
||||
$size = $adjust + 65821;
|
||||
}
|
||||
|
||||
return [$size, $offset + $bytesToRead];
|
||||
}
|
||||
|
||||
private function maybeSwitchByteOrder(string $bytes): string
|
||||
{
|
||||
return $this->switchByteOrder ? strrev($bytes) : $bytes;
|
||||
}
|
||||
|
||||
private function isPlatformLittleEndian(): bool
|
||||
{
|
||||
$testint = 0x00FF;
|
||||
$packed = pack('S', $testint);
|
||||
$rc = unpack('v', $packed);
|
||||
if ($rc === false) {
|
||||
throw new InvalidDatabaseException(
|
||||
'Could not unpack an unsigned short value from the given bytes.'
|
||||
);
|
||||
}
|
||||
|
||||
return $testint === current($rc);
|
||||
}
|
||||
}
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace MaxMind\Db\Reader;
|
||||
|
||||
/**
|
||||
* This class should be thrown when unexpected data is found in the database.
|
||||
*/
|
||||
// phpcs:disable
|
||||
class InvalidDatabaseException extends \Exception {}
|
||||
@@ -1,123 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace MaxMind\Db\Reader;
|
||||
|
||||
/**
|
||||
* This class provides the metadata for the MaxMind DB file.
|
||||
*/
|
||||
class Metadata
|
||||
{
|
||||
/**
|
||||
* This is an unsigned 16-bit integer indicating the major version number
|
||||
* for the database's binary format.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $binaryFormatMajorVersion;
|
||||
|
||||
/**
|
||||
* This is an unsigned 16-bit integer indicating the minor version number
|
||||
* for the database's binary format.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $binaryFormatMinorVersion;
|
||||
|
||||
/**
|
||||
* This is an unsigned 64-bit integer that contains the database build
|
||||
* timestamp as a Unix epoch value.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $buildEpoch;
|
||||
|
||||
/**
|
||||
* This is a string that indicates the structure of each data record
|
||||
* associated with an IP address. The actual definition of these
|
||||
* structures is left up to the database creator.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $databaseType;
|
||||
|
||||
/**
|
||||
* This key will always point to a map (associative array). The keys of
|
||||
* that map will be language codes, and the values will be a description
|
||||
* in that language as a UTF-8 string. May be undefined for some
|
||||
* databases.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
public $description;
|
||||
|
||||
/**
|
||||
* This is an unsigned 16-bit integer which is always 4 or 6. It indicates
|
||||
* whether the database contains IPv4 or IPv6 address data.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $ipVersion;
|
||||
|
||||
/**
|
||||
* An array of strings, each of which is a language code. A given record
|
||||
* may contain data items that have been localized to some or all of
|
||||
* these languages. This may be undefined.
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
public $languages;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $nodeByteSize;
|
||||
|
||||
/**
|
||||
* This is an unsigned 32-bit integer indicating the number of nodes in
|
||||
* the search tree.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $nodeCount;
|
||||
|
||||
/**
|
||||
* This is an unsigned 16-bit integer. It indicates the number of bits in a
|
||||
* record in the search tree. Note that each node consists of two records.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $recordSize;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $searchTreeSize;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $metadata
|
||||
*/
|
||||
public function __construct(array $metadata)
|
||||
{
|
||||
if (\func_num_args() !== 1) {
|
||||
throw new \ArgumentCountError(
|
||||
\sprintf('%s() expects exactly 1 parameter, %d given', __METHOD__, \func_num_args())
|
||||
);
|
||||
}
|
||||
|
||||
$this->binaryFormatMajorVersion
|
||||
= $metadata['binary_format_major_version'];
|
||||
$this->binaryFormatMinorVersion
|
||||
= $metadata['binary_format_minor_version'];
|
||||
$this->buildEpoch = $metadata['build_epoch'];
|
||||
$this->databaseType = $metadata['database_type'];
|
||||
$this->languages = $metadata['languages'];
|
||||
$this->description = $metadata['description'];
|
||||
$this->ipVersion = $metadata['ip_version'];
|
||||
$this->nodeCount = $metadata['node_count'];
|
||||
$this->recordSize = $metadata['record_size'];
|
||||
$this->nodeByteSize = $this->recordSize / 4;
|
||||
$this->searchTreeSize = $this->nodeCount * $this->nodeByteSize;
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace MaxMind\Db\Reader;
|
||||
|
||||
class Util
|
||||
{
|
||||
/**
|
||||
* @param resource $stream
|
||||
* @param int<0, max> $numberOfBytes
|
||||
*/
|
||||
public static function read($stream, int $offset, int $numberOfBytes): string
|
||||
{
|
||||
if ($numberOfBytes === 0) {
|
||||
return '';
|
||||
}
|
||||
if (fseek($stream, $offset) === 0) {
|
||||
$value = fread($stream, $numberOfBytes);
|
||||
|
||||
// We check that the number of bytes read is equal to the number
|
||||
// asked for. We use ftell as getting the length of $value is
|
||||
// much slower.
|
||||
if ($value !== false && ftell($stream) - $offset === $numberOfBytes) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidDatabaseException(
|
||||
'The MaxMind DB file contains bad data'
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user