' . esc_html__('Set up', 'purehost-send-mails') . ''); return $links; } public static function url(): string { return admin_url('admin.php?page=' . self::SLUG); } public const SLUG_EINSTELLUNGEN = 'sendmails-einstellungen'; public static function einstellungenUrl(): string { return admin_url('admin.php?page=' . self::SLUG_EINSTELLUNGEN); } /** Eigene Seite "Einstellungen" im Menue: API-Schluessel, SMTP, Absender, Bounces und Warteschlange. */ public static function einstellungenSeite(): void { ob_start(); try { self::einstellungenInhalt(); } finally { echo Ansicht::satzweise((string) ob_get_clean()); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Seite aus maskierten Teilen, nur
zwischen Saetzen ergaenzt } } private static function einstellungenInhalt(): void { if (!current_user_can('manage_options')) { return; } $meldung = get_transient('sendmails_meldung_' . get_current_user_id()); delete_transient('sendmails_meldung_' . get_current_user_id()); $schritte = self::schritte(); echo '
'; self::kopf($schritte, 'einstellungen'); if (is_array($meldung)) { printf('

%s

', esc_attr($meldung['typ']), esc_html($meldung['text'])); } self::einstellungen(); echo '
'; } // ------------------------------------------------------------------ Aktionen public static function aktion(): void { if (!current_user_can('manage_options')) { wp_die(esc_html__('Permission denied.', 'purehost-send-mails'), 403); } $tun = sanitize_key(wp_unslash($_POST['tun'] ?? '')); check_admin_referer('sendmails_' . $tun); $p = wp_unslash($_POST); [$typ, $text] = match ($tun) { 'api' => self::apiSpeichern($p), 'smtp' => self::smtpSpeichern($p), 'smtp_pruefen' => self::smtpPruefen(), 'einrichten' => self::einrichtenMitGespeichertem(), 'absender' => self::absenderSpeichern($p), 'testmail' => self::testmail($p), 'einschalten' => self::schalten(true), 'ausschalten' => self::schalten(false), 'selbsttest' => self::selbsttest(), 'abgleich' => self::abgleich(), 'warteschlange' => self::warteschlange(), 'ws_loeschen' => ['success', sprintf( /* translators: %d: count */ __('%d given-up emails removed.', 'purehost-send-mails'), Warteschlange::aufgegebeneLoeschen() )], 'optionen' => self::optionen($p), 'protokoll' => self::protokollSpeichern($p), 'schutz' => self::schutzSpeichern($p), 'kontingent' => self::kontingentSpeichern($p), 'absender_regeln' => self::absenderRegelnSpeichern($p), 'systemmails' => self::systemmailsSpeichern($p), 'design' => self::designSpeichern($p), 'umstieg' => \SendMails\Umstieg::ausfuehren(), 'kontingent_widerruf' => self::kontingentWiderrufen(), 'bremse' => self::bremse($p), 'wochenbericht' => Waechter::wochenbericht(true) /* translators: %s: email address */ ? ['success', sprintf(__('Weekly report sent to %s.', 'purehost-send-mails'), Waechter::empfaenger())] : ['error', __('The weekly report could not be sent.', 'purehost-send-mails')], default => ['error', __('Unknown action.', 'purehost-send-mails')], }; set_transient('sendmails_meldung_' . get_current_user_id(), ['typ' => $typ, 'text' => $text], 120); // Zurueck auf die Seite des Formulars (Uebersicht oder Einstellungen) $zurueck = (string) wp_get_referer(); wp_safe_redirect(str_contains($zurueck, 'page=' . self::SLUG_EINSTELLUNGEN) ? self::einstellungenUrl() : self::url()); exit; } private static function apiSpeichern(array $p): array { if (Einstellungen::ausKonstante('api_schluessel')) { return ['warning', __('The API key is defined in wp-config.php and cannot be changed here.', 'purehost-send-mails')]; } if (!empty($p['entfernen'])) { Einstellungen::speichern(['api_schluessel' => '']); Api::cacheLeeren(); return ['success', __('API key removed.', 'purehost-send-mails')]; } $schluessel = trim(sanitize_text_field((string) ($p['api_schluessel'] ?? ''))); if (!preg_match('/^sm_live_[A-Za-z0-9_.-]{10,190}$/', $schluessel)) { return ['error', __('This does not look like a send-mails.de API key. It starts with “sm_live_” and can be found in the portal under API keys.', 'purehost-send-mails')]; } $r = Api::anfrage('/v1/kontingent', [], $schluessel); if (!$r['ok']) { return ['error', $r['fehler']]; } Einstellungen::speichern(['api_schluessel' => Krypto::verschluesseln($schluessel)]); Api::cacheLeeren(); /* translators: %s: plan name */ $text = sprintf(__('API key saved. Connected to your account, plan %s.', 'purehost-send-mails'), (string) ($r['daten']['tarif'] ?? '–')); // SMTP-Zugang und Absender selbst einrichten, wenn der Schluessel das darf und nichts fest in wp-config.php steht if (Einstellungen::ausKonstante('smtp_benutzer') || Einstellungen::ausKonstante('smtp_passwort')) { return ['success', $text]; } [$typ, $zusatz] = self::automatischEinrichten($schluessel); return [$typ, $text . ' ' . $zusatz]; } /** Knopf in Schritt 2: mit dem gespeicherten Schluessel einrichten (z. B. nachdem er das Recht bekommen hat). */ private static function einrichtenMitGespeichertem(): array { if (Einstellungen::ausKonstante('smtp_benutzer') || Einstellungen::ausKonstante('smtp_passwort')) { return ['warning', __('The SMTP login is defined in wp-config.php and is not set up automatically.', 'purehost-send-mails')]; } $schluessel = Einstellungen::apiSchluessel(); if ($schluessel === '') { return ['error', __('No API key configured.', 'purehost-send-mails')]; } return self::automatischEinrichten($schluessel); } /** @return array{0:string,1:string} Meldungstyp und Text */ private static function automatischEinrichten(string $schluessel): array { $e = Api::einrichten($schluessel); if (!$e['ok']) { if ($e['status'] === 403) { return ['success', __('This key may not set up the SMTP login itself. Enter the login manually or create a key with the purpose “WordPress plugin” in the portal.', 'purehost-send-mails')]; } /* translators: %s: error message */ return ['warning', sprintf(__('Automatic setup of the SMTP login failed: %s', 'purehost-send-mails'), $e['fehler'])]; } $d = $e['daten']; $neu = [ 'smtp_benutzer' => sanitize_text_field((string) $d['smtp']['benutzername']), 'smtp_passwort' => Krypto::verschluesseln((string) $d['smtp']['passwort']), 'smtp_geprueft' => '', 'smtp_automatisch' => gmdate('c'), 'smtp_bereit_ab' => time() + max(0, min(600, (int) ($d['bereit_in_sekunden'] ?? 90))), 'testmail_ok' => '', ]; $vorschlag = sanitize_email((string) ($d['absender_vorschlag'] ?? '')); if ((string) Einstellungen::wert('absender_email') === '' && $vorschlag !== '') { $neu['absender_email'] = $vorschlag; } Einstellungen::speichern($neu); return ['success', __('SMTP login and sender were set up automatically. The login becomes active in about a minute, then the plugin checks it on its own.', 'purehost-send-mails')]; } /** Anmeldung des (automatisch angelegten) Zugangs pruefen; auch beim Laden der Seite, hoechstens alle 30 s. */ private static function smtpPruefen(): array { if (!Einstellungen::smtpBereit()) { return ['error', __('No SMTP login configured.', 'purehost-send-mails')]; } [$ok, $fehler] = Versand::verbindungPruefen(Einstellungen::smtpBenutzer(), Einstellungen::smtpPasswort()); if ($ok) { Einstellungen::speichern(['smtp_geprueft' => gmdate('c')]); return ['success', __('Login to smtp.send-mails.de successful.', 'purehost-send-mails')]; } if ((int) Einstellungen::wert('smtp_bereit_ab') > time() - 300) { return ['warning', __('The new SMTP login is not active everywhere yet. Please check again in a minute.', 'purehost-send-mails')]; } /* translators: %s: error message */ return ['error', sprintf(__('Login to smtp.send-mails.de failed: %s', 'purehost-send-mails'), $fehler)]; } /** Automatisch angelegter Zugang noch ungeprueft und Wartezeit vorbei: still pruefen (hoechstens alle 30 s). */ private static function automatischPruefen(): void { $o = Einstellungen::alle(); if ($o['smtp_automatisch'] === '' || $o['smtp_geprueft'] !== '' || !Einstellungen::smtpBereit() || time() < (int) $o['smtp_bereit_ab']) { return; } if (get_transient('sendmails_autopruefung')) { return; } set_transient('sendmails_autopruefung', 1, 30); [$ok] = Versand::verbindungPruefen(Einstellungen::smtpBenutzer(), Einstellungen::smtpPasswort()); if ($ok) { Einstellungen::speichern(['smtp_geprueft' => gmdate('c')]); } } private static function smtpSpeichern(array $p): array { $neu = []; if (!Einstellungen::ausKonstante('smtp_benutzer')) { $neu['smtp_benutzer'] = trim(sanitize_text_field((string) ($p['smtp_benutzer'] ?? ''))); } // Leeres Passwortfeld laesst das gespeicherte stehen; Passwoerter nicht durch sanitize_text_field (veraendert Sonderzeichen). $passwort = (string) ($p['smtp_passwort'] ?? ''); if (!Einstellungen::ausKonstante('smtp_passwort') && $passwort !== '') { if (preg_match('/[\r\n\0]/', $passwort)) { return ['error', __('The password contains invalid characters.', 'purehost-send-mails')]; } $neu['smtp_passwort'] = Krypto::verschluesseln($passwort); } $neu['smtp_geprueft'] = ''; $neu['smtp_automatisch'] = ''; Einstellungen::speichern($neu); if (!Einstellungen::smtpBereit()) { return ['error', __('Please enter the username and password of the SMTP login.', 'purehost-send-mails')]; } [$ok, $fehler] = Versand::verbindungPruefen(Einstellungen::smtpBenutzer(), Einstellungen::smtpPasswort()); if (!$ok) { /* translators: %s: error message */ return ['error', sprintf(__('Saved, but the login to smtp.send-mails.de failed: %s', 'purehost-send-mails'), $fehler)]; } Einstellungen::speichern(['smtp_geprueft' => gmdate('c')]); return ['success', __('SMTP login saved, login to smtp.send-mails.de successful.', 'purehost-send-mails')]; } private static function absenderSpeichern(array $p): array { $lokal = strtolower(trim(sanitize_text_field((string) ($p['absender_lokal'] ?? '')))); $domain = strtolower(trim(sanitize_text_field((string) ($p['absender_domain'] ?? '')))); $email = sanitize_email($lokal . '@' . $domain); if (!is_email($email)) { return ['error', __('Please enter a valid sender address.', 'purehost-send-mails')]; } $verifiziert = Api::verifizierteDomains(); if ($verifiziert !== null && !in_array($domain, $verifiziert, true)) { /* translators: %s: domain */ return ['error', sprintf(__('The domain %s is not verified at send-mails.de. Add it in the portal and create the DNS records.', 'purehost-send-mails'), $domain)]; } Einstellungen::speichern([ 'absender_email' => $email, 'absender_name' => trim(sanitize_text_field((string) ($p['absender_name'] ?? ''))), 'absender_erzwingen' => !empty($p['absender_erzwingen']), ]); return ['success', __('Sender saved.', 'purehost-send-mails')]; } /** Absender je Quelle: nur Adressen verifizierter Domains, hoechstens 20 Regeln, je Quelle eine. */ private static function absenderRegelnSpeichern(array $p): array { $verifiziert = Api::verifizierteDomains(); $regeln = []; foreach ((array) ($p['regel'] ?? []) as $r) { $quelle = sanitize_text_field((string) ($r['quelle'] ?? '')); $email = strtolower(sanitize_email((string) ($r['email'] ?? ''))); if ($quelle === '' || !empty($r['entfernen'])) { continue; } if (!is_email($email)) { /* translators: %s: plugin name */ return ['error', sprintf(__('Please enter a valid sender address for %s.', 'purehost-send-mails'), \SendMails\Protokoll::name($quelle))]; } $domain = substr((string) strrchr($email, '@'), 1); if ($verifiziert !== null && !in_array($domain, $verifiziert, true)) { /* translators: %s: domain */ return ['error', sprintf(__('The domain %s is not verified at send-mails.de. Add it in the portal and create the DNS records.', 'purehost-send-mails'), $domain)]; } $antwort = sanitize_email((string) ($r['antwort'] ?? '')); $regeln[$quelle] = ['quelle' => $quelle, 'email' => $email, 'name' => mb_substr(trim(sanitize_text_field((string) ($r['name'] ?? ''))), 0, 100), 'antwort' => is_email($antwort) ? strtolower($antwort) : '']; } Einstellungen::speichern(['absender_regeln' => array_slice(array_values($regeln), 0, 20)]); return ['success', __('Senders per source saved.', 'purehost-send-mails')]; } private static function designSpeichern(array $p): array { $logo = esc_url_raw(trim((string) ($p['design_logo'] ?? '')), ['https']); if (trim((string) ($p['design_logo'] ?? '')) !== '' && !str_starts_with($logo, 'https://')) { return ['error', __('The logo must be an https address.', 'purehost-send-mails')]; } Einstellungen::speichern([ 'design' => !empty($p['design']), 'design_umfang' => ($p['design_umfang'] ?? '') === 'alle' ? 'alle' : 'wordpress', 'design_farbe' => sanitize_hex_color((string) ($p['design_farbe'] ?? '')) ?: '#0f766e', 'design_logo' => $logo, 'design_fuss' => mb_substr(sanitize_textarea_field((string) ($p['design_fuss'] ?? '')), 0, 500), ]); return ['success', __('Settings saved.', 'purehost-send-mails')]; } /** Vorschau der Vorlage mit Beispieltext (eigener Tab). */ public static function vorschau(): void { if (!current_user_can('manage_options')) { wp_die(esc_html__('Permission denied.', 'purehost-send-mails'), 403); } check_admin_referer('sendmails_vorschau'); nocache_headers(); header('Content-Type: text/html; charset=utf-8'); header("Content-Security-Policy: default-src 'none'; img-src https: data:; style-src 'unsafe-inline'"); /* translators: 1: website name, 2: website address */ $text = sprintf(__("Hello,\n\nthis is how emails from WordPress look with the design of %1\$s, for example “Password reset” or “New comment”.\n\nLinks stay clickable: %2\$s\n\nKind regards", 'purehost-send-mails'), get_bloginfo('name'), home_url('/')); echo \SendMails\Design::html($text, __('Preview', 'purehost-send-mails')); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Vorlage maskiert jeden Teil exit; } private static function systemmailsSpeichern(array $p): array { $aus = array_values(array_intersect(array_keys(\SendMails\Systemmails::arten()), array_map('sanitize_key', (array) ($p['aus'] ?? [])))); Einstellungen::speichern(['systemmails_aus' => $aus]); return ['success', __('Settings saved.', 'purehost-send-mails')]; } private static function testmail(array $p): array { if (!Einstellungen::smtpBereit()) { return ['error', __('Set up the SMTP login first.', 'purehost-send-mails')]; } $an = sanitize_email((string) ($p['an'] ?? '')); if (!is_email($an)) { return ['error', __('Please enter a valid recipient address.', 'purehost-send-mails')]; } [$ok, $fehler] = Versand::test([ 'to' => $an, 'subject' => __('Test email from send-mails.de for WordPress', 'purehost-send-mails'), /* translators: %s: website address */ 'message' => sprintf(__("This test email comes from %s and was sent via send-mails.de.\n\nIf it has arrived, sending is set up correctly.", 'purehost-send-mails'), home_url()), ]); if (!$ok) { /* translators: %s: error message */ return ['error', sprintf(__('The test email was not accepted: %s', 'purehost-send-mails'), (string) $fehler['text'])]; } Einstellungen::speichern(['testmail_ok' => gmdate('c')]); /* translators: %s: email address */ return ['success', sprintf(__('Test email to %s was accepted by send-mails.de. Check the inbox.', 'purehost-send-mails'), $an)]; } /** Was zu tun ist, wenn ein anderes Mail-Plugin den Versand uebernimmt (Hinweis auf der Seite und Meldung nach dem Klick). */ private static function fremdHinweis(string $fremd): string { return Einstellungen::versandAktiv() /* translators: %1$s: plugin name */ ? sprintf(__('%1$s is active again and handles email sending for this website. Deactivate %1$s under Plugins so that emails go through send-mails.de again.', 'purehost-send-mails'), $fremd) /* translators: %1$s: plugin name */ : sprintf(__('%1$s still sends the emails of this website. To switch to send-mails.de, deactivate %1$s under Plugins and then click “Turn on sending” here right away.', 'purehost-send-mails'), $fremd); } /** Hinweis als HTML mit Link auf die Pluginliste, gefiltert auf genau dieses Plugin (dort steht "Deaktivieren"). */ private static function fremdHinweisHtml(string $fremd, string $typ): string { $umstieg = ''; $u = \SendMails\Umstieg::erkennen(); if ($u !== null && Einstellungen::smtpBereit() && current_user_can('activate_plugins')) { $text = $u['email'] !== '' && $u['verifiziert'] && (string) Einstellungen::wert('absender_email') === '' /* translators: 1: plugin name, 2: email address */ ? sprintf(__('Switch in one step: take over the sender %2$s, deactivate %1$s and turn on sending via send-mails.de.', 'purehost-send-mails'), $u['name'], $u['email']) /* translators: %s: plugin name */ : sprintf(__('Switch in one step: deactivate %s and turn on sending via send-mails.de.', 'purehost-send-mails'), $u['name']); $umstieg = self::formular('umstieg', '

' . esc_html($text) . '

', __('Switch now', 'purehost-send-mails')); } return '

' . esc_html(self::fremdHinweis($fremd)) . ' ' /* translators: %s: plugin name */ . esc_html(sprintf(__('Open %s in the plugin list', 'purehost-send-mails'), $fremd)) . '

' . $umstieg . '
'; } private static function schalten(bool $an): array { if ($an && !Einstellungen::smtpBereit()) { return ['error', __('Set up the SMTP login first.', 'purehost-send-mails')]; } $fremd = Versand::fremdesMailPlugin(); if ($an && $fremd !== null) { return ['error', self::fremdHinweis($fremd)]; } Einstellungen::speichern(['aktiv' => $an]); return $an ? ['success', __('Sending via send-mails.de is on. All emails from this website now go through smtp.send-mails.de.', 'purehost-send-mails')] : ['warning', __('Sending via send-mails.de is off. WordPress sends emails as before.', 'purehost-send-mails')]; } private static function selbsttest(): array { if (!Einstellungen::smtpBereit()) { return ['error', __('Set up the SMTP login first.', 'purehost-send-mails')]; } $fehler = []; // Nur Zustellung und Bounce; Test-Postfaecher zaehlen bei send-mails.de nur ins Kontingent. foreach (Testpostfach::adressen() as $an => $rolle) { [$ok, $f] = Versand::test([ 'to' => $an, 'subject' => __('Self-test send-mails.de for WordPress', 'purehost-send-mails'), 'message' => __('Self-test using the test mailboxes of send-mails.de.', 'purehost-send-mails'), ]); if (!$ok) { $fehler[] = $rolle . ': ' . Protokoll::ohneAdressen((string) $f['text']); } } if ($fehler) { return ['error', implode(' · ', $fehler)]; } return ['success', __('Two self-test emails are on their way (delivery and bounce). The bounce appears under “Recent bounces and complaints” after a few minutes, at the latest after the next sync.', 'purehost-send-mails')]; } private static function abgleich(): array { $r = Abgleich::laufen(); if (!$r['ok']) { return ['error', $r['fehler']]; } /* translators: %d: count */ return ['success', sprintf(_n('Sync finished, %d new event.', 'Sync finished, %d new events.', $r['neu'], 'purehost-send-mails'), $r['neu'])]; } private static function warteschlange(): array { $r = Warteschlange::abarbeiten(); /* translators: 1: sent, 2: waiting, 3: given up */ return ['success', sprintf(__('Queue: %1$d sent, %2$d still waiting, %3$d given up.', 'purehost-send-mails'), $r['gesendet'], $r['wartet'], $r['aufgegeben'])]; } /** Angehaltene Mails einer Quelle: senden, verwerfen oder der Quelle kuenftig vertrauen. */ private static function bremse(array $p): array { $quelle = sanitize_text_field((string) ($p['quelle'] ?? '')); $was = sanitize_key((string) ($p['was'] ?? '')); if ($quelle === '' || !in_array($was, ['freigeben', 'vertrauen', 'verwerfen'], true)) { return ['error', __('Unknown action.', 'purehost-send-mails')]; } $n = Bremse::entscheiden($quelle, $was); $name = Protokoll::name($quelle); if ($was === 'verwerfen') { /* translators: 1: count, 2: plugin name */ return ['success', sprintf(_n('%1$d held email from %2$s discarded.', '%1$d held emails from %2$s discarded.', $n, 'purehost-send-mails'), $n, $name)]; } Warteschlange::abarbeiten(); return ['success', $was === 'vertrauen' /* translators: 1: count, 2: plugin name */ ? sprintf(_n('%1$d email from %2$s released. This source is no longer held.', '%1$d emails from %2$s released. This source is no longer held.', $n, 'purehost-send-mails'), $n, $name) /* translators: 1: count, 2: plugin name */ : sprintf(_n('%1$d email from %2$s released. This source is not held for the next 24 hours.', '%1$d emails from %2$s released. This source is not held for the next 24 hours.', $n, 'purehost-send-mails'), $n, $name)]; } private const VERHALTEN = ['sperren', 'drosseln', 'nachberechnen']; private const BERECHNET = ['drosseln', 'nachberechnen']; /** Verhalten bei vollem Kontingent an send-mails.de (Recht "kontingent"), mit Zustimmung der Person, die hier klickt. */ private static function kontingentSpeichern(array $p): array { $k = Api::kontingent(true); if (!$k['ok']) { return ['error', $k['fehler']]; } $d = $k['daten']; $fest = (array) ($d['vom_admin_festgelegt'] ?? []); $verhalten = in_array('verhalten', $fest, true) ? (string) ($d['verhalten_gewaehlt'] ?? 'sperren') : sanitize_key((string) ($p['verhalten'] ?? '')); if (!in_array($verhalten, self::VERHALTEN, true)) { return ['error', __('Please choose an option.', 'purehost-send-mails')]; } $body = in_array('verhalten', $fest, true) ? [] : ['verhalten' => $verhalten]; $deckel = (int) ($d['kostendeckel_cent'] ?? 0); if (in_array($verhalten, self::BERECHNET, true) && !in_array('kostendeckel_cent', $fest, true)) { $deckel = (int) round((float) str_replace(',', '.', (string) ($p['kostendeckel'] ?? '0')) * 100); if ($deckel < 100 || $deckel > 10000000) { return ['error', __('Please enter a cost cap of at least 1 €.', 'purehost-send-mails')]; } $body['kostendeckel_cent'] = $deckel; } $neuZustimmen = in_array($verhalten, self::BERECHNET, true) && (empty($d['bestaetigung_gueltig']) || $deckel !== (int) ($d['kostendeckel_cent'] ?? 0)); if ($neuZustimmen) { if (empty($d['hinweis'])) { return ['error', __('The notice on charging the overage is being revised. Please contact support.', 'purehost-send-mails')]; } if (empty($p['haftung'])) { return ['error', __('Please confirm the notice on charging the overage.', 'purehost-send-mails')]; } $ich = wp_get_current_user(); $body += ['haftung_akzeptiert' => true, 'hinweis_version' => (string) ($d['hinweis_version'] ?? ''), 'bestaetigt_durch' => mb_substr(sprintf('%s <%s>, %s (WordPress-Plugin)', $ich->display_name, $ich->user_email, home_url()), 0, 300)]; } if (!$body) { return ['success', __('Settings saved.', 'purehost-send-mails')]; } $r = Api::kontingentRegeln($body); if (!$r['ok']) { return ['error', $r['fehler']]; } Api::kontingent(true); return ['success', match ($verhalten) { 'nachberechnen' => __('Saved: when the quota is used up, emails continue normally and the overage is charged.', 'purehost-send-mails'), 'drosseln' => __('Saved: when the quota is used up, emails continue at a reduced pace and the overage is charged.', 'purehost-send-mails'), default => __('Saved: when the quota is used up, sending stops.', 'purehost-send-mails'), }]; } private static function kontingentWiderrufen(): array { $r = Api::kontingentWiderrufen(); if (!$r['ok']) { return ['error', $r['fehler']]; } $d = (array) ($r['daten'] ?? []); if (in_array($d['verhalten_gewaehlt'] ?? '', self::BERECHNET, true) && !in_array('verhalten', (array) ($d['vom_admin_festgelegt'] ?? []), true)) { Api::kontingentRegeln(['verhalten' => 'sperren']); } Api::kontingent(true); return ['success', __('Consent withdrawn. When the quota is used up, sending stops.', 'purehost-send-mails')]; } private static function schutzSpeichern(array $p): array { $an = sanitize_email((string) ($p['warn_email'] ?? '')); if (($p['warn_email'] ?? '') !== '' && !is_email($an)) { return ['error', __('Please enter a valid address for warnings or leave the field empty.', 'purehost-send-mails')]; } $vertraut = array_values(array_intersect((array) Einstellungen::wert('bremse_vertraut'), array_map('sanitize_text_field', (array) ($p['vertraut'] ?? [])))); Einstellungen::speichern([ 'bremse' => !empty($p['bremse']), 'bremse_min' => max(10, min(10000, (int) ($p['bremse_min'] ?? 300))), 'bremse_vertraut' => $vertraut, 'warnungen' => !empty($p['warnungen']), 'warn_email' => $an, 'wochenbericht' => !empty($p['wochenbericht']), ]); return ['success', __('Settings saved.', 'purehost-send-mails')]; } /** Auswahl fuer die Aufbewahrung von Empfaengern und Betreff im E-Mail-Protokoll (Tage, 0 = nie speichern). */ private const PROTOKOLL_TAGE = [0, 7, 14, 30, 90]; private static function optionen(array $p): array { // Nur angezeigte (installierte) Tools aus dem Formular uebernehmen, die anderen behalten ihren Wert $adapter = (array) Einstellungen::wert('adapter'); foreach (Abgleich::ADAPTER as $a => $klasse) { if ($klasse::verfuegbar()) { $adapter[$a] = !empty($p['adapter'][$a]); } } Einstellungen::speichern([ 'warteschlange' => !empty($p['warteschlange']), 'weich_schwelle' => max(1, min(10, (int) ($p['weich_schwelle'] ?? 3))), 'weich_tage' => max(1, min(90, (int) ($p['weich_tage'] ?? 30))), 'adapter' => $adapter, ]); return ['success', __('Settings saved.', 'purehost-send-mails')]; } private static function protokollSpeichern(array $p): array { Einstellungen::speichern([ 'protokoll_tage' => in_array((int) ($p['protokoll_tage'] ?? 30), self::PROTOKOLL_TAGE, true) ? (int) $p['protokoll_tage'] : 30, 'protokoll_inhalt' => !empty($p['protokoll_inhalt']) && (int) ($p['protokoll_tage'] ?? 0) > 0, ]); // Kuerzere Aufbewahrung oder Inhalt aus: sofort leeren, nicht erst beim naechsten Abgleich Protokoll::aufraeumen(); return ['success', __('Settings saved.', 'purehost-send-mails')]; } // ------------------------------------------------------------------ Anzeige /** Schritte der Einrichtung mit Zustand; "fertig" nur, wenn nachweislich erledigt. */ public static function schritte(): array { $o = Einstellungen::alle(); $kontingent = Einstellungen::apiSchluessel() !== '' ? Api::kontingent() : null; $verifiziert = $kontingent && $kontingent['ok'] ? Api::verifizierteDomains() : null; $absenderDomain = substr((string) strrchr((string) $o['absender_email'], '@'), 1); return [ 'api' => (bool) ($kontingent['ok'] ?? false), 'smtp' => Einstellungen::smtpBereit() && ($o['smtp_geprueft'] ?? '') !== '', 'absender' => $o['absender_email'] !== '' && ($verifiziert === null || in_array($absenderDomain, $verifiziert, true)), 'testmail' => $o['testmail_ok'] !== '', 'aktiv' => Einstellungen::versandAktiv(), ]; } public static function anzeigen(): void { ob_start(); try { self::anzeigenInhalt(); } finally { echo Ansicht::satzweise((string) ob_get_clean()); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Seite aus maskierten Teilen, nur
zwischen Saetzen ergaenzt } } private static function anzeigenInhalt(): void { if (!current_user_can('manage_options')) { return; } self::automatischPruefen(); $meldung = get_transient('sendmails_meldung_' . get_current_user_id()); delete_transient('sendmails_meldung_' . get_current_user_id()); $schritte = self::schritte(); $fertig = !in_array(false, $schritte, true); echo '
'; self::kopf($schritte, 'uebersicht'); $fremd = Versand::fremdesMailPlugin(); $fremdGemeldet = $fremd !== null && is_array($meldung) && $meldung['text'] === self::fremdHinweis($fremd); if ($fremdGemeldet) { // Meldung nach dem Klick mit demselben Link wie der Hinweis Ansicht::html(self::fremdHinweisHtml($fremd, (string) $meldung['typ'])); } elseif (is_array($meldung)) { printf('

%s

', esc_attr($meldung['typ']), esc_html($meldung['text'])); } foreach (Einstellungen::verloreneFelder() as $f) { echo '

' . esc_html( $f === 'smtp_passwort' ? __('The stored SMTP password can no longer be read (the security keys in wp-config.php have changed). Please enter it again.', 'purehost-send-mails') : __('The stored API key can no longer be read (the security keys in wp-config.php have changed). Please enter it again.', 'purehost-send-mails') ) . '

'; } // Kam der Hinweis gerade schon als Meldung nach dem Klick, nicht ein zweites Mal if ($fremd !== null && !$fremdGemeldet) { Ansicht::html(self::fremdHinweisHtml($fremd, 'warning')); } self::angehaltenAnzeigen(); if (!$fertig) { self::ablauf($schritte); } if ($schritte['api']) { self::uebersicht(); self::domainCheck(); echo '

' . esc_html__('View all statistics', 'purehost-send-mails') . '

'; } self::warteschlangeAnzeigen(); self::werkzeuge($schritte); self::tools(); echo '

' . esc_html__('Settings', 'purehost-send-mails') . ' →

'; echo '
'; } /** Kopf: Marke links, Zustand rechts. Die h1 bleibt fuer Screenreader und die Position der WP-Hinweise. */ private static function kopf(array $s, string $aktiv = ''): void { echo '

send-mails.de

'; echo '
'; echo '
'; self::statuszeile($s); echo '
'; if ($aktiv !== '') { self::reiter($aktiv); } echo '
'; } /** Kopf fuer die anderen Seiten des Plugins (Statistik), gleiche Marke, Zustand und Reiter. */ public static function kopfFuer(string $aktiv): void { self::kopf(self::schritte(), $aktiv); } /** Reiter Uebersicht / Statistik / E-Mail-Protokoll / Einstellungen, der aktuelle hervorgehoben. */ private static function reiter(string $aktiv): void { $reiter = [ 'uebersicht' => [__('Overview', 'purehost-send-mails'), self::url()], 'statistik' => [__('Statistics', 'purehost-send-mails'), Statistik::url()], 'protokoll' => [__('Email log', 'purehost-send-mails'), Mailprotokoll::url()], 'einstellungen' => [__('Settings', 'purehost-send-mails'), self::einstellungenUrl()], ]; echo ''; } private static function statuszeile(array $s): void { $stand = Abgleich::stand(); $wartet = Warteschlange::anzahl('wartet'); echo '

'; Ansicht::html(Ansicht::marke($s['aktiv'] ? 'ok' : 'aus', $s['aktiv'] ? __('Sending via send-mails.de on', 'purehost-send-mails') : __('Sending via send-mails.de off', 'purehost-send-mails'))); Ansicht::html(Ansicht::marke($s['api'] ? 'ok' : 'fehler', $s['api'] ? __('API connected', 'purehost-send-mails') : __('API not connected', 'purehost-send-mails'))); if ($stand['letzter_lauf'] !== '') { /* translators: %s: date and time */ Ansicht::html(Ansicht::marke($stand['fehler'] === '' ? 'ok' : 'warn', sprintf(__('Sync %s', 'purehost-send-mails'), Ansicht::zeit($stand['letzter_lauf'])))); } if ($wartet > 0) { /* translators: %d: count */ Ansicht::html(Ansicht::marke('warn', sprintf(_n('%d email in the queue', '%d emails in the queue', $wartet, 'purehost-send-mails'), $wartet))); } echo '

'; } private static function formular(string $tun, string $inhalt, string $knopf, string $klasse = 'button-primary'): string { return '
' . '' . wp_nonce_field('sendmails_' . $tun, '_wpnonce', true, false) . $inhalt . ($knopf !== '' ? '

' : '') . '
'; } /** Einrichtung: jeder Schritt mit eigenem Knopf, der aktuelle offen ("Jetzt dran"). */ private static function ablauf(array $s): void { $titel = [ 'api' => __('Enter API key', 'purehost-send-mails'), 'smtp' => __('Enter SMTP login', 'purehost-send-mails'), 'absender' => __('Set sender address', 'purehost-send-mails'), 'testmail' => __('Send test email', 'purehost-send-mails'), 'aktiv' => __('Turn on sending', 'purehost-send-mails'), ]; $jetzt = array_search(false, $s, true); $erledigt = count(array_filter($s)); $position = array_search($jetzt, array_keys($titel), true) + 1; echo '
'; echo '
' . esc_html__('Setup', 'purehost-send-mails') . ' · ' /* translators: 1: current step, 2: number of steps */ . esc_html(sprintf(__('Step %1$d of %2$d', 'purehost-send-mails'), $position, count($titel))) . '' . '

' . esc_html__('Up next', 'purehost-send-mails') . ': ' . esc_html($titel[$jetzt]) . '

' . '' . esc_html__('Open customer portal', 'purehost-send-mails') . '
'; Ansicht::html('
'); echo '
    '; $nr = 0; foreach ($titel as $schritt => $text) { $nr++; $zustand = $s[$schritt] ? 'fertig' : ($schritt === $jetzt ? 'jetzt' : 'spaeter'); Ansicht::html('
  1. ' . '' . esc_html($text) . ($s[$schritt] ? ' (' . esc_html__('done', 'purehost-send-mails') . ')' : '') . '
  2. '); } echo '
'; Ansicht::html('
' . self::schrittInhalt($jetzt) . '
'); echo '
'; } private static function schrittInhalt(string $schritt): string { switch ($schritt) { case 'api': return '

' . esc_html__('In the customer portal under “API keys”, create a key with the purpose “WordPress plugin” and paste it here. The plugin uses it to set up the SMTP login and sender itself and shows quota, statistics, bounces and complaints.', 'purehost-send-mails') . '

' . self::apiFeld(); case 'smtp': if ((string) Einstellungen::wert('smtp_automatisch') !== '' && Einstellungen::smtpBereit()) { $rest = max(0, (int) Einstellungen::wert('smtp_bereit_ab') - time()); // Countdown: assets/admin.js zaehlt die Zahl in .sm-countdown herunter und laedt die Seite bei 0 neu $hinweis = $rest > 0 ? str_replace('{ZAHL}', '' . $rest . '', esc_html(sprintf( /* translators: %s: seconds (counts down on the page) */ __('Activation takes about %s more seconds. This page then checks the login automatically.', 'purehost-send-mails'), '{ZAHL}'))) : esc_html__('The login is being checked now.', 'purehost-send-mails'); return '

' . esc_html__('The SMTP login was created automatically and is being activated.', 'purehost-send-mails') . ' ' . $hinweis . '

' . self::formular('smtp_pruefen', '', __('Check login now', 'purehost-send-mails')) . '
' . esc_html__('Enter credentials manually instead', 'purehost-send-mails') . '' . self::smtpFelder() . '
'; } $auto = Einstellungen::apiSchluessel() !== '' && !Einstellungen::ausKonstante('smtp_benutzer') && !Einstellungen::ausKonstante('smtp_passwort'); return ($auto ? '

' . esc_html__('With an API key for the WordPress plugin, the SMTP login sets itself up. If your key has this permission by now, one click is enough:', 'purehost-send-mails') . '

' . self::formular('einrichten', '', __('Set up automatically with the API key', 'purehost-send-mails')) . '
' . esc_html__('Enter a different API key (purpose “WordPress plugin”)', 'purehost-send-mails') . '' . '

' . esc_html__('In the customer portal under “API keys”, create a key with the purpose “WordPress plugin” and paste it here. The previous key is replaced and setup runs right away.', 'purehost-send-mails') . '

' . self::apiFeld() . '
' . '
' . esc_html__('Enter credentials manually', 'purehost-send-mails') . '' : '') . '

' . esc_html__('Username and password of an SMTP login from the customer portal. The plugin checks the login immediately.', 'purehost-send-mails') . '

' . self::smtpFelder() . ($auto ? '
' : ''); case 'absender': return '

' . esc_html__('send-mails.de only accepts emails from verified domains. WordPress emails with a different sender (such as wordpress@…) get this address. The original one is kept as reply-to address.', 'purehost-send-mails') . '

' . self::absenderFelder(); case 'testmail': return self::testmailFeld(); case 'aktiv': return '

' . esc_html__('From now on, all emails from this website go through send-mails.de, including those of the newsletter tools.', 'purehost-send-mails') . '

' . self::formular('einschalten', '', __('Turn on sending', 'purehost-send-mails')); } return ''; } private static function apiFeld(): string { if (Einstellungen::ausKonstante('api_schluessel')) { return '

' . esc_html__('The API key is defined in wp-config.php (SENDMAILS_API_SCHLUESSEL).', 'purehost-send-mails') . '

'; } $gespeichert = (string) Einstellungen::wert('api_schluessel') !== ''; return self::formular('api', '

', __('Save and check', 'purehost-send-mails')); } /** Zugang per API-Schluessel angelegt (auch aeltere Installationen ohne Kennzeichen: Benutzername wp-k-t). */ private static function smtpAutomatisch(): bool { if (Einstellungen::ausKonstante('smtp_benutzer') || Einstellungen::ausKonstante('smtp_passwort')) { return false; } return (string) Einstellungen::wert('smtp_automatisch') !== '' || (bool) preg_match('/^wp-k\d+-t\d+$/', Einstellungen::smtpBenutzer()); } /** * Einstellungen: automatisch eingerichteter Zugang ohne Felder (dort gibt es nichts einzustellen, nur kaputt zu machen), * stattdessen Zustand und "Neu einrichten". Felder nur bei eigenem Zugang. */ private static function smtpBereich(): string { if (!self::smtpAutomatisch()) { return self::smtpFelder(); } $zustand = (string) Einstellungen::wert('smtp_geprueft') !== '' ? __('Set up automatically with your API key and checked. There is nothing to configure here.', 'purehost-send-mails') : __('Set up automatically with your API key. The login is checked as soon as the access is known everywhere.', 'purehost-send-mails'); return '

' . esc_html($zustand) . '

' . self::formular('einrichten', '', __('Set up again', 'purehost-send-mails'), 'button-secondary') . '

' . esc_html__('Only needed if sending fails with a login error: creates a new password for this website.', 'purehost-send-mails') . '

'; } private static function smtpFelder(): string { $benutzer = Einstellungen::ausKonstante('smtp_benutzer') ? '

' . esc_html__('Username from wp-config.php.', 'purehost-send-mails') . '

' : '

'; $passwort = Einstellungen::ausKonstante('smtp_passwort') ? '

' . esc_html__('Password from wp-config.php.', 'purehost-send-mails') . '

' : '

'; return self::formular('smtp', $benutzer . $passwort /* translators: 1: host, 2: port */ . '

' . esc_html(sprintf(__('Server %1$s, port %2$d, encrypted.', 'purehost-send-mails'), Einstellungen::smtpHost(), Einstellungen::smtpPort())) . '

', __('Save and check login', 'purehost-send-mails')); } private static function absenderFelder(): string { $email = (string) Einstellungen::wert('absender_email'); [$lokal, $domain] = $email !== '' ? explode('@', $email, 2) + [1 => ''] : ['noreply', '']; $verifiziert = Api::verifizierteDomains() ?? []; if ($verifiziert) { $auswahl = ''; } else { $auswahl = ''; $auswahl .= '
' . esc_html__('No verified domain found yet. Add your domain in the customer portal and create the DNS records shown there.', 'purehost-send-mails') . ''; } return self::formular('absender', '

' . '

' . '

', __('Save sender', 'purehost-send-mails')); } /** Tabelle "Absender je Quelle" mit einer leeren Zeile zum Hinzufuegen. */ private static function absenderRegelnFelder(): void { $regeln = array_values(array_filter((array) Einstellungen::wert('absender_regeln'), 'is_array')); $quellen = array_values(array_unique(array_merge(array_column($regeln, 'quelle'), array_filter(Protokoll::quellen(), static fn($q) => !in_array($q, ['test', 'sendmails'], true))))); $zeile = static function (int $i, array $r) use ($quellen): string { $auswahl = ''; return '' . $auswahl . '' . '' . '' . '' . (!empty($r['quelle']) ? '' : '') . ''; }; $zeilen = ''; foreach ($regeln as $i => $r) { $zeilen .= $zeile($i, $r); } $zeilen .= $zeile(count($regeln), []); echo '

' . esc_html__('Sender per source', 'purehost-send-mails') . '

' . esc_html__('A different sender for single plugins, e.g. WooCommerce from shop@ and forms from info@. The address must belong to a verified domain. Sources appear here once they have sent an email.', 'purehost-send-mails') . '

'; Ansicht::html(self::formular('absender_regeln', '' . $zeilen . '
' . esc_html__('Source', 'purehost-send-mails') . '' . esc_html__('Sender address', 'purehost-send-mails') . '' . esc_html__('Display name', 'purehost-send-mails') . '' . esc_html__('Reply-to (optional)', 'purehost-send-mails') . '
', __('Save', 'purehost-send-mails'), 'button-secondary')); } /** Einheitliches Design fuer reine Text-Mails, mit Vorschau. */ private static function designFelder(): void { $o = Einstellungen::alle(); echo '

' . esc_html__('Email design', 'purehost-send-mails') . '

'; $anderes = \SendMails\Design::anderesPlugin(); if ($anderes !== null) { /* translators: %s: plugin name */ echo '

' . esc_html(sprintf(__('%s already designs the emails of this website, so this design stays off.', 'purehost-send-mails'), $anderes)) . '

'; return; } echo '

' . esc_html__('Plain-text emails get a clean layout with your logo and colour. Emails that already have their own HTML stay unchanged. The original text is kept as text part.', 'purehost-send-mails') . '

'; Ansicht::html(self::formular('design', '

' . '


' . '

' . '


' . '


' . '


', __('Save', 'purehost-send-mails'), 'button-secondary')); echo '

' . esc_html__('Preview', 'purehost-send-mails') . ' ↗

'; } /** Mails von WordPress selbst, die sich abschalten lassen. */ private static function systemmailsFelder(): void { $aus = (array) Einstellungen::wert('systemmails_aus'); $felder = ''; foreach (\SendMails\Systemmails::arten() as $schluessel => [$titel, $text]) { $felder .= '


' . esc_html($text) . '

'; } echo '

' . esc_html__('Turn off WordPress notifications', 'purehost-send-mails') . '

' . esc_html__('Ticked notifications are no longer sent. Emails from plugins are not affected.', 'purehost-send-mails') . '

'; Ansicht::html(self::formular('systemmails', $felder, __('Save', 'purehost-send-mails'), 'button-secondary')); } private static function testmailFeld(string $klasse = 'button-primary'): string { return self::formular('testmail', '

', __('Send test email', 'purehost-send-mails'), $klasse); } private static function uebersicht(): void { $st = Api::statistik(); $k = Api::kontingent(); if (!$st['ok'] || !$k['ok']) { echo '

' . esc_html($st['ok'] ? $k['fehler'] : $st['fehler']) . '

'; return; } $kd = $k['daten']; $h = (array) ($st['daten']['kontingent'] ?? []); $q = (array) ($st['daten']['quoten'] ?? []); echo '
'; echo '
'; self::kachelTitel(__('Quota this month', 'purehost-send-mails'), Statistik::url()); Ansicht::html(Ansicht::halbkreis((int) ($kd['prozent'] ?? 0), Ansicht::zahl($kd['verbraucht'] ?? 0), /* translators: %s: number of emails */ sprintf(__('of %s emails', 'purehost-send-mails'), Ansicht::zahl($kd['mails_pro_monat'] ?? 0)))); echo '
'; echo '
' . esc_html__('Remaining', 'purehost-send-mails') . '
' . esc_html(Ansicht::zahl($h['verbleibend'] ?? max(0, (int) ($kd['mails_pro_monat'] ?? 0) - (int) ($kd['verbraucht'] ?? 0)))) . '
'; if (isset($h['hochrechnung'])) { echo '
' . esc_html__('Forecast for month end', 'purehost-send-mails') . '
' . esc_html(Ansicht::zahl($h['hochrechnung'])) . '
'; } echo '
' . esc_html__('Plan', 'purehost-send-mails') . '
' . esc_html((string) ($kd['tarif'] ?? '–')) . '
'; if ((int) ($kd['mehrmenge'] ?? 0) > 0) { echo '
' . esc_html__('Overage', 'purehost-send-mails') . '
' . esc_html(Ansicht::zahl($kd['mehrmenge']) . ' · ' . Ansicht::euro((int) ($kd['mehrmenge_cent'] ?? 0))) . '
'; } echo '
'; echo '
'; /* translators: %d: days */ self::kachelTitel(sprintf(__('Rates (last %d days)', 'purehost-send-mails'), (int) ($q['fenster_tage'] ?? 7)), Statistik::url('7t')); Ansicht::html(Ansicht::quote(__('Bounces', 'purehost-send-mails'), isset($q['bounce']) ? (float) $q['bounce'] : null, (float) ($q['bounce_grenze'] ?? 0.05), !empty($q['bounce_warnung']))); Ansicht::html(Ansicht::quote(__('Complaints', 'purehost-send-mails'), isset($q['beschwerde']) ? (float) $q['beschwerde'] : null, (float) ($q['beschwerde_grenze'] ?? 0.001), !empty($q['beschwerde_warnung']))); if (empty($q['gewertet'])) { /* translators: %s: minimum volume */ echo '

' . esc_html(sprintf(__('Rates count from %s emails in the period.', 'purehost-send-mails'), Ansicht::zahl($q['mindestmenge'] ?? 200))) . '

'; } echo '
'; echo '
'; self::kachelTitel(__('Sending in the last 30 days', 'purehost-send-mails'), Statistik::url('30t')); Ansicht::html(Ansicht::saeulen((array) ($st['daten']['reihe'] ?? []))); $s = (array) ($st['daten']['summe'] ?? []); /* translators: 1: handed over, 2: hard bounces, 3: complaints */ echo '

' . esc_html(sprintf(__('%1$s handed over · %2$s hard bounces · %3$s complaints', 'purehost-send-mails'), Ansicht::zahl($s['uebergeben'] ?? 0), Ansicht::zahl($s['bounce_hart'] ?? 0), Ansicht::zahl($s['beschwerden'] ?? 0))) . '

'; echo '
'; echo '
'; self::kachelTitel(__('Recent emails', 'purehost-send-mails'), Mailprotokoll::url(), __('Email log', 'purehost-send-mails')); $letzte = Protokoll::liste([], 1, 6)['zeilen']; if (!$letzte) { echo '

' . esc_html__('None yet.', 'purehost-send-mails') . '

'; } else { echo ''; foreach ($letzte as $z) { [$art, $text] = Mailprotokoll::status($z); $an = (string) $z['an'] !== '' ? Testpostfach::anzeige(trim(explode(',', (string) $z['an'])[0])) : ((string) $z['domain'] !== '' ? '…@' . $z['domain'] : '–'); echo ''; } echo '
' . esc_html__('Time', 'purehost-send-mails') . '' . esc_html__('To', 'purehost-send-mails') . '' . esc_html__('Subject', 'purehost-send-mails') . '' . esc_html__('Source', 'purehost-send-mails') . '' . esc_html__('Status', 'purehost-send-mails') . '
' . esc_html(Ansicht::zeit((string) $z['zeit'])) . '' . esc_html($an) . '' . esc_html((string) $z['betreff'] !== '' ? (string) $z['betreff'] : '–') . '' . esc_html(Protokoll::name((string) $z['quelle'])) . ''; Ansicht::html(Ansicht::marke($art, $text)); echo '
'; } echo '
'; echo '
'; self::kachelTitel(__('Recent bounces and complaints', 'purehost-send-mails'), Statistik::url() . '#sperrliste', __('Blocked addresses', 'purehost-send-mails')); $liste = Abgleich::letzte(); if (!$liste) { echo '

' . esc_html__('None yet.', 'purehost-send-mails') . '

'; } else { echo ''; foreach ($liste as $e) { $wirkung = $e['wirkung'] === null || $e['wirkung'] === '' ? __('counted only', 'purehost-send-mails') : Abgleich::ergebnisAnzeige((string) $e['ergebnis']); echo ''; } echo '
' . esc_html__('Time', 'purehost-send-mails') . '' . esc_html__('Recipient', 'purehost-send-mails') . '' . esc_html__('Reason', 'purehost-send-mails') . '' . esc_html__('In the newsletter tools', 'purehost-send-mails') . '
' . esc_html(Ansicht::zeit((string) $e['zeit'])) . '' . esc_html(Testpostfach::anzeige((string) $e['empfaenger'])) . '' . esc_html((string) $e['grund']) . '' . esc_html($wirkung !== '' ? $wirkung : __('not found', 'purehost-send-mails')) . '
'; } echo '
'; echo '
'; } /** * Domain-Check: DNS-Eintraege je Absenderdomain mit Pruefung gegen das oeffentliche DNS (wie im Kundenportal), * Kopierknopf je Wert. Domains, bei denen alles stimmt, zugeklappt. */ private static function domainCheck(): void { $neu = !empty($_GET['dns_neu']) && check_admin_referer('sendmails_dns_neu'); $r = Api::domainsDns($neu); if (!$r['ok']) { return; } $domains = array_values(array_filter((array) ($r['daten']['domains'] ?? []), 'is_array')); if (!$domains) { return; } $absender = substr((string) strrchr(strtolower((string) Einstellungen::wert('absender_email')), '@'), 1); $marken = [ 'ok' => ['ok', __('found', 'purehost-send-mails')], 'fehlt' => ['fehler', __('missing', 'purehost-send-mails')], 'abweichend' => ['fehler', __('different', 'purehost-send-mails')], 'unbekannt' => ['aus', __('not checkable', 'purehost-send-mails')], 'nicht_noetig' => ['aus', __('not needed', 'purehost-send-mails')], ]; $zwecke = ['dkim' => __('Signature (DKIM)', 'purehost-send-mails'), 'spf' => 'SPF', 'mail_from' => __('Return path', 'purehost-send-mails'), 'dmarc' => 'DMARC']; echo '

' . esc_html__('Sender domains', 'purehost-send-mails') . '

' . '' . esc_html__('Check again', 'purehost-send-mails') . ' ↻
'; echo '

' . esc_html__('These DNS entries belong to each sender domain. Signature and return path are required, DMARC is recommended. Entries are copied with the button and set at the DNS provider of the domain.', 'purehost-send-mails') . '

'; foreach ($domains as $d) { $eintraege = is_array($d['dns'] ?? null) ? $d['dns'] : null; $offen = $eintraege === null ? 0 : count(array_filter($eintraege, static fn($e) => ($e['status'] ?? '') !== 'ok' && empty($e['empfehlung']))); $empfohlen = $eintraege === null ? 0 : count(array_filter($eintraege, static fn($e) => ($e['status'] ?? '') !== 'ok' && ($e['status'] ?? '') !== 'nicht_noetig' && !empty($e['empfehlung']))); $verifiziert = ($d['status'] ?? '') === 'verifiziert'; $kopf = '' . esc_html((string) $d['domain']) . ' ' . Ansicht::marke($verifiziert ? 'ok' : 'warn', $verifiziert ? __('verified', 'purehost-send-mails') : __('not verified', 'purehost-send-mails')) . ((string) $d['domain'] === $absender ? ' ' . esc_html__('sender of this website', 'purehost-send-mails') . '' : '') . ' ' . esc_html($eintraege === null ? __('DNS entries appear as soon as the domain is set up.', 'purehost-send-mails') : ($offen > 0 /* translators: %d: number of DNS entries */ ? sprintf(_n('%d entry missing or different', '%d entries missing or different', $offen, 'purehost-send-mails'), $offen) : ($empfohlen > 0 ? __('all required entries found, recommendation open', 'purehost-send-mails') : __('all entries found', 'purehost-send-mails')))) . ''; echo '
0 || !$verifiziert ? ' open' : '') . '>'; Ansicht::html($kopf); echo ''; if ($eintraege) { echo ''; foreach ($eintraege as $e) { $status = (string) ($e['status'] ?? 'unbekannt'); [$art, $text] = $marken[$status] ?? ['aus', $status]; if ($status !== 'ok' && $status !== 'nicht_noetig' && !empty($e['empfehlung'])) { [$art, $text] = ['warn', __('recommended', 'purehost-send-mails')]; } $wert = (string) ($e['wert'] ?? ''); echo ''; } echo '
' . esc_html__('Purpose', 'purehost-send-mails') . '' . esc_html__('Type', 'purehost-send-mails') . '' . esc_html__('Name', 'purehost-send-mails') . '' . esc_html__('Value', 'purehost-send-mails') . '' . esc_html__('Status', 'purehost-send-mails') . '
' . esc_html($zwecke[$e['zweck'] ?? ''] ?? (string) ($e['zweck'] ?? '')) . '' . esc_html((string) ($e['typ'] ?? '')) . '' . esc_html((string) ($e['name'] ?? '')) . '' . esc_html($wert) . ' '; if (!empty($e['gefunden']) && $status !== 'ok') { /* translators: %s: DNS value found */ echo '
' . esc_html(sprintf(__('found: %s', 'purehost-send-mails'), (string) $e['gefunden'])) . ''; } if (!empty($e['hinweis']) && $status !== 'ok') { echo '
' . esc_html((string) $e['hinweis']) . ''; } echo '
'; Ansicht::html(Ansicht::marke($art, $text)); echo '
'; } echo '
'; } echo '
'; } /** Angehaltene Quellen (Schutz vor Massenversand) mit den drei Entscheidungen. */ private static function angehaltenAnzeigen(): void { foreach (Bremse::angehalten() as $b) { $name = Protokoll::name($b['quelle']); $knopf = static fn(string $was, string $text, string $klasse, string $frage = '') => '
' . '' . wp_nonce_field('sendmails_bremse', '_wpnonce', true, false) . '' . '
'; Ansicht::html('

' . esc_html(sprintf( /* translators: %s: plugin or theme name */ __('Emails from %s held', 'purehost-send-mails'), $name)) . '

' . esc_html(sprintf( /* translators: 1: plugin name, 2: emails in one hour, 3: usual maximum */ __('%1$s sent %2$d emails within one hour, usually at most %3$d. This often means that bots are abusing a form. To protect your domain and your quota, further emails from this source are held.', 'purehost-send-mails'), $name, (int) ($b['stunde'] ?? 0), (int) ($b['grenze'] ?? 0))) . '

' . esc_html(sprintf( /* translators: 1: count, 2: date and time */ _n('%1$d email waiting since %2$s.', '%1$d emails waiting since %2$s.', (int) $b['mails'], 'purehost-send-mails'), (int) $b['mails'], Ansicht::zeit((string) $b['erste']))) . ' ' . '' . esc_html__('Show emails', 'purehost-send-mails') . '

' . $knopf('freigeben', __('Send emails', 'purehost-send-mails'), 'button-primary') . $knopf('vertrauen', __('Send and always trust this source', 'purehost-send-mails'), 'button-secondary') . $knopf('verwerfen', __('Discard emails', 'purehost-send-mails'), 'button-link-delete', __('Discard the held emails? They cannot be restored.', 'purehost-send-mails')) . '
'); } } /** Kacheltitel mit Verweis auf die passende Statistik rechts daneben. */ private static function kachelTitel(string $titel, string $url, string $verweis = ''): void { echo ''; } private static function warteschlangeAnzeigen(): void { $liste = Warteschlange::liste(); if (!$liste) { return; } echo '

' . esc_html__('Queue', 'purehost-send-mails') . '

'; echo '

' . esc_html__('send-mails.de could not accept these emails temporarily. The plugin retries automatically for up to 24 hours.', 'purehost-send-mails') . '

'; echo ''; foreach ($liste as $z) { $zustand = match ((string) $z['status']) { /* translators: %s: date and time */ 'wartet' => sprintf(__('next attempt %s', 'purehost-send-mails'), Ansicht::zeit((string) $z['naechster'])), 'angehalten' => __('held', 'purehost-send-mails'), default => __('given up', 'purehost-send-mails'), }; echo ''; } echo '
' . esc_html__('Queued', 'purehost-send-mails') . '' . esc_html__('To', 'purehost-send-mails') . '' . esc_html__('Subject', 'purehost-send-mails') . '' . esc_html__('Attempts', 'purehost-send-mails') . '' . esc_html__('Status', 'purehost-send-mails') . '' . esc_html__('Last error', 'purehost-send-mails') . '
' . esc_html(Ansicht::zeit((string) $z['erstellt'])) . '' . esc_html($z['an']) . '' . esc_html($z['betreff']) . '' . (int) $z['versuche'] . '' . esc_html($zustand) . '' . esc_html((string) $z['letzter_fehler']) . '
'; Ansicht::html(self::formular('warteschlange', '', __('Retry now', 'purehost-send-mails'), 'button-secondary')); if (Warteschlange::anzahl('aufgegeben') > 0) { Ansicht::html(self::formular('ws_loeschen', '', __('Remove given-up emails', 'purehost-send-mails'), 'button-link-delete')); } echo '
'; } private static function werkzeuge(array $s): void { if (!$s['smtp']) { return; } $kachel = static fn (string $titel, string $text, string $inhalt): string => '

' . esc_html($titel) . '

' . '

' . esc_html($text) . '

' . $inhalt . '
'; echo '

' . esc_html__('Tools', 'purehost-send-mails') . '

'; Ansicht::html($kachel(__('Test email', 'purehost-send-mails'), __('Checks sending to an address of your choice.', 'purehost-send-mails'), self::testmailFeld('button-secondary'))); Ansicht::html($kachel(__('Self-test', 'purehost-send-mails'), __('One email each to a test mailbox for delivery and one for bounces. They only count towards the quota, not towards statistics and bounce rate.', 'purehost-send-mails'), self::formular('selbsttest', '', __('Start self-test', 'purehost-send-mails'), 'button-secondary'))); if ($s['api']) { $stand = Abgleich::stand(); Ansicht::html($kachel(__('Sync bounces', 'purehost-send-mails'), sprintf( /* translators: %s: date and time */ __('Fetches bounces and complaints right away, otherwise every 15 minutes. Last sync: %s', 'purehost-send-mails'), Ansicht::zeit((string) $stand['letzter_lauf'])), self::formular('abgleich', '', __('Sync now', 'purehost-send-mails'), 'button-secondary'))); } echo '
'; if ($s['aktiv']) { Ansicht::html('
' . esc_html__('Turn off sending', 'purehost-send-mails') . '

' . esc_html__('WordPress then sends emails as it did before the setup. The credentials stay saved.', 'purehost-send-mails') . '

' . self::formular('ausschalten', '', __('Turn off sending', 'purehost-send-mails'), 'sm-knopf-gefahr') . '
'); } echo '
'; } /** Erkannte Newsletter-Tools mit Zustand der Anbindung. */ private static function tools(): void { $aktiv = (array) Einstellungen::wert('adapter'); $zeilen = ''; foreach (Abgleich::ADAPTER as $schluessel => $klasse) { if (!$klasse::verfuegbar()) { continue; } $hinweis = empty($aktiv[$schluessel]) ? __('Bounces are not recorded here (turned off).', 'purehost-send-mails') : __('Bounces and complaints are recorded.', 'purehost-send-mails'); if ($schluessel === 'mailpoet') { /* translators: 1: host, 2: port */ $hinweis .= ' ' . sprintf(__('MailPoet does not send through WordPress. In MailPoet under Settings → Send With, choose “Other SMTP server” with %1$s, port %2$d, TLS and your SMTP login. MailPoet does not record bounces for other servers itself; this plugin does that.', 'purehost-send-mails'), Einstellungen::smtpHost(), Einstellungen::smtpPort()); } $zeilen .= '' . esc_html($klasse::name()) . '' . esc_html($hinweis) . ''; } echo '

' . esc_html__('Newsletter tools', 'purehost-send-mails') . '

'; Ansicht::html($zeilen !== '' ? '' . $zeilen . '
' : '

' . esc_html(sprintf( /* translators: %s: list of supported newsletter plugins */ __('No supported newsletter tool active (%s). Emails from WordPress and other plugins still go through send-mails.de.', 'purehost-send-mails'), self::toolNamen() )) . '

'); echo '
'; } /** Namen aller unterstuetzten Newsletter-Tools, kommagetrennt. */ private static function toolNamen(): string { return implode(', ', array_map(static fn($k) => $k::name(), Abgleich::ADAPTER)); } private static function einstellungen(): void { $o = Einstellungen::alle(); $bereiche = [ 'verbindung' => __('Connection', 'purehost-send-mails'), 'absender' => __('Sender', 'purehost-send-mails'), 'kontingent' => __('Quota', 'purehost-send-mails'), 'schutz' => __('Protection and warnings', 'purehost-send-mails'), 'protokoll' => __('Email log', 'purehost-send-mails'), 'bounces' => __('Bounces and queue', 'purehost-send-mails'), 'systemmails' => __('WordPress notifications', 'purehost-send-mails'), 'design' => __('Email design', 'purehost-send-mails'), ]; echo ''; // Jeder Bereich eine eigene Kachel; Bereiche ohne Inhalt (z. B. Kontingent ohne API-Schluessel) entfallen $kachel = static function (callable $inhalt): void { ob_start(); $inhalt(); $html = (string) ob_get_clean(); if (trim($html) !== '') { echo '
' . $html . '
'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- aus maskierten Teilen } }; $kachel(static function () use ($o): void { echo '

' . esc_html__('Connection', 'purehost-send-mails') . '

'; Ansicht::html(self::apiFeld()); if ((string) $o['api_schluessel'] !== '' && !Einstellungen::ausKonstante('api_schluessel')) { Ansicht::html(self::formular('api', '', __('Remove API key', 'purehost-send-mails'), 'button-link-delete')); } echo '

' . esc_html__('SMTP login', 'purehost-send-mails') . '

'; Ansicht::html(self::smtpBereich()); }); $kachel(static function (): void { echo '

' . esc_html__('Sender', 'purehost-send-mails') . '

'; Ansicht::html(self::absenderFelder()); self::absenderRegelnFelder(); }); $kachel(static fn() => self::kontingentFelder()); $kachel(static fn() => self::schutzFelder($o)); $kachel(static function () use ($o): void { $tage = ''; foreach (self::PROTOKOLL_TAGE as $t) { $tage .= ''; } echo '

' . esc_html__('Email log', 'purehost-send-mails') . '

'; Ansicht::html(self::formular('protokoll', '

' . '


' . esc_html__('Only for administrators. Contents can include personal data and links such as password resets; attachments are never stored.', 'purehost-send-mails') . '

', __('Save', 'purehost-send-mails'), 'button-secondary')); }); $kachel(static function () use ($o): void { // Nur Tools, die auf dieser Website installiert und aktiv sind $adapter = ''; foreach (Abgleich::ADAPTER as $schluessel => $klasse) { if ($klasse::verfuegbar()) { $adapter .= '
'; } } if ($adapter === '') { $adapter = '' . esc_html(sprintf( /* translators: %s: list of supported newsletter plugins */ __('No supported newsletter tool is active on this website (%s). As soon as one is active, it appears here.', 'purehost-send-mails'), self::toolNamen() )) . ''; } echo '

' . esc_html__('Bounces and queue', 'purehost-send-mails') . '

'; Ansicht::html(self::formular('optionen', '

' . '

' . sprintf( /* translators: 1: input field count, 2: input field days */ esc_html__('Soft bounces: mark as “bounced” after %1$s soft bounces within %2$s days.', 'purehost-send-mails'), '', '' ) . '

' . '

' . esc_html__('Record bounces in:', 'purehost-send-mails') . '
' . $adapter . '

', __('Save', 'purehost-send-mails'), 'button-secondary')); }); $kachel(static fn() => self::systemmailsFelder()); $kachel(static fn() => self::designFelder()); } /** Wahl bei vollem Kontingent wie im Kundenportal, Hinweistext von send-mails.de. */ private static function kontingentFelder(): void { if (Einstellungen::apiSchluessel() === '') { return; } $k = Api::kontingent(); if (!$k['ok']) { return; } $d = $k['daten']; $fest = (array) ($d['vom_admin_festgelegt'] ?? []); $gewaehlt = (string) ($d['verhalten_gewaehlt'] ?? 'sperren'); $preis = Ansicht::euro((int) ($d['preis_cent_pro_1000'] ?? 100)); $deckelCent = (int) ($d['kostendeckel_cent'] ?? 10000); $tempo = (int) round((float) ($d['drossel_faktor'] ?? 0.25) * 100) . ' %'; /* translators: %s: price per 1,000 emails */ $kosten = sprintf(__('Every started 1,000 emails above the quota cost %s incl. 19 %% VAT, at most up to the cost cap.', 'purehost-send-mails'), $preis); $wahl = [ 'sperren' => [__('Stop sending', 'purehost-send-mails'), __('send-mails.de rejects further emails until the end of the month.', 'purehost-send-mails')], /* translators: %s: percentage of the normal pace */ 'drosseln' => [__('Continue at a reduced pace', 'purehost-send-mails'), sprintf(__('Emails continue at %s of the normal pace.', 'purehost-send-mails'), $tempo) . ' ' . $kosten], 'nachberechnen' => [__('Continue normally and charge the overage', 'purehost-send-mails'), __('Emails continue normally.', 'purehost-send-mails') . ' ' . $kosten], ]; $radios = ''; foreach ($wahl as $wert => [$titel, $text]) { $radios .= ''; } $hinweis = ''; foreach ((array) ($d['hinweis'] ?? []) as $absatz) { $teile = explode('{deckel}', str_replace('{preis}', $preis, (string) $absatz)); $hinweis .= '

' . implode('' . esc_html(Ansicht::euro($deckelCent)) . '', array_map('esc_html', $teile)) . '

'; } $bereich = '
' . '


' . ($hinweis !== '' ? '
' . $hinweis . '
' : '

' . esc_html__('The notice on charging the overage is being revised. Please contact support.', 'purehost-send-mails') . '

') . (empty($d['bestaetigung_gueltig']) ? '' : '

' . esc_html__('Consent is given for the current price and cost cap. A different cost cap needs a new consent.', 'purehost-send-mails') . '

') . '

'; $stand = ($d['verhalten_gewaehlt'] ?? '') !== ($d['verhalten_wirksam'] ?? '') ? '

' . esc_html(sprintf( /* translators: %s: chosen option */ __('“%s” is chosen, but sending stops when the quota is used up until consent for the current price and cost cap is given.', 'purehost-send-mails'), $wahl[$gewaehlt][0] ?? $gewaehlt )) . '

' : ''; echo '

' . esc_html__('When the quota is used up', 'purehost-send-mails') . '

'; echo '

' . esc_html(sprintf( /* translators: 1: emails per month, 2: used, 3: percent */ __('Your plan includes %1$s emails per month, %2$s used (%3$d %%).', 'purehost-send-mails'), Ansicht::zahl($d['mails_pro_monat'] ?? 0), Ansicht::zahl($d['verbraucht'] ?? 0), (int) ($d['prozent'] ?? 0) )) . '

'; Ansicht::html($stand . self::formular('kontingent', '
' . $radios . $bereich . (in_array('verhalten', $fest, true) ? '

' . esc_html__('Our support has set this for your account.', 'purehost-send-mails') . '

' : '') . '
', __('Save', 'purehost-send-mails'), 'button-secondary')); if (!empty($d['bestaetigung_gueltig'])) { Ansicht::html(self::formular('kontingent_widerruf', '', __('Withdraw consent to charging the overage', 'purehost-send-mails'), 'button-link-delete')); } } private static function schutzFelder(array $o): void { $vertraut = ''; foreach ((array) $o['bremse_vertraut'] as $q) { $vertraut .= '
'; } echo '

' . esc_html__('Protection and warnings', 'purehost-send-mails') . '

'; Ansicht::html(self::formular('schutz', '


' . sprintf( /* translators: %s: input field */ esc_html__('Held from three times the busiest hour of the last 14 days, but not below %s emails per hour and source. Newsletter tools and test emails are never held.', 'purehost-send-mails'), '' ) . '

' . ($vertraut !== '' ? '

' . esc_html__('Never held (untick to remove):', 'purehost-send-mails') . '
' . $vertraut . '

' : '') . '


' . esc_html__('Quota at 80 and 100 %, rates close to the limit, held or waiting emails, domain no longer verified.', 'purehost-send-mails') . '

' . '

' . '



' . '' . esc_html__('Empty: the administration email address of this website.', 'purehost-send-mails') . '

', __('Save', 'purehost-send-mails'), 'button-secondary')); Ansicht::html(self::formular('wochenbericht', '', __('Send weekly report now', 'purehost-send-mails'), 'button-link')); } }