get($moduleName); $error = null; $notice = null; $testGroup = null; $dbTestMessages = []; require_admin(); if (!$module) { http_response_code(404); echo '
Modul nicht gefunden.
'; return; } $fields = (array)($module['setup']['fields'] ?? []); $hasGlobalDebugField = false; foreach ($fields as $field) { if ((string)($field['name'] ?? '') === 'debug_enabled') { $hasGlobalDebugField = true; break; } } if (!$hasGlobalDebugField) { $fields[] = [ 'name' => 'debug_enabled', 'label' => 'Modul-Debug aktivieren', 'type' => 'checkbox', 'help' => 'Wenn aktiv, darf das Modul Debug-Daten sammeln und den Debug-Bereich anzeigen.', ]; } $fieldTypes = []; $fieldMeta = []; foreach ($fields as $field) { $fname = (string)($field['name'] ?? ''); if ($fname === '') { continue; } $fieldTypes[$fname] = (string)($field['type'] ?? 'text'); $fieldMeta[$fname] = $field; } $isFxRatesSetup = $moduleName === 'fx-rates'; $current = modules()->settings($moduleName); $intervalTaskStatuses = modules()->intervalTaskStatuses($moduleName); $setupActions = modules()->hasFunction($moduleName, 'setup_actions') ? (array) module_fn($moduleName, 'setup_actions') : []; $defaults = $module['db_defaults'] ?? []; if (empty($current['db']) && is_array($defaults)) { $current['db'] = $defaults; } $metadataDefaults = $module['metadata_db_defaults'] ?? []; if (empty($current['metadata_db']) && is_array($metadataDefaults)) { $current['metadata_db'] = $metadataDefaults; } $dbDefaultsByGroup = [ 'db' => is_array($defaults) ? $defaults : [], 'metadata_db' => is_array($metadataDefaults) ? $metadataDefaults : [], ]; $setNested = function (array &$target, string $path, mixed $value): void { $parts = explode('.', $path); $last = array_pop($parts); $node = &$target; foreach ($parts as $part) { if (!isset($node[$part]) || !is_array($node[$part])) { $node[$part] = []; } $node = &$node[$part]; } if ($last !== null && $last !== '') { $node[$last] = $value; } }; $getNested = function (array $source, string $path): mixed { $node = $source; foreach (explode('.', $path) as $part) { if (!is_array($node) || !array_key_exists($part, $node)) { return null; } $node = $node[$part]; } return $node; }; $dbGroups = []; $fieldsByDbGroup = []; $generalFields = []; foreach ($fields as $field) { $name = (string)($field['name'] ?? ''); if (!str_contains($name, '.')) { continue; } [$group, $key] = explode('.', $name, 2); if ($key !== 'driver') { continue; } $label = (string)($field['label'] ?? $group); $label = trim(preg_replace('/\s+DB\s+Driver$/i', ' DB', $label) ?? $label); $label = $label !== '' ? $label : $group; $dbGroups[$group] = $label; } foreach ($fields as $field) { $name = (string)($field['name'] ?? ''); if (str_contains($name, '.')) { [$group] = explode('.', $name, 2); if (array_key_exists($group, $dbGroups)) { $fieldsByDbGroup[$group][] = $field; continue; } } $generalFields[] = $field; } $driverOptions = [ 'pgsql' => 'PostgreSQL', 'mysql' => 'MySQL / MariaDB', 'sqlite' => 'SQLite', ]; $describeDbConfig = static function (array $dbConfig): string { $driver = (string)($dbConfig['driver'] ?? ''); $host = (string)($dbConfig['host'] ?? ''); $port = (string)($dbConfig['port'] ?? ''); return 'Aktive Einstellung: Treiber ' . ($driver !== '' ? $driver : 'nicht gesetzt') . ', Host ' . ($host !== '' ? $host : 'nicht gesetzt') . ', Port ' . ($port !== '' ? $port : 'nicht gesetzt') . '.'; }; $describeDbDefaults = static function (array $dbDefaults): string { $driver = (string)($dbDefaults['driver'] ?? ''); $host = (string)($dbDefaults['host'] ?? ''); $port = (string)($dbDefaults['port'] ?? ''); if ($driver === '' && $host === '' && $port === '') { return ''; } return 'Standard: Treiber ' . ($driver !== '' ? $driver : 'nicht gesetzt') . ', Host ' . ($host !== '' ? $host : 'nicht gesetzt') . ', Port ' . ($port !== '' ? $port : 'nicht gesetzt') . '.'; }; $dbConfigWarning = static function (array $dbConfig): ?string { $driver = (string)($dbConfig['driver'] ?? ''); $port = (string)($dbConfig['port'] ?? ''); if ($driver === 'pgsql' && $port === '3306') { return 'Port 3306 ist typisch fuer MySQL/MariaDB, aber als Treiber ist PostgreSQL ausgewaehlt. Bitte den Treiber auf MySQL / MariaDB stellen.'; } if ($driver === 'mysql' && $port === '5432') { return 'Port 5432 ist typisch fuer PostgreSQL, aber als Treiber ist MySQL / MariaDB ausgewaehlt. Bitte Port oder Treiber pruefen.'; } return null; }; $dbConfigHint = static function (string $group, array $dbConfig, array $dbDefaults): ?string { if ($group !== 'metadata_db') { return null; } $driver = (string)($dbConfig['driver'] ?? ''); $host = (string)($dbConfig['host'] ?? ''); $port = (string)($dbConfig['port'] ?? ''); $defaultDriver = (string)($dbDefaults['driver'] ?? ''); $defaultHost = (string)($dbDefaults['host'] ?? ''); $defaultPort = (string)($dbDefaults['port'] ?? ''); if ($driver !== $defaultDriver || $host !== $defaultHost || $port !== $defaultPort) { return 'Diese Verbindung ist fuer Nexus-eigene DHCP-Zusatzinfos gedacht, nicht fuer die Nexus-App/Base-DB. Erwartet wird normalerweise: ' . ($defaultDriver !== '' ? $defaultDriver : 'Treiber offen') . ' auf ' . ($defaultHost !== '' ? $defaultHost : 'Host offen') . ':' . ($defaultPort !== '' ? $defaultPort : 'Port offen') . '.'; } return null; }; $fetchJsonWithApiKey = static function (string $url, string $apiKey, int $timeout = 10): array { $headers = [ 'Accept: application/json', 'x-api-key: ' . $apiKey, ]; $responseBody = null; $httpCode = 0; $curlError = ''; if (function_exists('curl_init')) { $ch = curl_init($url); if ($ch !== false) { curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_TIMEOUT => $timeout, CURLOPT_CONNECTTIMEOUT => min(5, $timeout), CURLOPT_HTTPHEADER => $headers, ]); $responseBody = curl_exec($ch); $curlError = curl_error($ch); $httpCode = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE); curl_close($ch); } } if (!is_string($responseBody) || $responseBody === '') { $context = stream_context_create([ 'http' => [ 'method' => 'GET', 'timeout' => $timeout, 'header' => implode("\r\n", $headers) . "\r\n", ], ]); $responseBody = @file_get_contents($url, false, $context); } if (!is_string($responseBody) || $responseBody === '') { return [ 'ok' => false, 'message' => 'Abruf fehlgeschlagen.' . ($curlError !== '' ? ' ' . $curlError : '') . ($httpCode > 0 ? ' HTTP ' . $httpCode : ''), ]; } $decoded = json_decode($responseBody, true); if (!is_array($decoded)) { return [ 'ok' => false, 'message' => 'Antwort ist kein gueltiges JSON.', ]; } foreach (['error', 'message', 'detail'] as $errorKey) { if (isset($decoded[$errorKey]) && is_string($decoded[$errorKey]) && trim($decoded[$errorKey]) !== '') { return [ 'ok' => false, 'message' => trim((string) $decoded[$errorKey]), ]; } } return [ 'ok' => true, 'data' => $decoded, ]; }; $normalizeDriver = static function (mixed $value): mixed { if (!is_string($value)) { return $value; } $normalized = strtolower(trim($value)); return match ($normalized) { 'postgres', 'postgresql' => 'pgsql', 'mariadb', 'mysql/mariadb', 'mysql / mariadb' => 'mysql', default => $normalized, }; }; $formatRunTimestamp = static function (?string $value): string { $value = trim((string) $value); if ($value === '') { return '-'; } $ts = strtotime($value); if ($ts === false) { return $value; } return date('Y-m-d H:i:s', $ts); }; $renderField = function (array $field) use (&$current, $getNested, $driverOptions): void { $name = (string)($field['name'] ?? ''); if ($name === '') { return; } $label = (string)($field['label'] ?? $name); $type = (string)($field['type'] ?? 'text'); $required = !empty($field['required']); $help = (string)($field['help'] ?? $field['description'] ?? ''); $postKey = str_replace('.', '_', $name); $value = ''; if ($name === 'kea_auto_init') { $value = !empty($current[$name]) ? '1' : '0'; } elseif (str_contains($name, '.')) { $value = (string)($getNested($current, $name) ?? ''); } else { $value = (string)($current[$name] ?? ''); } ?> trim((string) $item), $value ), static fn (string $item): bool => $item !== '')); } elseif (is_array($value)) { continue; } $value = is_string($value) ? trim($value) : $value; if (str_ends_with($name, '.driver')) { $value = $normalizeDriver($value); } if ($name === 'kea_auto_init') { $payload[$name] = $value === '1'; continue; } if (str_contains($name, '.')) { $setNested($payload, $name, $value); continue; } $payload[$name] = $value; } $current = array_replace_recursive($current, $payload); $postedTestGroup = (string)($_POST['test_db'] ?? ''); $postedResetGroup = (string)($_POST['reset_db'] ?? ''); $postedSetupAction = trim((string)($_POST['module_setup_action'] ?? '')); if ($postedSetupAction !== '') { if (!modules()->hasFunction($moduleName, 'run_setup_action')) { $error = 'Diese Setup-Aktion wird vom Modul nicht unterstuetzt.'; } else { try { $actionResult = module_fn($moduleName, 'run_setup_action', $postedSetupAction); $current = modules()->settings($moduleName); $notice = 'Setup-Aktion ausgefuehrt.'; if (is_array($actionResult)) { if (isset($actionResult['message']) && is_string($actionResult['message']) && trim($actionResult['message']) !== '') { $notice = trim((string) $actionResult['message']); } elseif (isset($actionResult['synced_count'])) { $notice = 'Waehrungskatalog synchronisiert. ' . (int) $actionResult['synced_count'] . ' Waehrungen verarbeitet.'; } } } catch (\Throwable $e) { $error = $e->getMessage(); } } } elseif ($postedResetGroup !== '') { $testGroup = $postedResetGroup; if (!array_key_exists($postedResetGroup, $dbGroups)) { $error = 'Unbekannte Datenbank-Konfiguration.'; } elseif (($dbDefaultsByGroup[$postedResetGroup] ?? []) === []) { $dbTestMessages[$postedResetGroup] = [ 'type' => 'error', 'text' => 'Fuer diese Datenbank sind keine Standardwerte hinterlegt.', ]; } else { $current[$postedResetGroup] = $dbDefaultsByGroup[$postedResetGroup]; $dbTestMessages[$postedResetGroup] = [ 'type' => 'success', 'text' => 'Standardwerte geladen. Bitte pruefen und speichern.', ]; } } elseif ($postedTestGroup !== '') { $testGroup = $postedTestGroup; if (!array_key_exists($postedTestGroup, $dbGroups)) { $error = 'Unbekannte Datenbank-Konfiguration.'; } else { $dbConfig = $getNested($current, $postedTestGroup); if (!is_array($dbConfig)) { $dbTestMessages[$postedTestGroup] = [ 'type' => 'error', 'text' => 'Datenbank-Konfiguration ist unvollstaendig.', ]; } else { $warning = $dbConfigWarning($dbConfig); if ($warning !== null) { $dbTestMessages[$postedTestGroup] = [ 'type' => 'error', 'text' => $warning . ' ' . $describeDbConfig($dbConfig), ]; } else { try { $dbConfig['options'] = array_replace([ \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC, ], (array)($dbConfig['options'] ?? [])); $testPdo = \App\Database::createFromArray($dbConfig); $testPdo->query('SELECT 1')->fetchColumn(); $dbTestMessages[$postedTestGroup] = [ 'type' => 'success', 'text' => 'Verbindung erfolgreich. ' . $describeDbConfig($dbConfig), ]; } catch (\Throwable $e) { $dbTestMessages[$postedTestGroup] = [ 'type' => 'error', 'text' => 'Verbindung fehlgeschlagen. ' . $describeDbConfig($dbConfig) . ' ' . $e->getMessage(), ]; } } } } } else { modules()->saveSettings($moduleName, $current); if ($isFxRatesSetup && modules()->hasFunction($moduleName, 'save_runtime_settings')) { module_fn($moduleName, 'save_runtime_settings', $payload); $current = modules()->settings($moduleName); } if (empty($payload['debug_enabled'])) { module_debug_clear($moduleName); } $notice = 'Setup gespeichert.'; $module = modules()->get($moduleName) ?: $module; } } $moduleStatusPanel = null; $activeDbGroup = $testGroup !== null && array_key_exists($testGroup, $dbGroups) ? $testGroup : (array_key_first($dbGroups) ?? ''); ?>
Setup

– Einrichtung

Trage die benötigten Informationen für das Modul ein.

['code' => $code, 'name' => $name], array_values($fxCatalogOptions), array_keys($fxCatalogOptions) ), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>
API

