Skip to content
Open
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
63 changes: 63 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# PM4 package: Laravel is provided by the host app; composer.json is only for this package's own PHP code/deps.
# JS build: Vue 2 + Laravel Mix (manifest: package.json). Built assets under public/js/ are not scanned by Dependabot.
#
# Policy: NO routine version-update PRs (open-pull-requests-limit: 0).
# Security/CVE PRs are handled by Dependabot security updates (org Settings → Code security).
# Security PRs are batched into one PR per ecosystem (patch/minor).
# Major security PRs will still open if no patch/minor fix exists — treat as manual review.
#
# Vue 2 pin: security fixes requiring Vue 3+ will be suppressed — accepted risk,
# migration not planned. Same applies to vue-loader, vue-template-compiler, @vue/cli.
#
# Webpack pin: develop lockfile pins 5.91.0; Dependabot security PRs may bump to 5.107+.
# 5.106.0 is the last release that still ships SizeFormatHelpers (Laravel Mix compat).
# Block webpack >= 5.107 so batched security PRs keep other bumps without breaking the build.
#
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
day: monday
open-pull-requests-limit: 0
ignore:
# If you ever raise `open-pull-requests-limit`, this skips routine major bumps.
# Note: update-types has no effect on security updates.
- dependency-name: "*"
update-types: ["version-update:semver-major"]
- dependency-name: "vue"
versions: [">=3.0.0"] # stay on Vue 2.x — suppresses security PRs requiring v3+ too
- dependency-name: "@vue/cli*"
versions: [">=5.0.0"] # CLI v5+ is Vue 3 era
- dependency-name: "vue-loader"
versions: [">=17.0.0"] # vue-loader v17+ drops Vue 2 support
- dependency-name: "vue-template-compiler"
versions: [">=3.0.0"] # must stay in sync with Vue 2.x
- dependency-name: "webpack"
versions: [">=5.107.0"] # 5.106.0 last with SizeFormatHelpers; block 5.107+ security bumps
groups:
npm-security:
applies-to: security-updates # batches all JS security PRs into one
patterns: # note: update-types has no effect here for security
- "*"
# version suppressions (vue, webpack, etc.) live in top-level `ignore` above — groups do not support `ignore`

- package-ecosystem: composer
directory: /
schedule:
interval: weekly
day: monday
open-pull-requests-limit: 0
ignore:
# If you ever raise `open-pull-requests-limit`, this skips routine major bumps.
# Note: update-types has no effect on security updates.
- dependency-name: "*"
update-types: ["version-update:semver-major"]
groups:
composer-security:
applies-to: security-updates # batches all PHP security PRs into one
patterns:
- "*"

200 changes: 200 additions & 0 deletions ProcessMaker/Managers/TaskSchedulerManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@
use PDOException;
use ProcessMaker\Facades\WorkflowManager;
use ProcessMaker\Jobs\StartEventConditional;
use ProcessMaker\Models\EnvironmentVariable;
use ProcessMaker\Models\Process;
use ProcessMaker\Models\ProcessRequest;
use ProcessMaker\Models\ProcessRequestLock;
use ProcessMaker\Models\ScheduledTask;
use ProcessMaker\Models\Setting;
use ProcessMaker\Models\TimerExpression;
use ProcessMaker\Nayra\Bpmn\Models\BoundaryEvent;
use ProcessMaker\Nayra\Bpmn\Models\DatePeriod;
Expand Down Expand Up @@ -398,13 +400,211 @@ public function executeTimerStartEvent(ScheduledTask $task, $config)
if (!$definitions->findElementById($config->element_id)) {
return;
}

if ($this->shouldSkipHandleRepliesTimerStart($process)) {
Log::info('Skipping Actions By Email Handle Replies timer event because ABE inbound mail configuration is not adequate', [
'process_id' => $process->id,
'process_name' => $process->name,
]);

return;
}

$event = $definitions->getEvent($config->element_id);
$data = [];

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

/**
* Determine if the timer should be skipped for the Actions By Email handle replies process.
*
* @param Process $process
* @return bool
*/
private function shouldSkipHandleRepliesTimerStart(Process $process): bool
{
if (!$this->isHandleRepliesProcess($process)) {
return false;
}

return !$this->hasAdequateAbeInboundConfiguration();
}

/**
* Whether Actions By Email has enough configuration to poll inbound mail (IMAP or OAuth).
*
* This is a heuristic in core: the connector may add more keys; we avoid starting the
* Handle Replies timer when the mailbox clearly is not set up (FOUR-30587).
*
* @return bool
*/
private function hasAdequateAbeInboundConfiguration(): bool
{
if (!Setting::readyToUseSettingsDatabase()) {
return false;
}

$authMethodIndex = (int) ($this->getAbeInboundSettingValue(['abe_imap_auth_method']) ?? 0);
$username = $this->getAbeInboundSettingValue(['abe_imap_username', 'email_connector_mail_username']);

if (!$this->hasValue($username)) {
return false;
}

if ($authMethodIndex === 1) {
return $this->hasEnvironmentVariables([
'ABE_GMAIL_API_CLIENT_ID',
'ABE_GMAIL_API_SECRET',
'ABE_GMAIL_API_ACCESS_TOKEN',
'ABE_GMAIL_API_REFRESH_TOKEN',
]);
}

if ($authMethodIndex === 2) {
return $this->hasEnvironmentVariables([
'ABE_OFFICE_365_CLIENT_ID',
'ABE_OFFICE_365_TENANT_ID',
'ABE_OFFICE_365_SECRET',
'ABE_OFFICE_365_ACCESS_TOKEN',
'ABE_OFFICE_365_REFRESH_TOKEN',
'ABE_OFFICE_365_ACCESS_TOKEN_EXPIRE_DATE',
]);
}

return $this->hasStandardAbeInboundConfiguration();
}

