Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
ddc0986
Fix sort in script executor
CarliPinell Dec 5, 2024
c6ba4e6
changed the navbar-icon when expended for mobile
gustavobascope Dec 24, 2024
03c5dff
FOUR-24085: Email Screen is not displaying its styles when it assigne…
fagubla Jul 25, 2025
a2c2559
Merge remote-tracking branch 'origin/develop' into bugfix/FOUR-24085
pmPaulis Aug 15, 2025
40735cb
Update TaskActionByEmail.php
fagubla Aug 22, 2025
8ce546d
FOUR-24683: [45464] Richtext field not rendering ul tag (regression)
henryjonathanquispe Aug 25, 2025
45037fa
FOUR-25919
pmPaulis Aug 25, 2025
0681def
FOUR-23734 [44349] - Reassignment User List Not Filtering by Regular …
gproly Aug 28, 2025
6088e4b
FOUR-23734 Remove console.log
gproly Aug 28, 2025
2bee0fb
enable find recrusively dependencies
marcoAntonioNina Sep 1, 2025
cf40dc1
FOUR-25919: solving obsevations
pmPaulis Sep 3, 2025
3c1e9ca
FOUR-25910
pmPaulis Sep 5, 2025
7e6f55d
Add scrollbar to right panel in request details
agustinbusso Sep 15, 2025
4f5db81
allow more than 1 manager per process
marcoAntonioNina Sep 30, 2025
ac2b6a0
change to manage multiple managers
marcoAntonioNina Sep 30, 2025
228e248
fix styles
marcoAntonioNina Sep 30, 2025
72318fd
set nullable
marcoAntonioNina Sep 30, 2025
103f212
Fix test
marcoAntonioNina Oct 1, 2025
f800432
Return int in fucntion
marcoAntonioNina Oct 1, 2025
e9fda96
preview managers export
marcoAntonioNina Oct 1, 2025
a41e840
add validation max managers
marcoAntonioNina Oct 1, 2025
a568aba
Change array_rand
marcoAntonioNina Oct 2, 2025
9d5adef
Change manager name and ids
marcoAntonioNina Oct 2, 2025
7750207
Validate values of managers
marcoAntonioNina Oct 2, 2025
951bb1f
Merge branch 'develop' of github.com:ProcessMaker/processmaker into t…
marcoAntonioNina Oct 2, 2025
7baa570
validate value is string or array
marcoAntonioNina Oct 2, 2025
50458bc
Fix value managerId
marcoAntonioNina Oct 2, 2025
c183389
Add IsManager middleware for enhanced access control
marcoAntonioNina Oct 3, 2025
af97cf6
Refactor TaskController to improve user filtering and caching logic
marcoAntonioNina Oct 3, 2025
ae2a94e
Fix error when dependent screen is missing
nolanpro Oct 8, 2025
ce25e78
Implement SAML email edit restriction alert in user profile updates
marcoAntonioNina Oct 14, 2025
f98013b
Fix alert message translation for SAML email edit restriction in user…
marcoAntonioNina Oct 14, 2025
a08445f
Add middleware hide server headers
marcoAntonioNina Oct 20, 2025
a2a6b7e
Add name attribute in task resource
agustinbusso Oct 20, 2025
3edc98e
Add Name Server
marcoAntonioNina Oct 20, 2025
adb49a7
Add queue metrics for prometheus
nolanpro Oct 21, 2025
02310ed
Add note
nolanpro Oct 21, 2025
ccadcca
Fix config key
nolanpro Oct 21, 2025
2180898
Handle cache prefixing in bootstrapper
nolanpro Oct 22, 2025
10779c3
Make metrics tenant aware
nolanpro Oct 22, 2025
0f0dd73
Fix schedule:run cron error
nolanpro Oct 23, 2025
a47b94b
Merge pull request #8570 from ProcessMaker/task/FOUR-27380
nolanpro Oct 23, 2025
a56fdfe
Version 4.15.9-RC1
processmaker-bot Oct 23, 2025
7e6bcac
Version 4.15.9
processmaker-bot Oct 23, 2025
0832e48
Fix multitenancy settings cache
nolanpro Oct 23, 2025
929a573
Refactor tenant queue authorization
nolanpro Oct 24, 2025
612afc4
Fix docker host url
nolanpro Oct 25, 2025
726b6a3
Merge pull request #8572 from ProcessMaker/bugfix/FOUR-27531
nolanpro Oct 27, 2025
5f26d05
Bump build
nolanpro Oct 27, 2025
e1d9637
Merge pull request #8558 from ProcessMaker/bugfix/FOUR-27169
nolanpro Oct 27, 2025
c008028
Merge pull request #8475 from ProcessMaker/feature/FOUR-25919
nolanpro Oct 27, 2025
73cc8c9
Merge pull request #8491 from ProcessMaker/bugfix/FOUR-25970
nolanpro Oct 27, 2025
1076120
Merge pull request #8394 from ProcessMaker/bugfix/FOUR-24085
nolanpro Oct 27, 2025
81dde41
Merge pull request #7856 from ProcessMaker/observation/FOUR-21268
nolanpro Oct 27, 2025
ec367da
Merge pull request #8467 from ProcessMaker/bugfix/FOUR-24683
nolanpro Oct 27, 2025
018d359
Merge pull request #8506 from ProcessMaker/bugfix/FOUR-25910-b
nolanpro Oct 27, 2025
923b7d0
Merge pull request #8518 from ProcessMaker/bugfix/FOUR-21536
nolanpro Oct 27, 2025
d1df158
Merge pull request #7807 from ProcessMaker/bugfix/FOUR-19358
nolanpro Oct 27, 2025
ae27fd4
Merge pull request #8568 from ProcessMaker/bugfix/FOUR-26739
nolanpro Oct 27, 2025
74b55ee
Merge pull request #8566 from ProcessMaker/bugfix/FOUR-26588
nolanpro Oct 27, 2025
5712645
Fix test
marcoAntonioNina Oct 27, 2025
97b618b
Merge pull request #8552 from ProcessMaker/task/FOUR-26776
nolanpro Oct 27, 2025
d3cb6b9
Merge pull request #8487 from ProcessMaker/bugfix/FOUR-23734
nolanpro Oct 27, 2025
c09606b
Merge branch 'develop' into bugfix/FOUR-26915
nolanpro Oct 27, 2025
be56304
Merge branch 'develop' into task/FOUR-26777
nolanpro Oct 27, 2025
2535d62
Merge pull request #8567 from ProcessMaker/bugfix/FOUR-26915
nolanpro Oct 28, 2025
8a60016
Merge pull request #8549 from ProcessMaker/task/FOUR-26777
nolanpro Oct 28, 2025
db0f9a4
Update dependencies
nolanpro Oct 28, 2025
ca0c63b
Version 4.15.10+beta-1
processmaker-bot Oct 28, 2025
4f6b3f8
fix(SmartExtract): adjust non-system task filtering based on HITL con…
eiresendez Oct 29, 2025
bf91d32
refine non-system task filtering based on HITL configuration
eiresendez Oct 29, 2025
461c42d
add mergeHitlCaseNumber method to handle case number retrieval from p…
eiresendez Oct 29, 2025
324bfa7
saving progress with smart-extract iframe
CarliPinell Oct 30, 2025
d98d998
Saving new approach for iframe with package smart-extract
CarliPinell Oct 30, 2025
ee43ceb
Saving changes smart-extract iframe working ok
CarliPinell Oct 30, 2025
caf85b7
Changes on edit.blade.php removing Manual Edit tab
CarliPinell Oct 30, 2025
45c9876
Merge branch 'task/FOUR-27118' of github.com:ProcessMaker/processmake…
CarliPinell Oct 30, 2025
f34e97f
smart extract iframe implemented
CarliPinell Oct 30, 2025
9dd8ccb
ready for PR
CarliPinell Oct 30, 2025
ec26600
Merge branch 'task/FOUR-27118-clean' of github.com:ProcessMaker/proce…
CarliPinell Oct 30, 2025
7195058
Merge branch 'epic/FOUR-26611' of github.com:ProcessMaker/processmake…
CarliPinell Oct 30, 2025
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
52 changes: 50 additions & 2 deletions ProcessMaker/AssignmentRules/ProcessManagerAssigned.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
use ProcessMaker\Exception\ThereIsNoProcessManagerAssignedException;
use ProcessMaker\Models\Process;
use ProcessMaker\Models\ProcessRequest;
use ProcessMaker\Models\ProcessRequestToken;
use ProcessMaker\Models\User;
use ProcessMaker\Nayra\Contracts\Bpmn\ActivityInterface;
use ProcessMaker\Nayra\Contracts\Bpmn\TokenInterface;

