From 33ebad60ba8a729fda81cd65472ff68b430131d4 Mon Sep 17 00:00:00 2001 From: Miguel Angel Date: Wed, 10 Sep 2025 15:21:01 -0400 Subject: [PATCH 1/8] feat: add saved_search_advanced_configuration to filesystem configuration --- .gitignore | 1 + ProcessMaker/Multitenancy/SwitchTenant.php | 2 ++ config/filesystems.php | 7 +++++++ 3 files changed, 10 insertions(+) diff --git a/.gitignore b/.gitignore index fcf4007765..f375b4a8bc 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ storage/ssl storage/api/* storage/data-sources/logs/* storage/decision-tables/* +storage/saved_search_advanced_configuration/* npm.sh laravel-echo-server.lock public/.htaccess diff --git a/ProcessMaker/Multitenancy/SwitchTenant.php b/ProcessMaker/Multitenancy/SwitchTenant.php index 2ca2efc3ec..dfc5332b35 100644 --- a/ProcessMaker/Multitenancy/SwitchTenant.php +++ b/ProcessMaker/Multitenancy/SwitchTenant.php @@ -76,6 +76,8 @@ public function makeCurrent(IsTenant $tenant): void 'filesystems.disks.samlidp.root' => storage_path('samlidp'), 'filesystems.disks.decision_tables.root' => storage_path('decision-tables'), 'filesystems.disks.decision_tables.url' => $tenant->config['app.url'] . '/storage/decision-tables', + 'filesystems.disks.saved_search_advanced_configuration.root' => storage_path('saved_search_advanced_configuration'), + 'filesystems.disks.decision_tables.url' => $tenant->config['app.url'] . '/storage/saved_search_advanced_configuration', 'filesystems.disks.tenant_translations' => [ 'driver' => 'local', 'root' => storage_path('lang'), diff --git a/config/filesystems.php b/config/filesystems.php index 182dde1afe..a480cecde9 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -131,6 +131,13 @@ // Others declared in packages // - translations - package-translations // - 'filesystems.disks.install' configured on the fly + + 'saved_search_advanced_configuration' => [ + 'driver' => 'local', + 'root' => storage_path('saved_search_advanced_configuration'), + 'url' => env('APP_URL') . '/storage/saved_search_advanced_configuration', + 'visibility' => 'private', + ], ], /* From bf587d1d0eaaffb7e0c20b903bd3ec1b32fa7d0f Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Tue, 16 Sep 2025 18:31:15 -0400 Subject: [PATCH 2/8] Add job validation and new scheduling method `scheduleDateJob` --- .../Managers/TaskSchedulerManager.php | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/ProcessMaker/Managers/TaskSchedulerManager.php b/ProcessMaker/Managers/TaskSchedulerManager.php index d59dc4db80..e459bd2cee 100644 --- a/ProcessMaker/Managers/TaskSchedulerManager.php +++ b/ProcessMaker/Managers/TaskSchedulerManager.php @@ -12,6 +12,7 @@ use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Schema; +use InvalidArgumentException; use PDOException; use ProcessMaker\Facades\WorkflowManager; use ProcessMaker\Jobs\StartEventConditional; @@ -574,6 +575,9 @@ public function scheduleCycle( */ public function scheduleCycleJob($interval, array $config): ScheduledTask { + if (!isset($config['job'])) { + throw new InvalidArgumentException('$config["job"] is required'); + } $configuration = [ 'type' => 'TimeCycle', 'interval' => $interval, @@ -590,6 +594,33 @@ public function scheduleCycleJob($interval, array $config): ScheduledTask return $scheduledTask; } + /** + * Schedule a job for a specific datetime + * + * @param string $datetime in ISO-8601 format + * @param array $config configuration + * + * @return ScheduledTask + */ + public function scheduleDateJob($datetime, array $config): ScheduledTask + { + if (!isset($config['job'])) { + throw new InvalidArgumentException('$config["job"] is required'); + } + $configuration = [ + 'type' => 'TimeDate', + 'date' => $datetime, + ...$config, + ]; + $scheduledTask = new ScheduledTask(); + $scheduledTask->configuration = json_encode($configuration); + $scheduledTask->type = 'SCHEDULED_JOB'; + $scheduledTask->last_execution = null; + $scheduledTask->save(); + + return $scheduledTask; + } + /** * Schedule a job execution after a time duration for the given BPMN element, * event definition and an optional Token object From 2b3db57ecbe6fb98786b4258f55991705dd02980 Mon Sep 17 00:00:00 2001 From: Miguel Angel Date: Wed, 6 May 2026 11:57:48 -0400 Subject: [PATCH 3/8] feat: update configuration structure for scheduled jobs --- ProcessMaker/Managers/TaskSchedulerManager.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/ProcessMaker/Managers/TaskSchedulerManager.php b/ProcessMaker/Managers/TaskSchedulerManager.php index 5d36c75e28..b12c3049da 100644 --- a/ProcessMaker/Managers/TaskSchedulerManager.php +++ b/ProcessMaker/Managers/TaskSchedulerManager.php @@ -202,10 +202,11 @@ private function processTaskWithAtomicClaim(ScheduledTask $task, DateTime $today { try { $config = json_decode($task->configuration); - $lastExecution = new DateTime($task->last_execution, new DateTimeZone('UTC')); - if ($lastExecution === null) { - return; + // SCHEDULED_JOB rows use last_execution = null until first run; BPMN timers always set last_execution. + $lastExecution = null; + if ($task->last_execution !== null && $task->last_execution !== '') { + $lastExecution = new DateTime($task->last_execution, new DateTimeZone('UTC')); } $owner = $task->processRequestToken ?: $task->processRequest ?: $task->process; @@ -721,11 +722,14 @@ public function scheduleDateJob($datetime, array $config): ScheduledTask if (!isset($config['job'])) { throw new InvalidArgumentException('$config["job"] is required'); } + + // Must use "interval" so nextDate(TimeDate) picks up the target datetime (same shape as BPMN timer tasks). $configuration = [ 'type' => 'TimeDate', - 'date' => $datetime, ...$config, + 'interval' => $datetime, ]; + $scheduledTask = new ScheduledTask(); $scheduledTask->configuration = json_encode($configuration); $scheduledTask->type = 'SCHEDULED_JOB'; From 8d15b575049ad97c7d0fae32c0cbf4b261658e4d Mon Sep 17 00:00:00 2001 From: Nolan Ehrstrom Date: Wed, 10 Jun 2026 09:34:45 -0700 Subject: [PATCH 4/8] Update dependency --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 5fe0d8c2c3..02c3a6260a 100644 --- a/composer.json +++ b/composer.json @@ -176,7 +176,7 @@ "package-product-analytics": "1.5.11", "package-projects": "1.12.9", "package-rpa": "1.1.2", - "package-savedsearch": "1.43.13", + "package-savedsearch": "1.43.12-RC1", "package-slideshow": "1.4.3", "package-smart-extract": "0.0.6", "package-signature": "1.15.5", From 1444838ce5d335b412c1e7e4a337a89482625a46 Mon Sep 17 00:00:00 2001 From: Nolan Ehrstrom Date: Wed, 10 Jun 2026 09:38:26 -0700 Subject: [PATCH 5/8] Version 2026.9.4-RC1 --- composer.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index 02c3a6260a..0c89adfb08 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "processmaker/processmaker", - "version": "2026.9.3", + "version": "2026.9.4-RC1", "description": "BPM PHP Software", "keywords": [ "php bpm processmaker" diff --git a/package-lock.json b/package-lock.json index 169b6cc347..f95f9faf2d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@processmaker/processmaker", - "version": "2026.9.3", + "version": "2026.9.4-RC1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@processmaker/processmaker", - "version": "2026.9.3", + "version": "2026.9.4-RC1", "hasInstallScript": true, "license": "ISC", "dependencies": { diff --git a/package.json b/package.json index b73db6ff33..abc5983b1c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@processmaker/processmaker", - "version": "2026.9.3", + "version": "2026.9.4-RC1", "description": "ProcessMaker 4", "author": "DevOps ", "license": "ISC", From 92dfff50f2f54493e4a7daf01628dd152dad16d8 Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Thu, 11 Jun 2026 11:34:52 -0400 Subject: [PATCH 6/8] feat(FOUR-31695): add support for Laravel Octane in core --- .gitignore | 5 ++++- ProcessMaker/Http/Middleware/SetLocale.php | 19 +++++++++---------- .../Repositories/SettingsConfigRepository.php | 2 +- composer.json | 1 + 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 8d89feb4b6..5c4cbe56fd 100644 --- a/.gitignore +++ b/.gitignore @@ -52,4 +52,7 @@ devhub/pm-font/dist test-db-snapshot.db snapshot_*.db storage/transitions -.envrc \ No newline at end of file +.envrc +**/caddy +frankenphp +frankenphp-worker.php diff --git a/ProcessMaker/Http/Middleware/SetLocale.php b/ProcessMaker/Http/Middleware/SetLocale.php index cb96e6787b..111993a973 100644 --- a/ProcessMaker/Http/Middleware/SetLocale.php +++ b/ProcessMaker/Http/Middleware/SetLocale.php @@ -2,9 +2,11 @@ namespace ProcessMaker\Http\Middleware; +use Carbon\Carbon; use Closure; use Illuminate\Http\Request; use Illuminate\Support\Facades\App; +use Illuminate\Support\Facades\Auth; /** * Sets the locale based on url parameter @@ -19,18 +21,15 @@ class SetLocale */ public function handle(Request $request, Closure $next) { - // Grab the locale - $locale = config('app.locale'); - $user = \Auth::user(); - if ($user && $user->language !== null) { - $locale = \Auth::user()->language; - } - if ($locale) { - // Use the App facade to set the locale for our request lifecycle - App::setLocale($locale); + $locale = config('app.locale', 'en'); + + if (($user = Auth::user()) && !empty($user->language)) { + $locale = $user->language; } - // Process next + App::setLocale($locale); + Carbon::setLocale($locale); + return $next($request); } } diff --git a/ProcessMaker/Repositories/SettingsConfigRepository.php b/ProcessMaker/Repositories/SettingsConfigRepository.php index 2f4cf6e0b2..f2707faf2f 100644 --- a/ProcessMaker/Repositories/SettingsConfigRepository.php +++ b/ProcessMaker/Repositories/SettingsConfigRepository.php @@ -39,7 +39,7 @@ public function get($key, $default = null) if ($key === 'session.lifetime') { $settingValue = $this->getFromSettings($key); - return $settingValue ?? $default; + return $settingValue ?: Arr::get($this->items, $key) ?: $default ?: 120; } if (Arr::has($this->items, $key)) { diff --git a/composer.json b/composer.json index 5fe0d8c2c3..d54b4c8259 100644 --- a/composer.json +++ b/composer.json @@ -26,6 +26,7 @@ "jenssegers/agent": "^2.6", "laravel/framework": "^13.0", "laravel/horizon": "^5.45", + "laravel/octane": "^2.17", "laravel/pail": "^1.2", "laravel/passport": "^13.7", "laravel/scout": "^11.1", From dade78b8e5bf75195e5f670b5e0e716f26e125bf Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Mon, 15 Jun 2026 11:34:35 -0400 Subject: [PATCH 7/8] v. performance1 --- composer.json | 4 +- composer.lock | 179 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 180 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 2430e35ae0..13708d2066 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "processmaker/processmaker", - "version": "2026.10.4", + "version": "2026.10.4+performance", "description": "BPM PHP Software", "keywords": [ "php bpm processmaker" @@ -250,4 +250,4 @@ "ignore": [] } } -} \ No newline at end of file +} diff --git a/composer.lock b/composer.lock index a6a7461cc8..28866187df 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "4a2425ff2b2568bf0ea0cd2133ec2640", + "content-hash": "d6cfb93f04ed1c4f1d478c972c667a9c", "packages": [ { "name": "babenkoivan/elastic-adapter", @@ -2745,6 +2745,94 @@ ], "time": "2020-06-13T08:05:20+00:00" }, + { + "name": "laminas/laminas-diactoros", + "version": "3.8.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "60c182916b2749480895601649563970f3f12ec4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/60c182916b2749480895601649563970f3f12ec4", + "reference": "60c182916b2749480895601649563970f3f12ec4", + "shasum": "" + }, + "require": { + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "psr/http-factory": "^1.1", + "psr/http-message": "^1.1 || ^2.0" + }, + "conflict": { + "amphp/amp": "<2.6.4" + }, + "provide": { + "psr/http-factory-implementation": "^1.0", + "psr/http-message-implementation": "^1.1 || ^2.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^2.2.0", + "laminas/laminas-coding-standard": "~3.1.0", + "php-http/psr7-integration-tests": "^1.4.0", + "phpunit/phpunit": "^10.5.36", + "psalm/plugin-phpunit": "^0.19.5", + "vimeo/psalm": "^6.13" + }, + "type": "library", + "extra": { + "laminas": { + "module": "Laminas\\Diactoros", + "config-provider": "Laminas\\Diactoros\\ConfigProvider" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2025-10-12T15:31:36+00:00" + }, { "name": "laravel/framework", "version": "v13.13.0", @@ -3049,6 +3137,95 @@ }, "time": "2026-06-03T15:11:37+00:00" }, + { + "name": "laravel/octane", + "version": "v2.17.5", + "source": { + "type": "git", + "url": "https://github.com/laravel/octane.git", + "reference": "058ae4d7109eed40836dc42960f9388b9bf71f73" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/octane/zipball/058ae4d7109eed40836dc42960f9388b9bf71f73", + "reference": "058ae4d7109eed40836dc42960f9388b9bf71f73", + "shasum": "" + }, + "require": { + "laminas/laminas-diactoros": "^3.0", + "laravel/framework": "^10.10.1|^11.0|^12.0|^13.0", + "laravel/prompts": "^0.1.24|^0.2.0|^0.3.0", + "laravel/serializable-closure": "^1.3|^2.0", + "nesbot/carbon": "^2.66.0|^3.0", + "php": "^8.1.0", + "symfony/console": "^6.0|^7.0|^8.0", + "symfony/psr-http-message-bridge": "^2.2.0|^6.4|^7.0|^8.0" + }, + "conflict": { + "spiral/roadrunner": "<2023.1.0", + "spiral/roadrunner-cli": "<2.6.0", + "spiral/roadrunner-http": "<3.3.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "^7.6.1", + "inertiajs/inertia-laravel": "^1.3.2|^2.0", + "laravel/scout": "^10.2.1", + "laravel/socialite": "^5.6.1", + "livewire/livewire": "^2.12.3|^3.0", + "nunomaduro/collision": "^6.4.0|^7.5.2|^8.0", + "orchestra/testbench": "^8.21|^9.0|^10.0|^11.0", + "phpstan/phpstan": "^2.1.7", + "phpunit/phpunit": "^10.4|^11.5|^12.0|^13.0", + "spiral/roadrunner-cli": "^2.6.0", + "spiral/roadrunner-http": "^3.3.0" + }, + "bin": [ + "bin/roadrunner-worker", + "bin/swoole-server" + ], + "type": "library", + "extra": { + "laravel": { + "aliases": { + "Octane": "Laravel\\Octane\\Facades\\Octane" + }, + "providers": [ + "Laravel\\Octane\\OctaneServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\Octane\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Supercharge your Laravel application's performance.", + "keywords": [ + "frankenphp", + "laravel", + "octane", + "roadrunner", + "swoole" + ], + "support": { + "issues": "https://github.com/laravel/octane/issues", + "source": "https://github.com/laravel/octane" + }, + "time": "2026-06-04T09:05:08+00:00" + }, { "name": "laravel/pail", "version": "v1.2.6", From c8ef12499e14b49111e557f12b119fae87b652f4 Mon Sep 17 00:00:00 2001 From: Daniel Ryan Date: Thu, 25 Jun 2026 11:03:58 -1000 Subject: [PATCH 8/8] add octane.php config --- config/octane.php | 224 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100644 config/octane.php diff --git a/config/octane.php b/config/octane.php new file mode 100644 index 0000000000..48b471594b --- /dev/null +++ b/config/octane.php @@ -0,0 +1,224 @@ + env('OCTANE_SERVER', 'roadrunner'), + + /* + |-------------------------------------------------------------------------- + | Force HTTPS + |-------------------------------------------------------------------------- + | + | When this configuration value is set to "true", Octane will inform the + | framework that all absolute links must be generated using the HTTPS + | protocol. Otherwise your links may be generated using plain HTTP. + | + */ + + 'https' => env('OCTANE_HTTPS', false), + + /* + |-------------------------------------------------------------------------- + | Octane Listeners + |-------------------------------------------------------------------------- + | + | All of the event listeners for Octane's events are defined below. These + | listeners are responsible for resetting your application's state for + | the next request. You may even add your own listeners to the list. + | + */ + + 'listeners' => [ + WorkerStarting::class => [ + EnsureUploadedFilesAreValid::class, + EnsureUploadedFilesCanBeMoved::class, + ], + + RequestReceived::class => [ + ...Octane::prepareApplicationForNextOperation(), + ...Octane::prepareApplicationForNextRequest(), + // + ], + + RequestHandled::class => [ + // + ], + + RequestTerminated::class => [ + // FlushUploadedFiles::class, + ], + + TaskReceived::class => [ + ...Octane::prepareApplicationForNextOperation(), + // + ], + + TaskTerminated::class => [ + // + ], + + TickReceived::class => [ + ...Octane::prepareApplicationForNextOperation(), + // + ], + + TickTerminated::class => [ + // + ], + + OperationTerminated::class => [ + FlushOnce::class, + FlushTemporaryContainerInstances::class, + // DisconnectFromDatabases::class, + // CollectGarbage::class, + ], + + WorkerErrorOccurred::class => [ + ReportException::class, + StopWorkerIfNecessary::class, + ], + + WorkerStopping::class => [ + CloseMonologHandlers::class, + ], + ], + + /* + |-------------------------------------------------------------------------- + | Warm / Flush Bindings + |-------------------------------------------------------------------------- + | + | The bindings listed below will either be pre-warmed when a worker boots + | or they will be flushed before every new request. Flushing a binding + | will force the container to resolve that binding again when asked. + | + */ + + 'warm' => [ + ...Octane::defaultServicesToWarm(), + ], + + 'flush' => [ + // + ], + + /* + |-------------------------------------------------------------------------- + | Octane Swoole Tables + |-------------------------------------------------------------------------- + | + | While using Swoole, you may define additional tables as required by the + | application. These tables can be used to store data that needs to be + | quickly accessed by other workers on the particular Swoole server. + | + */ + + 'tables' => [ + 'example:1000' => [ + 'name' => 'string:1000', + 'votes' => 'int', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Octane Swoole Cache Table + |-------------------------------------------------------------------------- + | + | While using Swoole, you may leverage the Octane cache, which is powered + | by a Swoole table. You may set the maximum number of rows as well as + | the number of bytes per row using the configuration options below. + | + */ + + 'cache' => [ + 'rows' => 1000, + 'bytes' => 10000, + ], + + /* + |-------------------------------------------------------------------------- + | File Watching + |-------------------------------------------------------------------------- + | + | The following list of files and directories will be watched when using + | the --watch option offered by Octane. If any of the directories and + | files are changed, Octane will automatically reload your workers. + | + */ + + 'watch' => [ + 'app', + 'bootstrap', + 'config/**/*.php', + 'database/**/*.php', + 'public/**/*.php', + 'resources/**/*.php', + 'routes', + 'composer.lock', + '.env', + ], + + /* + |-------------------------------------------------------------------------- + | Garbage Collection Threshold + |-------------------------------------------------------------------------- + | + | When executing long-lived PHP scripts such as Octane, memory can build + | up before being cleared by PHP. You can force Octane to run garbage + | collection if your application consumes this amount of megabytes. + | + */ + + 'garbage' => 50, + + /* + |-------------------------------------------------------------------------- + | Maximum Execution Time + |-------------------------------------------------------------------------- + | + | The following setting configures the maximum execution time for requests + | being handled by Octane. You may set this value to 0 to indicate that + | there isn't a specific time limit on Octane request execution time. + | + */ + + 'max_execution_time' => 120, + +]; \ No newline at end of file