Skip to content

Commit 4677567

Browse files
committed
Merge branch 'develop' into task/FOUR-28803
2 parents af40a9a + aeaabc1 commit 4677567

39 files changed

Lines changed: 1310 additions & 173 deletions

ProcessMaker/Helpers/DataTypeHelper.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ private static function isDate($value)
1010
{
1111
if (is_string($value)) {
1212
if (strlen($value) > 5) {
13+
if (!preg_match('/\d{4}-\d{2}-\d{2}/', $value)) {
14+
return false;
15+
}
1316
try {
1417
$parsed = Carbon::parse($value);
1518
if ($parsed->isMidnight()) {

ProcessMaker/Http/Controllers/Api/UserController.php

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -255,10 +255,13 @@ public function getUsersTaskCount(Request $request)
255255
$processRequestToken = ProcessRequestToken::findOrFail($request->input('assignable_for_task_id'));
256256
if (config('app.reassign_restrict_to_assignable_users')) {
257257
$include_ids = $processRequestToken->process->getAssignableUsersByAssignmentType($processRequestToken);
258-
}
259-
$assignmentRule = $processRequestToken->getAssignmentRule();
260-
if ($assignmentRule === 'rule_expression' && $request->has('form_data')) {
261-
$include_ids = $processRequestToken->getAssigneesFromExpression($request->input('form_data'));
258+
$assignmentRule = $processRequestToken->getAssignmentRule();
259+
if ($assignmentRule === 'rule_expression' && $request->has('form_data')) {
260+
$include_ids = $processRequestToken->getAssigneesFromExpression($request->input('form_data'));
261+
}
262+
if ($assignmentRule === 'process_variable' && $request->has('form_data')) {
263+
$include_ids = $processRequestToken->getUsersFromProcessVariable($request->input('form_data'));
264+
}
262265
}
263266
}
264267

ProcessMaker/Http/Controllers/TaskController.php

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ public function edit(ProcessRequestToken $task, string $preview = '')
140140
$element = $task->getDefinition(true);
141141
$screenFields = $screenVersion ? $screenVersion->screenFilteredFields() : [];
142142
$taskDraftsEnabled = TaskDraft::draftsEnabled();
143-
143+
$isSmartExtractTask = $task->element_name === 'Manual Document Review';
144144
// Remove screen parent to reduce the size of the response
145145
$screen = $task->screen;
146146
$screen['parent'] = null;
@@ -190,6 +190,25 @@ public function edit(ProcessRequestToken $task, string $preview = '')
190190
'datetime_format',
191191
]);
192192
$userConfiguration = (new UserConfigurationController())->index();
193+
$hitlEnabled = config('smart-extract.hitl_enabled', false) && $isSmartExtractTask;
194+
195+
// Build the iframe source
196+
$iframeSrc = null;
197+
if ($hitlEnabled) {
198+
$dashboardUrl = config('smart-extract.dashboard_url');
199+
$requestData = $task->processRequest->data ?? [];
200+
201+
$documentToken = $requestData['documentToken'] ?? null;
202+
$fileId = $requestData['fileId'] ?? null;
203+
204+
if ($documentToken && $fileId && !empty($dashboardUrl)) {
205+
$queryParams = http_build_query([
206+
'documentToken' => $documentToken,
207+
'fileId' => $fileId,
208+
]);
209+
$iframeSrc = $dashboardUrl . '?' . $queryParams;
210+
}
211+
}
193212

194213
return view('tasks.edit', [
195214
'task' => $task,
@@ -204,6 +223,8 @@ public function edit(ProcessRequestToken $task, string $preview = '')
204223
'screenFields' => $screenFields,
205224
'taskDraftsEnabled' => $taskDraftsEnabled,
206225
'userConfiguration' => $userConfiguration,
226+
'hitlEnabled' => $hitlEnabled,
227+
'iframeSrc' => $iframeSrc,
207228
]);
208229
}
209230
}

ProcessMaker/Http/Middleware/HideServerHeaders.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class HideServerHeaders
5555
/**
5656
* Handle an incoming request.
5757
*
58-
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
58+
* @param Closure(Request): (Response) $next
5959
*/
6060
public function handle(Request $request, Closure $next): Response
6161
{

ProcessMaker/Http/Middleware/IsManager.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class IsManager
1313
/**
1414
* Handle an incoming request.
1515
*
16-
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
16+
* @param Closure(Request): (Response) $next
1717
*/
1818
public function handle(Request $request, Closure $next): Response
1919
{

ProcessMaker/Http/Resources/Task.php

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ public function toArray($request)
7474

7575
$this->addAssignableUsers($array, $include);
7676

77+
$this->mergeHitlCaseNumber($array);
78+
7779
return $array;
7880
}
7981

@@ -114,6 +116,28 @@ private function addAssignableUsers(&$array, $include)
114116
}
115117
}
116118

