Frontend-Umbau (Kevin, 06.07.2026), Ergebnis des businessportal24-Audits: - Fake-Anzeigen und Mock-/Fallback-Daten entfernt (focus-ad, feed-ad, highlight-card, press-release-card, featured-releases, press-releases-grid); Sektionen rendern nur noch mit echten Daten oder gar nicht - Newsletter-Strip und Newsletter-Box zurueckgebaut (Feature nachgelagert, vor Reaktivierung DOI-Flow noetig) - Echte Boersendaten: MarketDataService (Yahoo-Finance-Quotes, config/market.php, Cache) + market:refresh alle 15 Min. im Scheduler; live-ticker blendet sich ohne Daten aus - EN-Lokalisierung der Kern-Seiten via lang/de/web.php + lang/en/web.php; Carbon-Locale folgt app()->getLocale() - Web-Tests auf den neuen Frontend-Stand nachgezogen (Suite: 993 gruen) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
109 lines
3.4 KiB
PHP
109 lines
3.4 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Web;
|
|
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
/**
|
|
* Kursdaten für den Ad-Hoc-Ticker der Web-Frontends.
|
|
*
|
|
* refresh() fragt die konfigurierten Symbole (config/market.php) über die
|
|
* Yahoo-Finance-Chart-API ab und legt sie in den Cache; die Views lesen nur
|
|
* aus dem Cache (slides()). Schlägt der Abruf für alle Symbole fehl, bleibt
|
|
* der bestehende Cache-Stand erhalten, damit ein API-Ausfall die Anzeige
|
|
* nicht sofort leert.
|
|
*/
|
|
class MarketDataService
|
|
{
|
|
/**
|
|
* @return list<array{label: string, value: string, delta: string, positive: bool}>
|
|
*/
|
|
public function slides(): array
|
|
{
|
|
if (! config('market.enabled')) {
|
|
return [];
|
|
}
|
|
|
|
/** @var list<array{label: string, value: string, delta: string, positive: bool}> $slides */
|
|
$slides = Cache::get((string) config('market.cache_key'), []);
|
|
|
|
return is_array($slides) ? $slides : [];
|
|
}
|
|
|
|
/**
|
|
* @return list<array{label: string, value: string, delta: string, positive: bool}>
|
|
*/
|
|
public function refresh(): array
|
|
{
|
|
$slides = [];
|
|
|
|
foreach ((array) config('market.symbols', []) as $config) {
|
|
$quote = $this->fetchQuote((string) $config['symbol']);
|
|
|
|
if ($quote === null) {
|
|
continue;
|
|
}
|
|
|
|
[$price, $previousClose] = $quote;
|
|
|
|
$deltaPercent = $previousClose > 0.0
|
|
? ($price - $previousClose) / $previousClose * 100
|
|
: 0.0;
|
|
|
|
$slides[] = [
|
|
'label' => (string) $config['label'],
|
|
'value' => number_format($price, (int) ($config['precision'] ?? 2), ',', '.').($config['suffix'] ?? ''),
|
|
'delta' => ($deltaPercent >= 0 ? '+' : '').number_format($deltaPercent, 1, ',', '.').'%',
|
|
'positive' => $deltaPercent >= 0,
|
|
];
|
|
}
|
|
|
|
if ($slides === []) {
|
|
Log::warning('market:refresh lieferte keine Kursdaten — bestehender Cache-Stand bleibt erhalten.');
|
|
|
|
return $this->slides();
|
|
}
|
|
|
|
Cache::put((string) config('market.cache_key'), $slides, (int) config('market.cache_ttl', 86400));
|
|
|
|
return $slides;
|
|
}
|
|
|
|
/**
|
|
* @return array{0: float, 1: float}|null [Kurs, Vortagesschluss]
|
|
*/
|
|
private function fetchQuote(string $symbol): ?array
|
|
{
|
|
try {
|
|
$response = Http::timeout(10)
|
|
->withHeaders(['User-Agent' => 'Mozilla/5.0 (compatible; PressekontoTicker/1.0)'])
|
|
->get(config('market.endpoint').rawurlencode($symbol), [
|
|
'range' => '1d',
|
|
'interval' => '1d',
|
|
]);
|
|
} catch (\Throwable $exception) {
|
|
Log::warning('Kursabruf fehlgeschlagen.', ['symbol' => $symbol, 'error' => $exception->getMessage()]);
|
|
|
|
return null;
|
|
}
|
|
|
|
if (! $response->successful()) {
|
|
Log::warning('Kursabruf fehlgeschlagen.', ['symbol' => $symbol, 'status' => $response->status()]);
|
|
|
|
return null;
|
|
}
|
|
|
|
$meta = $response->json('chart.result.0.meta');
|
|
|
|
$price = $meta['regularMarketPrice'] ?? null;
|
|
$previousClose = $meta['chartPreviousClose'] ?? $meta['previousClose'] ?? null;
|
|
|
|
if (! is_numeric($price) || ! is_numeric($previousClose)) {
|
|
return null;
|
|
}
|
|
|
|
return [(float) $price, (float) $previousClose];
|
|
}
|
|
}
|