diff --git a/apps/files/composer/composer/autoload_classmap.php b/apps/files/composer/composer/autoload_classmap.php index 68cdabb3dcdab..7e896bd51e506 100644 --- a/apps/files/composer/composer/autoload_classmap.php +++ b/apps/files/composer/composer/autoload_classmap.php @@ -58,9 +58,6 @@ 'OCA\\Files\\Event\\LoadSidebar' => $baseDir . '/../lib/Event/LoadSidebar.php', 'OCA\\Files\\Exception\\TransferOwnershipException' => $baseDir . '/../lib/Exception/TransferOwnershipException.php', 'OCA\\Files\\Helper' => $baseDir . '/../lib/Helper.php', - 'OCA\\Files\\Listener\\DeclarativeSettingsGetValueEventListener' => $baseDir . '/../lib/Listener/DeclarativeSettingsGetValueEventListener.php', - 'OCA\\Files\\Listener\\DeclarativeSettingsRegisterFormEventListener' => $baseDir . '/../lib/Listener/DeclarativeSettingsRegisterFormEventListener.php', - 'OCA\\Files\\Listener\\DeclarativeSettingsSetValueEventListener' => $baseDir . '/../lib/Listener/DeclarativeSettingsSetValueEventListener.php', 'OCA\\Files\\Listener\\LoadSearchPluginsListener' => $baseDir . '/../lib/Listener/LoadSearchPluginsListener.php', 'OCA\\Files\\Listener\\LoadSidebarListener' => $baseDir . '/../lib/Listener/LoadSidebarListener.php', 'OCA\\Files\\Listener\\RenderReferenceEventListener' => $baseDir . '/../lib/Listener/RenderReferenceEventListener.php', @@ -77,5 +74,6 @@ 'OCA\\Files\\Service\\TagService' => $baseDir . '/../lib/Service/TagService.php', 'OCA\\Files\\Service\\UserConfig' => $baseDir . '/../lib/Service/UserConfig.php', 'OCA\\Files\\Service\\ViewConfig' => $baseDir . '/../lib/Service/ViewConfig.php', + 'OCA\\Files\\Settings\\DeclarativeAdminSettings' => $baseDir . '/../lib/Settings/DeclarativeAdminSettings.php', 'OCA\\Files\\Settings\\PersonalSettings' => $baseDir . '/../lib/Settings/PersonalSettings.php', ); diff --git a/apps/files/composer/composer/autoload_static.php b/apps/files/composer/composer/autoload_static.php index ca88e773e4aa5..4d84920af65c4 100644 --- a/apps/files/composer/composer/autoload_static.php +++ b/apps/files/composer/composer/autoload_static.php @@ -73,9 +73,6 @@ class ComposerStaticInitFiles 'OCA\\Files\\Event\\LoadSidebar' => __DIR__ . '/..' . '/../lib/Event/LoadSidebar.php', 'OCA\\Files\\Exception\\TransferOwnershipException' => __DIR__ . '/..' . '/../lib/Exception/TransferOwnershipException.php', 'OCA\\Files\\Helper' => __DIR__ . '/..' . '/../lib/Helper.php', - 'OCA\\Files\\Listener\\DeclarativeSettingsGetValueEventListener' => __DIR__ . '/..' . '/../lib/Listener/DeclarativeSettingsGetValueEventListener.php', - 'OCA\\Files\\Listener\\DeclarativeSettingsRegisterFormEventListener' => __DIR__ . '/..' . '/../lib/Listener/DeclarativeSettingsRegisterFormEventListener.php', - 'OCA\\Files\\Listener\\DeclarativeSettingsSetValueEventListener' => __DIR__ . '/..' . '/../lib/Listener/DeclarativeSettingsSetValueEventListener.php', 'OCA\\Files\\Listener\\LoadSearchPluginsListener' => __DIR__ . '/..' . '/../lib/Listener/LoadSearchPluginsListener.php', 'OCA\\Files\\Listener\\LoadSidebarListener' => __DIR__ . '/..' . '/../lib/Listener/LoadSidebarListener.php', 'OCA\\Files\\Listener\\RenderReferenceEventListener' => __DIR__ . '/..' . '/../lib/Listener/RenderReferenceEventListener.php', @@ -92,6 +89,7 @@ class ComposerStaticInitFiles 'OCA\\Files\\Service\\TagService' => __DIR__ . '/..' . '/../lib/Service/TagService.php', 'OCA\\Files\\Service\\UserConfig' => __DIR__ . '/..' . '/../lib/Service/UserConfig.php', 'OCA\\Files\\Service\\ViewConfig' => __DIR__ . '/..' . '/../lib/Service/ViewConfig.php', + 'OCA\\Files\\Settings\\DeclarativeAdminSettings' => __DIR__ . '/..' . '/../lib/Settings/DeclarativeAdminSettings.php', 'OCA\\Files\\Settings\\PersonalSettings' => __DIR__ . '/..' . '/../lib/Settings/PersonalSettings.php', ); diff --git a/apps/files/lib/AppInfo/Application.php b/apps/files/lib/AppInfo/Application.php index 3d2d0527072a7..d50a8d14f5ae9 100644 --- a/apps/files/lib/AppInfo/Application.php +++ b/apps/files/lib/AppInfo/Application.php @@ -16,9 +16,6 @@ use OCA\Files\DirectEditingCapabilities; use OCA\Files\Event\LoadSearchPlugins; use OCA\Files\Event\LoadSidebar; -use OCA\Files\Listener\DeclarativeSettingsGetValueEventListener; -use OCA\Files\Listener\DeclarativeSettingsRegisterFormEventListener; -use OCA\Files\Listener\DeclarativeSettingsSetValueEventListener; use OCA\Files\Listener\LoadSearchPluginsListener; use OCA\Files\Listener\LoadSidebarListener; use OCA\Files\Listener\RenderReferenceEventListener; @@ -28,6 +25,7 @@ use OCA\Files\Service\TagService; use OCA\Files\Service\UserConfig; use OCA\Files\Service\ViewConfig; +use OCA\Files\Settings\DeclarativeAdminSettings; use OCP\Activity\IManager as IActivityManager; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; @@ -49,9 +47,6 @@ use OCP\IServerContainer; use OCP\ITagManager; use OCP\IUserSession; -use OCP\Settings\Events\DeclarativeSettingsGetValueEvent; -use OCP\Settings\Events\DeclarativeSettingsRegisterFormEvent; -use OCP\Settings\Events\DeclarativeSettingsSetValueEvent; use OCP\Share\IManager as IShareManager; use OCP\Util; use Psr\Container\ContainerInterface; @@ -111,6 +106,8 @@ public function register(IRegistrationContext $context): void { $context->registerCapability(Capabilities::class); $context->registerCapability(DirectEditingCapabilities::class); + $context->registerDeclarativeSettings(DeclarativeAdminSettings::class); + $context->registerEventListener(LoadSidebar::class, LoadSidebarListener::class); $context->registerEventListener(RenderReferenceEvent::class, RenderReferenceEventListener::class); $context->registerEventListener(BeforeNodeRenamedEvent::class, SyncLivePhotosListener::class); @@ -119,9 +116,6 @@ public function register(IRegistrationContext $context): void { $context->registerEventListener(BeforeNodeCopiedEvent::class, SyncLivePhotosListener::class); $context->registerEventListener(NodeCopiedEvent::class, SyncLivePhotosListener::class); $context->registerEventListener(LoadSearchPlugins::class, LoadSearchPluginsListener::class); - $context->registerEventListener(DeclarativeSettingsRegisterFormEvent::class, DeclarativeSettingsRegisterFormEventListener::class); - $context->registerEventListener(DeclarativeSettingsGetValueEvent::class, DeclarativeSettingsGetValueEventListener::class); - $context->registerEventListener(DeclarativeSettingsSetValueEvent::class, DeclarativeSettingsSetValueEventListener::class); $context->registerSearchProvider(FilesSearchProvider::class); diff --git a/apps/files/lib/Listener/DeclarativeSettingsGetValueEventListener.php b/apps/files/lib/Listener/DeclarativeSettingsGetValueEventListener.php deleted file mode 100644 index b1d0ee3a39599..0000000000000 --- a/apps/files/lib/Listener/DeclarativeSettingsGetValueEventListener.php +++ /dev/null @@ -1,39 +0,0 @@ - */ -class DeclarativeSettingsGetValueEventListener implements IEventListener { - - public function __construct( - private SettingsService $service, - ) { - } - - public function handle(Event $event): void { - if (!($event instanceof DeclarativeSettingsGetValueEvent)) { - return; - } - - if ($event->getApp() !== Application::APP_ID) { - return; - } - - $event->setValue( - match($event->getFieldId()) { - 'windows_support' => $this->service->hasFilesWindowsSupport(), - } - ); - } -} diff --git a/apps/files/lib/Listener/DeclarativeSettingsSetValueEventListener.php b/apps/files/lib/Listener/DeclarativeSettingsSetValueEventListener.php deleted file mode 100644 index a3be2b9141ef9..0000000000000 --- a/apps/files/lib/Listener/DeclarativeSettingsSetValueEventListener.php +++ /dev/null @@ -1,40 +0,0 @@ - */ -class DeclarativeSettingsSetValueEventListener implements IEventListener { - - public function __construct( - private SettingsService $service, - ) { - } - - public function handle(Event $event): void { - if (!($event instanceof DeclarativeSettingsSetValueEvent)) { - return; - } - - if ($event->getApp() !== Application::APP_ID) { - return; - } - - switch ($event->getFieldId()) { - case 'windows_support': - $this->service->setFilesWindowsSupport((bool)$event->getValue()); - $event->stopPropagation(); - break; - } - } -} diff --git a/apps/files/lib/Listener/DeclarativeSettingsRegisterFormEventListener.php b/apps/files/lib/Settings/DeclarativeAdminSettings.php similarity index 57% rename from apps/files/lib/Listener/DeclarativeSettingsRegisterFormEventListener.php rename to apps/files/lib/Settings/DeclarativeAdminSettings.php index 51832e89ecbfe..e509ad2233b39 100644 --- a/apps/files/lib/Listener/DeclarativeSettingsRegisterFormEventListener.php +++ b/apps/files/lib/Settings/DeclarativeAdminSettings.php @@ -2,32 +2,42 @@ declare(strict_types=1); /** - * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace OCA\Files\Listener; +namespace OCA\Files\Settings; -use OCA\Files\AppInfo\Application; -use OCP\EventDispatcher\Event; -use OCP\EventDispatcher\IEventListener; +use OCA\Files\Service\SettingsService; use OCP\IL10N; +use OCP\IUser; use OCP\Settings\DeclarativeSettingsTypes; -use OCP\Settings\Events\DeclarativeSettingsRegisterFormEvent; +use OCP\Settings\IDeclarativeSettingsFormWithHandlers; -/** @template-implements IEventListener */ -class DeclarativeSettingsRegisterFormEventListener implements IEventListener { +class DeclarativeAdminSettings implements IDeclarativeSettingsFormWithHandlers { public function __construct( private IL10N $l, + private SettingsService $service, ) { } - public function handle(Event $event): void { - if (!($event instanceof DeclarativeSettingsRegisterFormEvent)) { - return; + public function getValue(string $fieldId, IUser $user): mixed { + return match($fieldId) { + 'windows_support' => $this->service->hasFilesWindowsSupport(), + default => throw new \InvalidArgumentException('Unexpected field id ' . $fieldId), + }; + } + + public function setValue(string $fieldId, mixed $value, IUser $user): void { + switch ($fieldId) { + case 'windows_support': + $this->service->setFilesWindowsSupport((bool)$value); + break; } + } - $event->registerSchema(Application::APP_ID, [ + public function getSchema(): array { + return [ 'id' => 'files-filename-support', 'priority' => 10, 'section_type' => DeclarativeSettingsTypes::SECTION_TYPE_ADMIN, @@ -45,6 +55,6 @@ public function handle(Event $event): void { 'default' => false, ], ], - ]); + ]; } } diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 1219c0f5aa44c..545c33f2572a5 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -698,6 +698,7 @@ 'OCP\\Settings\\Events\\DeclarativeSettingsSetValueEvent' => $baseDir . '/lib/public/Settings/Events/DeclarativeSettingsSetValueEvent.php', 'OCP\\Settings\\IDeclarativeManager' => $baseDir . '/lib/public/Settings/IDeclarativeManager.php', 'OCP\\Settings\\IDeclarativeSettingsForm' => $baseDir . '/lib/public/Settings/IDeclarativeSettingsForm.php', + 'OCP\\Settings\\IDeclarativeSettingsFormWithHandlers' => $baseDir . '/lib/public/Settings/IDeclarativeSettingsFormWithHandlers.php', 'OCP\\Settings\\IDelegatedSettings' => $baseDir . '/lib/public/Settings/IDelegatedSettings.php', 'OCP\\Settings\\IIconSection' => $baseDir . '/lib/public/Settings/IIconSection.php', 'OCP\\Settings\\IManager' => $baseDir . '/lib/public/Settings/IManager.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 0e93ee8addd7e..fcb2a0cd8c9ba 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -731,6 +731,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\Settings\\Events\\DeclarativeSettingsSetValueEvent' => __DIR__ . '/../../..' . '/lib/public/Settings/Events/DeclarativeSettingsSetValueEvent.php', 'OCP\\Settings\\IDeclarativeManager' => __DIR__ . '/../../..' . '/lib/public/Settings/IDeclarativeManager.php', 'OCP\\Settings\\IDeclarativeSettingsForm' => __DIR__ . '/../../..' . '/lib/public/Settings/IDeclarativeSettingsForm.php', + 'OCP\\Settings\\IDeclarativeSettingsFormWithHandlers' => __DIR__ . '/../../..' . '/lib/public/Settings/IDeclarativeSettingsFormWithHandlers.php', 'OCP\\Settings\\IDelegatedSettings' => __DIR__ . '/../../..' . '/lib/public/Settings/IDelegatedSettings.php', 'OCP\\Settings\\IIconSection' => __DIR__ . '/../../..' . '/lib/public/Settings/IIconSection.php', 'OCP\\Settings\\IManager' => __DIR__ . '/../../..' . '/lib/public/Settings/IManager.php', diff --git a/lib/private/Settings/DeclarativeManager.php b/lib/private/Settings/DeclarativeManager.php index 5c2b868f41755..ee253b8644988 100644 --- a/lib/private/Settings/DeclarativeManager.php +++ b/lib/private/Settings/DeclarativeManager.php @@ -22,6 +22,7 @@ use OCP\Settings\Events\DeclarativeSettingsSetValueEvent; use OCP\Settings\IDeclarativeManager; use OCP\Settings\IDeclarativeSettingsForm; +use OCP\Settings\IDeclarativeSettingsFormWithHandlers; use Psr\Log\LoggerInterface; /** @@ -32,6 +33,15 @@ * @psalm-import-type DeclarativeSettingsFormSchemaWithoutValues from IDeclarativeSettingsForm */ class DeclarativeManager implements IDeclarativeManager { + + /** @var array> */ + private array $declarativeForms = []; + + /** + * @var array> + */ + private array $appSchemas = []; + public function __construct( private IEventDispatcher $eventDispatcher, private IGroupManager $groupManager, @@ -42,11 +52,6 @@ public function __construct( ) { } - /** - * @var array> - */ - private array $appSchemas = []; - /** * @inheritdoc */ @@ -77,11 +82,15 @@ public function registerSchema(string $app, array $schema): void { * @inheritdoc */ public function loadSchemas(): void { - $declarativeSettings = $this->coordinator->getRegistrationContext()->getDeclarativeSettings(); - foreach ($declarativeSettings as $declarativeSetting) { - /** @var IDeclarativeSettingsForm $declarativeSettingObject */ - $declarativeSettingObject = Server::get($declarativeSetting->getService()); - $this->registerSchema($declarativeSetting->getAppId(), $declarativeSettingObject->getSchema()); + if (empty($this->declarativeForms)) { + $declarativeSettings = $this->coordinator->getRegistrationContext()->getDeclarativeSettings(); + foreach ($declarativeSettings as $declarativeSetting) { + $app = $declarativeSetting->getAppId(); + /** @var IDeclarativeSettingsForm $declarativeForm */ + $declarativeForm = Server::get($declarativeSetting->getService()); + $this->registerSchema($app, $declarativeForm->getSchema()); + $this->declarativeForms[$app][] = $declarativeForm; + } } $this->eventDispatcher->dispatchTyped(new DeclarativeSettingsRegisterFormEvent($this)); @@ -224,6 +233,10 @@ private function getValue(IUser $user, string $app, string $formId, string $fiel $storageType = $this->getStorageType($app, $fieldId); switch ($storageType) { case DeclarativeSettingsTypes::STORAGE_TYPE_EXTERNAL: + $form = $this->getForm($app, $formId); + if ($form !== null && $form instanceof IDeclarativeSettingsFormWithHandlers) { + return $form->getValue($fieldId, $user); + } $event = new DeclarativeSettingsGetValueEvent($user, $app, $formId, $fieldId); $this->eventDispatcher->dispatchTyped($event); return $event->getValue(); @@ -244,6 +257,12 @@ public function setValue(IUser $user, string $app, string $formId, string $field $storageType = $this->getStorageType($app, $fieldId); switch ($storageType) { case DeclarativeSettingsTypes::STORAGE_TYPE_EXTERNAL: + $form = $this->getForm($app, $formId); + if ($form !== null && $form instanceof IDeclarativeSettingsFormWithHandlers) { + $form->setValue($fieldId, $value, $user); + break; + } + // fall back to event handling $this->eventDispatcher->dispatchTyped(new DeclarativeSettingsSetValueEvent($user, $app, $formId, $fieldId, $value)); break; case DeclarativeSettingsTypes::STORAGE_TYPE_INTERNAL: @@ -254,6 +273,20 @@ public function setValue(IUser $user, string $app, string $formId, string $field } } + /** + * If a declarative setting was registered as a form and not just a schema + * then this will yield the registering form. + */ + private function getForm(string $app, string $formId): ?IDeclarativeSettingsForm { + $allForms = $this->declarativeForms[$app] ?? []; + foreach ($allForms as $form) { + if ($form->getSchema()['id'] === $formId) { + return $form; + } + } + return null; + } + private function getInternalValue(IUser $user, string $app, string $formId, string $fieldId): mixed { $sectionType = $this->getSectionType($app, $fieldId); $defaultValue = $this->getDefaultValue($app, $formId, $fieldId); diff --git a/lib/public/Settings/DeclarativeSettingsTypes.php b/lib/public/Settings/DeclarativeSettingsTypes.php index 82444af9b823b..7edf0cbdd6ef4 100644 --- a/lib/public/Settings/DeclarativeSettingsTypes.php +++ b/lib/public/Settings/DeclarativeSettingsTypes.php @@ -32,8 +32,9 @@ final class DeclarativeSettingsTypes { /** * IDeclarativeSettingsForm storage_type which is determines where and how the config value is stored * - * - * For `external` storage_type the app implementing \OCP\Settings\SetDeclarativeSettingsValueEvent and \OCP\Settings\GetDeclarativeSettingsValueEvent events is responsible for storing and retrieving the config value. + * For `external` storage_type the app needs to either implement event listeners for \OCP\Settings\SetDeclarativeSettingsValueEvent + * and \OCP\Settings\GetDeclarativeSettingsValueEvent or the IDeclarativeSettingsForm also needs to implement + * IDeclarativeSettingsFormWithHandlers for storing and retrieving the config value. * * @since 29.0.0 */ @@ -43,7 +44,6 @@ final class DeclarativeSettingsTypes { * IDeclarativeSettingsForm storage_type which is determines where and how the config value is stored * * For `internal` storage_type the config value is stored in default `appconfig` and `preferences` tables. - * For `external` storage_type the app implementing \OCP\Settings\SetDeclarativeSettingsValueEvent and \OCP\Settings\GetDeclarativeSettingsValueEvent events is responsible for storing and retrieving the config value. * * @since 29.0.0 */ diff --git a/lib/public/Settings/IDeclarativeSettingsFormWithHandlers.php b/lib/public/Settings/IDeclarativeSettingsFormWithHandlers.php new file mode 100644 index 0000000000000..180df73d9951f --- /dev/null +++ b/lib/public/Settings/IDeclarativeSettingsFormWithHandlers.php @@ -0,0 +1,31 @@ + 'test_form_1', 'priority' => 10, @@ -518,4 +524,74 @@ public function testAdminFormUserUnauthorized(): void { $this->expectException(\Exception::class); $this->declarativeManager->getFormsWithValues($this->user, $schema['section_type'], $schema['section_id']); } + + /** + * Ensure that the `setValue` method is called if the form implements the handler interface. + */ + public function testSetValueWithHandler(): void { + $schema = self::validSchemaAllFields; + $schema['storage_type'] = DeclarativeSettingsTypes::STORAGE_TYPE_EXTERNAL; + + $form = $this->createMock(IDeclarativeSettingsFormWithHandlers::class); + $form->expects(self::atLeastOnce()) + ->method('getSchema') + ->willReturn($schema); + // The setter should be called once! + $form->expects(self::once()) + ->method('setValue') + ->with('test_field_2', 'some password', $this->adminUser); + + \OC::$server->registerService('OCA\\Testing\\Settings\\DeclarativeForm', fn () => $form, false); + + $context = $this->createMock(RegistrationContext::class); + $context->expects(self::atLeastOnce()) + ->method('getDeclarativeSettings') + ->willReturn([new ServiceRegistration('testing', 'OCA\\Testing\\Settings\\DeclarativeForm')]); + + $this->coordinator->expects(self::atLeastOnce()) + ->method('getRegistrationContext') + ->willReturn($context); + + $this->declarativeManager->loadSchemas(); + + $this->eventDispatcher->expects(self::never()) + ->method('dispatchTyped'); + + $this->declarativeManager->setValue($this->adminUser, 'testing', 'test_form_1', 'test_field_2', 'some password'); + } + + public function testGetValueWithHandler(): void { + $schema = self::validSchemaAllFields; + $schema['storage_type'] = DeclarativeSettingsTypes::STORAGE_TYPE_EXTERNAL; + + $form = $this->createMock(IDeclarativeSettingsFormWithHandlers::class); + $form->expects(self::atLeastOnce()) + ->method('getSchema') + ->willReturn($schema); + // The setter should be called once! + $form->expects(self::once()) + ->method('getValue') + ->with('test_field_2', $this->adminUser) + ->willReturn('very secret password'); + + \OC::$server->registerService('OCA\\Testing\\Settings\\DeclarativeForm', fn () => $form, false); + + $context = $this->createMock(RegistrationContext::class); + $context->expects(self::atLeastOnce()) + ->method('getDeclarativeSettings') + ->willReturn([new ServiceRegistration('testing', 'OCA\\Testing\\Settings\\DeclarativeForm')]); + + $this->coordinator->expects(self::atLeastOnce()) + ->method('getRegistrationContext') + ->willReturn($context); + + $this->declarativeManager->loadSchemas(); + + $this->eventDispatcher->expects(self::never()) + ->method('dispatchTyped'); + + $password = $this->invokePrivate($this->declarativeManager, 'getValue', [$this->adminUser, 'testing', 'test_form_1', 'test_field_2']); + self::assertEquals('very secret password', $password); + } + }