119+
private function mergeHitlCaseNumber(array &$array): void
120+
{
121+
if (!config('smart-extract.hitl_enabled')) {
122+
return;
123+
}
124+
125+
if (!empty(data_get($array, 'process_request.case_number'))) {
126+
return;
127+
}
128+
129+
$this->processRequest->loadMissing('parentRequest');
130+
$parentCaseNumber = $this->processRequest->parentRequest?->case_number;
131+
if (!$parentCaseNumber) {
132+
return;
133+
}
134+
135+
data_set($array, 'process_request.case_number', $parentCaseNumber);
136+
if (empty($array['case_number'])) {
137+
$array['case_number'] = $parentCaseNumber;
138+
}
139+
}
140+
117141
/**
118142
* Add the active users to the list of assigned users
119143
*
@@ -131,7 +155,7 @@ private function addActiveAssignedUsers(array $users, array $assignedUsers)
131155
->whereNotIn('status', Process::NOT_ASSIGNABLE_USER_STATUS)
132156
->whereIn('id', $chunk)
133157
->pluck('id')->toArray();
134-
$assignedUsers = array_merge($assignedUsers,$activeUsers);
158+
$assignedUsers = array_merge($assignedUsers, $activeUsers);
135159
}
136160

137161
return $assignedUsers;

ProcessMaker/Jobs/ErrorHandling.php

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -211,10 +211,59 @@ public function setDefaultsFromDataSourceConfig(array $config)
211211
public static function convertResponseToException($result)
212212
{
213213
if ($result['status'] === 'error') {
214-
if (str_starts_with($result['message'], 'Command exceeded timeout of')) {
215-
throw new ScriptTimeoutException($result['message']);
214+
$rawMessage = $result['message'] ?? '';
215+
if (str_starts_with((string) $rawMessage, 'Command exceeded timeout of')) {
216+
throw new ScriptTimeoutException((string) $rawMessage);
217+
}
218+
219+
$message = self::extractScriptErrorMessage($result);
220+
221+
if (empty($message)) {
222+
$message = $rawMessage ?: 'Script execution failed with unknown error';
223+
}
224+
225+
throw new ScriptException($message);
226+
}
227+
}
228+
229+
/**
230+
* Extract a concise error message from the microservice response.
231+
*/
232+
private static function extractScriptErrorMessage(array $result): string
233+
{
234+
$candidates = [
235+
$result['output']['error'] ?? null,
236+
$result['output']['exception'] ?? null,
237+
$result['output']['stderr'] ?? null,
238+
$result['output']['stdout'] ?? null,
239+
$result['message'] ?? null,
240+
];
241+
242+
foreach ($candidates as $candidate) {
243+
if (is_string($candidate) || is_numeric($candidate)) {
244+
$short = self::shortenMessage((string) $candidate);
245+
if (!empty($short)) {
246+
return $short;
247+
}
216248
}
217-
throw new ScriptException(json_encode($result, JSON_PRETTY_PRINT));
218249
}
250+
251+
return '';
252+
}
253+
254+
/**
255+
* Keep only the first line of the error and limit its length to avoid noisy traces.
256+
*/
257+
private static function shortenMessage(string $message): string
258+
{
259+
$firstLine = strtok($message, "\n");
260+
$firstLine = $firstLine === false ? $message : $firstLine;
261+
$trimmed = trim($firstLine);
262+
263+
if (strlen($trimmed) > 400) {
264+
return substr($trimmed, 0, 400) . '';
265+
}
266+
267+
return $trimmed;
219268
}
220269
}

ProcessMaker/Models/ProcessRequestToken.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -991,6 +991,60 @@ public function getAssignmentRule()
991991
return $assignment;
992992
}
993993

