, valid: bool, errors: array} */ public function validate(array $role, array $selected, array $available = []): array { return $this->validateAssignment($role, $selected, $available); } /** * Validate and normalize a role's selected permissions. When an available * list is supplied, selections outside that list are rejected rather than * silently discarded. * * @return array{role_id: int, permissions: list, valid: bool, errors: array} */ public function validateAssignment(array $role, array $selected, array $available = []): array { $roleId = $this->positiveId($role['id'] ?? $role['role_id'] ?? null); $normalized = ($this->permissions ?? new PermissionMatrix())->normalize($selected); $errors = []; if ($roleId === null) $errors['role_id'] = 'Role ID must be a positive integer.'; if (($this->roles ?? new RoleRecord())->isAdministrator($role)) { $errors['role'] = 'The protected Administrator role permissions cannot be changed.'; } if ($available !== []) { $allowed = ($this->permissions ?? new PermissionMatrix())->normalize($available); $unknown = array_values(array_diff($normalized, $allowed)); if ($unknown !== []) { $errors['permissions'] = 'Unknown permissions cannot be assigned: ' . implode(', ', $unknown) . '.'; } } return ['role_id' => $roleId ?? 0, 'permissions' => $normalized, 'valid' => $errors === [], 'errors' => $errors]; } /** Alias matching controller command terminology. */ public function validateForAssignment(array $role, array $selected, array $available = []): array { return $this->validateAssignment($role, $selected, $available); } /** * Validate a role edit and its optional permission set in one safe result. * Administrator cannot be renamed or have permissions changed. * * @return array */ public function validateForEdit(int $id, array $input, array $available = []): array { $record = $this->roles ?? new RoleRecord(); $result = $record->validate($input); $result['id'] = $id; if ($id < 1) $result['errors']['id'] = 'Role ID must be a positive integer.'; $current = ['id' => $id, 'name' => $id === 1 ? 'Administrator' : ($input['current_name'] ?? ($input['name'] ?? null))]; if (array_key_exists('current_name', $input)) { if (!$record->canRename($current, $result['name'])) $result['errors']['role'] = 'The protected Administrator role cannot be renamed.'; } if (array_key_exists('permissions', $input)) { $assignment = $this->validateAssignment(['id' => $id, 'name' => $current['name']], is_array($input['permissions']) ? $input['permissions'] : [], $available); $result['permissions'] = $assignment['permissions']; $result['errors'] = [...$result['errors'], ...$assignment['errors']]; } $result['valid'] = $result['errors'] === []; return $result; } public function canAssignPermissions(array $role): bool { return !($this->roles ?? new RoleRecord())->isAdministrator($role); } public function assertCanAssignPermissions(array $role): void { ($this->roles ?? new RoleRecord())->assertCanChangePermissions($role); } 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; } }