Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions ProcessMaker/Http/Controllers/Api/UserController.php
Original file line number Diff line number Diff line change
Expand Up @@ -255,10 +255,13 @@ public function getUsersTaskCount(Request $request)
$processRequestToken = ProcessRequestToken::findOrFail($request->input('assignable_for_task_id'));
if (config('app.reassign_restrict_to_assignable_users')) {
$include_ids = $processRequestToken->process->getAssignableUsersByAssignmentType($processRequestToken);
}
$assignmentRule = $processRequestToken->getAssignmentRule();
if ($assignmentRule === 'rule_expression' && $request->has('form_data')) {
$include_ids = $processRequestToken->getAssigneesFromExpression($request->input('form_data'));
$assignmentRule = $processRequestToken->getAssignmentRule();
if ($assignmentRule === 'rule_expression' && $request->has('form_data')) {
$include_ids = $processRequestToken->getAssigneesFromExpression($request->input('form_data'));
}
if ($assignmentRule === 'process_variable' && $request->has('form_data')) {
$include_ids = $processRequestToken->getUsersFromProcessVariable($request->input('form_data'));
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rule expression filtering removed when config disabled

High Severity

The rule_expression assignment logic was moved inside the reassign_restrict_to_assignable_users config check block. Previously, when this config was false, the rule_expression check would still execute and filter assignable users accordingly. Now, when the config is false, neither rule_expression nor process_variable filtering occurs, causing users with rule_expression assignment rules to see all users instead of filtered results. This is a regression in existing functionality.

Fix in Cursor Fix in Web

}
}

Expand Down
54 changes: 54 additions & 0 deletions ProcessMaker/Models/ProcessRequestToken.php
Original file line number Diff line number Diff line change
Expand Up @@ -991,6 +991,60 @@ public function getAssignmentRule()
return $assignment;
}

/**
* Get user IDs from process variables for task assignment.
*
* Extracts user IDs and group IDs from form data based on the activity's
* assignedUsers and assignedGroups properties. Retrieves all users from
* specified groups (including subgroups recursively) and combines them
* with directly assigned users.
*
* Used when assignment rule is 'process_variable'.
*
* @param array $form_data Form data containing process variable values.
* Keys must match activity's assignedUsers and
* assignedGroups properties. Values must be arrays.
*
* @return array Unique numeric user IDs (direct users + users from groups).
*/
public function getUsersFromProcessVariable(array $form_data)
{
$activity = $this->getBpmnDefinition()->getBpmnElementInstance();
$assignedUsers = $activity->getProperty('assignedUsers', null);
$assignedGroups = $activity->getProperty('assignedGroups', null);

$usersIds = [];
$groupsIds = [];

// Validate and get user IDs from form_data
if ($assignedUsers && isset($form_data[$assignedUsers]) && is_array($form_data[$assignedUsers])) {
$usersIds = $form_data[$assignedUsers];
}

// Validate and get group IDs from form_data
if ($assignedGroups && isset($form_data[$assignedGroups]) && is_array($form_data[$assignedGroups])) {
$groupsIds = $form_data[$assignedGroups];
}

// Get users from groups using the Process model method
$usersFromGroups = [];
if (!empty($groupsIds) && $this->process) {
// Use the getConsolidatedUsers method from the Process model
// This method gets users from groups including subgroups recursively
$this->process->getConsolidatedUsers($groupsIds, $usersFromGroups);
}

// Combine direct users with users from groups
$allUserIds = array_unique(array_merge($usersIds, $usersFromGroups));

// Convert to numeric array and filter valid values
$allUserIds = array_values(array_filter($allUserIds, function ($id) {
return !empty($id) && is_numeric($id) && $id > 0;
}));

return $allUserIds;
}

