From 7ce9173d5738f21ce3bef65bd03b73fe89f7aa91 Mon Sep 17 00:00:00 2001
From: Lars Gebhardt-Kusche
Date: Mon, 27 Apr 2026 00:11:03 +0200
Subject: [PATCH] rueckbau
---
.../boersenchecker/assets/boersenchecker.js | 2 +-
modules/boersenchecker/bootstrap.php | 384 ++++++------------
modules/boersenchecker/module.json | 6 +-
modules/boersenchecker/pages/chart_data.php | 8 +-
modules/boersenchecker/partials/dashboard.php | 6 +-
modules/boersenchecker/partials/home.php | 2 +-
.../boersenchecker/partials/instruments.php | 2 +-
.../src/Support/DashboardPage.php | 64 ++-
.../boersenchecker/src/Support/HomePage.php | 35 +-
.../src/Support/InstrumentPage.php | 57 ++-
partials/landingpages/modules/setup.php | 38 --
11 files changed, 259 insertions(+), 345 deletions(-)
diff --git a/modules/boersenchecker/assets/boersenchecker.js b/modules/boersenchecker/assets/boersenchecker.js
index c108095..5a098aa 100644
--- a/modules/boersenchecker/assets/boersenchecker.js
+++ b/modules/boersenchecker/assets/boersenchecker.js
@@ -115,7 +115,7 @@
}
currentPayload = payload;
renderChart(pointsForRange(payload, activeRange));
- if (statusNode) statusNode.textContent = `Quelle: Bavest | ISIN ${payload.isin || ''}`;
+ if (statusNode) statusNode.textContent = `Quelle: Alpha Vantage | Symbol ${payload.symbol || ''}`;
} catch (error) {
currentPayload = null;
chartShell.innerHTML = `${error.message}
`;
diff --git a/modules/boersenchecker/bootstrap.php b/modules/boersenchecker/bootstrap.php
index 1779560..ed6cd28 100644
--- a/modules/boersenchecker/bootstrap.php
+++ b/modules/boersenchecker/bootstrap.php
@@ -277,67 +277,39 @@ $mm->registerFunction($moduleName, 'fx_refresh', static function (string $baseCu
}
});
-$mm->registerFunction($moduleName, 'bavest_request', static function (
- string $path,
- array $payload = [],
- string $accept = 'application/json',
- string $method = 'POST'
+$mm->registerFunction($moduleName, 'alpha_vantage_request', static function (
+ string $functionName,
+ array $params = []
): array {
$settings = modules()->settings('boersenchecker');
- $apiKey = trim((string) ($settings['bavest_api_key'] ?? ''));
- $timeout = (int) ($settings['bavest_timeout_sec'] ?? 12);
+ $apiKey = trim((string) ($settings['alpha_vantage_api_key'] ?? ''));
+ $timeout = (int) (($settings['alpha_vantage_timeout_sec'] ?? null) ?: 12);
$timeout = $timeout > 0 ? $timeout : 12;
- $method = strtoupper(trim($method));
- $method = in_array($method, ['GET', 'POST'], true) ? $method : 'POST';
if ($apiKey === '') {
module_debug_push('boersenchecker', [
- 'label' => 'Bavest Request',
+ 'label' => 'Alpha Vantage Request',
'type' => 'api:error',
'request' => [
- 'method' => $method,
- 'path' => $path,
- 'payload' => $payload,
+ 'function' => $functionName,
+ 'params' => $params,
],
- 'message' => 'Bavest-API-Key fehlt. Bitte im Modul-Setup hinterlegen.',
+ 'message' => 'Alpha-Vantage-API-Key fehlt. Bitte im Modul-Setup hinterlegen.',
]);
return [
'ok' => false,
- 'message' => 'Bavest-API-Key fehlt. Bitte im Modul-Setup hinterlegen.',
+ 'message' => 'Alpha-Vantage-API-Key fehlt. Bitte im Modul-Setup hinterlegen.',
];
}
- $url = 'https://api.bavest.co/v2/' . ltrim($path, '/');
- $jsonPayload = '';
- if ($method === 'GET') {
- if ($payload !== []) {
- $query = http_build_query($payload, '', '&', PHP_QUERY_RFC3986);
- if ($query !== '') {
- $url .= (str_contains($url, '?') ? '&' : '?') . $query;
- }
- }
- } else {
- $jsonPayload = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
- if (!is_string($jsonPayload)) {
- return [
- 'ok' => false,
- 'message' => 'Bavest-Payload konnte nicht kodiert werden.',
- ];
- }
- }
-
- $headers = [
- 'Accept: ' . $accept,
- 'x-api-key: ' . $apiKey,
- ];
- if ($method === 'POST') {
- $headers[] = 'Content-Type: application/json';
- }
+ $url = 'https://www.alphavantage.co/query?' . http_build_query(array_merge([
+ 'function' => $functionName,
+ 'apikey' => $apiKey,
+ ], $params), '', '&', PHP_QUERY_RFC3986);
$responseBody = null;
$httpCode = 0;
$curlError = '';
-
if (function_exists('curl_init')) {
$ch = curl_init($url);
if ($ch !== false) {
@@ -346,12 +318,8 @@ $mm->registerFunction($moduleName, 'bavest_request', static function (
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_TIMEOUT => $timeout,
CURLOPT_CONNECTTIMEOUT => min(5, $timeout),
- CURLOPT_HTTPHEADER => $headers,
+ CURLOPT_HTTPHEADER => ['Accept: application/json'],
]);
- if ($method === 'POST') {
- curl_setopt($ch, CURLOPT_POST, true);
- curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonPayload);
- }
$responseBody = curl_exec($ch);
$curlError = curl_error($ch);
$httpCode = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
@@ -362,38 +330,33 @@ $mm->registerFunction($moduleName, 'bavest_request', static function (
if (!is_string($responseBody) || $responseBody === '') {
$context = stream_context_create([
'http' => [
- 'method' => $method,
+ 'method' => 'GET',
'timeout' => $timeout,
- 'header' => implode("\r\n", $headers) . "\r\n",
+ 'header' => "Accept: application/json\r\n",
],
]);
- if ($method === 'POST') {
- $contextOptions = stream_context_get_options($context);
- $contextOptions['http']['content'] = $jsonPayload;
- $context = stream_context_create($contextOptions);
- }
$responseBody = @file_get_contents($url, false, $context);
}
if (!is_string($responseBody) || $responseBody === '') {
module_debug_push('boersenchecker', [
- 'label' => 'Bavest Request',
+ 'label' => 'Alpha Vantage Request',
'type' => 'api:error',
'request' => [
- 'method' => $method,
+ 'function' => $functionName,
'url' => $url,
- 'payload' => $payload,
+ 'params' => $params,
],
'response' => [
'http_code' => $httpCode,
'curl_error' => $curlError,
'body' => null,
],
- 'message' => 'Bavest Anfrage fehlgeschlagen.',
+ 'message' => 'Alpha-Vantage-Anfrage fehlgeschlagen.',
]);
return [
'ok' => false,
- 'message' => 'Bavest Anfrage fehlgeschlagen.'
+ 'message' => 'Alpha-Vantage-Anfrage fehlgeschlagen.'
. ($curlError !== '' ? ' ' . $curlError : '')
. ($httpCode > 0 ? ' HTTP ' . $httpCode : ''),
];
@@ -402,35 +365,35 @@ $mm->registerFunction($moduleName, 'bavest_request', static function (
$decoded = json_decode($responseBody, true);
if (!is_array($decoded)) {
module_debug_push('boersenchecker', [
- 'label' => 'Bavest Request',
+ 'label' => 'Alpha Vantage Request',
'type' => 'api:error',
'request' => [
- 'method' => $method,
+ 'function' => $functionName,
'url' => $url,
- 'payload' => $payload,
+ 'params' => $params,
],
'response' => [
'http_code' => $httpCode,
'body_preview' => substr($responseBody, 0, 4000),
],
- 'message' => 'Bavest Antwort ist kein gueltiges JSON.',
+ 'message' => 'Alpha-Vantage-Antwort ist kein gueltiges JSON.',
]);
return [
'ok' => false,
- 'message' => 'Bavest Antwort ist kein gueltiges JSON.',
+ 'message' => 'Alpha-Vantage-Antwort ist kein gueltiges JSON.',
'raw_body' => $responseBody,
];
}
- foreach (['error', 'message', 'detail'] as $errorKey) {
+ foreach (['Error Message', 'Information', 'Note'] as $errorKey) {
if (isset($decoded[$errorKey]) && is_string($decoded[$errorKey]) && trim($decoded[$errorKey]) !== '') {
module_debug_push('boersenchecker', [
- 'label' => 'Bavest Request',
+ 'label' => 'Alpha Vantage Request',
'type' => 'api:error',
'request' => [
- 'method' => $method,
+ 'function' => $functionName,
'url' => $url,
- 'payload' => $payload,
+ 'params' => $params,
],
'response' => [
'http_code' => $httpCode,
@@ -447,12 +410,12 @@ $mm->registerFunction($moduleName, 'bavest_request', static function (
}
module_debug_push('boersenchecker', [
- 'label' => 'Bavest Request',
+ 'label' => 'Alpha Vantage Request',
'type' => 'api:response',
'request' => [
- 'method' => $method,
+ 'function' => $functionName,
'url' => $url,
- 'payload' => $payload,
+ 'params' => $params,
],
'response' => [
'http_code' => $httpCode,
@@ -470,7 +433,7 @@ $mm->registerFunction($moduleName, 'display_timezone', static function (): \Date
return new \DateTimeZone('Europe/Berlin');
});
-$mm->registerFunction($moduleName, 'normalize_bavest_timestamp_utc', static function (mixed $value): string {
+$mm->registerFunction($moduleName, 'normalize_market_timestamp_utc', static function (mixed $value): string {
if (is_numeric($value)) {
return gmdate('Y-m-d H:i:s', (int) $value);
}
@@ -502,7 +465,7 @@ $mm->registerFunction($moduleName, 'format_datetime_for_display', static functio
$displayTimezone = new \DateTimeZone('Europe/Berlin');
$source = trim((string) $source);
- if (str_starts_with($source, 'bavest:')) {
+ if (str_starts_with($source, 'bavest:') || str_starts_with($source, 'alphavantage:')) {
$date = \DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $raw, new \DateTimeZone('UTC'));
if (!$date instanceof \DateTimeImmutable) {
try {
@@ -529,155 +492,82 @@ $mm->registerFunction($moduleName, 'local_now_input_value', static function ():
return (new \DateTimeImmutable('now', new \DateTimeZone('Europe/Berlin')))->format('Y-m-d\TH:i');
});
-$mm->registerFunction($moduleName, 'bavest_extract_quote', static function (array $entry): ?array {
- $candidates = [$entry];
- foreach (['quote', 'data', 'result', 'security'] as $nestedKey) {
- if (isset($entry[$nestedKey]) && is_array($entry[$nestedKey])) {
- $candidates[] = $entry[$nestedKey];
- }
+$mm->registerFunction($moduleName, 'alpha_vantage_extract_global_quote', static function (array $entry): ?array {
+ $quote = is_array($entry['Global Quote'] ?? null) ? $entry['Global Quote'] : $entry;
+ $price = $quote['05. price'] ?? null;
+ if (!is_numeric($price)) {
+ return null;
}
- foreach ($candidates as $candidate) {
- $price = null;
- foreach (['price', 'close', 'last', 'lastPrice', 'currentPrice', 'c'] as $priceKey) {
- if (is_numeric($candidate[$priceKey] ?? null)) {
- $price = (float) $candidate[$priceKey];
- break;
- }
- }
- if ($price === null) {
- continue;
- }
-
- $timestamp = module_fn(
- 'boersenchecker',
- 'normalize_bavest_timestamp_utc',
- $candidate['timestamp'] ?? $candidate['time'] ?? $candidate['date'] ?? null
- );
-
- return [
- 'symbol' => trim((string) ($candidate['symbol'] ?? $candidate['ticker'] ?? $entry['symbol'] ?? '')),
- 'isin' => trim((string) ($candidate['isin'] ?? $entry['isin'] ?? '')),
- 'price' => $price,
- 'currency' => strtoupper(trim((string) ($candidate['currency'] ?? $candidate['quoteCurrency'] ?? $entry['currency'] ?? 'EUR'))) ?: 'EUR',
- 'fetched_at' => $timestamp,
- 'source' => 'bavest:quote',
- 'raw' => $candidate,
- ];
- }
-
- return null;
+ return [
+ 'symbol' => trim((string) ($quote['01. symbol'] ?? '')),
+ 'price' => (float) $price,
+ 'currency' => '',
+ 'fetched_at' => gmdate('Y-m-d H:i:s'),
+ 'market_date' => trim((string) ($quote['07. latest trading day'] ?? '')),
+ 'source' => 'alphavantage:global_quote',
+ 'raw' => $quote,
+ ];
});
-$mm->registerFunction($moduleName, 'bavest_fetch_quote_by_isin', static function (string $isin): array {
- $isin = strtoupper(trim($isin));
- if ($isin === '') {
+$mm->registerFunction($moduleName, 'alpha_vantage_fetch_quote_by_symbol', static function (string $symbol): array {
+ $symbol = strtoupper(trim($symbol));
+ if ($symbol === '') {
return [
'ok' => false,
- 'message' => 'Keine ISIN hinterlegt.',
+ 'message' => 'Kein Symbol hinterlegt.',
];
}
- $response = module_fn('boersenchecker', 'bavest_request', 'timeseries/quote', ['isin' => $isin], 'application/json', 'GET');
+ $response = module_fn('boersenchecker', 'alpha_vantage_request', 'GLOBAL_QUOTE', [
+ 'symbol' => $symbol,
+ ]);
if (empty($response['ok'])) {
return $response;
}
- $data = is_array($response['data'] ?? null) ? $response['data'] : [];
- if (isset($data['data']) && is_array($data['data'])) {
- $data = [
- 'isin' => $isin,
- 'data' => $data['data'],
- ];
- } else {
- $data['isin'] = $data['isin'] ?? $isin;
- }
- $quote = module_fn('boersenchecker', 'bavest_extract_quote', $data);
+ $quote = module_fn('boersenchecker', 'alpha_vantage_extract_global_quote', (array) ($response['data'] ?? []));
if (!is_array($quote)) {
return [
'ok' => false,
- 'message' => 'Bavest lieferte keinen Preis fuer die ISIN ' . $isin . '.',
+ 'message' => 'Alpha Vantage lieferte keinen Preis fuer das Symbol ' . $symbol . '.',
];
}
return ['ok' => true] + $quote;
});
-$mm->registerFunction($moduleName, 'bavest_fetch_bulk_quotes', static function (array $instruments): array {
- $payloadSymbols = [];
- $indexByIsin = [];
+$mm->registerFunction($moduleName, 'alpha_vantage_fetch_quotes', static function (array $instruments): array {
+ $quotes = [];
+ $errors = [];
foreach ($instruments as $instrument) {
if (!is_array($instrument)) {
continue;
}
- $isin = strtoupper(trim((string) ($instrument['isin'] ?? '')));
- if ($isin === '') {
+ $instrumentId = (int) ($instrument['id'] ?? 0);
+ $symbol = strtoupper(trim((string) ($instrument['symbol'] ?? '')));
+ if ($instrumentId <= 0 || $symbol === '') {
continue;
}
- $payloadSymbols[] = ['isin' => $isin];
- $indexByIsin[$isin] = (int) ($instrument['id'] ?? 0);
- }
- if ($payloadSymbols === []) {
- return [
- 'ok' => false,
- 'message' => 'Keine ISIN fuer den Bulk-Abruf verfuegbar.',
- 'quotes' => [],
- ];
- }
-
- $response = module_fn('boersenchecker', 'bavest_request', 'bulk', [
- 'symbols' => $payloadSymbols,
- 'endpoint' => 'quote',
- 'params' => new stdClass(),
- ], 'application/json', 'POST');
- if (empty($response['ok'])) {
- return $response + ['quotes' => []];
- }
-
- $data = $response['data'] ?? [];
- $items = [];
- if (is_array($data)) {
- if (isset($data['data']) && is_array($data['data'])) {
- $items = $data['data'];
- } elseif (isset($data['results']) && is_array($data['results'])) {
- $items = $data['results'];
- } elseif (array_is_list($data)) {
- $items = $data;
- } else {
- $items = [$data];
- }
- }
-
- $quotes = [];
- foreach ($items as $offset => $item) {
- if (!is_array($item)) {
+ $result = module_fn('boersenchecker', 'alpha_vantage_fetch_quote_by_symbol', $symbol);
+ if (empty($result['ok'])) {
+ $errors[] = $symbol . ': ' . (string) ($result['message'] ?? 'API-Abruf fehlgeschlagen.');
continue;
}
- $quote = module_fn('boersenchecker', 'bavest_extract_quote', $item);
- if (!is_array($quote)) {
- continue;
- }
- $isin = strtoupper(trim((string) ($quote['isin'] ?? $item['isin'] ?? '')));
- $instrumentId = $isin !== '' ? ($indexByIsin[$isin] ?? 0) : 0;
- if ($instrumentId <= 0 && isset($payloadSymbols[$offset]['isin'])) {
- $instrumentId = (int) ($indexByIsin[(string) $payloadSymbols[$offset]['isin']] ?? 0);
- $quote['isin'] = (string) $payloadSymbols[$offset]['isin'];
- }
- if ($instrumentId <= 0) {
- continue;
- }
- $quotes[$instrumentId] = $quote + ['instrument_id' => $instrumentId];
+
+ $quotes[$instrumentId] = $result + ['instrument_id' => $instrumentId];
}
return [
'ok' => true,
'quotes' => $quotes,
- 'message' => count($quotes) . ' Kurse aus Bavest Bulk geladen.',
+ 'errors' => $errors,
+ 'message' => count($quotes) . ' Kurse ueber Alpha Vantage geladen.',
];
});
-$mm->registerFunction($moduleName, 'bavest_search_symbols', static function (string $keywords): array {
+$mm->registerFunction($moduleName, 'alpha_vantage_search_symbols', static function (string $keywords): array {
$keywords = trim($keywords);
if ($keywords === '') {
return [
@@ -687,60 +577,40 @@ $mm->registerFunction($moduleName, 'bavest_search_symbols', static function (str
];
}
- $response = module_fn('boersenchecker', 'bavest_request', 'reference/search/aggregated', [
- 'q' => $keywords,
- 'limit' => 25,
- ], 'application/json', 'GET');
+ $response = module_fn('boersenchecker', 'alpha_vantage_request', 'SYMBOL_SEARCH', [
+ 'keywords' => $keywords,
+ ]);
if (empty($response['ok'])) {
return $response + ['results' => []];
}
- $data = $response['data'] ?? [];
- $items = [];
- if (is_array($data['data']['results'] ?? null)) {
- $items = $data['data']['results'];
- } elseif (is_array($data['results'] ?? null)) {
- $items = $data['results'];
- }
-
+ $data = is_array($response['data'] ?? null) ? $response['data'] : [];
+ $items = is_array($data['bestMatches'] ?? null) ? $data['bestMatches'] : [];
$results = [];
foreach ($items as $item) {
if (!is_array($item)) {
continue;
}
- $name = trim((string) ($item['name'] ?? $item['companyName'] ?? $item['securityName'] ?? ''));
- $rootTicker = trim((string) ($item['root_ticker'] ?? ''));
- $listings = is_array($item['listings'] ?? null) ? $item['listings'] : [];
-
- foreach ($listings as $listing) {
- if (!is_array($listing)) {
- continue;
- }
-
- $symbol = trim((string) ($listing['symbol'] ?? $rootTicker));
- $isin = strtoupper(trim((string) ($listing['isin'] ?? '')));
- $region = trim((string) ($listing['exchange'] ?? $listing['region'] ?? ''));
- $type = trim((string) ($listing['type'] ?? ''));
- $currency = strtoupper(trim((string) ($listing['currency'] ?? '')));
-
- if ($symbol === '' && $name === '' && $isin === '') {
- continue;
- }
-
- $results[] = [
- 'symbol' => $symbol,
- 'name' => $name,
- 'isin' => $isin,
- 'type' => $type,
- 'region' => $region,
- 'currency' => $currency,
- 'match_score' => '',
- 'raw' => [
- 'security' => $item,
- 'listing' => $listing,
- ],
- ];
+ $symbol = trim((string) ($item['1. symbol'] ?? ''));
+ $name = trim((string) ($item['2. name'] ?? ''));
+ $type = trim((string) ($item['3. type'] ?? ''));
+ $region = trim((string) ($item['4. region'] ?? ''));
+ $currency = strtoupper(trim((string) ($item['8. currency'] ?? '')));
+ $matchScore = trim((string) ($item['9. matchScore'] ?? ''));
+ if ($symbol === '' && $name === '') {
+ continue;
}
+
+ $results[] = [
+ 'symbol' => $symbol,
+ 'name' => $name,
+ 'isin' => '',
+ 'type' => $type,
+ 'region' => $region,
+ 'currency' => $currency,
+ 'match_score' => $matchScore,
+ 'raw' => $item,
+ ];
}
return [
@@ -750,17 +620,17 @@ $mm->registerFunction($moduleName, 'bavest_search_symbols', static function (str
];
});
-$mm->registerFunction($moduleName, 'bavest_fetch_chart_series', static function (string $isin): array {
- $isin = strtoupper(trim($isin));
- if ($isin === '') {
- return ['ok' => false, 'message' => 'Keine ISIN angegeben.'];
+$mm->registerFunction($moduleName, 'alpha_vantage_fetch_chart_series', static function (string $symbol): array {
+ $symbol = strtoupper(trim($symbol));
+ if ($symbol === '') {
+ return ['ok' => false, 'message' => 'Kein Symbol angegeben.'];
}
- $cacheDir = sys_get_temp_dir() . '/boersenchecker-bavest';
+ $cacheDir = sys_get_temp_dir() . '/boersenchecker-alphavantage';
if (!is_dir($cacheDir)) {
@mkdir($cacheDir, 0775, true);
}
- $cachePath = $cacheDir . '/' . md5('historical-price|' . $isin) . '.json';
+ $cachePath = $cacheDir . '/' . md5('time_series_daily_adjusted|' . $symbol) . '.json';
$decoded = null;
if (is_file($cachePath) && (time() - filemtime($cachePath)) < (6 * 3600)) {
@@ -769,11 +639,10 @@ $mm->registerFunction($moduleName, 'bavest_fetch_chart_series', static function
}
if (!is_array($decoded)) {
- $response = module_fn('boersenchecker', 'bavest_request', 'timeseries/history', [
- 'isin' => $isin,
- 'from' => date('Y-m-d', strtotime('-6 years')),
- 'to' => date('Y-m-d'),
- ], 'application/json', 'GET');
+ $response = module_fn('boersenchecker', 'alpha_vantage_request', 'TIME_SERIES_DAILY_ADJUSTED', [
+ 'symbol' => $symbol,
+ 'outputsize' => 'full',
+ ]);
if (empty($response['ok'])) {
return $response;
}
@@ -781,40 +650,29 @@ $mm->registerFunction($moduleName, 'bavest_fetch_chart_series', static function
@file_put_contents($cachePath, json_encode($decoded, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
}
- $rows = [];
- if (isset($decoded['data']['prices']) && is_array($decoded['data']['prices'])) {
- $rows = $decoded['data']['prices'];
- } elseif (isset($decoded['data']) && is_array($decoded['data'])) {
- $rows = $decoded['data'];
- } elseif (isset($decoded['results']) && is_array($decoded['results'])) {
- $rows = $decoded['results'];
- } elseif (array_is_list($decoded)) {
- $rows = $decoded;
- }
+ $rows = is_array($decoded['Time Series (Daily)'] ?? null)
+ ? $decoded['Time Series (Daily)']
+ : (is_array($decoded['Time Series (Daily) Adjusted'] ?? null) ? $decoded['Time Series (Daily) Adjusted'] : []);
- $dailyByDate = [];
- foreach ($rows as $row) {
- if (!is_array($row)) {
+ $daily = [];
+ foreach ($rows as $date => $row) {
+ if (!is_array($row) || !preg_match('/^\d{4}-\d{2}-\d{2}$/', (string) $date)) {
continue;
}
- $date = trim((string) ($row['date'] ?? $row['time'] ?? $row['timestamp'] ?? ''));
- $close = $row['close'] ?? $row['price'] ?? $row['c'] ?? null;
- if ($date === '' || !is_numeric($close)) {
+ $close = $row['5. adjusted close'] ?? $row['4. close'] ?? null;
+ if (!is_numeric($close)) {
continue;
}
- $normalizedDate = date('Y-m-d', strtotime($date) ?: time());
- $dailyByDate[$normalizedDate] = [
- 'date' => date('Y-m-d', strtotime($date) ?: time()),
+ $daily[] = [
+ 'date' => $date,
'close' => (float) $close,
];
}
- $daily = array_values($dailyByDate);
-
usort($daily, static fn (array $left, array $right): int => strcmp((string) $left['date'], (string) $right['date']));
if ($daily === []) {
return [
'ok' => false,
- 'message' => 'Keine historischen Schlusskurse fuer ' . $isin . ' verfuegbar.',
+ 'message' => 'Keine historischen Schlusskurse fuer ' . $symbol . ' verfuegbar.',
];
}
@@ -829,11 +687,11 @@ $mm->registerFunction($moduleName, 'bavest_fetch_chart_series', static function
return [
'ok' => true,
- 'isin' => $isin,
+ 'symbol' => $symbol,
'daily' => $daily,
'weekly' => $aggregate($daily, 'o-W'),
'monthly' => $aggregate($daily, 'Y-m'),
- 'source' => 'bavest:timeseries/history',
+ 'source' => 'alphavantage:time_series_daily_adjusted',
];
});
@@ -849,7 +707,7 @@ $mm->registerFunction($moduleName, 'store_market_quote', static function (
$quotedAt = trim($quotedAt);
$currency = strtoupper(trim($currency)) ?: 'EUR';
- $source = trim($source) !== '' ? trim($source) : 'bavest:quote';
+ $source = trim($source) !== '' ? trim($source) : 'alphavantage:global_quote';
$checkStmt = $pdo->prepare(
'SELECT id
diff --git a/modules/boersenchecker/module.json b/modules/boersenchecker/module.json
index 2d4bc25..c8b747d 100644
--- a/modules/boersenchecker/module.json
+++ b/modules/boersenchecker/module.json
@@ -15,9 +15,9 @@
{ "name": "db.password", "label": "DB Passwort", "type": "password", "required": false },
{ "name": "report_currency", "label": "Standard-Berichtswahrung", "type": "text", "required": false, "help": "Zielwaehrung fuer Portfolio-Summen, z.B. EUR." },
{ "name": "fx_max_age_hours", "label": "Maximales FX-Alter (Stunden)", "type": "number", "required": false, "help": "Wird bei manueller Aktualisierung ueber den Mining-Checker genutzt." },
- { "name": "bavest_api_key", "label": "Bavest API Key", "type": "password", "required": false, "help": "API Key fuer Aktienkursabrufe und Suche ueber Bavest." },
- { "name": "bavest_timeout_sec", "label": "Bavest Timeout (Sek.)", "type": "number", "required": false, "help": "HTTP-Timeout fuer API-Abrufe." },
- { "name": "bavest_min_interval_minutes", "label": "Bavest Mindestabstand (Min.)", "type": "number", "required": false, "help": "Wenn bereits ein frischer Bavest-Kurs existiert, wird dieser wiederverwendet statt erneut abzurufen." }
+ { "name": "alpha_vantage_api_key", "label": "Alpha Vantage API Key", "type": "password", "required": false, "help": "API Key fuer Aktienkursabrufe und Suche ueber Alpha Vantage." },
+ { "name": "alpha_vantage_timeout_sec", "label": "Alpha Vantage Timeout (Sek.)", "type": "number", "required": false, "help": "HTTP-Timeout fuer API-Abrufe." },
+ { "name": "alpha_vantage_min_interval_minutes", "label": "Alpha Vantage Mindestabstand (Min.)", "type": "number", "required": false, "help": "Wenn bereits ein frischer Alpha-Vantage-Kurs existiert, wird dieser wiederverwendet statt erneut abzurufen." }
]
},
"db_defaults": {
diff --git a/modules/boersenchecker/pages/chart_data.php b/modules/boersenchecker/pages/chart_data.php
index 808dc26..4f22f4c 100644
--- a/modules/boersenchecker/pages/chart_data.php
+++ b/modules/boersenchecker/pages/chart_data.php
@@ -37,12 +37,12 @@ if (!is_array($instrument)) {
exit;
}
-$isin = strtoupper(trim((string) ($instrument['isin'] ?? '')));
-if ($isin === '') {
- echo json_encode(['ok' => false, 'message' => 'Fuer diese Aktie ist keine ISIN hinterlegt.'], JSON_UNESCAPED_UNICODE);
+$symbol = strtoupper(trim((string) ($instrument['symbol'] ?? '')));
+if ($symbol === '') {
+ echo json_encode(['ok' => false, 'message' => 'Fuer diese Aktie ist kein Symbol hinterlegt.'], JSON_UNESCAPED_UNICODE);
exit;
}
-$result = module_fn('boersenchecker', 'bavest_fetch_chart_series', $isin);
+$result = module_fn('boersenchecker', 'alpha_vantage_fetch_chart_series', $symbol);
echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
exit;
diff --git a/modules/boersenchecker/partials/dashboard.php b/modules/boersenchecker/partials/dashboard.php
index f19882e..203dd70 100644
--- a/modules/boersenchecker/partials/dashboard.php
+++ b/modules/boersenchecker/partials/dashboard.php
@@ -81,7 +81,7 @@
angestossen und respektiert die dortige Max-Age-Logik.
- Aktienkurse werden ueber Bavest per ISIN abgerufen. Das Ticker-Symbol bleibt als Zusatzinformation erhalten.
+ Aktienkurse werden ueber Alpha Vantage anhand des hinterlegten Symbols abgerufen. Die ISIN bleibt als Stammdatum erhalten.
- Bavest Mindestabstand: = e((string) $marketDataMinIntervalMinutes) ?> Min.
+ Alpha Vantage Mindestabstand: = e((string) $marketDataMinIntervalMinutes) ?> Min.
API-Key und Timeout werden ueber
Modul-Setup gepflegt.
@@ -199,7 +199,7 @@
Wertpapiersuche
-
Bavest-Suchergebnisse pruefen und Daten direkt ins Positionsformular uebernehmen.
+
Alpha-Vantage-Suchergebnisse pruefen und Daten direkt ins Positionsformular uebernehmen.
diff --git a/modules/boersenchecker/partials/home.php b/modules/boersenchecker/partials/home.php
index bfe6d98..1b966d4 100644
--- a/modules/boersenchecker/partials/home.php
+++ b/modules/boersenchecker/partials/home.php
@@ -68,7 +68,7 @@
Marktdaten
-
Aktuelle Kurse fuer das gewaehlte Depot ueber Bavest per ISIN abrufen.
+
Aktuelle Kurse fuer das gewaehlte Depot ueber Alpha Vantage anhand des hinterlegten Symbols abrufen.
0 ? '' : 'disabled' ?>>Aktuelle Kurse abrufen
diff --git a/modules/boersenchecker/partials/instruments.php b/modules/boersenchecker/partials/instruments.php
index 4831a06..935a8c3 100644
--- a/modules/boersenchecker/partials/instruments.php
+++ b/modules/boersenchecker/partials/instruments.php
@@ -37,7 +37,7 @@
Wertpapiersuche
-
Bavest-Suchergebnisse finden und direkt fuer die Aktie uebernehmen.
+
Alpha-Vantage-Suchergebnisse finden und direkt fuer die Aktie uebernehmen.
diff --git a/modules/boersenchecker/src/Support/DashboardPage.php b/modules/boersenchecker/src/Support/DashboardPage.php
index 1e245a5..0eefdeb 100644
--- a/modules/boersenchecker/src/Support/DashboardPage.php
+++ b/modules/boersenchecker/src/Support/DashboardPage.php
@@ -49,7 +49,7 @@ final class DashboardPage
$this->fxMaxAgeHours = 6.0;
}
- $this->marketDataMinIntervalMinutes = (int) ($this->moduleSettings['bavest_min_interval_minutes'] ?? 60);
+ $this->marketDataMinIntervalMinutes = (int) (($this->moduleSettings['alpha_vantage_min_interval_minutes'] ?? null) ?: 60);
if ($this->marketDataMinIntervalMinutes <= 0) {
$this->marketDataMinIntervalMinutes = 60;
}
@@ -410,22 +410,31 @@ final class DashboardPage
}
$instrumentId = (int) $row['instrument_id'];
- $isin = strtoupper(trim((string) ($row['isin'] ?? '')));
+ $symbol = strtoupper(trim((string) ($row['symbol'] ?? '')));
$quoteCurrency = $this->normalizeCurrency((string) ($row['quote_currency'] ?? $this->defaultReportCurrency));
- if ($isin === '') {
- throw new RuntimeException('Fuer diese Aktie ist noch keine ISIN hinterlegt.');
+ if ($symbol === '') {
+ throw new RuntimeException('Fuer diese Aktie ist noch kein Symbol hinterlegt.');
}
$latestApiQuote = $this->latestApiQuoteForInstrument($instrumentId);
$latestTimestamp = is_array($latestApiQuote) ? strtotime((string) ($latestApiQuote['quoted_at'] ?? '')) : false;
if ($latestTimestamp !== false && (time() - $latestTimestamp) < ($this->marketDataMinIntervalMinutes * 60)) {
- return 'Vorhandener Bavest-Kurs fuer ' . (string) $row['instrument_name'] . ' wiederverwendet.';
+ return 'Vorhandener Alpha-Vantage-Kurs fuer ' . (string) $row['instrument_name'] . ' wiederverwendet.';
}
- $apiResult = \module_fn('boersenchecker', 'bavest_fetch_quote_by_isin', $isin);
+ $apiResult = \module_fn('boersenchecker', 'alpha_vantage_fetch_quote_by_symbol', $symbol);
if (empty($apiResult['ok'])) {
throw new RuntimeException((string) ($apiResult['message'] ?? 'API-Abruf fehlgeschlagen.'));
}
+ if ($this->isApiSnapshotStale($latestApiQuote, (string) ($apiResult['fetched_at'] ?? ''))) {
+ $displayTime = (string) \module_fn(
+ 'boersenchecker',
+ 'format_datetime_for_display',
+ (string) ($apiResult['fetched_at'] ?? ''),
+ (string) ($apiResult['source'] ?? 'alphavantage:global_quote')
+ );
+ return 'Alpha Vantage lieferte fuer ' . (string) $row['instrument_name'] . ' keinen neueren Snapshot als ' . $displayTime . '.';
+ }
$storeResult = \module_fn(
'boersenchecker',
@@ -437,9 +446,9 @@ final class DashboardPage
(string) $apiResult['source']
);
if (!empty($storeResult['inserted'])) {
- return 'Bavest-Kurs fuer ' . (string) $row['instrument_name'] . ' gespeichert.';
+ return 'Alpha-Vantage-Kurs fuer ' . (string) $row['instrument_name'] . ' gespeichert.';
}
- return 'Vorhandener Bavest-Snapshot fuer ' . (string) $row['instrument_name'] . ' wiederverwendet.';
+ return 'Vorhandener Alpha-Vantage-Snapshot fuer ' . (string) $row['instrument_name'] . ' wiederverwendet.';
}
private function refreshMarketDataAll(): string
@@ -464,6 +473,7 @@ final class DashboardPage
$fetched = 0;
$reused = 0;
+ $stale = 0;
$skipped = 0;
$failed = 0;
$errors = [];
@@ -471,8 +481,8 @@ final class DashboardPage
$bulkRows = [];
foreach ($rows as $row) {
$instrumentId = (int) ($row['id'] ?? 0);
- $isin = strtoupper(trim((string) ($row['isin'] ?? '')));
- if ($instrumentId <= 0 || $isin === '') {
+ $symbol = strtoupper(trim((string) ($row['symbol'] ?? '')));
+ if ($instrumentId <= 0 || $symbol === '') {
$skipped++;
continue;
}
@@ -488,9 +498,9 @@ final class DashboardPage
}
if ($bulkRows !== []) {
- $bulkResult = \module_fn('boersenchecker', 'bavest_fetch_bulk_quotes', $bulkRows);
+ $bulkResult = \module_fn('boersenchecker', 'alpha_vantage_fetch_quotes', $bulkRows);
if (empty($bulkResult['ok'])) {
- throw new RuntimeException((string) ($bulkResult['message'] ?? 'Bavest Bulk-Abruf fehlgeschlagen.'));
+ throw new RuntimeException((string) ($bulkResult['message'] ?? 'Alpha-Vantage-Abruf fehlgeschlagen.'));
}
$bulkQuotes = is_array($bulkResult['quotes'] ?? null) ? $bulkResult['quotes'] : [];
@@ -500,7 +510,12 @@ final class DashboardPage
$apiResult = $bulkQuotes[$instrumentId] ?? null;
if (!is_array($apiResult) || !is_numeric($apiResult['price'] ?? null)) {
$failed++;
- $errors[] = (string) ($row['name'] ?? $instrumentId) . ': kein Preis in der Bavest-Antwort.';
+ $errors[] = (string) ($row['name'] ?? $instrumentId) . ': kein Preis in der Alpha-Vantage-Antwort.';
+ continue;
+ }
+ $latestApiQuote = $this->latestApiQuoteForInstrument($instrumentId);
+ if ($this->isApiSnapshotStale($latestApiQuote, (string) ($apiResult['fetched_at'] ?? ''))) {
+ $stale++;
continue;
}
@@ -523,19 +538,19 @@ final class DashboardPage
if ($errors !== []) {
throw new RuntimeException(
- 'Bavest: ' . $fetched . ' neu, ' . $reused . ' wiederverwendet, ' . $skipped . ' ohne ISIN, ' . $failed . ' Fehler. '
+ 'Alpha Vantage: ' . $fetched . ' neu, ' . $reused . ' wiederverwendet, ' . $stale . ' nicht neuer, ' . $skipped . ' ohne Symbol, ' . $failed . ' Fehler. '
. implode(' | ', array_slice($errors, 0, 3))
);
}
- return 'Bavest: ' . $fetched . ' neu, ' . $reused . ' wiederverwendet, ' . $skipped . ' ohne ISIN, ' . $failed . ' Fehler.';
+ return 'Alpha Vantage: ' . $fetched . ' neu, ' . $reused . ' wiederverwendet, ' . $stale . ' nicht neuer, ' . $skipped . ' ohne Symbol, ' . $failed . ' Fehler.';
}
private function searchSymbol(): string
{
$keywords = trim((string) ($_POST['search_keywords'] ?? ''));
$this->symbolSearchKeywords = $keywords;
- $result = \module_fn('boersenchecker', 'bavest_search_symbols', $keywords);
+ $result = \module_fn('boersenchecker', 'alpha_vantage_search_symbols', $keywords);
$this->symbolSearchResults = is_array($result['results'] ?? null) ? $result['results'] : [];
if (empty($result['ok'])) {
@@ -740,12 +755,27 @@ final class DashboardPage
);
$stmt->execute([
'instrument_id' => $instrumentId,
- 'source' => 'bavest:%',
+ 'source' => 'alphavantage:%',
]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return is_array($row) ? $row : null;
}
+ private function isApiSnapshotStale(?array $latestQuote, string $incomingQuotedAt): bool
+ {
+ if (!is_array($latestQuote)) {
+ return false;
+ }
+
+ $latestTimestamp = strtotime((string) ($latestQuote['quoted_at'] ?? ''));
+ $incomingTimestamp = strtotime(trim($incomingQuotedAt));
+ if ($latestTimestamp === false || $incomingTimestamp === false) {
+ return false;
+ }
+
+ return $incomingTimestamp <= $latestTimestamp;
+ }
+
private function upsertInstrument(array $payload): int
{
return $this->instrumentRegistry->save($payload);
diff --git a/modules/boersenchecker/src/Support/HomePage.php b/modules/boersenchecker/src/Support/HomePage.php
index c761e1d..760ee6b 100644
--- a/modules/boersenchecker/src/Support/HomePage.php
+++ b/modules/boersenchecker/src/Support/HomePage.php
@@ -26,7 +26,7 @@ final class HomePage
$settings = \modules()->settings('boersenchecker');
$this->defaultReportCurrency = strtoupper(trim((string) ($settings['report_currency'] ?? 'EUR'))) ?: 'EUR';
- $this->marketDataMinIntervalMinutes = (int) ($settings['bavest_min_interval_minutes'] ?? 60);
+ $this->marketDataMinIntervalMinutes = (int) (($settings['alpha_vantage_min_interval_minutes'] ?? null) ?: 60);
if ($this->marketDataMinIntervalMinutes <= 0) {
$this->marketDataMinIntervalMinutes = 60;
}
@@ -136,11 +136,12 @@ final class HomePage
$updated = 0;
$reused = 0;
+ $stale = 0;
$bulkCandidates = [];
foreach ($rows as $row) {
$instrumentId = (int) ($row['id'] ?? 0);
- $isin = strtoupper(trim((string) ($row['isin'] ?? '')));
- if ($instrumentId <= 0 || $isin === '') {
+ $symbol = strtoupper(trim((string) ($row['symbol'] ?? '')));
+ if ($instrumentId <= 0 || $symbol === '') {
continue;
}
@@ -155,7 +156,7 @@ final class HomePage
}
if ($bulkCandidates !== []) {
- $bulkResult = \module_fn('boersenchecker', 'bavest_fetch_bulk_quotes', $bulkCandidates);
+ $bulkResult = \module_fn('boersenchecker', 'alpha_vantage_fetch_quotes', $bulkCandidates);
$quotes = is_array($bulkResult['quotes'] ?? null) ? $bulkResult['quotes'] : [];
foreach ($bulkCandidates as $row) {
$instrumentId = (int) ($row['id'] ?? 0);
@@ -163,6 +164,11 @@ final class HomePage
if (!is_array($quote) || !is_numeric($quote['price'] ?? null)) {
continue;
}
+ $latest = $this->latestApiQuoteForInstrument($instrumentId);
+ if ($this->isApiSnapshotStale($latest, (string) ($quote['fetched_at'] ?? ''))) {
+ $stale++;
+ continue;
+ }
$storeResult = \module_fn(
'boersenchecker',
'store_market_quote',
@@ -170,7 +176,7 @@ final class HomePage
(float) $quote['price'],
strtoupper(trim((string) ($quote['currency'] ?? $row['quote_currency'] ?? $this->defaultReportCurrency))) ?: $this->defaultReportCurrency,
(string) ($quote['fetched_at'] ?? gmdate('Y-m-d H:i:s')),
- (string) ($quote['source'] ?? 'bavest:quote')
+ (string) ($quote['source'] ?? 'alphavantage:global_quote')
);
if (!empty($storeResult['inserted'])) {
$updated++;
@@ -180,7 +186,7 @@ final class HomePage
}
}
- return 'Aktuelle Kurse: ' . $updated . ' aktualisiert, ' . $reused . ' wiederverwendet.';
+ return 'Aktuelle Kurse: ' . $updated . ' aktualisiert, ' . $reused . ' wiederverwendet, ' . $stale . ' API-Snapshots waren nicht neuer.';
}
private function fetchPortfolios(): array
@@ -254,12 +260,27 @@ final class HomePage
);
$stmt->execute([
'instrument_id' => $instrumentId,
- 'source' => 'bavest:%',
+ 'source' => 'alphavantage:%',
]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return is_array($row) ? $row : null;
}
+ private function isApiSnapshotStale(?array $latestQuote, string $incomingQuotedAt): bool
+ {
+ if (!is_array($latestQuote)) {
+ return false;
+ }
+
+ $latestTimestamp = strtotime((string) ($latestQuote['quoted_at'] ?? ''));
+ $incomingTimestamp = strtotime(trim($incomingQuotedAt));
+ if ($latestTimestamp === false || $incomingTimestamp === false) {
+ return false;
+ }
+
+ return $incomingTimestamp <= $latestTimestamp;
+ }
+
private function buildSummary(array $positions): array
{
$invested = 0.0;
diff --git a/modules/boersenchecker/src/Support/InstrumentPage.php b/modules/boersenchecker/src/Support/InstrumentPage.php
index 3e3c384..890361a 100644
--- a/modules/boersenchecker/src/Support/InstrumentPage.php
+++ b/modules/boersenchecker/src/Support/InstrumentPage.php
@@ -214,15 +214,25 @@ final class InstrumentPage
{
$instrumentId = (int) ($_POST['instrument_id'] ?? 0);
$instrument = $this->assertInstrumentAccessible($instrumentId);
- $isin = strtoupper(trim((string) ($instrument['isin'] ?? '')));
- if ($isin === '') {
- throw new RuntimeException('Fuer diese Aktie ist keine ISIN hinterlegt.');
+ $symbol = strtoupper(trim((string) ($instrument['symbol'] ?? '')));
+ if ($symbol === '') {
+ throw new RuntimeException('Fuer diese Aktie ist kein Symbol hinterlegt.');
}
- $apiResult = \module_fn('boersenchecker', 'bavest_fetch_quote_by_isin', $isin);
+ $apiResult = \module_fn('boersenchecker', 'alpha_vantage_fetch_quote_by_symbol', $symbol);
if (empty($apiResult['ok'])) {
throw new RuntimeException((string) ($apiResult['message'] ?? 'API-Abruf fehlgeschlagen.'));
}
+ $latestApiQuote = $this->latestApiQuoteForInstrument($instrumentId);
+ if ($this->isApiSnapshotStale($latestApiQuote, (string) ($apiResult['fetched_at'] ?? ''))) {
+ $displayTime = (string) \module_fn(
+ 'boersenchecker',
+ 'format_datetime_for_display',
+ (string) ($apiResult['fetched_at'] ?? ''),
+ (string) ($apiResult['source'] ?? 'alphavantage:global_quote')
+ );
+ return 'Alpha Vantage lieferte keinen neueren Snapshot als ' . $displayTime . '.';
+ }
$storeResult = \module_fn(
'boersenchecker',
@@ -235,14 +245,14 @@ final class InstrumentPage
);
return !empty($storeResult['inserted'])
- ? 'Bavest-Kurs gespeichert.'
- : 'Vorhandener Bavest-Snapshot wiederverwendet.';
+ ? 'Alpha-Vantage-Kurs gespeichert.'
+ : 'Vorhandener Alpha-Vantage-Snapshot wiederverwendet.';
}
private function searchSymbol(): string
{
$this->searchKeywords = trim((string) ($_POST['search_keywords'] ?? ''));
- $result = \module_fn('boersenchecker', 'bavest_search_symbols', $this->searchKeywords);
+ $result = \module_fn('boersenchecker', 'alpha_vantage_search_symbols', $this->searchKeywords);
$this->searchResults = is_array($result['results'] ?? null) ? $result['results'] : [];
if (empty($result['ok'])) {
throw new RuntimeException((string) ($result['message'] ?? 'Symbolsuche fehlgeschlagen.'));
@@ -294,4 +304,37 @@ final class InstrumentPage
return (new \DateTimeImmutable('now', $timezone))->format('Y-m-d H:i:s');
}
}
+
+ private function latestApiQuoteForInstrument(int $instrumentId): ?array
+ {
+ $stmt = $this->pdo->prepare(
+ 'SELECT *
+ FROM ' . $this->quoteTable . '
+ WHERE instrument_id = :instrument_id
+ AND source LIKE :source
+ ORDER BY quoted_at DESC, created_at DESC, id DESC
+ LIMIT 1'
+ );
+ $stmt->execute([
+ 'instrument_id' => $instrumentId,
+ 'source' => 'alphavantage:%',
+ ]);
+ $row = $stmt->fetch(PDO::FETCH_ASSOC);
+ return is_array($row) ? $row : null;
+ }
+
+ private function isApiSnapshotStale(?array $latestQuote, string $incomingQuotedAt): bool
+ {
+ if (!is_array($latestQuote)) {
+ return false;
+ }
+
+ $latestTimestamp = strtotime((string) ($latestQuote['quoted_at'] ?? ''));
+ $incomingTimestamp = strtotime(trim($incomingQuotedAt));
+ if ($latestTimestamp === false || $incomingTimestamp === false) {
+ return false;
+ }
+
+ return $incomingTimestamp <= $latestTimestamp;
+ }
}
diff --git a/partials/landingpages/modules/setup.php b/partials/landingpages/modules/setup.php
index 8fa80a1..dfa7ef9 100644
--- a/partials/landingpages/modules/setup.php
+++ b/partials/landingpages/modules/setup.php
@@ -398,44 +398,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
}
$moduleStatusPanel = null;
-if ($moduleName === 'boersenchecker') {
- $quotaApiKey = trim((string) ($current['bavest_api_key'] ?? ''));
- if ($quotaApiKey === '') {
- $moduleStatusPanel = [
- 'title' => 'Bavest Quota',
- 'type' => 'hint',
- 'text' => 'Kein Bavest API Key hinterlegt. Nach dem Speichern wird hier die aktuelle Quota automatisch angezeigt.',
- 'stats' => [],
- ];
- } else {
- $quotaTimeout = (int) ($current['bavest_timeout_sec'] ?? 10);
- $quotaTimeout = $quotaTimeout > 0 ? $quotaTimeout : 10;
- $quotaResult = $fetchJsonWithApiKey('https://api.bavest.co/v2/account/quota', $quotaApiKey, $quotaTimeout);
-
- if (!empty($quotaResult['ok'])) {
- $quotaData = is_array($quotaResult['data']['data'] ?? null) ? $quotaResult['data']['data'] : [];
- $moduleStatusPanel = [
- 'title' => 'Bavest Quota',
- 'type' => 'success',
- 'text' => 'Quota erfolgreich geprueft.',
- 'stats' => [
- ['label' => 'Periode', 'value' => (string) ($quotaData['period'] ?? '-')],
- ['label' => 'Verbrauch', 'value' => (string) ($quotaData['usage'] ?? '0')],
- ['label' => 'Limit', 'value' => ($quotaData['limit'] ?? null) !== null ? (string) $quotaData['limit'] : 'unbegrenzt'],
- ['label' => 'Verbleibend', 'value' => ($quotaData['remaining'] ?? null) !== null ? (string) $quotaData['remaining'] : 'unbegrenzt'],
- ['label' => 'API-Key ID', 'value' => (string) ($quotaData['apiKey'] ?? '-')],
- ],
- ];
- } else {
- $moduleStatusPanel = [
- 'title' => 'Bavest Quota',
- 'type' => 'error',
- 'text' => 'Quota konnte nicht geprueft werden: ' . (string) ($quotaResult['message'] ?? 'Unbekannter Fehler'),
- 'stats' => [],
- ];
- }
- }
-}
$activeDbGroup = $testGroup !== null && array_key_exists($testGroup, $dbGroups)
? $testGroup