Skip to content

Commit c4ab139

Browse files
committed
Merge branch 'develop' into FOUR-28426
2 parents 6d022d3 + 0531f87 commit c4ab139

16 files changed

Lines changed: 2628 additions & 2874 deletions

File tree

.github/dependabot.yml

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# PM4 package: Laravel is provided by the host app; composer.json is only for this package's own PHP code/deps.
2+
# JS build: Vue 2 + Laravel Mix (manifest: package.json). Built assets under public/js/ are not scanned by Dependabot.
3+
#
4+
# Policy: NO routine version-update PRs (open-pull-requests-limit: 0).
5+
# Security/CVE PRs are handled by Dependabot security updates (org Settings → Code security).
6+
# Security PRs are batched into one PR per ecosystem (patch/minor).
7+
# Major security PRs will still open if no patch/minor fix exists — treat as manual review.
8+
#
9+
# Vue 2 pin: security fixes requiring Vue 3+ will be suppressed — accepted risk,
10+
# migration not planned. Same applies to vue-loader, vue-template-compiler, @vue/cli.
11+
#
12+
# Webpack pin: develop lockfile pins 5.91.0; Dependabot security PRs may bump to 5.107+.
13+
# 5.106.0 is the last release that still ships SizeFormatHelpers (Laravel Mix compat).
14+
# Block webpack >= 5.107 so batched security PRs keep other bumps without breaking the build.
15+
#
16+
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
17+
version: 2
18+
updates:
19+
- package-ecosystem: npm
20+
directory: /
21+
schedule:
22+
interval: weekly
23+
day: monday
24+
open-pull-requests-limit: 0
25+
ignore:
26+
# If you ever raise `open-pull-requests-limit`, this skips routine major bumps.
27+
# Note: update-types has no effect on security updates.
28+
- dependency-name: "*"
29+
update-types: ["version-update:semver-major"]
30+
- dependency-name: "vue"
31+
versions: [">=3.0.0"] # stay on Vue 2.x — suppresses security PRs requiring v3+ too
32+
- dependency-name: "@vue/cli*"
33+
versions: [">=5.0.0"] # CLI v5+ is Vue 3 era
34+
- dependency-name: "vue-loader"
35+
versions: [">=17.0.0"] # vue-loader v17+ drops Vue 2 support
36+
- dependency-name: "vue-template-compiler"
37+
versions: [">=3.0.0"] # must stay in sync with Vue 2.x
38+
- dependency-name: "webpack"
39+
versions: [">=5.107.0"] # 5.106.0 last with SizeFormatHelpers; block 5.107+ security bumps
40+
groups:
41+
npm-security:
42+
applies-to: security-updates # batches all JS security PRs into one
43+
patterns: # note: update-types has no effect here for security
44+
- "*"
45+
# version suppressions (vue, webpack, etc.) live in top-level `ignore` above — groups do not support `ignore`
46+
47+
- package-ecosystem: composer
48+
directory: /
49+
schedule:
50+
interval: weekly
51+
day: monday
52+
open-pull-requests-limit: 0
53+
ignore:
54+
# If you ever raise `open-pull-requests-limit`, this skips routine major bumps.
55+
# Note: update-types has no effect on security updates.
56+
- dependency-name: "*"
57+
update-types: ["version-update:semver-major"]
58+
groups:
59+
composer-security:
60+
applies-to: security-updates # batches all PHP security PRs into one
61+
patterns:
62+
- "*"
63+

.github/workflows/nightly.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Nightly
1+
name: Nightly Vulnerability Scan
22

33
on:
44
schedule:

ProcessMaker/Http/Controllers/HomeController.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,16 @@ public function redirectToIntended()
6060
return redirect($url)->withCookie(\Cookie::forget('processmaker_intended'));
6161
}
6262

63+
// No intended URL. Honor the tenant's package-dynamic-ui home page
64+
// if it's installed, so admins'configured landing pages are still
65+
// respected, before falling back to /requests.
66+
if (Auth::check() && class_exists(\ProcessMaker\Package\PackageDynamicUI\Models\DynamicUI::class)) {
67+
$homePage = \ProcessMaker\Package\PackageDynamicUI\Models\DynamicUI::getHomePage(Auth::user());
68+
if (!empty($homePage)) {
69+
return redirect($homePage);
70+
}
71+
}
72+
6373
return redirect()->route('requests.index');
6474
}
6575
}

