Zeilen für Protokoll, Kommandozeile oder Backend */ public function run(bool $refreshStatistics = false): array { $settings = $this->site->settings(); if (! $settings->complete()) { return [['host' => null, 'message' => Message::of('connection.missing_config')]]; } $hosts = DomainCollector::hostnames($this->site->siteUrls()); if ($hosts === []) { return [['host' => null, 'message' => Message::of('report.no_public_address')]]; } $payload = InsightPayload::build( $this->site->wordpressVersion(), $this->site->phpVersion(), $this->site->extensions(), $this->site->mailer(), $this->site->sites(), $this->site->maintenance(), // die Installation nennt sich mit ihrem ersten Hostnamen – sortiert, also bei jedem Lauf derselbe $hosts[0], // Plattform-Gesundheit nur, wenn eingeschaltet: ohne Umgebung bewertet DomainWarn sie nicht und eröffnet // keinen Vorfall „Plattform kritisch“ – einen offenen schließt die nächste Meldung $settings->platformHealth ? $this->site->environment() : [], ); $api = $this->factory->create($settings); $reporter = new InsightReporter($api, $hosts[0]); $pingUrl = $settings->heartbeatUrl !== '' ? $settings->heartbeatUrl : null; $lines = []; // Kennung je Hostname aus der Meldung: Der Stand der gehosteten Funktionen braucht sie gleich noch einmal $known = []; // der Heartbeat hängt an der ersten überwachten Domain: eine Website, ein geplanter Lauf, ein Herzschlag $heartbeatDone = false; foreach ($hosts as $host) { try { $result = $reporter->report($host, $payload); $known[$host] = $result['domain']; $lines[] = ['host' => $host, 'message' => $result['message']]; if ($result['domain'] === null || $heartbeatDone) { continue; } $used = $reporter->heartbeat($result['domain'], $pingUrl); $heartbeatDone = true; if ($used !== $pingUrl) { // neu eingerichtet: merken. Verworfen und nicht neu einzurichten (kein Platz im Tarif): vergessen, // statt jeden Lauf eine Adresse zu pingen, die es nicht mehr gibt $settings = $settings->withHeartbeatUrl($used ?? ''); $this->site->saveSettings($settings); } $lines[] = ['host' => $host, 'message' => Message::of($used === null ? 'report.heartbeat_unavailable' : 'report.heartbeat_sent')]; } catch (TransportException $e) { // Fehler der Gegenstelle tragen deren Text; er kommt schon in der Sprache, die die API gewählt hat $lines[] = ['host' => $host, 'message' => Message::of('connection.unreachable', ['detail' => $e->getMessage()])]; } } // beim ersten Lauf hat die API das Kürzel genannt; gemerkt spart es jedem weiteren Lauf eine Abfrage. Vor den // Kennzahlen: Ein geändertes Kürzel verwirft gespeicherte Kennzahlen (WpSiteContext::saveSettings) – danach // gemerkt, löschte es die eben geholten gleich wieder, und das Widget bliebe bis zum nächsten Lauf leer try { if ($api->organization() !== '' && $api->organization() !== $settings->organization) { $settings = $settings->withOrganization($api->organization()); $this->site->saveSettings($settings); } } catch (TransportException) { // ohne Kürzel fragt organization() das Konto ab; antwortet es nicht, steht das schon je Host in den // Zeilen – gemerkt wird dann eben beim nächsten Lauf, der geplante Lauf darf daran nicht scheitern } $this->refreshHosting($api, $settings, $hosts, $known); $this->refreshStatistics($api, $settings, $known, $refreshStatistics); return $lines; } /** * Den Stand der gehosteten Funktionen erneuern, wenn er älter als eine Dreiviertelstunde ist: Er gilt eine Stunde, * und der Lauf kommt alle 15 Minuten – so ist er beim Abruf der security.txt nie abgelaufen. Scheitert das, bleibt * der alte Stand; die Meldung selbst ist davon nicht betroffen. * * @param list $hosts * @param array $known */ private function refreshHosting(DomainWarnApi $api, Settings $settings, array $hosts, array $known): void { $now = time(); if (SiteHosting::fresh($this->site->hostingSnapshot(), $now, SiteHosting::REFRESH_AFTER_SECONDS)) { return; } try { $this->site->saveHostingSnapshot((new HostingRefresh($api))->run($hosts, $settings->securityTxtBase(), $now, $known)); } catch (TransportException) { // DomainWarn gerade nicht erreichbar: der nächste Lauf versucht es wieder } } /** * Den Stand der Kennzahlen für die Übersicht im Backend erneuern, höchstens einmal je Stunde (oder auf * Knopfdruck). Er ist nur zum Anschauen da: Was hier schiefgeht – DomainWarn nicht erreichbar, eine Antwort in * unerwarteter Form –, lässt den alten Stand stehen und darf weder die Meldung noch den Heartbeat berühren. * Deshalb wird hier alles abgefangen, nicht nur Fehler der Gegenstelle. * * @param array $known */ private function refreshStatistics(DomainWarnApi $api, Settings $settings, array $known, bool $force): void { $now = time(); $previous = $this->site->statisticsSnapshot(); if (! $force && SiteStatistics::fresh($previous, $now)) { return; } // die Adresse der Website zuerst: ihre Domain steht oben, und ihre Prüfungen werden bevorzugt $hosts = []; foreach ($this->site->siteUrls() as $url) { $host = DomainCollector::host($url); if ($host !== null && ! in_array($host, $hosts, true)) { $hosts[] = $host; } } try { $dashboard = SiteStatistics::dashboardUrl($settings->apiBaseUrl, $this->site->hostingSnapshot()); $this->site->saveStatisticsSnapshot((new SiteStatistics($api))->run($hosts, $now, $known, $dashboard, $previous)); } catch (\Throwable) { // der nächste Lauf versucht es wieder; bis dahin zeigt die Übersicht den letzten Stand } } }