Skip to content
Merged
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
64 changes: 58 additions & 6 deletions ProcessMaker/Http/Controllers/Api/UserController.php
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,55 @@ public function index(Request $request)
* ),
* ),
* )
*
* @OA\Post(
* path="/users_task_count",
* summary="Returns all users and their total tasks (POST version for large form_data)",
* operationId="postUsersTaskCount",
* tags={"Users"},
* @OA\RequestBody(
* description="Request body for filtering users",
* @OA\JsonContent(
* @OA\Property(
* property="filter",
* type="string",
* description="Filter results by string. Searches First Name, Last Name, Email, or Username."
* ),
* @OA\Property(
* property="include_ids",
* type="string",
* description="Comma separated list of user IDs to include in the response. Eg. 1,2,3"
* ),
* @OA\Property(
* property="assignable_for_task_id",
* type="integer",
* description="Task ID to get assignable users for"
* ),
* @OA\Property(
* property="form_data",
* type="object",
* description="Form data used to evaluate rule expressions for task assignment"
* ),
* ),
* ),
* @OA\Response(
* response=200,
* description="List of users with task counts",
* @OA\JsonContent(
* type="object",
* @OA\Property(
* property="data",
* type="array",
* @OA\Items(ref="#/components/schemas/users"),
* ),
* @OA\Property(
* property="meta",
* type="object",
* ref="#/components/schemas/metadata",
* ),
* ),
* ),
* )
*/
public function getUsersTaskCount(Request $request)
{
Expand Down Expand Up @@ -223,7 +272,8 @@ public function getUsersTaskCount(Request $request)
->withCount('activeTasks')
->orderBy(
$request->input('order_by', 'username'),
$request->input('order_direction', 'ASC'))
$request->input('order_direction', 'ASC')
)
->paginate(50);

return new ApiCollection($response);
Expand Down Expand Up @@ -359,8 +409,8 @@ public function getPinnnedControls(User $user)
$meta = $user->meta ? (array) $user->meta : [];

return array_key_exists('pinnedControls', $meta)
? $meta['pinnedControls']
: [];
? $meta['pinnedControls']
: [];
}

/**
Expand Down Expand Up @@ -774,10 +824,12 @@ private function uploadAvatar(User $user, Request $request)
// Validate image content
if ($type === 'svg') {
// For SVG files, validate against XSS
if (preg_match('/<script/i', $data) ||
if (
preg_match('/<script/i', $data) ||
preg_match('/on\w+\s*=/i', $data) ||
preg_match('/javascript:/i', $data) ||
preg_match('/data:/i', $data)) {
preg_match('/data:/i', $data)
) {
throw new \Exception('SVG contains potentially malicious content');
}
} else {
Expand Down Expand Up @@ -853,7 +905,7 @@ public function restore(Request $request)
// Otherwise, search trashed users
// for the user to restore
$user = User::onlyTrashed()->where($input, $request->input($input))
->first();
->first();
}

if ($user instanceof User) {
Expand Down
2 changes: 1 addition & 1 deletion resources/js/common/reassignMixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export default {
}
}

ProcessMaker.apiClient.get('users_task_count', { params }).then(response => {
ProcessMaker.apiClient.post('users_task_count', { params }).then(response => {
this.reassignUsers = [];
response.data.data.forEach((user) => {
this.reassignUsers.push({
Expand Down
2 changes: 2 additions & 0 deletions routes/api.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@
Route::put('users/update_language', [UserController::class, 'updateLanguage'])->name('users.updateLanguage');
Route::get('users_task_count', [UserController::class, 'getUsersTaskCount'])->name('users.users_task_count')
->middleware('can:view-users');
Route::post('users_task_count', [UserController::class, 'getUsersTaskCount'])->name('users.users_task_count_post')
->middleware('can:view-users');

// User Groups
Route::put('users/{user}/groups', [UserController::class, 'updateGroups'])->name('users.groups.update')->middleware('can:edit-users');
Expand Down
74 changes: 74 additions & 0 deletions tests/Feature/Api/UsersTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -933,6 +933,80 @@ public function testGetUsersTaskCount()
$this->assertContains($admin->id, collect($users)->pluck('id')->toArray());
}

public function testPostUsersTaskCount()
{
$admin = $this->user;
$user = User::factory()->create();
$groupUser = User::factory()->create();
$group = Group::factory()->create();
GroupMember::factory()->create([
'group_id' => $group->id,
'member_id' => $groupUser->id,
'member_type' => User::class,
]);

$process = Process::factory()->create([
'user_id' => $admin->id,
'manager_id' => $admin->id,
]);

$bpmn = file_get_contents(__DIR__ . '/../../Fixtures/task_with_user_group_assignment.bpmn');
$bpmn = str_replace([
'[assigned-users]',
'[assigned-groups]',
], [
$user->id,
$group->id,
], $bpmn);

$process->bpmn = $bpmn;
$process->save();

$request = ProcessRequest::factory()->create([
'process_id' => $process->id,
'user_id' => $admin->id,
]);

$tasks = ProcessRequestToken::factory(3)->create([
'process_id' => $process->id,
'process_request_id' => $request->id,
'element_id' => 'node_2',
'user_id' => $user->id,
'status' => 'ACTIVE',
]);

// Test POST endpoint with form_data in request body
$result = $this->apiCall('POST', route('api.users.users_task_count_post'), [
'assignable_for_task_id' => $tasks[0]->id,
'form_data' => [
'some_field' => 'some_value',
],
]);

$users = $result->json()['data'];

// Assert only the $user and $groupUser are in the list
$this->assertContains($user->id, array_column($users, 'id'));
$this->assertContains($groupUser->id, array_column($users, 'id'));

// Assert the $user has 3 active tasks
$tokenCount = collect($users)->first(fn ($r) => $r['id'] === $user->id)['active_tasks_count'];
$this->assertEquals(3, $tokenCount);

// Test POST endpoint without assignable_for_task_id
$result = $this->apiCall('POST', route('api.users.users_task_count_post'));
$users = $result->json()['data'];

// Assert the list of users now contains the admin user
$this->assertContains($admin->id, collect($users)->pluck('id')->toArray());

// Test POST endpoint with filter in request body
$result = $this->apiCall('POST', route('api.users.users_task_count_post'), [
'filter' => $user->firstname,
]);
$result->assertStatus(200);
}

/**
* Test save and get filters per user saved in cache
*/
Expand Down
Loading