*/ public function validate(array $input, array $existingUsers = [], ?int $currentId = null): array { $result = ($this->users ?? new UserRecord())->validate($input); $email = $result['email'] ?? ''; if ($currentId !== null && is_string($email) && $email !== '' && $this->hasDuplicateEmail($email, $existingUsers, $currentId)) { $result['errors']['email'] = 'Email address is already in use.'; } $result['valid'] = $result['errors'] === []; return $result; } /** Controller-friendly user-create DTO. */ public function validateForCreate(array $input, array $existingUsers = []): array { $result = ($this->users ?? new UserRecord())->validateForCreate($input); $email = $result['email'] ?? ''; if (is_string($email) && $email !== '' && $this->hasDuplicateEmail($email, $existingUsers, 0)) { $result['errors']['email'] = 'Email address is already in use.'; $result['valid'] = false; } return $result; } /** @return array */ public function validateForEdit(int $id, array $input, array $existingUsers = []): array { $errors = []; if ($id < 1) { $errors['id'] = 'User ID must be a positive integer.'; } $result = ($this->users ?? new UserRecord())->validate($input); $existing = $existingUsers[array_search($id, array_map(static fn($row) => is_array($row) ? (int)($row['id'] ?? 0) : 0, $existingUsers), true)] ?? []; if ($this->isProtectedAdministrator(['id' => $id, ...$existing]) && array_key_exists('role_id', $input) && (int)$input['role_id'] !== 1) $result['errors']['role_id'] = 'The protected Administrator account cannot be reassigned.'; $email = $result['email'] ?? ''; if (is_string($email) && $email !== '' && $this->hasDuplicateEmail($email, $existingUsers, $id)) { $result['errors']['email'] = 'Email address is already in use.'; } $result['id'] = $id; $result['errors'] = [...$errors, ...$result['errors']]; $result['valid'] = $result['errors'] === []; return $result; } /** Alias matching controller command terminology. */ public function validateEdit(int $id, array $input, array $existingUsers = []): array { return $this->validateForEdit($id, $input, $existingUsers); } /** @return array{valid: bool, id: int, is_active: bool, errors: array} */ public function validateDeactivate(array $user): array { return $this->validateTransition($user, true, false, 'deactivated'); } /** @return array{valid: bool, id: int, is_active: bool, errors: array} */ public function validateReactivate(array $user): array { return $this->validateTransition($user, false, true, 'reactivated'); } /** @return array{valid: bool, id: int, is_active: bool, errors: array} */ public function deactivate(array $user): array { return $this->validateDeactivate($user); } /** @return array{valid: bool, id: int, is_active: bool, errors: array} */ public function reactivate(array $user): array { return $this->validateReactivate($user); } /** Validate reset input without ever returning the plaintext password. */ /** @return array{valid: bool, errors: array} */ public function validatePasswordReset(array $user, mixed $password = null): array { $payload = $password === null && array_key_exists('password', $user); if ($payload) $password = $user['password']; $errors = []; if (!$payload && $this->positiveId($user['id'] ?? null) === null) { $errors['id'] = 'User ID must be a positive integer.'; } $check = ($this->passwordPolicy ?? new PasswordPolicy())->validateReset($password); if (!$check['valid']) { $errors['password'] = 'Password requires: ' . implode(', ', $check['errors']) . '.'; } return ['valid' => $errors === [], 'errors' => $errors]; } /** Aliases useful to controllers accepting a reset command payload. */ /** @return array{valid: bool, errors: array} */ public function validateReset(array $user, mixed $password = null): array { return $this->validatePasswordReset($user, $password); } /** @return array{valid: bool, errors: array} */ public function validateResetPassword(array $user, mixed $password = null): array { return $this->validatePasswordReset($user, $password); } /** @return array */ public function display(array $user): array { return ($this->users ?? new UserRecord())->display($user); } /** @return array */ public function toDisplay(array $user): array { return $this->display($user); } public function isProtectedAdministrator(array $user): bool { if (isset($user['id']) && (int)$user['id'] === 1) return true; if (isset($user['role_id']) && (int)$user['role_id'] === 1) return true; $role = $user['role_name'] ?? $user['role'] ?? null; return is_scalar($role) && strtolower(trim((string) $role)) === 'administrator'; } private function validateTransition(array $user, bool $from, bool $to, string $action): array { $id = $this->positiveId($user['id'] ?? null); $active = $this->asBool($user['is_active'] ?? $user['active'] ?? null); $errors = []; if ($id === null) { $errors['id'] = 'User ID must be a positive integer.'; } if ($this->isProtectedAdministrator($user)) { $errors['role'] = 'The protected Administrator account cannot be deactivated.'; } elseif ($active !== $from) { $errors['is_active'] = "Only {$this->stateName($from)} users can be {$action}."; } return ['valid' => $errors === [], 'id' => $id ?? 0, 'is_active' => $to, 'errors' => $errors]; } private function hasDuplicateEmail(string $email, array $rows, int $currentId): bool { foreach ($rows as $row) { if (!is_array($row) || $this->positiveId($row['id'] ?? null) === $currentId) continue; $other = $row['email'] ?? null; if (is_scalar($other) && strtolower(trim((string) $other)) === $email) return true; } return false; } private function stateName(bool $active): string { return $active ? 'active' : 'inactive'; } private function positiveId(mixed $value): ?int { if (is_int($value) && $value > 0) return $value; if (is_string($value) && preg_match('/^[1-9]\d*$/', trim($value)) === 1) { $id = filter_var(trim($value), FILTER_VALIDATE_INT); return $id === false ? null : $id; } return null; } private function asBool(mixed $value): ?bool { if (is_bool($value)) return $value; if (is_int($value) && ($value === 0 || $value === 1)) return $value === 1; if (is_string($value)) { $value = strtolower(trim($value)); if (in_array($value, ['1', 'true', 'yes', 'on'], true)) return true; if (in_array($value, ['0', 'false', 'no', 'off'], true)) return false; } return null; } }