Expand All @@ -24,16 +26,62 @@ class ProcessManagerAssigned implements AssignmentRuleInterface
* @param TokenInterface $token
* @param Process $process
* @param ProcessRequest $request
* @return int
* @return int|null
* @throws ThereIsNoProcessManagerAssignedException
*/
public function getNextUser(ActivityInterface $task, TokenInterface $token, Process $process, ProcessRequest $request)
{
$user_id = $request->processVersion->manager_id;
// review for multiple managers
$managers = $request->processVersion->manager_id;
$user_id = $this->getNextManagerAssigned($managers, $task, $request);
if (!$user_id) {
throw new ThereIsNoProcessManagerAssignedException($task);
}

return $user_id;
}

/**
* Get the round robin manager using a true round robin algorithm
*
* @param array $managers
* @param ActivityInterface $task
* @param ProcessRequest $request
* @return int|null
*/
private function getNextManagerAssigned($managers, $task, $request)
{
// Validate input
if (empty($managers) || !is_array($managers)) {
return null;
}

// If only one manager, return it
if (count($managers) === 1) {
return $managers[0];
}

// get the last manager assigned to the task across all requests
$last = ProcessRequestToken::where('process_id', $request->process_id)
->where('element_id', $task->getId())
->whereIn('user_id', $managers)
->orderBy('created_at', 'desc')
->first();

$user_id = $last ? $last->user_id : null;

sort($managers);

$key = array_search($user_id, $managers);
if ($key === false) {
// If no previous manager found, start with the first manager
$key = 0;
} else {
// Move to the next manager in the round-robin
$key = ($key + 1) % count($managers);
}
$user_id = $managers[$key];

return $user_id;
}
}
3 changes: 3 additions & 0 deletions ProcessMaker/Console/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ protected function schedule(Schedule $schedule)
$schedule->command('metrics:clear')->cron("*/{$clearInterval} * * * *");
break;
}

