5, 'redirection' => 0, 'headers' => [ 'Authorization' => 'Bearer ' . $schluessel, 'Accept' => 'application/json', 'User-Agent' => 'sendmails-wordpress/' . SENDMAILS_VERSION, ], ]); if (is_wp_error($antwort)) { /* translators: %s: technical error message */ return self::fehler(0, 'nicht_erreichbar', sprintf(__('api.send-mails.de is not reachable (%s).', 'purehost-send-mails'), $antwort->get_error_message())); } $status = (int) wp_remote_retrieve_response_code($antwort); $daten = json_decode((string) wp_remote_retrieve_body($antwort), true); if ($status >= 200 && $status < 300) { return ['ok' => true, 'status' => $status, 'daten' => is_array($daten) ? $daten : [], 'fehler' => '', 'code' => '']; } $code = is_array($daten) ? (string) ($daten['fehler']['code'] ?? '') : ''; $nachricht = is_array($daten) ? (string) ($daten['fehler']['nachricht'] ?? '') : ''; if ($status === 401) { $nachricht = __('The API key is invalid or was revoked in the portal.', 'purehost-send-mails'); } elseif ($status === 403 && $code === 'verboten') { $nachricht = __('The API key lacks the “read” permission. Create a key with this permission in the portal.', 'purehost-send-mails'); } elseif ($nachricht === '') { /* translators: %d: HTTP status */ $nachricht = sprintf(__('Response from api.send-mails.de with status %d.', 'purehost-send-mails'), $status); } return self::fehler($status, $code ?: 'http_' . $status, $nachricht); } /** * Automatische Einrichtung: der Schluessel (Recht "einrichten") legt einen eigenen SMTP-Zugang an. * @return array{ok:bool,status:int,daten:array,fehler:string,code:string} */ public static function einrichten(string $schluessel): array { $antwort = wp_remote_post(Einstellungen::apiUrl() . '/v1/einrichtung', [ 'timeout' => 15, 'redirection' => 0, 'headers' => [ 'Authorization' => 'Bearer ' . $schluessel, 'Accept' => 'application/json', 'Content-Type' => 'application/json', 'User-Agent' => 'sendmails-wordpress/' . SENDMAILS_VERSION, ], 'body' => (string) wp_json_encode(['website' => home_url('/')]), ]); if (is_wp_error($antwort)) { /* translators: %s: technical error message */ return self::fehler(0, 'nicht_erreichbar', sprintf(__('api.send-mails.de is not reachable (%s).', 'purehost-send-mails'), $antwort->get_error_message())); } $status = (int) wp_remote_retrieve_response_code($antwort); $daten = json_decode((string) wp_remote_retrieve_body($antwort), true); if ($status === 200 && is_array($daten) && !empty($daten['smtp']['benutzername']) && !empty($daten['smtp']['passwort'])) { Einstellungen::smtpHostsMerken($daten['smtp']['hosts'] ?? null); return ['ok' => true, 'status' => $status, 'daten' => $daten, 'fehler' => '', 'code' => '']; } $code = is_array($daten) ? (string) ($daten['fehler']['code'] ?? '') : ''; $nachricht = is_array($daten) ? (string) ($daten['fehler']['nachricht'] ?? '') : ''; return self::fehler($status, $code ?: 'http_' . $status, $nachricht !== '' ? $nachricht : sprintf( /* translators: %d: HTTP status */ __('Response from api.send-mails.de with status %d.', 'purehost-send-mails'), $status)); } /** Wie anfrage(), Erfolg fuenf Minuten, Fehler eine Minute zwischengespeichert. */ public static function zwischengespeichert(string $pfad, array $query = [], bool $neu = false, bool $nurGespeichert = false): ?array { // Generation im Schluessel: cacheLeeren() wirkt so auch mit Objekt-Cache (Redis, Memcached). $generation = (int) get_option('sendmails_cache_gen', 0); $schluessel = 'sendmails_api_' . md5($generation . '|' . $pfad . '?' . http_build_query($query) . '|' . Einstellungen::apiSchluessel()); if (!$neu) { $alt = get_transient($schluessel); if (is_array($alt)) { return $alt; } } if ($nurGespeichert) { return null; } $r = self::anfrage($pfad, $query); // Fehler kurz merken: ist die API weg, soll nicht jede Mail und jede Admin-Seite 10 s warten. set_transient($schluessel, $r, $r['ok'] ? self::ZWISCHENSPEICHER_S : 60); return $r; } public static function kontingent(bool $neu = false): array { $r = (array) self::zwischengespeichert('/v1/kontingent', [], $neu); if (!empty($r['ok'])) { Einstellungen::smtpHostsMerken($r['daten']['smtp_hosts'] ?? null); } return $r; } /** Domains mit DNS-Eintraegen und ihrer Pruefung gegen das oeffentliche DNS (Domain-Check). */ public static function domainsDns(bool $neu = false): array { return (array) self::zwischengespeichert('/v1/domains', ['dns' => '1'], $neu); } /** Nur aus dem Zwischenspeicher, null wenn nichts da ist (fuer Hinweise auf jeder Admin-Seite). */ public static function kontingentGespeichert(): ?array { return self::zwischengespeichert('/v1/kontingent', [], false, true); } public const ZEITRAEUME = ['7t', '30t', 'monat', 'vormonat']; public static function statistik(bool $neu = false, string $zeitraum = '30t'): array { $zeitraum = in_array($zeitraum, self::ZEITRAEUME, true) ? $zeitraum : '30t'; return (array) self::zwischengespeichert('/v1/statistik', ['zeitraum' => $zeitraum], $neu); } /** CSV der Statistik unveraendert durchreichen (Export auf der Statistik-Seite). */ public static function statistikCsv(string $zeitraum): ?string { $zeitraum = in_array($zeitraum, self::ZEITRAEUME, true) ? $zeitraum : '30t'; return self::csv('/v1/statistik.csv?zeitraum=' . rawurlencode($zeitraum)); } /** Alle Bounces und Beschwerden mit Diagnose als CSV (wie der Export im Kundenportal). */ public static function ereignisseCsv(): ?string { return self::csv('/v1/ereignisse.csv'); } private static function csv(string $pfad): ?string { $schluessel = Einstellungen::apiSchluessel(); if ($schluessel === '') { return null; } $antwort = wp_remote_get(Einstellungen::apiUrl() . $pfad, [ 'timeout' => 20, 'redirection' => 0, 'headers' => ['Authorization' => 'Bearer ' . $schluessel, 'User-Agent' => 'sendmails-wordpress/' . SENDMAILS_VERSION], ]); if (is_wp_error($antwort) || (int) wp_remote_retrieve_response_code($antwort) !== 200) { return null; } return (string) wp_remote_retrieve_body($antwort); } /** Adresse auf die Sperrliste setzen (Recht "sperrliste"). */ public static function sperren(string $adresse): array { return self::schreiben('POST', '/v1/sperrliste', ['adresse' => $adresse]); } /** Selbst eingetragene Adresse wieder zulassen; Bounces und Beschwerden lehnt send-mails.de ab. */ public static function entsperren(string $adresse): array { return self::schreiben('DELETE', '/v1/sperrliste/' . rawurlencode($adresse)); } /** Verhalten bei vollem Kontingent setzen, auf Wunsch mit Bestaetigung des Haftungshinweises (Recht "kontingent"). */ public static function kontingentRegeln(array $body): array { return self::schreiben('PUT', '/v1/kontingent/regeln', $body, __('change behaviour when the quota is used up', 'purehost-send-mails')); } /** Zustimmung zur Berechnung der Mehrmenge widerrufen. */ public static function kontingentWiderrufen(): array { return self::schreiben('DELETE', '/v1/kontingent/bestaetigung', null, __('change behaviour when the quota is used up', 'purehost-send-mails')); } /** $recht: Name des noetigen Schluesselrechts fuer die Meldung bei 403. */ private static function schreiben(string $methode, string $pfad, ?array $body = null, string $recht = ''): array { $schluessel = Einstellungen::apiSchluessel(); if ($schluessel === '') { return self::fehler(0, 'kein_schluessel', __('No API key configured.', 'purehost-send-mails')); } $kopf = ['Authorization' => 'Bearer ' . $schluessel, 'Accept' => 'application/json', 'User-Agent' => 'sendmails-wordpress/' . SENDMAILS_VERSION]; $args = ['method' => $methode, 'timeout' => 10, 'redirection' => 0, 'headers' => $kopf]; if ($body !== null) { $args['headers']['Content-Type'] = 'application/json'; $args['body'] = (string) wp_json_encode($body); } $antwort = wp_remote_request(Einstellungen::apiUrl() . $pfad, $args); if (is_wp_error($antwort)) { /* translators: %s: technical error message */ return self::fehler(0, 'nicht_erreichbar', sprintf(__('api.send-mails.de is not reachable (%s).', 'purehost-send-mails'), $antwort->get_error_message())); } $status = (int) wp_remote_retrieve_response_code($antwort); $daten = json_decode((string) wp_remote_retrieve_body($antwort), true); if ($status >= 200 && $status < 300) { self::cacheLeeren(); return ['ok' => true, 'status' => $status, 'daten' => is_array($daten) ? $daten : [], 'fehler' => '', 'code' => '']; } $code = is_array($daten) ? (string) ($daten['fehler']['code'] ?? '') : ''; $nachricht = is_array($daten) ? (string) ($daten['fehler']['nachricht'] ?? '') : ''; if ($status === 403 && str_contains($nachricht, 'Recht')) { $nachricht = sprintf( /* translators: %s: name of the API key permission */ __('The API key lacks the “%s” permission. Add it to the key in the customer portal or create a new key for the WordPress plugin.', 'purehost-send-mails'), $recht !== '' ? $recht : __('change blocklist', 'purehost-send-mails') ); } elseif ($nachricht === '') { /* translators: %d: HTTP status */ $nachricht = sprintf(__('Response from api.send-mails.de with status %d.', 'purehost-send-mails'), $status); } return self::fehler($status, $code ?: 'http_' . $status, $nachricht); } /** Gesperrte Adressen des Kunden, neueste zuerst; $vor blaettert weiter (Wert "weiter" der vorigen Seite). */ public static function sperrliste(bool $neu = false, string $suche = '', string $vor = ''): array { $q = ['limit' => 50]; if ($suche !== '') { $q['suche'] = $suche; } if (preg_match('/^\d{1,18}$/', $vor)) { $q['vor'] = $vor; } return (array) self::zwischengespeichert('/v1/sperrliste', $q, $neu); } public static function domains(bool $neu = false): array { $r = (array) self::zwischengespeichert('/v1/domains', [], $neu); if ($r['ok'] ?? false) { $liste = []; foreach ((array) ($r['daten']['domains'] ?? []) as $d) { if (($d['status'] ?? '') === 'verifiziert' && !empty($d['domain'])) { $liste[] = strtolower((string) $d['domain']); } } if (get_option(self::DOMAINS) !== $liste) { update_option(self::DOMAINS, $liste, true); } } return $r; } public const DOMAINS = 'sendmails_domains_bekannt'; /** Verifizierte Absenderdomains, null wenn unbekannt (kein Schluessel, API weg). */ public static function verifizierteDomains(): ?array { $r = self::domains(); return $r['ok'] ? self::bekannteDomains() : null; } /** Zuletzt erfolgreich abgerufene Liste, ohne Netzwerk (fuer den Versandweg). */ public static function bekannteDomains(): ?array { $liste = get_option(self::DOMAINS, null); return is_array($liste) ? $liste : null; } public static function cacheLeeren(): void { update_option('sendmails_cache_gen', (int) get_option('sendmails_cache_gen', 0) + 1, true); } private static function fehler(int $status, string $code, string $nachricht): array { return ['ok' => false, 'status' => $status, 'daten' => null, 'fehler' => $nachricht, 'code' => $code]; } }