/**
* Get the assignees for the token.
*
Expand Down
44 changes: 21 additions & 23 deletions resources/js/common/reassignMixin.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { getReassignUsers as getReassignUsersApi } from "../tasks/api";

export default {
data() {
return {
Expand All @@ -21,32 +23,28 @@ export default {
this.allowReassignment = response.data[this.task.id];
});
},
getReassignUsers(filter = null) {
const params = { };
if (filter) {
params.filter = filter;
}
if (this.task?.id) {
params.assignable_for_task_id = this.task.id;
// The variables are needed to calculate the rule expression.
if (this?.formData) {
params.form_data = this.formData;
delete params.form_data._user;
delete params.form_data._request;
delete params.form_data._process;
}
}
async getReassignUsers(filter = null) {
try {
const response = await getReassignUsersApi(
filter,
this.task?.id,
this.task?.request_data,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mixin uses stale request_data instead of current formData

Medium Severity

The mixin's getReassignUsers method now uses this.task?.request_data instead of this.formData. In tasks/edit.js, the show() method directly calls this.getReassignUsers() from the mixin, and the component has its own formData property that holds the user's current form edits. The change means rule expression and process variable evaluation will use the task's original stored data rather than the user's current form modifications, potentially showing incorrect assignable users if the form data affects assignment rules.

Fix in Cursor Fix in Web

this.currentTaskUserId
);

ProcessMaker.apiClient.post('users_task_count', params ).then(response => {
this.reassignUsers = [];
response.data.data.forEach((user) => {
this.reassignUsers.push({
text: user.fullname,
value: user.id,
active_tasks_count: user.active_tasks_count
if (response?.data) {
response.data.forEach((user) => {
this.reassignUsers.push({
text: user.fullname,
value: user.id,
active_tasks_count: user.active_tasks_count
});
});
});
});
}
} catch (error) {
console.error('Error loading reassign users:', error);
}
},
onReassignInput: _.debounce(function (filter) {
this.getReassignUsers(filter);
Expand Down
41 changes: 39 additions & 2 deletions resources/js/tasks/api/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,49 @@
import { getApi } from "../variables/index";

export const getReassignUsers = async (filter = null, taskId = null, currentTaskUserId = null) => {
/**
* Get reassign users using POST with form_data (for rule expression evaluation)
* This replaces the obsolete GET method with the advanced POST logic from reassignMixin
*
* @param {string|null} filter - Filter string to search users
* @param {number|null} taskId - Task ID to get assignable users for
* @param {Object|null} formData - Form data needed to calculate rule expressions
* @param {number|null} currentTaskUserId - User ID to exclude from results (matches: task?.user_id ?? task?.user?.id)
* @returns {Promise<Object>} Response data with users array
*/
export const getReassignUsers = async (
filter = null,
taskId = null,
formData = null,
currentTaskUserId = null
) => {
const api = getApi();
const response = await api.get("users_task_count", { params: { filter, assignable_for_task_id: taskId, include_current_user: true } });
const params = {};

if (filter) {
params.filter = filter;
}

if (taskId) {
params.assignable_for_task_id = taskId;

// The variables are needed to calculate the rule expression.
if (formData) {
params.form_data = { ...formData };
// Remove internal variables
delete params.form_data._user;
delete params.form_data._request;
delete params.form_data._process;
}
}

const response = await api.post("users_task_count", params);
const data = response.data;

// Filter out current user to prevent self-reassignment (matches mixin logic)
if (currentTaskUserId && Array.isArray(data?.data)) {
data.data = data.data.filter((user) => user.id !== currentTaskUserId);
}

return data;
};

Expand Down
3 changes: 2 additions & 1 deletion resources/js/tasks/components/TasksPreview.vue
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@
<TaskPreviewAssignment
v-if="showReassignment"
:task="task"
:form-data="formData"
:current-task-user-id="currentTaskUserId"
@on-cancel-reassign="showReassignment = false"
@on-reassign-user="e=> reassignUser(e,false)"
/>
Expand Down Expand Up @@ -413,7 +415,6 @@ export default {
},
openReassignment() {
this.showReassignment = !this.showReassignment;
this.getReassignUsers();
},
getTaskDefinitionForReassignmentPermission() {
ProcessMaker.apiClient
Expand Down
41 changes: 30 additions & 11 deletions resources/js/tasks/components/taskPreview/TaskPreviewAssignment.vue
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ const props = defineProps({
type: Object,
required: true,
},
formData: {
type: Object,
default: null,
},
currentTaskUserId: {
type: Number,
default: null,
},
});

const emit = defineEmits(["on-reassign-user"]);
Expand All @@ -80,18 +88,29 @@ const disabledAssign = ref(false);
// Computed properties
const disabled = computed(() => !selectedUser.value || !comments.value?.trim());

// Load the reassign users
// Load the reassign users using the centralized function with form_data
const loadReassignUsers = async (filter) => {
const response = await getReassignUsers(filter, props.task.id, props.task.user_id);

reassignUsers.value = [];
response.data.forEach((user) => {
reassignUsers.value.push({
text: user.fullname,
value: user.id,
active_tasks_count: user.active_tasks_count,
});
});
try {
const response = await getReassignUsers(
filter,
props.task?.id,
props.formData,
props.currentTaskUserId
);

reassignUsers.value = [];
if (response?.data) {
response.data.forEach((user) => {
reassignUsers.value.push({
text: user.fullname,
value: user.id,
active_tasks_count: user.active_tasks_count,
});
});
}
} catch (error) {
console.error('Error loading reassign users:', error);
}
};

/**
Expand Down
Loading
Loading