Schluessel, Abstand in Sekunden, Betreff, Text */ public static function warnungen(): array { $w = []; $portal = 'https://portal.send-mails.de/#/kunde/konto'; $wahl = Admin\Seite::einstellungenUrl() . '#kontingent'; foreach (Bremse::angehalten() as $b) { $name = Protokoll::name($b['quelle']); $w[] = ['bremse:' . $b['quelle'], 6 * HOUR_IN_SECONDS, /* translators: %s: plugin or theme name */ sprintf(__('send-mails.de: emails from %s held', 'purehost-send-mails'), $name), sprintf( /* translators: 1: plugin name, 2: emails in the last hour, 3: usual maximum, 4: held emails, 5: link */ __("%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, the plugin holds further emails from this source.\n\n%4\$d emails are waiting for your decision: send them, discard them or trust this source from now on.\n\n%5\$s", 'purehost-send-mails'), $name, (int) ($b['stunde'] ?? 0), (int) ($b['grenze'] ?? 0), (int) $b['mails'], Admin\Seite::url() )]; } $k = Einstellungen::apiSchluessel() !== '' ? Api::kontingentGespeichert() : null; if ($k && $k['ok']) { $d = $k['daten']; if (($d['status'] ?? 'aktiv') !== 'aktiv' || !empty($d['missbrauch_gesperrt'])) { $w[] = ['konto_gesperrt', DAY_IN_SECONDS, __('send-mails.de: your account is suspended', 'purehost-send-mails'), /* translators: %s: link */ sprintf(__("send-mails.de does not accept emails from this website at the moment because the account is suspended. Details are in the customer portal:\n\n%s", 'purehost-send-mails'), $portal)]; } $p = (int) ($d['prozent'] ?? 0); $verhalten = (string) ($d['verhalten_wirksam'] ?? 'sperren'); $folge = match ($verhalten) { 'drosseln' => __('When the quota is used up, emails continue at a reduced pace and the overage is charged up to your cost cap.', 'purehost-send-mails'), 'nachberechnen' => __('When the quota is used up, emails continue normally and the overage is charged up to your cost cap.', 'purehost-send-mails'), default => __('When the quota is used up, send-mails.de accepts no more emails until the end of the month.', 'purehost-send-mails'), }; $monat = substr((string) ($d['monat'] ?? gmdate('Y-m')), 0, 7); if ($p >= 100) { $w[] = ['kontingent100:' . $monat, 40 * DAY_IN_SECONDS, __('send-mails.de: monthly quota used up', 'purehost-send-mails'), /* translators: 1: used, 2: quota, 3: consequence, 4: link */ sprintf(__("This month %1\$s of %2\$s emails have been sent. %3\$s\n\nYou can choose what happens when the quota is used up in the plugin settings:\n\n%4\$s", 'purehost-send-mails'), number_format_i18n((int) ($d['verbraucht'] ?? 0)), number_format_i18n((int) ($d['mails_pro_monat'] ?? 0)), $folge, $wahl)]; } elseif ($p >= 80) { /* translators: %d: percent */ $w[] = ['kontingent80:' . $monat, 40 * DAY_IN_SECONDS, sprintf(__('send-mails.de: %d %% of the monthly quota used', 'purehost-send-mails'), $p), /* translators: 1: used, 2: quota, 3: consequence, 4: link */ sprintf(__("This month %1\$s of %2\$s emails have been sent. %3\$s\n\nYou can choose what happens when the quota is used up in the plugin settings:\n\n%4\$s", 'purehost-send-mails'), number_format_i18n((int) ($d['verbraucht'] ?? 0)), number_format_i18n((int) ($d['mails_pro_monat'] ?? 0)), $folge, $wahl)]; } } $st = Einstellungen::apiSchluessel() !== '' ? Api::statistik(false, '7t') : null; $q = $st && $st['ok'] ? (array) ($st['daten']['quoten'] ?? []) : []; $sperrliste = Admin\Statistik::url() . '#sperrliste'; foreach (['bounce' => __('bounce rate', 'purehost-send-mails'), 'beschwerde' => __('complaint rate', 'purehost-send-mails')] as $feld => $name) { if (!empty($q[$feld . '_warnung']) && !empty($q['gewertet'])) { $w[] = ['quote_' . $feld, DAY_IN_SECONDS, /* translators: %s: bounce rate or complaint rate */ sprintf(__('send-mails.de: %s close to the limit', 'purehost-send-mails'), $name), sprintf( /* translators: 1: bounce rate or complaint rate, 2: days, 3: current value, 4: limit, 5: link */ __("The %1\$s of the last %2\$d days is %3\$s, the limit is %4\$s. Above the limit, send-mails.de suspends sending to protect all customers. Check where the bounces come from and remove invalid addresses from your lists:\n\n%5\$s", 'purehost-send-mails'), $name, (int) ($q['fenster_tage'] ?? 7), Admin\Ansicht::prozent((float) ($q[$feld] ?? 0)), Admin\Ansicht::prozent((float) ($q[$feld . '_grenze'] ?? 0)), $sperrliste )]; } } $wartet = self::aeltesteWartende(); if ($wartet['anzahl'] > 0 && $wartet['erste'] > 0 && time() - $wartet['erste'] > self::WARTESCHLANGE_S) { $w[] = ['warteschlange', 6 * HOUR_IN_SECONDS, __('send-mails.de: emails are waiting in the queue', 'purehost-send-mails'), /* translators: 1: count, 2: last error, 3: link */ sprintf(__("%1\$d emails could not be handed over to send-mails.de for more than 30 minutes. The plugin keeps retrying for up to 24 hours. Last error: %2\$s\n\n%3\$s", 'purehost-send-mails'), $wartet['anzahl'], $wartet['fehler'] !== '' ? $wartet['fehler'] : '–', Admin\Seite::url())]; } $aufgegeben = Warteschlange::anzahl('aufgegeben'); if ($aufgegeben > 0) { $w[] = ['aufgegeben', DAY_IN_SECONDS, __('send-mails.de: emails could not be delivered', 'purehost-send-mails'), /* translators: 1: count, 2: link */ sprintf(__("%1\$d emails could not be delivered even after 24 hours and were given up. Details and the recipients are on the overview page:\n\n%2\$s", 'purehost-send-mails'), $aufgegeben, Admin\Seite::url())]; } $absender = strtolower((string) Einstellungen::wert('absender_email')); $verifiziert = Api::bekannteDomains(); $domain = substr((string) strrchr($absender, '@'), 1); if (Einstellungen::versandAktiv() && $domain !== '' && $verifiziert !== null && !in_array($domain, $verifiziert, true)) { $w[] = ['domain:' . $domain, DAY_IN_SECONDS, /* translators: %s: domain */ sprintf(__('send-mails.de: sender domain %s is not verified', 'purehost-send-mails'), $domain), /* translators: 1: domain, 2: link */ sprintf(__("The sender domain %1\$s is currently not verified at send-mails.de, for example because a DNS entry was removed. Until it is verified again, emails with this sender are not accepted. Check the DNS entries in the customer portal:\n\n%2\$s", 'purehost-send-mails'), $domain, 'https://portal.send-mails.de/#/kunde/domains')]; } return $w; } /** @return array{anzahl: int, erste: int, fehler: string} */ private static function aeltesteWartende(): array { global $wpdb; $z = $wpdb->get_row($wpdb->prepare( "SELECT COUNT(*) AS anzahl, MIN(erstellt) AS erste, MAX(letzter_fehler) AS fehler FROM %i WHERE status = 'wartet'", Warteschlange::tabelle() ), ARRAY_A); return ['anzahl' => (int) ($z['anzahl'] ?? 0), 'erste' => !empty($z['erste']) ? (int) strtotime($z['erste'] . ' UTC') : 0, 'fehler' => (string) ($z['fehler'] ?? '')]; } /** Warnung senden, wenn sie in den letzten $abstand Sekunden nicht schon kam. */ public static function melden(string $schluessel, int $abstand, string $betreff, string $text): bool { $gemeldet = get_option(self::GEMELDET, []); $gemeldet = is_array($gemeldet) ? $gemeldet : []; if (time() - (int) ($gemeldet[$schluessel] ?? 0) < $abstand) { return false; } $gemeldet[$schluessel] = time(); // Alte Schluessel (z. B. Kontingent vergangener Monate) nicht ewig mitschleppen $gemeldet = array_filter($gemeldet, static fn($t) => time() - (int) $t < 60 * DAY_IN_SECONDS); update_option(self::GEMELDET, $gemeldet, false); return self::senden($betreff, $text); } private static function senden(string $betreff, string $text): bool { $fuss = "\n\n-- \n" . sprintf( /* translators: 1: website address, 2: settings link */ __("Sent by the send-mails.de plugin on %1\$s. Warnings and weekly report can be turned off in the settings:\n%2\$s", 'purehost-send-mails'), home_url(), Admin\Seite::einstellungenUrl() ); return (bool) Protokoll::mitQuelle('sendmails', static fn() => wp_mail(self::empfaenger(), $betreff, $text . $fuss)); } /** Montags ab 8 Uhr (Zeitzone der Website) einmal je Woche, nur wenn es in der Woche Mails gab. */ public static function wochenbericht(bool $erzwingen = false): bool { $woche = wp_date('o-W'); if (!$erzwingen && ((int) wp_date('N') !== 1 || (int) wp_date('G') < 8 || get_option(self::WOCHE) === $woche)) { return false; } $z = Protokoll::zahlen(7); if (!$erzwingen) { update_option(self::WOCHE, $woche, false); } if ($z['gesamt'] === 0 && !$erzwingen) { return false; } $zeilen = [ /* translators: %s: count */ sprintf(__('Emails sent: %s', 'purehost-send-mails'), number_format_i18n($z['gesamt'])), /* translators: %s: count */ sprintf(__('Delivered: %s', 'purehost-send-mails'), number_format_i18n($z['zugestellt'])), /* translators: 1: hard bounces, 2: soft bounces */ sprintf(__('Bounces: %1$s hard, %2$s soft', 'purehost-send-mails'), number_format_i18n($z['bounce_hart']), number_format_i18n($z['bounce_weich'])), /* translators: %s: count */ sprintf(__('Complaints: %s', 'purehost-send-mails'), number_format_i18n($z['beschwerde'])), /* translators: %s: count */ sprintf(__('Rejected or given up: %s', 'purehost-send-mails'), number_format_i18n($z['fehler'])), ]; if ($z['angehalten'] > 0) { /* translators: %s: count */ $zeilen[] = sprintf(__('Held (protection against mass sending): %s', 'purehost-send-mails'), number_format_i18n($z['angehalten'])); } $quellen = ''; foreach (array_slice($z['quellen'], 0, 5) as $q) { $quellen .= '- ' . Protokoll::name((string) $q['quelle']) . ': ' . number_format_i18n((int) $q['mails']) . "\n"; } $k = Einstellungen::apiSchluessel() !== '' ? Api::kontingent() : null; $kontingent = ''; if ($k && $k['ok']) { $kontingent = "\n" . sprintf( /* translators: 1: used, 2: quota, 3: percent */ __('Quota this month: %1$s of %2$s emails (%3$d %%)', 'purehost-send-mails'), number_format_i18n((int) ($k['daten']['verbraucht'] ?? 0)), number_format_i18n((int) ($k['daten']['mails_pro_monat'] ?? 0)), (int) ($k['daten']['prozent'] ?? 0) ) . "\n"; } $text = sprintf( /* translators: %s: website address */ __('Emails of %s in the last 7 days:', 'purehost-send-mails'), home_url() ) . "\n\n" . implode("\n", $zeilen) . "\n" . $kontingent . ($quellen !== '' ? "\n" . __('Most emails came from:', 'purehost-send-mails') . "\n" . $quellen : '') . "\n" . __('Email log:', 'purehost-send-mails') . ' ' . Admin\Mailprotokoll::url() . "\n" . __('Statistics:', 'purehost-send-mails') . ' ' . Admin\Statistik::url(); return self::senden(__('send-mails.de: weekly report', 'purehost-send-mails'), $text); } /** Zustand der Bremse ohne angehaltene Mails (verworfen, aufgeraeumt) und abgelaufene Freigaben entfernen. */ private static function bremseAufraeumen(): void { Warteschlange::aufraeumen(); $zustand = Bremse::zustand(); if (!$zustand) { return; } $angehalten = Bremse::angehalten(); $neu = array_filter($zustand, static fn($z, $q) => isset($angehalten[$q]) || (int) ($z['frei_bis'] ?? 0) > time(), ARRAY_FILTER_USE_BOTH); if ($neu !== $zustand) { update_option(Bremse::ZUSTAND, $neu, false); } } }