> */ public function alle(?string $neueste): array { $zeilen = $this->db->query('SELECT * FROM installation ORDER BY domain, erste_meldung')->fetchAll(); $auftraege = []; foreach ($this->db->query('SELECT a.* FROM auftrag a JOIN (SELECT installation, MAX(id) AS id FROM auftrag GROUP BY installation) j ON j.id = a.id')->fetchAll() as $auftrag) { $auftraege[$auftrag['installation']] = $auftrag; } return array_map(fn (array $zeile): array => $this->anreichern($zeile, $auftraege[$zeile['id']] ?? null, $neueste), $zeilen); } /** @return array|null */ public function eine(string $id, ?string $neueste): ?array { $abfrage = $this->db->prepare('SELECT * FROM installation WHERE id = ?'); $abfrage->execute([$id]); $zeile = $abfrage->fetch() ?: null; if (null === $zeile) { return null; } $verlauf = $this->db->prepare('SELECT * FROM auftrag WHERE installation = ? ORDER BY id DESC LIMIT 30'); $verlauf->execute([$id]); $auftraege = $verlauf->fetchAll(); $eine = $this->anreichern($zeile, $auftraege[0] ?? null, $neueste); $eine['verlauf'] = array_map(fn (array $a): array => [...$a, 'zustand' => $this->auftragZustand($a)], $auftraege); return $eine; } /** * Ein Update in Auftrag geben. Gibt null zurück oder den Grund, warum nicht. */ public function beauftragen(string $id, string $fassung): ?string { $eine = $this->eine($id, $fassung); if (null === $eine) { return 'Diese Installation gibt es nicht.'; } if (!$eine['kann_update']) { return $eine['kein_update_grund']; } $offen = $eine['auftrag']; if (null !== $offen && null === $offen['abgeholt'] && null === $offen['erledigt']) { $this->db->prepare('UPDATE auftrag SET fassung = ?, erstellt = ? WHERE id = ?')->execute([$fassung, Datenbank::jetzt(), $offen['id']]); return null; } $this->db->prepare('INSERT INTO auftrag (installation, aktion, fassung, erstellt) VALUES (?, ?, ?, ?)') ->execute([$id, 'update', $fassung, Datenbank::jetzt()]); return null; } /** Einen noch nicht abgeholten Auftrag zurückziehen. */ public function zurueckziehen(string $id): bool { $abfrage = $this->db->prepare('DELETE FROM auftrag WHERE installation = ? AND abgeholt IS NULL AND erledigt IS NULL'); $abfrage->execute([$id]); return $abfrage->rowCount() > 0; } public function notiz(string $id, string $notiz): void { $this->db->prepare('UPDATE installation SET notiz = ? WHERE id = ?')->execute([mb_substr(trim($notiz), 0, 200), $id]); } public function entfernen(string $id): void { $this->db->prepare('DELETE FROM installation WHERE id = ?')->execute([$id]); } /** * @param array $zeile * @param array|null $auftrag * * @return array */ private function anreichern(array $zeile, ?array $auftrag, ?string $neueste): array { $alter = time() - (int) strtotime((string) $zeile['letzte_meldung']); $veraltet = null !== $neueste && 'unbekannt' !== $zeile['fassung'] && version_compare((string) $zeile['fassung'], $neueste, '<'); $zustand = null === $auftrag ? null : $this->auftragZustand($auftrag); // Ein wartender Auftrag lässt sich noch ändern; ein abgeholter läuft. $laeuft = 'laeuft' === ($zustand['art'] ?? null); $grund = match (true) { null === $neueste => 'Die neueste Fassung ist gerade nicht bekannt.', !$veraltet => 'Diese Installation ist auf dem neuesten Stand.', 'git' === $zeile['art'] => 'Eine Git-Arbeitskopie wird über Git aktualisiert, nicht aus der Ferne.', $laeuft => 'Ein Update läuft gerade.', default => '', }; return [ ...$zeile, 'alter' => $alter, 'still' => $alter > self::STILL_STUNDEN * 3600, 'veraltet' => $veraltet, 'unbekannt' => '' === trim((string) $zeile['notiz']), 'auftrag' => $auftrag, 'auftrag_zustand' => $zustand, 'kann_update' => '' === $grund, 'kein_update_grund' => $grund, ]; } /** * @param array $auftrag * * @return array{art: string, text: string} */ private function auftragZustand(array $auftrag): array { if (null !== $auftrag['erledigt']) { return (int) $auftrag['ok'] === 1 ? ['art' => 'ok', 'text' => sprintf('Update auf %s erledigt', $auftrag['fassung'])] : ['art' => 'fehler', 'text' => sprintf('Update auf %s gescheitert', $auftrag['fassung'])]; } if (null === $auftrag['abgeholt']) { return ['art' => 'wartet', 'text' => sprintf('Update auf %s wartet auf die nächste Meldung', $auftrag['fassung'])]; } if ((int) strtotime((string) $auftrag['abgeholt']) < time() - self::AUFTRAG_STUNDEN * 3600) { return ['art' => 'fehler', 'text' => sprintf('Update auf %s: keine Rückmeldung', $auftrag['fassung'])]; } return ['art' => 'laeuft', 'text' => sprintf('Update auf %s läuft', $auftrag['fassung'])]; } }