// 5 minutes is recommended in https://laravel.com/docs/12.x/horizon#metrics
$schedule->command('horizon:snapshot')->everyFiveMinutes();
}

/**
Expand Down
10 changes: 10 additions & 0 deletions ProcessMaker/Exception/MultitenancyAccessedLandlord.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,21 @@
use Exception;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use ProcessMaker\Facades\Metrics;

class MultitenancyAccessedLandlord extends Exception
{
public function render(Request $request): Response
{
// If we're trying to access the /metrics route, collect landlord metrics and render them
if ($request->path() === 'metrics') {
Metrics::collectQueueMetrics();

return response(Metrics::renderMetrics(), 200, [
'Content-Type' => 'text/plain; version=0.0.4',
]);
}

return response()->view('multitenancy.landlord-landing-page');
}

Expand Down
67 changes: 27 additions & 40 deletions ProcessMaker/Http/Controllers/Admin/TenantQueueController.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,36 +14,12 @@

class TenantQueueController extends Controller
{
/**
* Constructor to check if tenant tracking is enabled.
*/
public function __construct()
{
// Check if tenant job tracking is enabled
$enabled = TenantQueueServiceProvider::enabled();

if (!$enabled) {
if (!app()->runningInConsole()) {
abort(404, 'Tenant queue tracking is disabled');
}
}

// If the route binding has a tenant id, check if the user is allowed to access the tenant queue
if ($id = (int) request()->route('tenantId')) {
if (!TenantQueueServiceProvider::allowAllTenats() && $id !== app('currentTenant')?->id) {
throw new AuthorizationException();
}
}
}

