Skip to content

Commit b37e2fe

Browse files
committed
Merge branch 'feature/FOUR-29866' into automated-performance-tests
2 parents 2eba029 + ebd7bc6 commit b37e2fe

9 files changed

Lines changed: 492 additions & 29 deletions

File tree

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace ProcessMaker\Console\Commands;
6+
7+
use Illuminate\Console\Command;
8+
use ProcessMaker\Models\User;
9+
10+
class GenerateAccessToken extends Command
11+
{
12+
protected $signature = 'processmaker:generate-access-token {username : The username of the user}';
13+
14+
protected $description = 'Generate an API access token for a user';
15+
16+
public function handle(): int
17+
{
18+
$user = User::where('username', $this->argument('username'))->first();
19+
20+
if (!$user) {
21+
return 1;
22+
}
23+
24+
$tokenResult = $user->createToken('api-user-token');
25+
$this->line($tokenResult->accessToken);
26+
27+
return 0;
28+
}
29+
}

ProcessMaker/Models/ProcessRequestToken.php

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
use ProcessMaker\Events\ActivityAssigned;
1818
use ProcessMaker\Events\ActivityReassignment;
1919
use ProcessMaker\Facades\WorkflowUserManager;
20+
use ProcessMaker\Managers\DataManager;
21+
use ProcessMaker\Models\MustacheExpressionEvaluator;
2022
use ProcessMaker\Nayra\Bpmn\TokenTrait;
2123
use ProcessMaker\Nayra\Contracts\Bpmn\ActivityInterface;
2224
use ProcessMaker\Nayra\Contracts\Bpmn\FlowElementInterface;
@@ -1440,6 +1442,59 @@ public function reassign($toUserId, User $requestingUser, $comments = '')
14401442
}
14411443
}
14421444

