Skip to content

Commit e8b2db1

Browse files
committed
Merge branch 'develop' into bugfix/FOUR-27904
2 parents 050514a + 965d673 commit e8b2db1

17 files changed

Lines changed: 1425 additions & 89 deletions

File tree

ProcessMaker/Application.php

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -114,11 +114,4 @@ public function bootstrapWith(array $bootstrappers)
114114

115115
return parent::bootstrapWith($bootstrappers);
116116
}
117-
118-
public function reactivateConsoleApp()
119-
{
120-
Container::setInstance($this);
121-
$this->hasBeenBootstrapped = false;
122-
$this->make(Kernel::class)->bootstrap();
123-
}
124117
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
namespace ProcessMaker\Console\Commands;
4+
5+
use Illuminate\Queue\Console\ListenCommand as BaseCommand;
6+
7+
// https://github.com/laravel/horizon/issues/624
8+
class HorizonListen extends BaseCommand
9+
{
10+
/**
11+
* The console command name.
12+
*
13+
* @var string
14+
*/
15+
protected $signature = 'horizon:listen
16+
{connection? : The name of connection}
17+
{--name=default : The name of the worker}
18+
{--delay=0 : The number of seconds to delay failed jobs (Deprecated)}
19+
{--backoff=0 : The number of seconds to wait before retrying a job that encountered an uncaught exception}
20+
{--max-time=0 : The maximum number of seconds the worker should run}
21+
{--max-jobs=0 : The number of jobs to process before stopping}
22+
{--force : Force the worker to run even in maintenance mode}
23+
{--memory=128 : The memory limit in megabytes}
24+
{--queue= : The queue to listen on}
25+
{--sleep=3 : Number of seconds to sleep when no job is available}
26+
{--timeout=60 : The number of seconds a child process can run}
27+
{--tries=1 : Number of times to attempt a job before logging it failed}
28+
{--supervisor= : The name of the supervisor the worker belongs to}
29+
{--rest=0 : Number of seconds to rest between jobs}';
30+
31+
/**
32+
* Indicates whether the command should be shown in the Artisan command list.
33+
*
34+
* @var bool
35+
*/
36+
protected $hidden = true;
37+
38+
/**
39+
* Execute the console command.
40+
*
41+
* @return void
42+
*/
43+
public function handle()
44+
{
45+
if (config('horizon.fast_termination')) {
46+
ignore_user_abort(true);
47+
}
48+
49+
parent::handle();
50+
}
51+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace ProcessMaker\Contracts;
4+
5+
use ProcessMaker\Models\ProcessRequestToken;
6+
7+
/**
8+
* @see \ProcessMaker\Services\ConditionalRedirectService
9+
* @package ProcessMaker\Contracts
10+
*/
11+
interface ConditionalRedirectServiceInterface
12+
{
13+
/**
14+
* Process a set of conditions and return the first that satisfies for an array of data.
15+
*
16+
* @param array $conditionalRedirect
17+
* @param array $data
18+
*
19+
* @return array|null
20+
*/
21+
public function resolve(array $conditionalRedirect, array $data): ?array;
22+
23+
/**
24+
* Process a set of conditions and return the first that satisfies for a process request token.
25+
*
26+
* @param array $conditionalRedirect
27+
* @param ProcessRequestToken $token
28+
*
29+
* @return array|null
30+
*/
31+
public function resolveForToken(array $conditionalRedirect, ProcessRequestToken $token): ?array;
32+
}

ProcessMaker/Events/ProcessUpdated.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ class ProcessUpdated implements ShouldBroadcastNow
2626

2727
public $activeTokens;
2828

29+
public $elementDestination;
30+
2931
/**
3032
* Create a new event instance.
3133
*
@@ -41,6 +43,7 @@ public function __construct(ProcessRequest $processRequest, $event, TokenInterfa
4143
if ($token) {
4244
$this->tokenId = $token->getId();
4345
$this->elementType = $token->element_type;
46+
$this->elementDestination = $token->elementDestination;
4447
}
4548
}
4649

ProcessMaker/Models/MessageEventDefinition.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,6 @@ public function execute(EventDefinitionInterface $event, FlowNodeInterface $targ
4848
}
4949
}
5050

51-
return $this;
51+
return parent::execute($event, $target, $targetRequest, $token);
5252
}
5353
}

ProcessMaker/Models/ProcessRequestToken.php

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Laravel\Scout\Searchable;
1414
use Log;
1515
use ProcessMaker\Casts\MillisecondsToDateCast;
16+
use ProcessMaker\Contracts\ConditionalRedirectServiceInterface;
1617
use ProcessMaker\Events\ActivityAssigned;
1718
use ProcessMaker\Events\ActivityReassignment;
1819
use ProcessMaker\Facades\WorkflowUserManager;
@@ -753,13 +754,35 @@ public function valueAliasStatus($value, $expression, $callback = null, User $us
753754

754755
$value = mb_strtolower($value);
755756

756-
return function ($query) use ($value, $statusMap, $expression, $user) {
757+
// Capture processManagerIds from the builder if it's available
758+
// The $callback parameter is actually the $builder from ExtendedPMQL
759+
$builder = $callback;
760+
$processManagerIds = null;
761+
if ($builder && method_exists($builder, 'getQuery')) {
762+
$processManagerIds = $builder->getQuery()->processManagerIds ?? null;
763+
}
764+
765+
return function ($query) use ($value, $statusMap, $expression, $user, $processManagerIds) {
766+
// Also check in the current query in case it's available there
767+
$currentProcessManagerIds = $query->getQuery()->processManagerIds ?? null;
768+
$finalProcessManagerIds = $processManagerIds ?? $currentProcessManagerIds;
769+
$isProcessManager = !empty($finalProcessManagerIds);
770+
757771
if ($value === 'self service') {
758772
if (!$user) {
759773
$user = auth()->user();
760774
}
761775

762-
if ($user) {
776+
if ($isProcessManager) {
777+
// When processesIManage is active, include self-service tasks from managed processes
778+
$selfServiceTaskIds = ProcessRequestToken::select(['id'])
779+
->whereIn('process_id', $finalProcessManagerIds)
780+
->where('is_self_service', 1)
781+
->whereNull('user_id')
782+
->where('status', 'ACTIVE');
783+
784+
$query->whereIn('process_request_tokens.id', $selfServiceTaskIds);
785+
} elseif ($user) {
763786
$taskIds = $user->availableSelfServiceTaskIds();
764787
$query->whereIn('process_request_tokens.id', $taskIds);
765788
} else {
@@ -1369,10 +1392,28 @@ public function reassign($toUserId, User $requestingUser)
13691392
*
13701393
* @return array|null Returns the destination URL.
13711394
*/
1372-
private function getElementDestination($elementDestinationType, $elementDestinationProp): ?array
1395+
private function getElementDestination($elementDestinationType, $elementDestinationProp, array $conditionalRedirectProp): ?array
13731396
{
13741397
$elementDestination = null;
13751398

1399+
if (!empty($conditionalRedirectProp['isEnabled']) && !empty($conditionalRedirectProp['conditions'])) {
1400+
$result = $this->evaluateConditionalRedirect(app(ConditionalRedirectServiceInterface::class), $conditionalRedirectProp);
1401+
if ($result) {
1402+
$elementDestinationType = $result['taskDestination']['value'];
1403+
1404+
$url = match ($elementDestinationType) {
1405+
'customDashboard' => $result['customDashboard']['url'] ?? null,
1406+
'externalURL' => $result['externalUrl'] ?? null,
1407+
default => null,
1408+
};
1409+
1410+
$elementDestinationProp = [
1411+
'value' => [
1412+
'url' => $url,
1413+
],
1414+
];
1415+
}
1416+
}
13761417
switch ($elementDestinationType) {
13771418
case 'anotherProcess':
13781419
case 'customDashboard':
@@ -1416,6 +1457,15 @@ private function getElementDestination($elementDestinationType, $elementDestinat
14161457
];
14171458
}
14181459

1460+
private function evaluateConditionalRedirect(ConditionalRedirectServiceInterface $conditionalRedirectService, array $conditionalRedirectProp): ?array
1461+
{
1462+
if (!$conditionalRedirectProp['isEnabled']) {
1463+
return null;
1464+
}
1465+
1466+
return $conditionalRedirectService->resolveForToken($conditionalRedirectProp['conditions'], $this);
1467+
}
1468+
14191469
/**
14201470
* Determines the destination URL based on the element destination type specified in the definition.
14211471
*
@@ -1426,6 +1476,8 @@ public function getElementDestinationAttribute(): ?array
14261476
$definition = $this->getDefinition();
14271477
$elementDestinationProp = $definition['elementDestination'] ?? null;
14281478
$elementDestinationType = null;
1479+
$conditionalRedirectProp = $definition['conditionalRedirect'] ?? '[]';
1480+
$conditionalRedirectProp = json_decode($conditionalRedirectProp, true);
14291481

14301482
try {
14311483
$elementDestinationProp = json_decode($elementDestinationProp, true);
@@ -1436,7 +1488,7 @@ public function getElementDestinationAttribute(): ?array
14361488
return null;
14371489
}
14381490

1439-
return $this->getElementDestination($elementDestinationType, $elementDestinationProp);
1491+
return $this->getElementDestination($elementDestinationType, $elementDestinationProp, $conditionalRedirectProp);
14401492
}
14411493

14421494
/**

ProcessMaker/Providers/ProcessMakerServiceProvider.php

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use Illuminate\Queue\Events\JobAttempted;
1313
use Illuminate\Queue\Events\JobProcessing;
1414
use Illuminate\Queue\Events\JobRetryRequested;
15+
use Illuminate\Queue\Listener;
1516
use Illuminate\Support\Arr;
1617
use Illuminate\Support\Env;
1718
use Illuminate\Support\Facades;
@@ -22,9 +23,13 @@
2223
use Illuminate\Support\Facades\URL;
2324
use Laravel\Dusk\DuskServiceProvider;
2425
use Laravel\Horizon\Horizon;
26+
use Laravel\Horizon\SystemProcessCounter;
27+
use Laravel\Horizon\WorkerCommandString;
2528
use Lavary\Menu\Menu;
2629
use ProcessMaker\Cache\Settings\SettingCacheManager;
30+
use ProcessMaker\Console\Commands\HorizonListen;
2731
use ProcessMaker\Console\Migration\ExtendedMigrateCommand;
32+
use ProcessMaker\Contracts\ConditionalRedirectServiceInterface;
2833
use ProcessMaker\Events\ActivityAssigned;
2934
use ProcessMaker\Events\ScreenBuilderStarting;
3035
use ProcessMaker\Events\TenantResolved;
@@ -46,6 +51,7 @@
4651
use ProcessMaker\PolicyExtension;
4752
use ProcessMaker\Providers\PermissionServiceProvider;
4853
use ProcessMaker\Repositories\SettingsConfigRepository;
54+
use ProcessMaker\Services\ConditionalRedirectService;
4955
use RuntimeException;
5056
use Spatie\Multitenancy\Events\MadeTenantCurrentEvent;
5157
use Spatie\Multitenancy\Events\TenantNotFoundForRequestEvent;
@@ -71,9 +77,6 @@ class ProcessMakerServiceProvider extends ServiceProvider
7177
// Track the landlord values for multitenancy
7278
private static $landlordValues = null;
7379

74-
// Cache tenant app containers to save memory
75-
private static $tenantAppContainers = [];
76-
7780
public function boot(): void
7881
{
7982
// Track the start time for service providers boot
@@ -241,6 +244,21 @@ public function register(): void
241244
});
242245

243246
$this->app->instance('tenant-resolved', false);
247+
248+
/**
249+
* Conditional Redirect Service
250+
* This service is used to evaluate the conditional redirect property of a process request token.
251+
*/
252+
$this->app->bind(ConditionalRedirectServiceInterface::class, ConditionalRedirectService::class);
253+
254+
$this->app->when(HorizonListen::class)->needs(Listener::class)->give(function ($app) {
255+
return new Listener(base_path());
256+
});
257+
258+
if (config('app.multitenancy')) {
259+
WorkerCommandString::$command = 'exec @php artisan horizon:listen';
260+
SystemProcessCounter::$command = 'horizon:listen';
261+
}
244262
}
245263

246264
/**
@@ -261,13 +279,10 @@ private static function bootstrapTenantApp(JobProcessing|JobRetryRequested $even
261279
// Create a new tenant app instance
262280
$_SERVER['TENANT'] = $tenantId;
263281

264-
if (!isset(self::$tenantAppContainers[$tenantId])) {
265-
self::$tenantAppContainers[$tenantId] = require base_path('bootstrap/app.php');
266-
}
267-
self::$tenantAppContainers[$tenantId]->reactivateConsoleApp();
268-
269-
// Change the job's app service container to the tenant app
270-
$event->job->getRedisQueue()->setContainer(self::$tenantAppContainers[$tenantId]);
282+
$app = require base_path('bootstrap/app.php');
283+
$app->make(\ProcessMaker\Console\Kernel::class)->bootstrap();
284+
\Illuminate\Container\Container::setInstance($app);
285+
$event->job->getRedisQueue()->setContainer($app);
271286
}
272287
}
273288

0 commit comments

Comments
 (0)