From e8ffdead39af51303bf696a8720fda4f72b5cc37 Mon Sep 17 00:00:00 2001 From: Sami Mazouz Date: Mon, 1 Nov 2021 15:41:19 +0100 Subject: [PATCH] feat: Allow registering settings as `Less` config vars through Settings Extender (#3011) Co-authored-by: Alexander Skvortsov <38059171+askvortsov1@users.noreply.github.com> --- framework/core/src/Extend/Settings.php | 37 +++++++++++++ .../core/src/Forum/ForumServiceProvider.php | 6 +- .../core/src/Forum/ValidateCustomLess.php | 33 +++++++++-- .../src/Frontend/FrontendServiceProvider.php | 40 +++++++++++--- .../src/Frontend/RecompileFrontendAssets.php | 1 + .../src/Settings/SettingsServiceProvider.php | 5 +- .../core/tests/fixtures/less/config.less | 3 + .../integration/extenders/SettingsTest.php | 55 +++++++++++++++++++ 8 files changed, 163 insertions(+), 17 deletions(-) create mode 100644 framework/core/tests/fixtures/less/config.less diff --git a/framework/core/src/Extend/Settings.php b/framework/core/src/Extend/Settings.php index 24e1689a1..986de3e33 100644 --- a/framework/core/src/Extend/Settings.php +++ b/framework/core/src/Extend/Settings.php @@ -21,6 +21,7 @@ class Settings implements ExtenderInterface { private $settings = []; private $defaults = []; + private $lessConfigs = []; /** * Serialize a setting value to the ForumSerializer attributes. @@ -61,6 +62,28 @@ class Settings implements ExtenderInterface return $this; } + /** + * Register a setting as a LESS configuration variable. + * + * @param string $configName: The name of the configuration variable, in hyphen case. + * @param string $key: The key of the setting. + * @param string|callable|null $callback: Optional callback to modify the value. + * + * The callback can be a closure or an invokable class, and should accept: + * - mixed $value: The value of the setting. + * + * The callable should return: + * - mixed $value: The modified value. + * + * @return self + */ + public function registerLessConfigVar(string $configName, string $key, $callback = null): self + { + $this->lessConfigs[$configName] = compact('key', 'callback'); + + return $this; + } + public function extend(Container $container, Extension $extension = null) { if (! empty($this->defaults)) { @@ -97,5 +120,19 @@ class Settings implements ExtenderInterface } ); } + + if (! empty($this->lessConfigs)) { + $container->extend('flarum.less.config', function (array $existingConfig, Container $container) { + $config = $this->lessConfigs; + + foreach ($config as $var => $data) { + if (isset($data['callback'])) { + $config[$var]['callback'] = ContainerUtil::wrapCallback($data['callback'], $container); + } + } + + return array_merge($existingConfig, $config); + }); + } } } diff --git a/framework/core/src/Forum/ForumServiceProvider.php b/framework/core/src/Forum/ForumServiceProvider.php index 6cdd80908..3b0a952fe 100644 --- a/framework/core/src/Forum/ForumServiceProvider.php +++ b/framework/core/src/Forum/ForumServiceProvider.php @@ -163,7 +163,8 @@ class ForumServiceProvider extends AbstractServiceProvider $validator = new ValidateCustomLess( $container->make('flarum.assets.forum'), $container->make('flarum.locales'), - $container + $container, + $container->make('flarum.less.config') ); $validator->whenSettingsSaved($event); } @@ -175,7 +176,8 @@ class ForumServiceProvider extends AbstractServiceProvider $validator = new ValidateCustomLess( $container->make('flarum.assets.forum'), $container->make('flarum.locales'), - $container + $container, + $container->make('flarum.less.config') ); $validator->whenSettingsSaving($event); } diff --git a/framework/core/src/Forum/ValidateCustomLess.php b/framework/core/src/Forum/ValidateCustomLess.php index dc6e529d8..85cfd8b95 100644 --- a/framework/core/src/Forum/ValidateCustomLess.php +++ b/framework/core/src/Forum/ValidateCustomLess.php @@ -43,20 +43,21 @@ class ValidateCustomLess protected $container; /** - * @param Assets $assets - * @param LocaleManager $locales - * @param Container $container + * @var array */ - public function __construct(Assets $assets, LocaleManager $locales, Container $container) + protected $customLessSettings; + + public function __construct(Assets $assets, LocaleManager $locales, Container $container, array $customLessSettings = []) { $this->assets = $assets; $this->locales = $locales; $this->container = $container; + $this->customLessSettings = $customLessSettings; } public function whenSettingsSaving(Saving $event) { - if (! isset($event->settings['custom_less'])) { + if (! isset($event->settings['custom_less']) && ! $this->hasDirtyCustomLessSettings($event)) { return; } @@ -95,7 +96,7 @@ class ValidateCustomLess public function whenSettingsSaved(Saved $event) { - if (! isset($event->settings['custom_less'])) { + if (! isset($event->settings['custom_less']) && ! $this->hasDirtyCustomLessSettings($event)) { return; } @@ -105,4 +106,24 @@ class ValidateCustomLess $this->assets->makeLocaleCss($locale)->flush(); } } + + /** + * @param Saved|Saving $event + * @return bool + */ + protected function hasDirtyCustomLessSettings($event): bool + { + if (empty($this->customLessSettings)) { + return false; + } + + $dirtySettings = array_intersect( + array_keys($event->settings), + array_map(function ($setting) { + return $setting['key']; + }, $this->customLessSettings) + ); + + return ! empty($dirtySettings); + } } diff --git a/framework/core/src/Frontend/FrontendServiceProvider.php b/framework/core/src/Frontend/FrontendServiceProvider.php index f8cde1689..9506c9755 100644 --- a/framework/core/src/Frontend/FrontendServiceProvider.php +++ b/framework/core/src/Frontend/FrontendServiceProvider.php @@ -106,6 +106,29 @@ class FrontendServiceProvider extends AbstractServiceProvider ]; } ); + + $this->container->singleton('flarum.less.config', function (Container $container) { + return [ + 'config-primary-color' => [ + 'key' => 'theme_primary_color', + ], + 'config-secondary-color' => [ + 'key' => 'theme_secondary_color', + ], + 'config-dark-mode' => [ + 'key' => 'theme_dark_mode', + 'callback' => function ($value) { + return $value ? 'true' : 'false'; + }, + ], + 'config-colored-header' => [ + 'key' => 'theme_colored_header', + 'callback' => function ($value) { + return $value ? 'true' : 'false'; + }, + ], + ]; + }); } /** @@ -132,17 +155,18 @@ class FrontendServiceProvider extends AbstractServiceProvider private function addLessVariables(SourceCollector $sources) { $sources->addString(function () { + $vars = $this->container->make('flarum.less.config'); $settings = $this->container->make(SettingsRepositoryInterface::class); - $vars = [ - 'config-primary-color' => $settings->get('theme_primary_color', '#000'), - 'config-secondary-color' => $settings->get('theme_secondary_color', '#000'), - 'config-dark-mode' => $settings->get('theme_dark_mode') ? 'true' : 'false', - 'config-colored-header' => $settings->get('theme_colored_header') ? 'true' : 'false' - ]; + return array_reduce(array_keys($vars), function ($string, $name) use ($vars, $settings) { + $var = $vars[$name]; + $value = $settings->get($var['key'], $var['default'] ?? null); - return array_reduce(array_keys($vars), function ($string, $name) use ($vars) { - return $string."@$name: {$vars[$name]};"; + if (isset($var['callback'])) { + $value = $var['callback']($value); + } + + return $string."@$name: {$value};"; }, ''); }); } diff --git a/framework/core/src/Frontend/RecompileFrontendAssets.php b/framework/core/src/Frontend/RecompileFrontendAssets.php index 8a0b9a90d..0c4614957 100644 --- a/framework/core/src/Frontend/RecompileFrontendAssets.php +++ b/framework/core/src/Frontend/RecompileFrontendAssets.php @@ -39,6 +39,7 @@ class RecompileFrontendAssets public function whenSettingsSaved(Saved $event) { + // @deprecated 'theme_' check, to be removed in 2.0 if (preg_grep('/^theme_/i', array_keys($event->settings))) { $this->flushCss(); } diff --git a/framework/core/src/Settings/SettingsServiceProvider.php b/framework/core/src/Settings/SettingsServiceProvider.php index bb2090b03..4f12a8b15 100644 --- a/framework/core/src/Settings/SettingsServiceProvider.php +++ b/framework/core/src/Settings/SettingsServiceProvider.php @@ -22,7 +22,10 @@ class SettingsServiceProvider extends AbstractServiceProvider public function register() { $this->container->singleton('flarum.settings.default', function () { - return new Collection(); + return new Collection([ + 'theme_primary_color' => '#4D698E', + 'theme_secondary_color' => '#4D698E', + ]); }); $this->container->singleton(SettingsRepositoryInterface::class, function (Container $container) { diff --git a/framework/core/tests/fixtures/less/config.less b/framework/core/tests/fixtures/less/config.less new file mode 100644 index 000000000..73b9d1397 --- /dev/null +++ b/framework/core/tests/fixtures/less/config.less @@ -0,0 +1,3 @@ +body { + --custom-config-setting: @custom-config-setting; +} diff --git a/framework/core/tests/integration/extenders/SettingsTest.php b/framework/core/tests/integration/extenders/SettingsTest.php index 454fd370f..588da8da6 100644 --- a/framework/core/tests/integration/extenders/SettingsTest.php +++ b/framework/core/tests/integration/extenders/SettingsTest.php @@ -220,6 +220,61 @@ class SettingsTest extends TestCase $this->assertEquals(null, $value); } + + /** + * @test + */ + public function custom_less_var_does_not_work_by_default() + { + $this->extend( + (new Extend\Frontend('forum')) + ->css(__DIR__.'/../../fixtures/less/config.less'), + ); + + $response = $this->send($this->request('GET', '/')); + + $this->assertEquals(500, $response->getStatusCode()); + } + + /** + * @test + */ + public function custom_less_var_works_if_registered() + { + $this->extend( + (new Extend\Frontend('forum')) + ->css(__DIR__.'/../../fixtures/less/config.less'), + (new Extend\Settings()) + ->registerLessConfigVar('custom-config-setting', 'custom-prefix.custom_setting') + ); + + $response = $this->send($this->request('GET', '/')); + + $cssFilePath = $this->app()->getContainer()->make('filesystem')->disk('flarum-assets')->path('forum.css'); + $this->assertStringContainsString('--custom-config-setting:customValue', file_get_contents($cssFilePath)); + + $this->assertEquals(200, $response->getStatusCode()); + } + + /** + * @test + */ + public function cant_save_setting_if_invalid_less_var() + { + $this->extend( + (new Extend\Settings()) + ->registerLessConfigVar('custom-config-setting2', 'custom-prefix.custom_setting2') + ); + + $response = $this->send($this->request('POST', '/api/settings', [ + 'authenticatedAs' => 1, + 'json' => [ + 'custom-prefix.custom_setting2' => '@muralf' + ], + ])); + + $this->assertEquals(422, $response->getStatusCode()); + } } class CustomInvokableClass