/**
* Show the tenant jobs dashboard.
*/
public function index()
{
if (!Auth::user()->is_administrator) {
throw new AuthorizationException();
}
$this->checkPermissions();

return view('admin.tenant-queues.index');
}
Expand All @@ -53,9 +29,7 @@ public function index()
*/
public function getTenants(): JsonResponse
{
if (!Auth::user()->is_administrator) {
throw new AuthorizationException();
}
$this->checkPermissions();

$tenantsWithJobs = TenantQueueServiceProvider::getTenantsWithJobs();

Expand Down Expand Up @@ -87,9 +61,7 @@ public function getTenants(): JsonResponse
*/
public function getTenantJobs(Request $request, string $tenantId): JsonResponse
{
if (!Auth::user()->is_administrator) {
throw new AuthorizationException();
}
$this->checkPermissions();

$status = $request->get('status');
$limit = min((int) $request->get('limit', 50), 100); // Max 100 jobs
Expand Down Expand Up @@ -125,9 +97,7 @@ public function getTenantStats(string $tenantId): JsonResponse
*/
public function getOverallStats(): JsonResponse
{
if (!Auth::user()->is_administrator) {
throw new AuthorizationException();
}
$this->checkPermissions();

$tenantsWithJobs = TenantQueueServiceProvider::getTenantsWithJobs();

Expand Down Expand Up @@ -163,9 +133,7 @@ public function getOverallStats(): JsonResponse
*/
public function getJobDetails(string $tenantId, string $jobId): JsonResponse
{
if (!Auth::user()->is_administrator) {
throw new AuthorizationException();
}
$this->checkPermissions();

$tenantKey = "tenant_jobs:{$tenantId}:{$jobId}";
$jobData = Redis::hgetall($tenantKey);
Expand Down Expand Up @@ -199,9 +167,7 @@ public function getJobDetails(string $tenantId, string $jobId): JsonResponse
*/
public function clearTenantJobs(string $tenantId): JsonResponse
{
if (!Auth::user()->is_administrator) {
throw new AuthorizationException();
}
$this->checkPermissions();

try {
$pattern = "tenant_jobs:{$tenantId}:*";
Expand All @@ -228,4 +194,25 @@ public function clearTenantJobs(string $tenantId): JsonResponse
return response()->json(['error' => 'Failed to clear tenant job data'], 500);
}
}

private function checkPermissions(): void
{
// Check if tenant job tracking is enabled
$enabled = TenantQueueServiceProvider::enabled();

if (!$enabled) {
throw new AuthorizationException('Tenant queue tracking is disabled');
}

if (!Auth::user()->is_administrator) {
throw new AuthorizationException();
}

// If the route binding has a tenant id, check if the user is allowed to access the tenant queue
if ($id = (int) request()->route('tenantId')) {
if (!TenantQueueServiceProvider::allowAllTenats() && $id !== app('currentTenant')?->id) {
throw new AuthorizationException();
}
}
}
}
55 changes: 52 additions & 3 deletions ProcessMaker/Http/Controllers/Api/ProcessController.php
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,7 @@ public function store(Request $request)

//set manager id
if ($request->has('manager_id')) {
$process->manager_id = $request->input('manager_id', null);
$process->manager_id = $this->validateMaxManagers($request);
}

if (isset($data['bpmn'])) {
Expand Down Expand Up @@ -542,7 +542,7 @@ public function update(Request $request, Process $process)

$process->fill($request->except('notifications', 'task_notifications', 'notification_settings', 'cancel_request', 'cancel_request_id', 'start_request_id', 'edit_data', 'edit_data_id', 'projects'));
if ($request->has('manager_id')) {
$process->manager_id = $request->input('manager_id', null);
$process->manager_id = $this->validateMaxManagers($request);
}

if ($request->has('user_id')) {
Expand Down Expand Up @@ -621,6 +621,55 @@ public function update(Request $request, Process $process)
return new Resource($process->refresh());
}

private function validateMaxManagers(Request $request)
{
$managerIds = $request->input('manager_id', []);

// Handle different input types
if (is_string($managerIds)) {
// If it's a string, try to decode it as JSON
if (empty($managerIds)) {
$managerIds = [];
} else {
$decoded = json_decode($managerIds, true);

// Handle JSON decode failure
if (json_last_error() !== JSON_ERROR_NONE) {
throw new \Illuminate\Validation\ValidationException(
validator([], []),
['manager_id' => [__('Invalid JSON format for manager_id')]]
);
}

$managerIds = $decoded;
}
}

// Ensure we have an array
if (!is_array($managerIds)) {
// If it's a single value (not array), convert to array
$managerIds = [$managerIds];
}

// Filter out null, empty values and validate each manager ID
$managerIds = array_filter($managerIds, function ($id) {
return $id !== null && $id !== '' && is_numeric($id) && $id > 0;
});

// Re-index the array to remove gaps from filtered values
$managerIds = array_values($managerIds);

// Validate maximum number of managers
if (count($managerIds) > 10) {
throw new \Illuminate\Validation\ValidationException(
validator([], []),
['manager_id' => [__('Maximum number of managers is :max', ['max' => 10])]]
);
}

return $managerIds;
}

/**
* Validate the structure of stages.
*
Expand Down Expand Up @@ -1714,7 +1763,7 @@ protected function checkUserCanStartProcess($event, $currentUser, $process, $req
}
break;
case 'process_manager':
$response = $currentUser === $process->manager_id;
$response = in_array($currentUser, $process->manager_id ?? []);
break;
}
}
Expand Down
10 changes: 9 additions & 1 deletion ProcessMaker/Http/Controllers/Api/ScriptExecutorController.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,15 @@ public function index(Request $request)
{
$this->checkAuth($request);

return new ApiCollection(ScriptExecutor::nonSystem()->get());
$query = ScriptExecutor::nonSystem();

if ($request->has('order_by')) {
$order_by = $request->input('order_by');
$order_direction = $request->input('order_direction', 'ASC');
$query->orderBy($order_by, $order_direction);
}

return new ApiCollection($query->get());
}

/**
Expand Down
15 changes: 10 additions & 5 deletions ProcessMaker/Http/Controllers/Api/TaskController.php
Original file line number Diff line number Diff line change
Expand Up @@ -146,15 +146,15 @@ public function index(Request $request, $getTotal = false, User $user = null)

$this->applyAdvancedFilter($query, $request);

$this->applyForCurrentUser($query, $user);

// Apply filter overdue
$query->overdue($request->input('overdue'));

if ($request->input('processesIManage') === 'true') {
$this->applyProcessManager($query, $user);
} else {
$this->applyForCurrentUser($query, $user);
}

// Apply filter overdue
$query->overdue($request->input('overdue'));

// If only the total is being requested (by a Saved Search), send it now
if ($getTotal === true) {
return $query->count();
Expand All @@ -168,6 +168,11 @@ public function index(Request $request, $getTotal = false, User $user = null)

$response = $this->applyUserFilter($response, $request, $user);

if ($response->total() > 0 && $request->input('processesIManage') === 'true') {
// enable user manager in cache
$this->enableUserManager($user);
}

$inOverdueQuery = ProcessRequestToken::query()
->whereIn('id', $response->pluck('id'))
->where('due_at', '<', Carbon::now());
Expand Down
6 changes: 5 additions & 1 deletion ProcessMaker/Http/Controllers/Api/UserController.php
Original file line number Diff line number Diff line change
Expand Up @@ -209,10 +209,14 @@ public function getUsersTaskCount(Request $request)
$include_ids = explode(',', $include_ids_string);
} elseif ($request->has('assignable_for_task_id')) {
$task = ProcessRequestToken::findOrFail($request->input('assignable_for_task_id'));
if ($task->getAssignmentRule() === 'user_group') {
$assignmentRule = $task->getAssignmentRule();
if ($assignmentRule === 'user_group') {
// Limit the list of users to those that can be assigned to the task
$include_ids = $task->process->getAssignableUsers($task->element_id);
}
if ($assignmentRule === 'rule_expression' && $request->has('form_data')) {
$include_ids = $task->getAssigneesFromExpression($request->input('form_data'));
}
}

if (!empty($include_ids)) {
Expand Down
2 changes: 1 addition & 1 deletion ProcessMaker/Http/Controllers/CasesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public function show($case_number)
// The user can see the comments
$canViewComments = (Auth::user()->hasPermissionsFor('comments')->count() > 0) || class_exists(PackageServiceProvider::class);
// The user is Manager from the main request
$isProcessManager = $request->process?->manager_id === Auth::user()->id;
$isProcessManager = in_array(Auth::user()->id, $request->process?->manager_id ?? []);
// Check if the user has permission print for request
$canPrintScreens = $canOpenCase = $this->canUserCanOpenCase($allRequests);
if (!$canOpenCase && !$isProcessManager) {
Expand Down
Loading
Loading