*/ public static function reserved(): array { $pool = [ ...(array) config('usernames.reserved', []), ...(array) config('skinbase.reserved_usernames', []), ]; return array_values(array_unique(array_map(static fn (string $v): string => strtolower(trim($v)), $pool))); } public static function normalize(string $value): string { return strtolower(trim($value)); } public static function sanitizeLegacy(string $value): string { $value = Str::ascii($value); $value = strtolower(trim($value)); $value = preg_replace('/[^a-z0-9_-]+/', '_', $value) ?? ''; $value = trim($value, '_-'); if ($value === '') { return 'user'; } return substr($value, 0, self::max()); } public static function isReserved(string $username): bool { return in_array(self::normalize($username), self::reserved(), true); } public static function similarReserved(string $username): ?string { $normalized = self::normalize($username); $reduced = self::reduceForSimilarity($normalized); $threshold = (int) config('usernames.similarity_threshold', 2); foreach (self::reserved() as $reserved) { if (levenshtein($reduced, self::reduceForSimilarity($reserved)) <= $threshold) { return $reserved; } } return null; } public static function hasApprovedOverride(string $username, ?int $userId = null): bool { if (! Schema::hasTable('username_approval_requests')) { return false; } $normalized = self::normalize($username); return DB::table('username_approval_requests') ->where('requested_username', $normalized) ->where('status', 'approved') ->when($userId !== null, fn ($q) => $q->where(function ($sub) use ($userId) { $sub->where('user_id', $userId)->orWhereNull('user_id'); })) ->exists(); } public static function uniqueCandidate(string $base, ?int $ignoreUserId = null): string { $base = self::sanitizeLegacy($base); if ($base === '' || self::isReserved($base) || self::similarReserved($base) !== null) { $base = 'user'; } $max = self::max(); $candidate = substr($base, 0, $max); $suffix = 1; while (self::exists($candidate, $ignoreUserId) || self::isReserved($candidate) || self::similarReserved($candidate) !== null) { $suffixStr = (string) $suffix; $prefixLen = max(1, $max - strlen($suffixStr)); $candidate = substr($base, 0, $prefixLen) . $suffixStr; $suffix++; } return $candidate; } private static function exists(string $username, ?int $ignoreUserId = null): bool { $query = User::query()->whereRaw('LOWER(username) = ?', [strtolower($username)]); if ($ignoreUserId !== null) { $query->where('id', '!=', $ignoreUserId); } return $query->exists(); } private static function reduceForSimilarity(string $value): string { return preg_replace('/[0-9_-]+/', '', strtolower($value)) ?? strtolower($value); } }