diff --git a/ProcessMaker/Multitenancy/SwitchTenant.php b/ProcessMaker/Multitenancy/SwitchTenant.php index cd35cd353c..72619d3a82 100644 --- a/ProcessMaker/Multitenancy/SwitchTenant.php +++ b/ProcessMaker/Multitenancy/SwitchTenant.php @@ -5,6 +5,7 @@ use Illuminate\Contracts\Routing\UrlGenerator; use Illuminate\Support\Arr; use Illuminate\Support\Env; +use Illuminate\Support\Facades\Storage; use Laravel\Passport\ApiTokenCookieFactory; use Laravel\Passport\ClientRepository; use League\OAuth2\Server\AuthorizationServer; @@ -53,12 +54,25 @@ public function forgetCurrent(): void $app = app(); $app->useStoragePath(base_path('storage')); + // Restore landlord disk roots. Cached adapters still point at the previous + // tenant until flushFilesystemDisks() runs below. + foreach ($this->landlordConfig('filesystems.disks') ?? [] as $disk => $config) { + if (isset($config['root'])) { + $this->setConfig('filesystems.disks.' . $disk . '.root', $config['root']); + } + if (isset($config['url'])) { + $this->setConfig('filesystems.disks.' . $disk . '.url', $config['url']); + } + } + + $app->useLangPath(resource_path('lang')); + $this->setConfig('filesystems.disks.lang.root', lang_path()); + $this->flushFilesystemDisks($app); + $this->setConfig('logging.channels.daily.path', storage_path('logs/processmaker.log')); $app->make('log')->reset(); $app->forgetInstance('log'); - $app->useLangPath(resource_path('lang')); - // app key / encrypter $this->setConfig('app.key', $this->landlordConfig('app.key')); $this->flushTenantSensitiveSingletons($app); @@ -88,6 +102,20 @@ private function flushTenantSensitiveSingletons(Application $app): void } } + /** + * Drop cached filesystem disks so Storage picks up the tenant (or landlord) root. + * + * Horizon/Octane reuse the same worker process across tenants. FilesystemManager + * caches disk adapters, so without this, Storage keeps writing/reading the previous + * root (often landlord storage/app) after config roots were already rewritten. + */ + private function flushFilesystemDisks(Application $app): void + { + $disks = array_keys($this->landlordConfig('filesystems.disks') ?? config('filesystems.disks', [])); + Storage::forgetDisk($disks); + $app->forgetInstance('filesystem'); + } + private function landlordConfig($key) { return Arr::get(self::$landlordValues, $key); @@ -146,6 +174,7 @@ private function overrideConfigs(Application $app, IsTenant $tenant) // Lang path $app->useLangPath(resource_path('lang/tenant_' . $tenant->id)); $this->setConfig('filesystems.disks.lang.root', lang_path()); + $this->flushFilesystemDisks($app); // app key / encrypter $landlordEncrypter = $app->make('encrypter'); diff --git a/tests/unit/ProcessMaker/Multitenancy/SwitchTenantTest.php b/tests/unit/ProcessMaker/Multitenancy/SwitchTenantTest.php index 22a33b5ed4..ab017f1bf7 100644 --- a/tests/unit/ProcessMaker/Multitenancy/SwitchTenantTest.php +++ b/tests/unit/ProcessMaker/Multitenancy/SwitchTenantTest.php @@ -7,6 +7,7 @@ use Illuminate\Broadcasting\BroadcastManager; use Illuminate\Encryption\Encrypter; use Illuminate\Support\Facades\Crypt; +use Illuminate\Support\Facades\Storage; use Laravel\Passport\ClientRepository; use League\OAuth2\Server\AuthorizationServer; use League\OAuth2\Server\ResourceServer; @@ -68,6 +69,33 @@ public function test_make_current_flushes_passport_singletons_and_auth_guards(): } } + public function test_make_current_flushes_cached_filesystem_disks_to_tenant_root(): void + { + // Warm the landlord disk the way a Horizon worker might before the first tenant job. + $landlordPath = Storage::disk('local')->path('import/options.json'); + $this->assertStringContainsString('/storage/app/', $landlordPath); + + $tenant = $this->fakeTenant(); + $switch = new SwitchTenant(); + + try { + $switch->makeCurrent($tenant); + + $tenantPath = Storage::disk('local')->path('import/options.json'); + $this->assertSame( + storage_path('app/import/options.json'), + $tenantPath + ); + $this->assertStringContainsString('/storage/tenant_' . $tenant->id . '/', $tenantPath); + $this->assertStringNotContainsString('/storage/app/', $tenantPath); + } finally { + $switch->forgetCurrent(); + } + + $restoredPath = Storage::disk('local')->path('import/options.json'); + $this->assertSame($landlordPath, $restoredPath); + } + private function fakeTenant(): Tenant { $tenantKey = 'base64:' . base64_encode(Encrypter::generateKey(config('app.cipher')));