diff --git a/ProcessMaker/Managers/ControllerAddonsRegistry.php b/ProcessMaker/Managers/ControllerAddonsRegistry.php new file mode 100644 index 0000000000..2159b56523 --- /dev/null +++ b/ProcessMaker/Managers/ControllerAddonsRegistry.php @@ -0,0 +1,45 @@ +addons[] = $config; + } + + /** + * Get configured addons for a controller. + */ + public function getAddons(string $scope, string $method, array $data): array + { + $addons = []; + + foreach ($this->addons as $addon) { + if ($addon['method'] !== $method || $addon['scope'] !== $scope) { + continue; + } + + if (isset($addon['data']) && is_callable($addon['data'])) { + $data = call_user_func($addon['data'], $data); + } + + $addon['content'] = isset($addon['view']) && !isset($addon['content']) + ? view($addon['view'], $data)->render() + : (isset($addon['content']) ? $addon['content'] : ''); + $addon['script'] = isset($addon['script']) && is_string($addon['script']) + ? view($addon['script'], $data)->render() + : ''; + $addons[] = $addon; + } + + return $addons; + } +} diff --git a/ProcessMaker/Providers/ProcessMakerServiceProvider.php b/ProcessMaker/Providers/ProcessMakerServiceProvider.php index 55ef2cfa55..04d81fbeda 100644 --- a/ProcessMaker/Providers/ProcessMakerServiceProvider.php +++ b/ProcessMaker/Providers/ProcessMakerServiceProvider.php @@ -136,6 +136,10 @@ public function register(): void return new Managers\LoginManager(); }); + $this->app->singleton(Managers\ControllerAddonsRegistry::class, function () { + return new Managers\ControllerAddonsRegistry(); + }); + /* * Maps our Index Manager as a singleton. The Index Manager is used * to manage customizations to the search indexer. diff --git a/ProcessMaker/Traits/HasControllerAddons.php b/ProcessMaker/Traits/HasControllerAddons.php index 0889ac1d0d..374bb9c36d 100644 --- a/ProcessMaker/Traits/HasControllerAddons.php +++ b/ProcessMaker/Traits/HasControllerAddons.php @@ -2,10 +2,10 @@ namespace ProcessMaker\Traits; +use ProcessMaker\Managers\ControllerAddonsRegistry; + trait HasControllerAddons { - private static $addons = []; - /** * Get configured addons for this controller * @@ -16,26 +16,7 @@ trait HasControllerAddons */ protected function getPluginAddons($method, array $data) { - if (!isset(static::$addons)) { - return; - } - - $addons = []; - foreach (static::$addons as $addon) { - // The addon must have the requested method and must be associated to the current controller - if ($addon['method'] === $method && $addon['scope'] === get_class($this)) { - if (isset($addon['data']) && is_callable($addon['data'])) { - $data = call_user_func($addon['data'], $data); - } - $addon['content'] = isset($addon['view']) && !isset($addon['content']) - ? view($addon['view'], $data)->render() : (isset($addon['content']) - ? $addon['content'] : ''); - $addon['script'] = isset($addon['script']) && is_string($addon['script']) ? view($addon['script'], $data)->render() : ''; - $addons[] = $addon; - } - } - - return $addons; + return app(ControllerAddonsRegistry::class)->getAddons(static::class, $method, $data); } /** @@ -47,8 +28,6 @@ protected function getPluginAddons($method, array $data) */ public static function registerAddon(array $config) { - // Add the controller to which the addon is attached - $config['scope'] = static::class; - static::$addons[] = $config; + app(ControllerAddonsRegistry::class)->register(static::class, $config); } } diff --git a/tests/unit/ProcessMaker/Managers/ControllerAddonsRegistryTest.php b/tests/unit/ProcessMaker/Managers/ControllerAddonsRegistryTest.php new file mode 100644 index 0000000000..bc5d018391 --- /dev/null +++ b/tests/unit/ProcessMaker/Managers/ControllerAddonsRegistryTest.php @@ -0,0 +1,136 @@ +registry = new ControllerAddonsRegistry(); + $this->bindRegistryInContainer(); + } + + protected function tearDown(): void + { + Container::setInstance($this->previousContainer); + + parent::tearDown(); + } + + public function test_register_addon_is_retrieved_for_matching_scope_and_method(): void + { + $this->registry->register(UserController::class, [ + 'id' => 'test-addon', + 'method' => 'edit', + 'title' => 'Test Addon', + 'content' => 'addon-content', + ]); + + $addons = $this->registry->getAddons(UserController::class, 'edit', []); + + $this->assertCount(1, $addons); + $this->assertSame('test-addon', $addons[0]['id']); + $this->assertSame('addon-content', $addons[0]['content']); + } + + public function test_addons_from_other_controllers_are_not_returned(): void + { + $this->registry->register(UserController::class, [ + 'id' => 'user-addon', + 'method' => 'edit', + 'content' => 'user-content', + ]); + $this->registry->register('Other\\Controller', [ + 'id' => 'other-addon', + 'method' => 'edit', + 'content' => 'other-content', + ]); + + $addons = $this->registry->getAddons(UserController::class, 'edit', []); + + $this->assertCount(1, $addons); + $this->assertSame('user-addon', $addons[0]['id']); + } + + public function test_get_plugin_addons_does_not_mutate_registered_addons(): void + { + $this->registry->register(UserController::class, [ + 'id' => 'test-addon', + 'method' => 'edit', + 'content' => 'original-content', + ]); + + $this->registry->getAddons(UserController::class, 'edit', []); + $this->registry->getAddons(UserController::class, 'edit', []); + + $addons = $this->registry->getAddons(UserController::class, 'edit', []); + + $this->assertCount(1, $addons); + $this->assertSame('original-content', $addons[0]['content']); + } + + public function test_register_addon_static_method_delegates_to_registry(): void + { + UserController::registerAddon([ + 'id' => 'static-addon', + 'method' => 'edit.settings', + 'content' => 'settings-content', + ]); + + $controller = new UserController(); + $addons = $this->invokeGetPluginAddons($controller, 'edit.settings', []); + + $this->assertCount(1, $addons); + $this->assertSame('static-addon', $addons[0]['id']); + } + + public function test_callable_data_modifier_is_applied_when_resolving_addons(): void + { + $this->registry->register(UserController::class, [ + 'id' => 'callable-addon', + 'method' => 'edit', + 'content' => 'content', + 'data' => fn (array $data) => array_merge($data, ['extra' => 'value']), + ]); + + $addons = $this->registry->getAddons(UserController::class, 'edit', ['base' => 'data']); + + $this->assertCount(1, $addons); + } + + private function bindRegistryInContainer(): void + { + $this->previousContainer = Container::getInstance(); + + $container = new Container(); + $container->singleton(ControllerAddonsRegistry::class, fn () => $this->registry); + Container::setInstance($container); + + if (!function_exists('app')) { + require_once dirname(__DIR__, 4) . '/vendor/laravel/framework/src/Illuminate/Foundation/helpers.php'; + } + } + + /** + * @return array> + */ + private function invokeGetPluginAddons(object $controller, string $method, array $data): array + { + $reflection = new \ReflectionMethod($controller, 'getPluginAddons'); + + return $reflection->invoke($controller, $method, $data); + } +}