>|null */ private ?array $items = null; private function getFile(): CompiledYamlFile { return CompiledYamlFile::instance(self::FILE); } /** * @return array> */ private function load(): array { if ($this->items === null) { $content = $this->getFile()->content(); $this->items = is_array($content) ? $content : []; } return $this->items; } private function persist(): void { $file = $this->getFile(); $file->save($this->items ?? []); $file->free(); } /** * Generate a unique, URL-safe token (40 hex chars). */ public function generateToken(): string { $items = $this->load(); do { try { $token = bin2hex(random_bytes(20)); } catch (\Exception) { $token = md5(uniqid((string) mt_rand(), true)) . md5(uniqid((string) mt_rand(), true)); } } while (isset($items[$token])); return $token; } /** * @return array> token => record */ public function all(): array { return $this->load(); } /** * @return array|null */ public function get(string $token): ?array { $items = $this->load(); return $items[$token] ?? null; } public function getByEmail(string $email): ?array { $email = strtolower(trim($email)); foreach ($this->load() as $record) { if (strtolower((string) ($record['email'] ?? '')) === $email) { return $record; } } return null; } /** * @param array $record must contain a 'token' key */ public function add(array $record): void { $token = (string) ($record['token'] ?? ''); if ($token === '') { throw new \InvalidArgumentException('Invite record requires a token.'); } $this->load(); $this->items[$token] = $record; $this->persist(); } public function remove(string $token): bool { $this->load(); if (!isset($this->items[$token])) { return false; } unset($this->items[$token]); $this->persist(); return true; } /** * Drop any invites whose expiry has passed. * * @return int number of invites removed */ public function purgeExpired(): int { $this->load(); $removed = 0; foreach ($this->items as $token => $record) { if (self::isExpired($record)) { unset($this->items[$token]); $removed++; } } if ($removed > 0) { $this->persist(); } return $removed; } /** * @param array $record */ public static function isExpired(array $record): bool { $expires = (int) ($record['expires'] ?? 0); return $expires > 0 && time() > $expires; } }