Skip to content

Commit be56304

Browse files
committed
Merge branch 'develop' into task/FOUR-26777
2 parents 5712645 + d3cb6b9 commit be56304

File tree

84 files changed

+1650
-726
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

84 files changed

+1650
-726
lines changed

ProcessMaker/Application.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,25 @@
55
use Igaster\LaravelTheme\Facades\Theme;
66
use Illuminate\Filesystem\Filesystem;
77
use Illuminate\Foundation\Application as IlluminateApplication;
8+
use Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables;
9+
use Illuminate\Foundation\Bootstrap\RegisterProviders;
810
use Illuminate\Foundation\PackageManifest;
11+
use Illuminate\Support\Env;
12+
use Illuminate\Support\Facades\App;
913
use Illuminate\Support\Facades\Auth;
14+
use Illuminate\Support\Facades\Config;
15+
use ProcessMaker\Multitenancy\Tenant;
16+
use ProcessMaker\Multitenancy\TenantBootstrapper;
1017

1118
/**
1219
* Class Application.
1320
*/
1421
class Application extends IlluminateApplication
1522
{
23+
public $overrideTenantId = null;
24+
25+
public $skipCacheEvents = false;
26+
1627
/**
1728
* Sets the timezone for the application and for php with the specified timezone.
1829
*
@@ -90,4 +101,15 @@ public function registerConfiguredProviders()
90101

91102
parent::registerConfiguredProviders();
92103
}
104+
105+
public function bootstrapWith(array $bootstrappers)
106+
{
107+
// Insert TenantBootstrapper after LoadEnvironmentVariables
108+
if ($bootstrappers[0] !== LoadEnvironmentVariables::class) {
109+
throw new \Exception('LoadEnvironmentVariables is not the first bootstrapper. Did a laravel upgrade change this?');
110+
}
111+
array_splice($bootstrappers, 1, 0, [TenantBootstrapper::class]);
112+
113+
return parent::bootstrapWith($bootstrappers);
114+
}
93115
}

ProcessMaker/Console/Commands/ProcessMakerLicenseRemove.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ class ProcessMakerLicenseRemove extends Command
3434
*/
3535
public function handle()
3636
{
37-
if (Storage::disk('root')->exists('license.json')) {
37+
if (Storage::disk('local')->exists('license.json')) {
3838
if ($this->option('force') || $this->confirm('Are you sure you want to remove the license.json file?')) {
39-
Storage::disk('root')->delete('license.json');
39+
Storage::disk('local')->delete('license.json');
4040
$this->info('license.json removed successfully!');
4141

4242
$this->info('Calling package:discover to update the package cache with enabled packages');

ProcessMaker/Console/Commands/ProcessMakerLicenseUpdate.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public function handle()
4040
return 1;
4141
}
4242

43-
Storage::disk('root')->put('license.json', $content);
43+
Storage::disk('local')->put('license.json', $content);
4444

4545
$this->info('Calling package:discover to update the package cache with enabled packages');
4646
Artisan::call('package:discover');

ProcessMaker/Console/Commands/TenantsCreate.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,9 @@ public function handle()
198198
$this->line('- Run migrations and seed the database');
199199
$this->line('- Run the install command for each package');
200200
$this->line('- Run artisan upgrade');
201-
$this->line('- Install passport by calling passport:install');
201+
$this->line('- Install passport by calling passport:install (create the default clients');
202+
$this->line('- Reset the admin password with auth:set-password');
203+
$this->line('- Run processmaker:initialize-script-microservice');
202204
$this->info("For example, `TENANT={$tenant->id} php artisan migrate:fresh --seed`");
203205
}
204206
}

ProcessMaker/Console/Commands/TenantsList.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class TenantsList extends Command
1515
*
1616
* @var string
1717
*/
18-
protected $signature = 'tenants:list {--ids : Only output the ids}';
18+
protected $signature = 'tenants:list {--ids : Only output the ids} {--json : Output the tenants as JSON}';
1919

2020
/**
2121
* The console command description.
@@ -40,6 +40,12 @@ public function handle()
4040
return;
4141
}
4242

43+
if ($this->option('json')) {
44+
$this->line(json_encode($tenants->toArray(), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
45+
46+
return;
47+
}
48+
4349
$formattedTenants = $tenants->map(function ($tenant) {
4450
$config = $tenant->config;
4551

ProcessMaker/Console/Commands/TenantsVerify.php

Lines changed: 61 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@
33
namespace ProcessMaker\Console\Commands;
44

55
use Illuminate\Console\Command;
6+
use Illuminate\Contracts\Encryption\DecryptException;
67
use Illuminate\Support\Facades\Config;
8+
use Illuminate\Support\Facades\Crypt;
9+
use Illuminate\Support\Facades\File;
710
use Illuminate\Support\Facades\Log;
11+
use ProcessMaker\Models\EnvironmentVariable;
12+
use ProcessMaker\Models\User;
813
use Spatie\Multitenancy\Models\Tenant;
914

1015
class TenantsVerify extends Command
@@ -14,7 +19,7 @@ class TenantsVerify extends Command
1419
*
1520
* @var string
1621
*/
17-
protected $signature = 'tenants:verify {--verify-against= : The tenant ID to verify against}';
22+
protected $signature = 'tenants:verify';
1823

1924
/**
2025
* The console command description.
@@ -23,17 +28,6 @@ class TenantsVerify extends Command
2328
*/
2429
protected $description = 'Verify tenant configuration and storage paths';
2530

26-
/**
27-
* Strip protocol from URL
28-
*
29-
* @param string $url
30-
* @return string
31-
*/
32-
private function stripProtocol(string $url): string
33-
{
34-
return preg_replace('#^https?://#', '', $url);
35-
}
36-
3731
/**
3832
* Execute the console command.
3933
*
@@ -46,85 +40,72 @@ public function handle()
4640
$currentTenant = app('currentTenant');
4741
}
4842

49-
$verifyAgainstId = $this->option('verify-against');
50-
51-
if (!$currentTenant) {
52-
$this->error('No current tenant found');
43+
if (config('app.multitenancy') && !$currentTenant) {
44+
$this->error('Multitenancy enabled but no current tenant found.');
5345

5446
return;
5547
}
5648

57-
$this->info('Current Tenant ID: ' . $currentTenant->id);
58-
$this->line('----------------------------------------');
49+
$this->info('Current Tenant ID: ' . ($currentTenant?->id ?? 'NONE'));
5950

60-
// Expected paths and configurations
61-
$expectedStoragePath = base_path('storage/tenant_' . $currentTenant->id);
62-
$actualConfigs = [
63-
'filesystems.disks.local.root' => storage_path('app'),
64-
'cache.prefix' => config('cache.prefix'),
65-
'app.url' => config('app.url'),
66-
'script-runner-microservice.callback' => config('script-runner-microservice.callback'),
51+
$paths = [
52+
['Storage Path', storage_path()],
53+
['Config Cache Path', app()->getCachedConfigPath()],
54+
['Lang Path', lang_path()],
6755
];
6856

69-
// Display current values
70-
$this->info('Current Storage Path: ' . storage_path());
71-
$this->line('----------------------------------------');
72-
73-
$this->info('Current Configuration Values:');
74-
foreach ($actualConfigs as $key => $expectedValue) {
75-
$currentValue = config($key);
76-
$this->line("{$key}: {$currentValue}");
77-
}
78-
79-
// If verify-against is specified, perform verification
80-
if ($verifyAgainstId) {
81-
$this->line('----------------------------------------');
82-
$this->info("Verifying against tenant ID: {$verifyAgainstId}");
57+
// Display paths in a nice table
58+
$this->table(['Path', 'Value'], $paths);
59+
60+
$configs = [
61+
'app.key',
62+
'app.url',
63+
'app.instance',
64+
'cache.prefix',
65+
'database.redis.options.prefix',
66+
'cache.stores.cache_settings.prefix',
67+
'script-runner-microservice.callback',
68+
'database.connections.processmaker.database',
69+
'logging.channels.daily.path',
70+
'filesystems.disks.public.root',
71+
'filesystems.disks.local.root',
72+
'filesystems.disks.lang.root',
73+
];
8374

84-
$expectedStoragePath = base_path('storage/tenant_' . $verifyAgainstId);
85-
$expectedConfigs = [
86-
'filesystems.disks.local.root' => $expectedStoragePath . '/app',
87-
'cache.prefix' => 'tenant_id_' . $verifyAgainstId,
88-
'app.url' => config('app.url'),
75+
$configs = array_map(function ($config) {
76+
return [
77+
$config,
78+
config($config),
8979
];
90-
91-
$hasMismatch = false;
92-
93-
// Verify storage path
94-
if (storage_path() !== $expectedStoragePath) {
95-
$this->error('Storage path mismatch!');
96-
$this->line("Expected: {$expectedStoragePath}");
97-
$this->line('Current: ' . storage_path());
98-
$hasMismatch = true;
99-
}
100-
101-
// Verify tenant URL if tenant exists
102-
$verifyTenant = Tenant::find($verifyAgainstId);
103-
if ($verifyTenant && $verifyTenant->domain !== $this->stripProtocol(config('app.url'))) {
104-
$this->error('Tenant URL mismatch!');
105-
$this->line("Expected: {$verifyTenant->domain}");
106-
$this->line('Current: ' . config('app.url'));
107-
$hasMismatch = true;
108-
}
109-
110-
// Verify config values
111-
foreach ($expectedConfigs as $key => $expectedValue) {
112-
$currentValue = config($key);
113-
if ($currentValue !== $expectedValue) {
114-
$this->error("Config mismatch for {$key}!");
115-
$this->line("Expected: {$expectedValue}");
116-
$this->line("Current: {$currentValue}");
117-
$hasMismatch = true;
118-
}
80+
}, $configs);
81+
82+
// Display configs in a nice table
83+
$this->table(['Config', 'Value'], $configs);
84+
85+
$env = EnvironmentVariable::first();
86+
if (!$env) {
87+
$decrypted = 'No environment variables found to test decryption';
88+
} else {
89+
$encryptedValue = $env->getAttributes()['value'];
90+
try {
91+
Crypt::decryptString($encryptedValue);
92+
$decrypted = 'OK';
93+
} catch (DecryptException $e) {
94+
$decrypted = 'FAILED! ' . $e->getMessage();
11995
}
120-
121-
if (!$hasMismatch) {
122-
$this->info('All configurations match as expected!');
123-
}
124-
125-
return $hasMismatch ? Command::FAILURE : Command::SUCCESS;
12696
}
12797

128-
return Command::SUCCESS;
98+
$other = [
99+
['Landlord Config Cache Path', base_path('bootstrap/cache/config.php')],
100+
['Landlord Config Is Cached', File::exists(base_path('bootstrap/cache/config.php')) ? 'Yes' : 'No'],
101+
['Tenant Config Cache Path', app()->getCachedConfigPath()],
102+
['Tenant Config Is Cached', File::exists(app()->getCachedConfigPath()) ? 'Yes' : 'No'],
103+
['First username (database check)', User::first()?->username ?? 'No users found'],
104+
['Decrypted check', substr($decrypted, 0, 50)],
105+
['Original App URL (landlord)', $currentTenant?->getOriginalValue('APP_URL') ?? config('app.url')],
106+
];
107+
108+
// Display other in a nice table
109+
$this->table(['Other', 'Value'], $other);
129110
}
130111
}

ProcessMaker/Console/Kernel.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ protected function schedule(Schedule $schedule)
8888
$schedule->command('metrics:clear')->cron("*/{$clearInterval} * * * *");
8989
break;
9090
}
91+
92+
// 5 minutes is recommended in https://laravel.com/docs/12.x/horizon#metrics
93+
$schedule->command('horizon:snapshot')->everyFiveMinutes();
9194
}
9295

