Files
WebAPP/app/Services/SupportApiService.php
Rhino 2e24a40d68 Stand: SMTP-Test, Admin-Mail-Tab, Notifiable-Fix, Lazy-Quill
- Fix: Notifiable-Trait zum User-Model hinzugefuegt (behebt notify()-500er)
- Installer: SMTP-Verbindungstest mit EsmtpTransport + Ueberspringen-Link
- Admin: Neuer E-Mail-Tab mit SMTP-Konfiguration + Verbindungstest
- Admin: Lazy Quill-Initialisierung (nur sichtbare Locale wird geladen)
- Uebersetzungen: 17 neue Mail-Keys in allen 6 Sprachen

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 07:30:37 +01:00

260 lines
8.2 KiB
PHP

<?php
namespace App\Services;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
class SupportApiService
{
private ?array $installedData = null;
// ─── Registration ────────────────────────────────────
public function register(array $data): ?array
{
try {
$response = $this->httpClient()
->post('/register', $data);
if ($response->successful()) {
$result = $response->json();
$this->saveCredentials(
$result['installation_id'] ?? '',
$result['api_token'] ?? ''
);
return $result;
}
return null;
} catch (\Exception $e) {
Log::warning('Support API registration failed: ' . $e->getMessage());
return null;
}
}
public function isRegistered(): bool
{
$data = $this->readInstalled();
return !empty($data['installation_id']) && !empty($data['api_token']);
}
// ─── License ─────────────────────────────────────────
public function validateLicense(string $key): ?array
{
try {
$response = $this->authenticatedClient()
->post('/license/validate', ['license_key' => $key]);
if ($response->successful()) {
$result = $response->json();
Cache::put('support.license_valid', $result['valid'] ?? false, 86400);
return $result;
}
return null;
} catch (\Exception $e) {
Log::warning('License validation failed: ' . $e->getMessage());
return null;
}
}
// ─── Updates ─────────────────────────────────────────
public function checkForUpdate(bool $force = false): ?array
{
$cacheKey = 'support.update_check';
if (!$force && Cache::has($cacheKey)) {
return Cache::get($cacheKey);
}
try {
$params = [
'current_version' => config('app.version'),
'app_name' => \App\Models\Setting::get('app_name', config('app.name')),
];
$logoUrl = $this->getLogoUrl();
if ($logoUrl) {
$params['logo_url'] = $logoUrl;
}
$response = $this->authenticatedClient()
->get('/version/check', $params);
if ($response->successful()) {
$result = $response->json();
Cache::put($cacheKey, $result, 86400);
return $result;
}
return null;
} catch (\Exception $e) {
Log::warning('Update check failed: ' . $e->getMessage());
return null;
}
}
public function hasUpdate(): bool
{
$cached = Cache::get('support.update_check');
if (!$cached) {
return false;
}
return version_compare($cached['latest_version'] ?? '0.0.0', config('app.version'), '>');
}
// ─── Tickets ─────────────────────────────────────────
public function getTickets(): ?array
{
try {
$response = $this->authenticatedClient()->get('/tickets');
if ($response->successful()) {
return $response->json();
}
return null;
} catch (\Exception $e) {
Log::warning('Ticket list fetch failed: ' . $e->getMessage());
return null;
}
}
public function getTicket(int $id): ?array
{
try {
$response = $this->authenticatedClient()->get("/tickets/{$id}");
if ($response->successful()) {
return $response->json();
}
return null;
} catch (\Exception $e) {
Log::warning("Ticket #{$id} fetch failed: " . $e->getMessage());
return null;
}
}
public function createTicket(array $data): ?array
{
try {
$response = $this->authenticatedClient()->post('/tickets', $data);
if ($response->successful()) {
return $response->json();
}
return null;
} catch (\Exception $e) {
Log::warning('Ticket creation failed: ' . $e->getMessage());
return null;
}
}
public function replyToTicket(int $id, array $data): ?array
{
try {
$response = $this->authenticatedClient()
->post("/tickets/{$id}/messages", $data);
if ($response->successful()) {
return $response->json();
}
return null;
} catch (\Exception $e) {
Log::warning("Ticket #{$id} reply failed: " . $e->getMessage());
return null;
}
}
// ─── System Info ─────────────────────────────────────
public function getSystemInfo(): array
{
return [
'app_version' => config('app.version'),
'php_version' => PHP_VERSION,
'laravel_version' => app()->version(),
'db_driver' => config('database.default'),
'locale' => app()->getLocale(),
'os' => PHP_OS,
];
}
public function getLogoUrl(): ?string
{
$favicon = \App\Models\Setting::get('app_favicon');
if ($favicon) {
return rtrim(config('app.url'), '/') . '/storage/' . $favicon;
}
return null;
}
// ─── Storage/Installed Access ────────────────────────
public function readInstalled(): array
{
if ($this->installedData !== null) {
return $this->installedData;
}
$path = storage_path('installed');
if (!file_exists($path)) {
return $this->installedData = [];
}
$data = json_decode(file_get_contents($path), true);
return $this->installedData = is_array($data) ? $data : [];
}
// ─── Private Helpers ─────────────────────────────────
private function httpClient(): \Illuminate\Http\Client\PendingRequest
{
$apiUrl = config('support.api_url');
// SSRF-Schutz: Nur HTTPS und keine privaten IPs (T06)
$parsed = parse_url($apiUrl);
$scheme = $parsed['scheme'] ?? '';
$host = $parsed['host'] ?? '';
if ($scheme !== 'https') {
throw new \RuntimeException('Support API URL must use HTTPS.');
}
$ip = gethostbyname($host);
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) {
throw new \RuntimeException('Support API URL must not resolve to a private/reserved IP.');
}
// DNS-Rebinding verhindern: aufgelöste IP direkt verwenden (V07)
$resolvedUrl = str_replace($host, $ip, $apiUrl);
return Http::baseUrl($resolvedUrl)
->timeout(config('support.timeout', 10))
->connectTimeout(config('support.connect_timeout', 5))
->withHeaders(['Accept' => 'application/json', 'Host' => $host]);
}
private function authenticatedClient(): \Illuminate\Http\Client\PendingRequest
{
$token = $this->readInstalled()['api_token'] ?? '';
return $this->httpClient()->withToken($token);
}
private function saveCredentials(string $installationId, string $apiToken): void
{
$path = storage_path('installed');
$data = $this->readInstalled();
$data['installation_id'] = $installationId;
$data['api_token'] = $apiToken;
$data['registered_at'] = now()->toIso8601String();
file_put_contents($path, json_encode($data, JSON_PRETTY_PRINT));
chmod($path, 0600);
// Reset memoized data
$this->installedData = $data;
}
}