/**
* IMAP configuration for standard authentication mode.
*/
private function hasStandardAbeInboundConfiguration(): bool
{
$password = $this->getAbeInboundSettingValue(['abe_imap_password', 'email_connector_mail_password']);
if (!$this->hasValue($password)) {
return false;
}

$inboxUri = $this->getAbeInboundSettingValue(['abe_imap_inbox_uri']);
if ($this->hasValue($inboxUri)) {
return true;
}

$server = $this->getAbeInboundSettingValue(['abe_imap_server', 'email_connector_mail_host']);
$port = $this->getAbeInboundSettingValue(['abe_imap_port', 'email_connector_mail_port']);

return $this->hasValue($server) && $this->hasValue($port);
}

/**
* Reads the first non-empty value from supported Actions By Email / mail settings keys.
*
* @param array $keys
* @return string|null
*/
private function getAbeInboundSettingValue(array $keys): ?string
{
$settings = Setting::query()
->whereIn('key', $keys)
->get()
->keyBy('key');

if ($settings->isEmpty()) {
return null;
}

foreach ($keys as $key) {
$setting = $settings->get($key);
if (!$setting) {
continue;
}

$value = $this->extractSettingValue($setting->config);
if ($this->hasValue($value)) {
return (string) $value;
}
}

return null;
}

/**
* Normalize setting values from different possible setting formats.
*
* @param mixed $value
* @return mixed
*/
private function extractSettingValue($value)
{
if (is_object($value)) {
$value = (array) $value;
}

if (is_array($value)) {
if (array_key_exists('value', $value)) {
return $value['value'];
}

return $value;
}

return $value;
}

/**
* Check if a setting value is present and not empty.
*
* @param mixed $value
* @return bool
*/
private function hasValue($value): bool
{
if (is_array($value)) {
foreach ($value as $item) {
if ($this->hasValue($item)) {
return true;
}
}

return false;
}

return is_scalar($value) && trim((string) $value) !== '';
}

/**
* Identify the handle replies process by name.
*
* @param Process $process
* @return bool
*/
private function isHandleRepliesProcess(Process $process): bool
{
if ((string) $process->package_key === 'package-actions-by-email/handle-replies') {
return true;
}

$name = (string) $process->name;

return stripos($name, 'actions by email') !== false && stripos($name, 'handle replies') !== false;
}

private function hasEnvironmentVariables(array $names): bool
{
$values = EnvironmentVariable::query()
->whereIn('name', $names)
->pluck('value', 'name');

foreach ($names as $name) {
if (!isset($values[$name]) || trim((string) $values[$name]) === '') {
return false;
}
}

return true;
}

/**
* Execute a timer start event
*
Expand Down
12 changes: 6 additions & 6 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "processmaker/processmaker",
"version": "2026.10.1",
"version": "2026.10.2",
"description": "BPM PHP Software",
"keywords": [
"php bpm processmaker"
Expand All @@ -25,7 +25,7 @@
"igaster/laravel-theme": "^2.0",
"jenssegers/agent": "^2.6",
"laravel/framework": "^13.13",
"laravel/horizon": "^5.45",
"laravel/horizon": "^5.47",
"laravel/pail": "^1.2",
"laravel/passport": "^13.7",
"laravel/scout": "^11.1",
Expand Down Expand Up @@ -111,7 +111,7 @@
"Gmail"
],
"processmaker": {
"build": "444708ef",
"build": "dc7c7e21",
"cicd-enabled": true,
"custom": {
"package-ellucian-ethos": "1.19.10",
Expand Down Expand Up @@ -153,9 +153,9 @@
"connector-slack": "1.9.6",
"docker-executor-node-ssr": "1.7.4",
"package-ab-testing": "1.4.2",
"package-actions-by-email": "1.22.14",
"package-actions-by-email": "1.22.15",
"package-advanced-user-manager": "1.13.3",
"package-ai": "1.16.19",
"package-ai": "1.16.20",
"package-analytics-reporting": "1.11.5",
"package-auth": "1.24.15",
"package-collections": "2.27.6",
Expand Down Expand Up @@ -248,4 +248,4 @@
"ignore": []
}
}
}
}
14 changes: 7 additions & 7 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions config/l5-swagger.php
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,9 @@
'description' => 'Laravel passport oauth2 security.',
'flows' => [
'authorizationCode' => [
'authorizationUrl' => config('app.url') . '/oauth/authorize',
'tokenUrl' => config('app.url') . '/oauth/token',
'refreshUrl' => config('app.url') . '/token/refresh',
'authorizationUrl' => '/oauth/authorize',
'tokenUrl' => '/oauth/token',
'refreshUrl' => '/token/refresh',
'scopes' => (object) [],
],
],
Expand Down
Loading
Loading