clock->now(); $day = gmdate('Ymd', $now); $state = $this->state(); $row = $state[$kind] ?? null; $fresh = !is_array($row) || ($kind !== 'site' && ($row['day'] ?? '') !== $day); if ($fresh) { $row = ['day' => $day, 'first' => $now, 'last' => $now, 'seen' => [], 'w' => 0]; } $isNew = $kind !== 'site' && !in_array($who, $row['seen'], true) && count($row['seen']) < self::MAX_SEEN; if (!$fresh && !$isNew && $now - (int) $row['w'] < self::WRITE_INTERVAL) { return; } if ($isNew) { $row['seen'][] = $who; } $row['last'] = $now; $row['w'] = $now; $state[$kind] = $row; $this->options->set(self::OPTION, $state); } /** * What an administrator should be told now. * * @return array keys: site, client, shared, unknown */ public function current(): array { $now = $this->clock->now(); $day = gmdate('Ymd', $now); $out = []; foreach ($this->state() as $kind => $row) { if (!is_array($row) || !in_array($kind, ['site', 'client', 'shared', 'unknown'], true)) { continue; } $live = $kind === 'site' ? $now - (int) ($row['last'] ?? 0) < self::SITE_WINDOW : ($row['day'] ?? '') === $day; if ($live) { $out[$kind] = ['first' => (int) ($row['first'] ?? 0), 'last' => (int) ($row['last'] ?? 0), 'clients' => count((array) ($row['seen'] ?? []))]; } } return $out; } /** Called when an administrator saves new limits: the site alert is stale from that moment. */ public function clearSite(): void { $state = $this->state(); if (isset($state['site'])) { unset($state['site']); $this->options->set(self::OPTION, $state); } } /** @return array */ private function state(): array { $state = $this->options->get(self::OPTION, []); return is_array($state) ? $state : []; } }