9396
/**

ProcessMaker/Exception/Handler.php

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ class Handler extends ExceptionHandler
4343
*/
4444
public function report(Throwable $exception)
4545
{
46+
if (!App::getFacadeRoot()) {
47+
error_log(get_class($exception) . ': ' . $exception->getMessage());
48+
49+
return;
50+
}
4651
if (App::environment() == 'testing' && env('TESTING_VERBOSE')) {
4752
// If we're verbose, we should print ALL Exceptions to the screen
4853
echo $exception->getMessage() . "\n";
@@ -146,18 +151,4 @@ protected function convertExceptionToArray(Throwable $e)
146151
'message' => $this->isHttpException($e) ? $e->getMessage() : 'Server Error',
147152
];
148153
}
149-
150-
/**
151-
* Errors in the console must have an exit status > 0 for CI to see it as an error.
152-
* This prevents the symfony console from handling the error and returning an
153-
* exit status of 0, which it does by default surprisingly.
154-
*
155-
* @param \Symfony\Component\Console\Output\OutputInterface $output
156-
* @param Throwable $e
157-
* @return void
158-
*/
159-
public function renderForConsole($output, Throwable $e)
160-
{
161-
throw $e;
162-
}
163154
}

ProcessMaker/Exception/MultitenancyAccessedLandlord.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,21 @@
55
use Exception;
66
use Illuminate\Http\Request;
77
use Illuminate\Http\Response;
8+
use ProcessMaker\Facades\Metrics;
89

