$roles * * @return array{orphan_admin: bool, reasons: list, email_domain_mismatch: bool} */ public static function evaluate( array $roles, ?string $lastLogin, ?string $trackingSince, string $email, string $siteHost, \DateTimeImmutable $now, ): array { $threshold = $now->modify('-' . self::INACTIVE_DAYS . ' days'); $isAdmin = in_array('administrator', $roles, true); $reasons = []; $last = self::date($lastLogin); $since = self::date($trackingSince); if ($isAdmin && $last !== null && $last < $threshold) { $reasons[] = 'inactive_90d'; } elseif ($isAdmin && $last === null && $since !== null && $since < $threshold) { $reasons[] = 'never_seen'; } $emailDomain = strtolower((string) substr((string) strrchr($email, '@'), 1)); $host = strtolower((string) preg_replace('/^www\./', '', $siteHost)); $mismatch = $emailDomain !== '' && $host !== '' && $emailDomain !== $host && !str_ends_with($host, '.' . $emailDomain) && !str_ends_with($emailDomain, '.' . $host); return ['orphan_admin' => $reasons !== [], 'reasons' => $reasons, 'email_domain_mismatch' => $mismatch]; } private static function date(?string $value): ?\DateTimeImmutable { if ($value === null || trim($value) === '') { return null; } try { return new \DateTimeImmutable($value, new \DateTimeZone('UTC')); } catch (\Exception) { return null; } } }