ProcessMaker/Jobs/RunServiceTask.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ class RunServiceTask extends BpmnAction implements ShouldQueue
3636
/**
3737
* Create a new job instance.
3838
*
39-
* @param \ProcessMaker\Models\Process $definitions
40-
* @param \ProcessMaker\Models\ProcessRequest $instance
41-
* @param \ProcessMaker\Models\ProcessRequestToken $token
39+
* @param Definitions $definitions
40+
* @param ProcessRequest $instance
41+
* @param ProcessRequestToken $token
4242
* @param array $data
4343
*/
4444
public function __construct(Definitions $definitions, ProcessRequest $instance, ProcessRequestToken $token, array $data, $attemptNum = 1)
@@ -136,7 +136,7 @@ public function action(ProcessRequestToken $token = null, ServiceTaskInterface $
136136
$token->logError($modifiedException, $element);
137137

138138
Log::error('Service task failed: ' . $implementation . ' - ' . $message);
139-
Log::error($exception->getTraceAsString());
139+
Log::debug($exception->getTraceAsString());
140140
}
141141
}
142142

ProcessMaker/Managers/TaskSchedulerManager.php

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@
1616
use PDOException;
1717
use ProcessMaker\Facades\WorkflowManager;
1818
use ProcessMaker\Jobs\StartEventConditional;
19+
use ProcessMaker\Models\EnvironmentVariable;
1920
use ProcessMaker\Models\Process;
2021
use ProcessMaker\Models\ProcessRequest;
2122
use ProcessMaker\Models\ProcessRequestLock;
2223
use ProcessMaker\Models\ScheduledTask;
24+
use ProcessMaker\Models\Setting;
2325
use ProcessMaker\Models\TimerExpression;
2426
use ProcessMaker\Nayra\Bpmn\Models\BoundaryEvent;
2527
use ProcessMaker\Nayra\Bpmn\Models\DatePeriod;
@@ -398,13 +400,211 @@ public function executeTimerStartEvent(ScheduledTask $task, $config)
398400
if (!$definitions->findElementById($config->element_id)) {
399401
return;
400402
}
403+
404+
if ($this->shouldSkipHandleRepliesTimerStart($process)) {
405+
Log::info('Skipping Actions By Email Handle Replies timer event because ABE inbound mail configuration is not adequate', [
406+
'process_id' => $process->id,
407+
'process_name' => $process->name,
408+
]);
409+
410+
return;
411+
}
412+
401413
$event = $definitions->getEvent($config->element_id);
402414
$data = [];
403415

404416
//Trigger the start event
405417
$processRequest = WorkflowManager::triggerStartEvent($process, $event, $data);
406418
}
407419