910
class MultitenancyAccessedLandlord extends Exception
1011
{
1112
public function render(Request $request): Response
1213
{
14+
// If we're trying to access the /metrics route, collect landlord metrics and render them
15+
if ($request->path() === 'metrics') {
16+
Metrics::collectQueueMetrics();
17+
18+
return response(Metrics::renderMetrics(), 200, [
19+
'Content-Type' => 'text/plain; version=0.0.4',
20+
]);
21+
}
22+
1323
return response()->view('multitenancy.landlord-landing-page');
1424
}
1525

ProcessMaker/Http/Controllers/Admin/QueuesController.php

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,28 @@
55
use Illuminate\Auth\Access\AuthorizationException;
66
use ProcessMaker\Events\QueueManagementAccessed;
77
use ProcessMaker\Http\Controllers\Controller;
8+
use ProcessMaker\Providers\TenantQueueServiceProvider;
89

910
class QueuesController extends Controller
1011
{
1112
public function index()
1213
{
13-
if (auth()->user()->is_administrator) {
14-
// Register the Event
15-
QueueManagementAccessed::dispatch();
14+
if (!auth()->user()->is_administrator) {
15+
throw new AuthorizationException();
16+
}
1617

17-
return view('admin.queues.index');
18+
if (config('app.multitenancy')) {
19+
if (!TenantQueueServiceProvider::allowAllTenats()) {
20+
// Its multitenancy and they don't have access to all tenants so
21+
// redirect to the tenant-filtered queue management page.
22+
// Otherwise, show the horizon queue manager.
23+
return redirect()->route('tenant-queue.index');
24+
}
1825
}
1926

20-
throw new AuthorizationException();
27+
// Register the Event
28+
QueueManagementAccessed::dispatch();
29+
30+
return view('admin.queues.index');
2131
}
2232
}

0 commit comments

Comments
 (0)