From 8372363cc2be79548df75fbc720299516477c98f Mon Sep 17 00:00:00 2001 From: Sami Mazouz Date: Tue, 14 Mar 2023 21:53:16 +0100 Subject: [PATCH] feat: conditional extenders (#3759) --- framework/core/src/Extend/Conditional.php | 61 +++++++ framework/core/src/Extension/Extension.php | 8 +- framework/core/src/User/Access/Gate.php | 4 +- .../integration/extenders/ConditionalTest.php | 162 ++++++++++++++++++ .../integration/extenders/LocalesTest.php | 8 +- 5 files changed, 233 insertions(+), 10 deletions(-) create mode 100644 framework/core/src/Extend/Conditional.php create mode 100644 framework/core/tests/integration/extenders/ConditionalTest.php diff --git a/framework/core/src/Extend/Conditional.php b/framework/core/src/Extend/Conditional.php new file mode 100644 index 000000000..b9bbc764a --- /dev/null +++ b/framework/core/src/Extend/Conditional.php @@ -0,0 +1,61 @@ + + */ + protected $conditions = []; + + /** + * @param ExtenderInterface[] $extenders + */ + public function whenExtensionEnabled(string $extensionId, array $extenders): self + { + return $this->when(function (ExtensionManager $extensions) use ($extensionId) { + return $extensions->isEnabled($extensionId); + }, $extenders); + } + + /** + * @param bool|callable $condition + * @param ExtenderInterface[] $extenders + */ + public function when($condition, array $extenders): self + { + $this->conditions[] = [ + 'condition' => $condition, + 'extenders' => $extenders, + ]; + + return $this; + } + + public function extend(Container $container, Extension $extension = null) + { + foreach ($this->conditions as $condition) { + if (is_callable($condition['condition'])) { + $condition['condition'] = $container->call($condition['condition']); + } + + if ($condition['condition']) { + foreach ($condition['extenders'] as $extender) { + $extender->extend($container, $extension); + } + } + } + } +} diff --git a/framework/core/src/Extension/Extension.php b/framework/core/src/Extension/Extension.php index 15892470e..6971ecb18 100644 --- a/framework/core/src/Extension/Extension.php +++ b/framework/core/src/Extension/Extension.php @@ -414,19 +414,19 @@ class Extension implements Arrayable $links['source'] = $sourceUrl; } - if (($discussUrl = $this->composerJsonAttribute('support.forum'))) { + if ($discussUrl = $this->composerJsonAttribute('support.forum')) { $links['discuss'] = $discussUrl; } - if (($documentationUrl = $this->composerJsonAttribute('support.docs'))) { + if ($documentationUrl = $this->composerJsonAttribute('support.docs')) { $links['documentation'] = $documentationUrl; } - if (($websiteUrl = $this->composerJsonAttribute('homepage'))) { + if ($websiteUrl = $this->composerJsonAttribute('homepage')) { $links['website'] = $websiteUrl; } - if (($supportEmail = $this->composerJsonAttribute('support.email'))) { + if ($supportEmail = $this->composerJsonAttribute('support.email')) { $links['support'] = "mailto:$supportEmail"; } diff --git a/framework/core/src/User/Access/Gate.php b/framework/core/src/User/Access/Gate.php index 5970b3043..e8eaab1a3 100644 --- a/framework/core/src/User/Access/Gate.php +++ b/framework/core/src/User/Access/Gate.php @@ -65,7 +65,7 @@ class Gate $appliedPolicies = []; if ($model) { - $modelClasses = is_string($model) ? [$model] : array_merge(class_parents(($model)), [get_class($model)]); + $modelClasses = is_string($model) ? [$model] : array_merge(class_parents($model), [get_class($model)]); foreach ($modelClasses as $class) { $appliedPolicies = array_merge($appliedPolicies, $this->getPolicies($class)); @@ -87,7 +87,7 @@ class Gate // If no policy covered this permission query, we will only grant // the permission if the actor's groups have it. Otherwise, we will // not allow the user to perform this action. - if ($actor->isAdmin() || ($actor->hasPermission($ability))) { + if ($actor->isAdmin() || $actor->hasPermission($ability)) { return true; } diff --git a/framework/core/tests/integration/extenders/ConditionalTest.php b/framework/core/tests/integration/extenders/ConditionalTest.php new file mode 100644 index 000000000..ec96423e1 --- /dev/null +++ b/framework/core/tests/integration/extenders/ConditionalTest.php @@ -0,0 +1,162 @@ +extend( + (new Extend\Conditional()) + ->when(true, [ + (new Extend\ApiSerializer(ForumSerializer::class)) + ->attributes(function () { + return [ + 'customConditionalAttribute' => true + ]; + }) + ]) + ); + + $this->app(); + + $response = $this->send( + $this->request('GET', '/api', [ + 'authenticatedAs' => 1, + ]) + ); + + $payload = json_decode($response->getBody()->getContents(), true); + + $this->assertArrayHasKey('customConditionalAttribute', $payload['data']['attributes']); + } + + /** @test */ + public function conditional_does_not_work_if_condition_is_primitive_false() + { + $this->extend( + (new Extend\Conditional()) + ->when(false, [ + (new Extend\ApiSerializer(ForumSerializer::class)) + ->attributes(function () { + return [ + 'customConditionalAttribute' => true + ]; + }) + ]) + ); + + $this->app(); + + $response = $this->send( + $this->request('GET', '/api', [ + 'authenticatedAs' => 1, + ]) + ); + + $payload = json_decode($response->getBody()->getContents(), true); + + $this->assertArrayNotHasKey('customConditionalAttribute', $payload['data']['attributes']); + } + + /** @test */ + public function conditional_works_if_condition_is_callable_true() + { + $this->extend( + (new Extend\Conditional()) + ->when(function () { + return true; + }, [ + (new Extend\ApiSerializer(ForumSerializer::class)) + ->attributes(function () { + return [ + 'customConditionalAttribute' => true + ]; + }) + ]) + ); + + $this->app(); + + $response = $this->send( + $this->request('GET', '/api', [ + 'authenticatedAs' => 1, + ]) + ); + + $payload = json_decode($response->getBody()->getContents(), true); + + $this->assertArrayHasKey('customConditionalAttribute', $payload['data']['attributes']); + } + + /** @test */ + public function conditional_does_not_work_if_condition_is_callable_false() + { + $this->extend( + (new Extend\Conditional()) + ->when(function () { + return false; + }, [ + (new Extend\ApiSerializer(ForumSerializer::class)) + ->attributes(function () { + return [ + 'customConditionalAttribute' => true + ]; + }) + ]) + ); + + $this->app(); + + $response = $this->send( + $this->request('GET', '/api', [ + 'authenticatedAs' => 1, + ]) + ); + + $payload = json_decode($response->getBody()->getContents(), true); + + $this->assertArrayNotHasKey('customConditionalAttribute', $payload['data']['attributes']); + } + + /** @test */ + public function conditional_injects_dependencies_to_condition_callable() + { + $this->expectNotToPerformAssertions(); + + $this->extend( + (new Extend\Conditional()) + ->when(function (?ExtensionManager $extensions) { + if (! $extensions) { + throw new Exception('ExtensionManager not injected'); + } + }, [ + (new Extend\ApiSerializer(ForumSerializer::class)) + ->attributes(function () { + return [ + 'customConditionalAttribute' => true + ]; + }) + ]) + ); + + $this->app(); + } +} diff --git a/framework/core/tests/integration/extenders/LocalesTest.php b/framework/core/tests/integration/extenders/LocalesTest.php index 0e91994cf..8920d4f97 100644 --- a/framework/core/tests/integration/extenders/LocalesTest.php +++ b/framework/core/tests/integration/extenders/LocalesTest.php @@ -42,7 +42,7 @@ class LocalesTest extends TestCase public function custom_translation_exists_if_added() { $this->extend( - (new Extend\Locales(dirname(__FILE__, 3).'/fixtures/locales')) + new Extend\Locales(dirname(__FILE__, 3).'/fixtures/locales') ); $this->app()->getContainer()->make('flarum.locales'); @@ -57,7 +57,7 @@ class LocalesTest extends TestCase public function custom_translation_exists_if_added_with_intl_suffix() { $this->extend( - (new Extend\Locales(dirname(__FILE__, 3).'/fixtures/locales')) + new Extend\Locales(dirname(__FILE__, 3).'/fixtures/locales') ); $this->app()->getContainer()->make('flarum.locales'); @@ -72,7 +72,7 @@ class LocalesTest extends TestCase public function messageformat_works_in_translations() { $this->extend( - (new Extend\Locales(dirname(__FILE__, 3).'/fixtures/locales')) + new Extend\Locales(dirname(__FILE__, 3).'/fixtures/locales') ); $this->app()->getContainer()->make('flarum.locales'); @@ -87,7 +87,7 @@ class LocalesTest extends TestCase public function laravel_interface_methods_work() { $this->extend( - (new Extend\Locales(dirname(__FILE__, 3).'/fixtures/locales')) + new Extend\Locales(dirname(__FILE__, 3).'/fixtures/locales') ); $this->app()->getContainer()->make('flarum.locales');