420+
/**
421+
* Determine if the timer should be skipped for the Actions By Email handle replies process.
422+
*
423+
* @param Process $process
424+
* @return bool
425+
*/
426+
private function shouldSkipHandleRepliesTimerStart(Process $process): bool
427+
{
428+
if (!$this->isHandleRepliesProcess($process)) {
429+
return false;
430+
}
431+
432+
return !$this->hasAdequateAbeInboundConfiguration();
433+
}
434+
435+
/**
436+
* Whether Actions By Email has enough configuration to poll inbound mail (IMAP or OAuth).
437+
*
438+
* This is a heuristic in core: the connector may add more keys; we avoid starting the
439+
* Handle Replies timer when the mailbox clearly is not set up (FOUR-30587).
440+
*
441+
* @return bool
442+
*/
443+
private function hasAdequateAbeInboundConfiguration(): bool
444+
{
445+
if (!Setting::readyToUseSettingsDatabase()) {
446+
return false;
447+
}
448+
449+
$authMethodIndex = (int) ($this->getAbeInboundSettingValue(['abe_imap_auth_method']) ?? 0);
450+
$username = $this->getAbeInboundSettingValue(['abe_imap_username', 'email_connector_mail_username']);
451+
452+
if (!$this->hasValue($username)) {
453+
return false;
454+
}
455+
456+
if ($authMethodIndex === 1) {
457+
return $this->hasEnvironmentVariables([
458+
'ABE_GMAIL_API_CLIENT_ID',
459+
'ABE_GMAIL_API_SECRET',
460+
'ABE_GMAIL_API_ACCESS_TOKEN',
461+
'ABE_GMAIL_API_REFRESH_TOKEN',
462+
]);
463+
}
464+
465+
if ($authMethodIndex === 2) {
466+
return $this->hasEnvironmentVariables([
467+
'ABE_OFFICE_365_CLIENT_ID',
468+
'ABE_OFFICE_365_TENANT_ID',
469+
'ABE_OFFICE_365_SECRET',
470+
'ABE_OFFICE_365_ACCESS_TOKEN',
471+
'ABE_OFFICE_365_REFRESH_TOKEN',
472+
'ABE_OFFICE_365_ACCESS_TOKEN_EXPIRE_DATE',
473+
]);
474+
}
475+
476+
return $this->hasStandardAbeInboundConfiguration();
477+
}
478+
479+
/**
480+
* IMAP configuration for standard authentication mode.
481+
*/
482+
private function hasStandardAbeInboundConfiguration(): bool
483+
{
484+
$password = $this->getAbeInboundSettingValue(['abe_imap_password', 'email_connector_mail_password']);
485+
if (!$this->hasValue($password)) {
486+
return false;
487+
}
488+
489+
$inboxUri = $this->getAbeInboundSettingValue(['abe_imap_inbox_uri']);
490+
if ($this->hasValue($inboxUri)) {
491+
return true;
492+
}
493+
494+
$server = $this->getAbeInboundSettingValue(['abe_imap_server', 'email_connector_mail_host']);
495+
$port = $this->getAbeInboundSettingValue(['abe_imap_port', 'email_connector_mail_port']);
496+
497+
return $this->hasValue($server) && $this->hasValue($port);
498+
}
499+
500+
/**
501+
* Reads the first non-empty value from supported Actions By Email / mail settings keys.
502+
*
503+
* @param array $keys
504+
* @return string|null
505+
*/
506+
private function getAbeInboundSettingValue(array $keys): ?string
507+
{
508+
$settings = Setting::query()
509+
->whereIn('key', $keys)
510+
->get()
511+
->keyBy('key');
512+
513+
if ($settings->isEmpty()) {
514+
return null;
515+
}
516+
517+
foreach ($keys as $key) {
518+
$setting = $settings->get($key);
519+
if (!$setting) {
520+
continue;
521+
}
522+
523+
$value = $this->extractSettingValue($setting->config);
524+
if ($this->hasValue($value)) {
525+
return (string) $value;
526+
}
527+
}
528+
529+
return null;
530+
}
531+
532+
/**
533+
* Normalize setting values from different possible setting formats.
534+
*
535+
* @param mixed $value
536+
* @return mixed
537+
*/
538+
private function extractSettingValue($value)
539+
{
540+
if (is_object($value)) {
541+
$value = (array) $value;
542+
}
543+
544+
if (is_array($value)) {
545+
if (array_key_exists('value', $value)) {
546+
return $value['value'];
547+
}
548+
549+
return $value;
550+
}
551+
552+
return $value;
553+
}
554+
555+
/**
556+
* Check if a setting value is present and not empty.
557+
*
558+
* @param mixed $value
559+
* @return bool
560+
*/
561+
private function hasValue($value): bool
562+
{
563+
if (is_array($value)) {
564+
foreach ($value as $item) {
565+
if ($this->hasValue($item)) {
566+
return true;
567+
}
568+
}
569+
570+
return false;
571+
}
572+
573+
return is_scalar($value) && trim((string) $value) !== '';
574+
}
575+
576+
/**
577+
* Identify the handle replies process by name.
578+
*
579+
* @param Process $process
580+
* @return bool
581+
*/
582+
private function isHandleRepliesProcess(Process $process): bool
583+
{
584+
if ((string) $process->package_key === 'package-actions-by-email/handle-replies') {
585+
return true;
586+
}
587+
588+
$name = (string) $process->name;
589+
590+
return stripos($name, 'actions by email') !== false && stripos($name, 'handle replies') !== false;
591+
}
592+
593+
private function hasEnvironmentVariables(array $names): bool
594+
{
595+
$values = EnvironmentVariable::query()
596+
->whereIn('name', $names)
597+
->pluck('value', 'name');
598+
599+
foreach ($names as $name) {
600+
if (!isset($values[$name]) || trim((string) $values[$name]) === '') {
601+
return false;
602+
}
603+
}
604+
605+
return true;
606+
}
607+
408608
/**
409609
* Execute a timer start event
410610
*

ProcessMaker/Models/ProcessRequest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -586,7 +586,8 @@ public function logError(Throwable $exception, FlowElementInterface $element = n
586586
$errors[] = $error;
587587
$this->errors = $errors;
588588
$this->status = 'ERROR';
589-
Log::error($exception);
589+
Log::error($exception->getMessage());
590+
Log::debug($exception->getTraceAsString());
590591
if (!$this->isNonPersistent()) {
591592
$this->save();
592593
// Update Case status

ProcessMaker/ScriptRunners/Base.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,12 @@ private function getEnvironmentVariables($useEscape = true)
188188
// Add the url to the host
189189
if ($useEscape) {
190190
$variablesParameter[] = 'HOST_URL=' . escapeshellarg(config('app.docker_host_url'));
191+
$variablesParameter[] = 'SMART_EXTRACT_API_HOST=' . escapeshellarg(config('smart-extract.api_host'));
192+
$variablesParameter[] = 'SMART_EXTRACT_REQUEST_TIMEOUT=' . escapeshellarg((string) config('smart-extract.request_timeout'));
191193
} else {
192194
$variablesParameter[] = 'HOST_URL=' . config('app.docker_host_url');
195+
$variablesParameter[] = 'SMART_EXTRACT_API_HOST=' . config('smart-extract.api_host');
196+
$variablesParameter[] = 'SMART_EXTRACT_REQUEST_TIMEOUT=' . config('smart-extract.request_timeout');
193197
}
194198

195199
return $variablesParameter;

0 commit comments

Comments
 (0)