*/ public static function categories(): array { return [self::CATEGORY_HOSTING, self::CATEGORY_VPN, self::CATEGORY_EMAIL, self::CATEGORY_DOMAIN, self::CATEGORY_DATABASE, self::CATEGORY_SSH, self::CATEGORY_API, self::CATEGORY_OTHER, self::CATEGORY_MICROSOFT, self::CATEGORY_NETWORK, self::CATEGORY_ROUTER, self::CATEGORY_INFRASTRUCTURE]; } /** @return array{category:string, label:string, username:string|null, notes:string|null} */ public function normalize(array $information): array { return [ 'category' => strtolower($this->text($information['category'] ?? null) ?? ''), 'label' => $this->text($information['label'] ?? null) ?? '', 'username' => $this->text($information['username'] ?? null), 'notes' => $this->text($information['notes'] ?? null), ]; } /** @return array{valid:bool,errors:array,category:string,label:string,username:string|null,notes:string|null} */ public function validate(array $information): array { $normalized = $this->normalize($information); $errors = []; if (!in_array($normalized['category'], self::categories(), true)) { $errors['category'] = 'Credential category is invalid.'; } if ($normalized['label'] === '') { $errors['label'] = 'Credential label is required.'; } elseif ($normalized['label'] !== '' && mb_strlen($normalized['label']) > 120 || ($normalized['label'] !== '' && $this->hasControlCharacter($normalized['label']))) { $errors['label'] = 'Credential label must be 120 characters or fewer and contain no control characters.'; } if ($normalized['username'] !== null && (mb_strlen($normalized['username']) > 190 || $this->hasControlCharacter($normalized['username']))) { $errors['username'] = 'Credential username must be 190 characters or fewer and contain no control characters.'; } if ($normalized['notes'] !== null && mb_strlen($normalized['notes']) > 2000) { $errors['notes'] = 'Credential notes must be 2000 characters or fewer.'; } return [...$normalized, 'valid' => $errors === [], 'errors' => $errors]; } /** @return array */ public function display(array $information): array { $safe = []; foreach (['id', 'category', 'label', 'username', 'notes'] as $field) { if (array_key_exists($field, $information)) $safe[$field] = $information[$field]; } return $safe; } /** @return array */ public function toDisplay(array $information): array { return $this->display($information); } private function text(mixed $value): ?string { if ($value === null) return null; $text = trim(is_scalar($value) ? (string) $value : ''); return $text === '' ? null : $text; } private function hasControlCharacter(string $value): bool { return preg_match('/[\x00-\x1F\x7F]/', $value) === 1; } }