Provider und Abruf

Waehrungen

Waehrungseinstellungen

Die Auswahl wird erst verfuegbar, nachdem der Waehrungskatalog synchronisiert wurde.

Noch kein Waehrungskatalog vorhanden. Fuehre zuerst unten die Modulaktion Waehrungskatalog synchronisieren aus.
Scheduler

Taeglicher Abruf

DB

Datenbank

>
Aktionen

Modulaktionen

Waehrungssynch Laedt die verfuegbaren Waehrungen einmalig aus dem konfigurierten FX-Provider.
Letzter Sync:
Automationen

Intervall-Aufgaben

Diese Aufgaben werden beim ersten gueltigen Modulaufruf nach Ablauf des Intervalls automatisch ausgefuehrt.

Intervall: Stunden Letzter Start: Letzter Erfolg: Naechster Lauf:
Allgemein

Moduleinstellungen

Automationen

Intervall-Aufgaben

Diese Aufgaben werden beim ersten gueltigen Modulaufruf nach Ablauf des Intervalls automatisch ausgefuehrt.

Intervall: Stunden Letzter Start: Letzter Erfolg: Naechster Lauf: Status: Meldung:
Aktionen

Modulaktionen

Seltene Wartungsaktionen koennen direkt hier aus dem Setup ausgefuehrt werden.

Status

Der Status wird beim Aufruf der Setup-Seite automatisch geprueft.

Datenbanken

Verbindungen

Jede Verbindung kann getrennt konfiguriert und getestet werden.

$label): ?>
$label): ?>
>

konfigurieren