diff --git a/ProcessMaker/Traits/TaskControllerIndexMethods.php b/ProcessMaker/Traits/TaskControllerIndexMethods.php index be27fa8804..d773ffb241 100644 --- a/ProcessMaker/Traits/TaskControllerIndexMethods.php +++ b/ProcessMaker/Traits/TaskControllerIndexMethods.php @@ -295,6 +295,18 @@ private function applyPmql($query, $request, $user) { $pmql = $request->input('pmql', ''); if (!empty($pmql)) { + if ($this->advancedFilterHasStatus($request)) { + $pmql = $this->removeStatusFromPmql($pmql); + + if ($this->advancedFilterHasSelfServiceStatus($request)) { + $pmql = $this->removeUserIdFromPmql($pmql); + } + } + + if (empty($pmql)) { + return; + } + try { $query->pmql($pmql, null, $user); } catch (QueryException $e) { @@ -305,6 +317,60 @@ private function applyPmql($query, $request, $user) } } + private function advancedFilterHasStatus($request): bool + { + return !empty($this->getAdvancedFilterArray($request)); + } + + private function advancedFilterHasSelfServiceStatus($request): bool + { + foreach ($this->getAdvancedFilterArray($request) as $filter) { + $values = (array) ($filter['value'] ?? []); + foreach ($values as $v) { + if (mb_strtolower($v) === 'self service') { + return true; + } + } + } + + return false; + } + + private function getAdvancedFilterArray($request): array + { + $advancedFilter = $request->input('advanced_filter', ''); + if (empty($advancedFilter)) { + return []; + } + + $filterArray = is_string($advancedFilter) ? json_decode($advancedFilter, true) : $advancedFilter; + if (!is_array($filterArray)) { + return []; + } + + return array_filter($filterArray, function ($filter) { + return isset($filter['subject']['type']) && $filter['subject']['type'] === 'Status'; + }); + } + + private function removeStatusFromPmql(string $pmql): string + { + $pmql = preg_replace('/\s+AND\s+\(status\s*=\s*"[^"]*"\)/i', '', $pmql); + $pmql = preg_replace('/\(status\s*=\s*"[^"]*"\)\s+AND\s+/i', '', $pmql); + $pmql = preg_replace('/\(status\s*=\s*"[^"]*"\)/i', '', $pmql); + + return trim($pmql); + } + + private function removeUserIdFromPmql(string $pmql): string + { + $pmql = preg_replace('/\s+AND\s+\(user_id\s*=\s*\d+\)/i', '', $pmql); + $pmql = preg_replace('/\(user_id\s*=\s*\d+\)\s+AND\s+/i', '', $pmql); + $pmql = preg_replace('/\(user_id\s*=\s*\d+\)/i', '', $pmql); + + return trim($pmql); + } + private function applyAdvancedFilter($query, $request) { if ($advancedFilter = $request->input('advanced_filter', '')) { diff --git a/tests/Feature/Api/TasksTest.php b/tests/Feature/Api/TasksTest.php index d3007914f3..2cc9abbcb8 100644 --- a/tests/Feature/Api/TasksTest.php +++ b/tests/Feature/Api/TasksTest.php @@ -831,6 +831,122 @@ public function testAdvancedFilterByProcessRequestName() $this->assertEquals($hitTask->id, $json['data'][0]['id']); } + public function testAdvancedStatusFilterOverridesPmqlStatus() + { + $user = User::factory()->create(['is_administrator' => true]); + + $activeTask = ProcessRequestToken::factory()->create([ + 'status' => 'ACTIVE', + 'element_type' => 'task', + 'user_id' => $user->id, + 'is_self_service' => 0, + ]); + + $completedTask = ProcessRequestToken::factory()->create([ + 'status' => 'CLOSED', + 'element_type' => 'task', + 'user_id' => $user->id, + 'is_self_service' => 0, + ]); + + $statusFilter = json_encode([ + [ + 'subject' => ['type' => 'Status'], + 'operator' => '=', + 'value' => 'Completed', + ], + ]); + + $response = $this->actingAs($user, 'api')->get(route('api.tasks.index', [ + 'pmql' => '(user_id = ' . $user->id . ') AND (status = "In Progress")', + 'advanced_filter' => $statusFilter, + ])); + + $response->assertStatus(200); + $data = $response->json('data'); + $returnedIds = collect($data)->pluck('id')->toArray(); + + $this->assertContains($completedTask->id, $returnedIds); + $this->assertNotContains($activeTask->id, $returnedIds); + } + + public function testPmqlStatusPreservedWhenNoAdvancedStatusFilter() + { + $user = User::factory()->create(['is_administrator' => true]); + + $activeTask = ProcessRequestToken::factory()->create([ + 'status' => 'ACTIVE', + 'element_type' => 'task', + 'user_id' => $user->id, + 'is_self_service' => 0, + ]); + + $completedTask = ProcessRequestToken::factory()->create([ + 'status' => 'CLOSED', + 'element_type' => 'task', + 'user_id' => $user->id, + 'is_self_service' => 0, + ]); + + $response = $this->actingAs($user, 'api')->get(route('api.tasks.index', [ + 'pmql' => '(user_id = ' . $user->id . ') AND (status = "In Progress")', + ])); + + $response->assertStatus(200); + $data = $response->json('data'); + $returnedIds = collect($data)->pluck('id')->toArray(); + + $this->assertContains($activeTask->id, $returnedIds); + $this->assertNotContains($completedTask->id, $returnedIds); + } + + public function testSelfServiceFilterOverridesPmqlStatusAndUserId() + { + $user = User::factory()->create(['is_administrator' => true]); + $group = Group::factory()->create(); + + GroupMember::factory()->create([ + 'group_id' => $group->id, + 'member_id' => $user->id, + 'member_type' => User::class, + ]); + + $selfServiceTask = ProcessRequestToken::factory()->create([ + 'status' => 'ACTIVE', + 'element_type' => 'task', + 'user_id' => null, + 'is_self_service' => 1, + 'self_service_groups' => ['groups' => [strval($group->id)], 'users' => []], + ]); + + $regularTask = ProcessRequestToken::factory()->create([ + 'status' => 'ACTIVE', + 'element_type' => 'task', + 'user_id' => $user->id, + 'is_self_service' => 0, + ]); + + $statusFilter = json_encode([ + [ + 'subject' => ['type' => 'Status'], + 'operator' => '=', + 'value' => 'Self Service', + ], + ]); + + $response = $this->actingAs($user, 'api')->get(route('api.tasks.index', [ + 'pmql' => '(user_id = ' . $user->id . ') AND (status = "In Progress")', + 'advanced_filter' => $statusFilter, + ])); + + $response->assertStatus(200); + $data = $response->json('data'); + $returnedIds = collect($data)->pluck('id')->toArray(); + + $this->assertContains($selfServiceTask->id, $returnedIds); + $this->assertNotContains($regularTask->id, $returnedIds); + } + public function testGetScreenFields() { $this->be($this->user);