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
155 changes: 155 additions & 0 deletions ProcessMaker/Managers/TaskSchedulerManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use ProcessMaker\Models\ProcessRequestLock;
use ProcessMaker\Models\ScheduledTask;
use ProcessMaker\Models\TimerExpression;
use ProcessMaker\Models\Setting;
use ProcessMaker\Nayra\Bpmn\Models\BoundaryEvent;
use ProcessMaker\Nayra\Bpmn\Models\DatePeriod;
use ProcessMaker\Nayra\Bpmn\Models\IntermediateCatchEvent;
Expand Down Expand Up @@ -398,13 +399,167 @@ 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;
}

$host = $this->getAbeInboundSettingValue(['abe_imap_host', 'email_connector_mail_host']);
$username = $this->getAbeInboundSettingValue(['abe_imap_username', 'email_connector_mail_username']);

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

return $this->hasAnyAbeInboundCredential();
}

/**
* Standard password or OAuth-style tokens stored under abe_imap_* settings.
*/
private function hasAnyAbeInboundCredential(): bool
{
if ($this->hasValue($this->getAbeInboundSettingValue(['abe_imap_password', 'email_connector_mail_password']))) {
return true;
}

return Setting::query()
->whereRaw('LOWER(`key`) LIKE ?', ['abe_imap%'])
->where(function ($query) {
$query->whereRaw('LOWER(`key`) LIKE ?', ['%access_token%'])
->orWhereRaw('LOWER(`key`) LIKE ?', ['%refresh_token%']);
})
->get()
->contains(function ($setting) {
return $this->hasValue($this->extractSettingValue($setting->config));
});
}

/**
* Reads the first non-empty value from supported Actions By Email / mail settings keys.
*
* @param array $prefixes
* @return string|null
*/
private function getAbeInboundSettingValue(array $prefixes): ?string
{
$settings = Setting::query()->where(function ($query) use ($prefixes) {
foreach ($prefixes as $prefix) {
$query->orWhereRaw('LOWER(`key`) LIKE ?', [strtolower($prefix) . '%']);
}
})->get();

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

foreach ($settings as $setting) {
$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
{
$name = (string) $process->name;

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

/**
* Execute a timer start event
*
Expand Down
21 changes: 21 additions & 0 deletions tests/Feature/Api/TimerStartEventTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -228,4 +228,25 @@ public function testScheduleMustNotStartTimerEventWhenProcessInactive()
$task->type = 'TIMER_START_EVENT';
$manager->executeTimerStartEvent($task, json_decode($task->configuration));
}

public function testScheduleMustNotStartHandleRepliesTimerWhenAbeInboundConfigInadequate()
{
// triggerStartEvent must not run when ABE inbound mail settings are missing/inadequate
WorkflowManager::shouldReceive('triggerStartEvent')
->never()
->with(\Mockery::any(), \Mockery::any(), \Mockery::any());

$data = [];
$data['name'] = 'Actions By Email - Handle Replies';
$data['bpmn'] = Process::getProcessTemplate('TimerStartEvent.bpmn');

$process = Process::factory()->create($data);

$manager = new TaskSchedulerManager();
$task = new ScheduledTask();
$task->process_id = $process->id;
$task->configuration = '{"type":"TimeCycle","interval":"R4\/2019-02-13T13:08:00Z\/PT1M", "element_id" : "_9"}';
$task->type = 'TIMER_START_EVENT';
$manager->executeTimerStartEvent($task, json_decode($task->configuration));
}
}
Loading