1445+
/**
1446+
* Build context for Mustache (end event external URL). Same as scripts/screens: _user, _request, process data, APP_URL.
1447+
*/
1448+
private function getElementDestinationMustacheContext(): array
1449+
{
1450+
try {
1451+
$context = (new DataManager())->getData($this);
1452+
} catch (Throwable $e) {
1453+
Log::warning('Failed to load Mustache context via DataManager, falling back to request data', [
1454+
'token_id' => $this->id,
1455+
'error' => $e->getMessage(),
1456+
]);
1457+
$request = $this->processRequest;
1458+
$context = array_merge($request->data ?? [], $request ? (new DataManager())->updateRequestMagicVariable([], $request) : []);
1459+
$user = $this->user ?? auth()->user();
1460+
if ($user) {
1461+
$userData = $user->attributesToArray();
1462+
unset($userData['remember_token']);
1463+
$context['_user'] = $userData;
1464+
}
1465+
}
1466+
1467+
$context['APP_URL'] = config('app.url');
1468+
1469+
// Never expose remember_token to Mustache (defense in depth; DataManager/fallback may already strip it)
1470+
if (isset($context['_user']) && is_array($context['_user'])) {
1471+
unset($context['_user']['remember_token']);
1472+
}
1473+
1474+
// Normalize to plain arrays/scalars so Mustache resolves all keys (common PHP idiom)
1475+
$json = json_encode($context, JSON_THROW_ON_ERROR);
1476+
$normalized = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
1477+
1478+
return is_array($normalized) ? $normalized : [];
1479+
}
1480+
1481+
/**
1482+
* Resolve Mustache in end event external URL. FEEL is not supported here; use Mustache only.
1483+
* Context: APP_URL, _request, _user, process variables (same as getElementDestinationMustacheContext).
1484+
*
1485+
* Example (Mustache):
1486+
* {{APP_URL}}/admin/users/{{_request.id}}/edit -> https://example.com/admin/users/123/edit
1487+
* {{APP_URL}}/webentry/{{_request.id}} -> https://example.com/webentry/123
1488+
* {{APP_URL}}/path/{{my_process_var}} -> uses process variable my_process_var
1489+
*/
1490+
private function resolveElementDestinationUrl(string $url): string
1491+
{
1492+
$url = html_entity_decode($url, ENT_QUOTES | ENT_HTML401, 'UTF-8');
1493+
$context = $this->getElementDestinationMustacheContext();
1494+
1495+
return (new MustacheExpressionEvaluator())->render($url, $context);
1496+
}
1497+
14431498
/**
14441499
* Determines the destination based on the type of element destination property
14451500
*
@@ -1481,6 +1536,11 @@ private function getElementDestination($elementDestinationType, $elementDestinat
14811536
$elementDestination = $elementDestinationProp['value']['url'] ?? null;
14821537
}
14831538
}
1539+
if (is_string($elementDestination) && $elementDestination !== '') {
1540+
if ($elementDestinationType === 'externalURL' || $elementDestinationType === 'customDashboard') {
1541+
$elementDestination = $this->resolveElementDestinationUrl($elementDestination);
1542+
}
1543+
}
14841544
break;
14851545
case 'taskList':
14861546
$elementDestination = route('tasks.index');

ProcessMaker/RollbackProcessRequest.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ public function rollback(
127127
break;
128128
}
129129

130+
// update the process request status to active
130131
$processRequest = $this->newTask->processRequest;
131132
$processRequest->status = 'ACTIVE';
132133
$process = $processRequest->process;
@@ -135,9 +136,12 @@ public function rollback(
135136
)->id;
136137
$processRequest->saveOrFail();
137138

139+
// update the current task status to closed
138140
$currentTask->status = 'CLOSED';
139141
$currentTask->saveOrFail();
140142

143+
$this->syncParentProcessStatus($processRequest);
144+
141145
return $this->newTask;
142146
}
143147

@@ -209,4 +213,25 @@ private function addComment() : void
209213
'case_number' => isset($this->currentTask->case_number) ? $this->currentTask->case_number : null,
210214
]);
211215
}
216+
217+
/**
218+
* When the rolled-back request is a subprocess, reactivate the parent request
219+
* and the parent's call activity tokens that were in error.
220+
*/
221+
private function syncParentProcessStatus(ProcessRequest $processRequest): void
222+
{
223+
$parentRequest = $processRequest->parentRequest;
224+
if (!$parentRequest) {
225+
return;
226+
}
227+
228+
if (in_array($parentRequest->status, ['ERROR', 'FAILING'])) {
229+
$parentRequest->status = 'ACTIVE';
230+
$parentRequest->saveOrFail();
231+
}
232+
233+
ProcessRequestToken::where('subprocess_request_id', $processRequest->id)
234+
->whereIn('status', ['ERROR', 'FAILING'])
235+
->update(['status' => 'ACTIVE']);
236+
}
212237
}