994+
/**
995+
* Get user IDs from process variables for task assignment.
996+
*
997+
* Extracts user IDs and group IDs from form data based on the activity's
998+
* assignedUsers and assignedGroups properties. Retrieves all users from
999+
* specified groups (including subgroups recursively) and combines them
1000+
* with directly assigned users.
1001+
*
1002+
* Used when assignment rule is 'process_variable'.
1003+
*
1004+
* @param array $form_data Form data containing process variable values.
1005+
* Keys must match activity's assignedUsers and
1006+
* assignedGroups properties. Values must be arrays.
1007+
*
1008+
* @return array Unique numeric user IDs (direct users + users from groups).
1009+
*/
1010+
public function getUsersFromProcessVariable(array $form_data)
1011+
{
1012+
$activity = $this->getBpmnDefinition()->getBpmnElementInstance();
1013+
$assignedUsers = $activity->getProperty('assignedUsers', null);
1014+
$assignedGroups = $activity->getProperty('assignedGroups', null);
1015+
1016+
$usersIds = [];
1017+
$groupsIds = [];
1018+
1019+
// Validate and get user IDs from form_data
1020+
if ($assignedUsers && isset($form_data[$assignedUsers]) && is_array($form_data[$assignedUsers])) {
1021+
$usersIds = $form_data[$assignedUsers];
1022+
}
1023+
1024+
// Validate and get group IDs from form_data
1025+
if ($assignedGroups && isset($form_data[$assignedGroups]) && is_array($form_data[$assignedGroups])) {
1026+
$groupsIds = $form_data[$assignedGroups];
1027+
}
1028+
1029+
// Get users from groups using the Process model method
1030+
$usersFromGroups = [];
1031+
if (!empty($groupsIds) && $this->process) {
1032+
// Use the getConsolidatedUsers method from the Process model
1033+
// This method gets users from groups including subgroups recursively
1034+
$this->process->getConsolidatedUsers($groupsIds, $usersFromGroups);
1035+
}
1036+
1037+
// Combine direct users with users from groups
1038+
$allUserIds = array_unique(array_merge($usersIds, $usersFromGroups));
1039+
1040+
// Convert to numeric array and filter valid values
1041+
$allUserIds = array_values(array_filter($allUserIds, function ($id) {
1042+
return !empty($id) && is_numeric($id) && $id > 0;
1043+
}));
1044+
1045+
return $allUserIds;
1046+
}
1047+
9941048
/**
9951049
* Get the assignees for the token.
9961050
*

ProcessMaker/Traits/TaskControllerIndexMethods.php

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ private function excludeNonVisibleTasks($query, $request)
155155
{
156156
$nonSystem = filter_var($request->input('non_system'), FILTER_VALIDATE_BOOLEAN);
157157
$allTasks = filter_var($request->input('all_tasks'), FILTER_VALIDATE_BOOLEAN);
158+
$hitlEnabled = filter_var(config('smart-extract.hitl_enabled'), FILTER_VALIDATE_BOOLEAN);
158159
$query->when(!$allTasks, function ($query) {
159160
$query->where(function ($query) {
160161
$query->where('element_type', '=', 'task');
@@ -164,8 +165,20 @@ private function excludeNonVisibleTasks($query, $request)
164165
});
165166
});
166167
})
167-
->when($nonSystem, function ($query) {
168-
$query->nonSystem();
168+
->when($nonSystem, function ($query) use ($hitlEnabled) {
169+
if (!$hitlEnabled) {
170+
$query->nonSystem();
171+
172+
return;
173+
}
174+
175+
$query->where(function ($query) {
176+
$query->nonSystem();
177+
$query->orWhere(function ($query) {
178+
$query->where('element_type', '=', 'task');
179+
$query->where('element_name', '=', 'Manual Document Review');
180+
});
181+
});
169182
});
170183
}
171184

composer.json

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "processmaker/processmaker",
3-
"version": "2026.1.2",
3+
"version": "2026.2.1",
44
"description": "BPM PHP Software",
55
"keywords": [
66
"php bpm processmaker"
@@ -152,33 +152,34 @@
152152
"package-ab-testing": "1.4.0",
153153
"package-actions-by-email": "1.22.9",
154154
"package-advanced-user-manager": "1.13.2",
155-
"package-ai": "1.16.11",
155+
"package-ai": "1.16.12",
156156
"package-analytics-reporting": "1.11.2",
157157
"package-auth": "1.24.11",
158158
"package-collections": "2.27.2",
159159
"package-comments": "1.16.2",
160160
"package-conversational-forms": "1.15.0",
161-
"package-data-sources": "1.34.3",
161+
"package-data-sources": "1.34.4",
162162
"package-decision-engine": "1.16.1",
163163
"package-dynamic-ui": "1.28.3",
164-
"package-email-start-event": "1.0.8",
164+
"package-email-start-event": "1.0.9",
165165
"package-files": "1.23.4",
166166
"package-googleplaces": "1.12.0",
167167
"package-photo-video": "1.6.1",
168168
"package-pm-blocks": "1.12.7",
169169
"package-process-documenter": "1.12.0",
170170
"package-process-optimization": "1.10.0",
171171
"package-product-analytics": "1.5.11",
172-
"package-projects": "1.12.6",
172+
"package-projects": "1.12.7",
173173
"package-rpa": "1.1.1",
174174
"package-savedsearch": "1.43.7",
175175
"package-slideshow": "1.4.3",
176-
"package-signature": "1.15.3",
176+
"package-smart-extract": "0.0.3",
177+
"package-signature": "1.15.2",
177178
"package-testing": "1.8.1",
178179
"package-translations": "2.14.5",
179180
"package-versions": "1.13.0",
180181
"package-vocabularies": "2.17.1",
181-
"package-webentry": "2.29.12",
182+
"package-webentry": "2.29.13",
182183
"package-api-testing": "1.3.1",
183184
"package-variable-finder": "1.0.5",
184185
"packages": "^0"

0 commit comments

Comments
 (0)