ProcessMaker/Services/ConditionalRedirectService.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ public function __construct()
103103
public function resolve(array $conditionalRedirect, array $data): ?array
104104
{
105105
$this->errors = [];
106+
$data = $this->normalizeDataForFeel($data);
107+
106108
foreach ($conditionalRedirect as $item) {
107109
if (!isset($item['condition'])) {
108110
throw new InvalidArgumentException('Condition is required');
@@ -125,6 +127,29 @@ public function resolve(array $conditionalRedirect, array $data): ?array
125127
return null;
126128
}
127129

130+
/**
131+
* Normalize data so FEEL comparisons like form_input_1==0 work when form sends string "0".
132+
* Converts numeric strings to int/float so that the first matching condition is the intended one.
133+
*
134+
* @param array $data
135+
* @return array
136+
*/
137+
private function normalizeDataForFeel(array $data): array
138+
{
139+
$normalized = [];
140+
foreach ($data as $key => $value) {
141+
if (is_array($value)) {
142+
$normalized[$key] = $this->normalizeDataForFeel($value);
143+
} elseif (is_string($value) && is_numeric($value)) {
144+
$normalized[$key] = str_contains($value, '.') ? (float) $value : (int) $value;
145+
} else {
146+
$normalized[$key] = $value;
147+
}
148+
}
149+
150+
return $normalized;
151+
}
152+
128153
/**
129154
* Process conditional redirects for a specific process request token
130155
*

composer.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "processmaker/processmaker",
3-
"version": "2026.4.5",
3+
"version": "2026.5.1",
44
"description": "BPM PHP Software",
55
"keywords": [
66
"php bpm processmaker"
@@ -108,7 +108,7 @@
108108
"Gmail"
109109
],
110110
"processmaker": {
111-
"build": "d3bb8c4e",
111+
"build": "2cdff483",
112112
"cicd-enabled": true,
113113
"custom": {
114114
"package-ellucian-ethos": "1.19.10",
@@ -159,14 +159,14 @@
159159
"package-collections": "2.27.3",
160160
"package-comments": "1.16.2",
161161
"package-conversational-forms": "1.15.0",
162-
"package-data-sources": "1.34.6",
162+
"package-data-sources": "1.34.7",
163163
"package-decision-engine": "1.16.1",
164164
"package-dynamic-ui": "1.28.3",
165165
"package-email-start-event": "1.0.9",
166166
"package-files": "1.23.5",
167167
"package-googleplaces": "1.12.0",
168168
"package-photo-video": "1.6.1",
169-
"package-pm-blocks": "1.12.7",
169+
"package-pm-blocks": "1.12.8",
170170
"package-process-documenter": "1.12.0",
171171
"package-process-optimization": "1.10.0",
172172
"package-product-analytics": "1.5.11",
@@ -180,7 +180,7 @@
180180
"package-translations": "2.14.5",
181181
"package-versions": "1.13.0",
182182
"package-vocabularies": "2.17.1",
183-
"package-webentry": "2.29.14",
183+
"package-webentry": "2.29.15",
184184
"package-api-testing": "1.3.1",
185185
"package-variable-finder": "1.0.5",
186186
"packages": "^0"

package-lock.json

Lines changed: 18 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@processmaker/processmaker",
3-
"version": "2026.4.5",
3+
"version": "2026.5.1",
44
"description": "ProcessMaker 4",
55
"author": "DevOps <devops@processmaker.com>",
66
"license": "ISC",
@@ -61,9 +61,9 @@
6161
"@fortawesome/free-solid-svg-icons": "^5.15.1",
6262
"@fortawesome/vue-fontawesome": "^0.1.9",
6363
"@panter/vue-i18next": "^0.15.2",
64-
"@processmaker/modeler": "1.69.31",
64+
"@processmaker/modeler": "1.69.33",
6565
"@processmaker/processmaker-bpmn-moddle": "0.16.0",
66-
"@processmaker/screen-builder": "3.8.26",
66+
"@processmaker/screen-builder": "3.8.27",
6767
"@processmaker/vue-form-elements": "0.65.6",
6868
"@processmaker/vue-multiselect": "2.3.0",
6969
"@tinymce/tinymce-vue": "2.0.0",

resources/js/requests/components/RequestsListing.vue

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,12 @@ export default {
353353
openRequest(data, index) {
354354
return `/requests/${data.id}`;
355355
},
356+
openCase(data) {
357+
if (data && data.case_number) {
358+
return `/cases/${data.case_number}`;
359+
}
360+
return this.openRequest(data, 1);
361+
},
356362
openTask(task) {
357363
return `/tasks/${task.id}/edit`;
358364
},
@@ -406,14 +412,14 @@ export default {
406412
},
407413
formatCaseNumber(value) {
408414
return `
409-
<a href="${this.openRequest(value, 1)}"
415+
<a href="${this.openCase(value)}"
410416
class="text-nowrap">
411417
# ${value.case_number}
412418
</a>`;
413419
},
414420
formatCaseTitle(value) {
415421
return `
416-
<a href="${this.openRequest(value, 1)}"
422+
<a href="${this.openCase(value)}"
417423
class="text-nowrap">
418424
${value.case_title_formatted || value.case_title || value.name}
419425
</a>`;
@@ -457,8 +463,8 @@ export default {
457463
data.data = this.jsonRows(data.data);
458464
for (let record of data.data) {
459465
//format Status
460-
record["case_number"] = this.formatCaseNumber(record);
461466
record["case_title"] = this.formatCaseTitle(record);
467+
record["case_number"] = this.formatCaseNumber(record);
462468
if (record["active_tasks"]) {
463469
record["active_tasks"] = this.formatActiveTasks(record["active_tasks"]);
464470
}

0 commit comments

Comments
 (0)