diff options
12 files changed, 342 insertions, 18 deletions
diff --git a/apps/testing/composer/composer/autoload_classmap.php b/apps/testing/composer/composer/autoload_classmap.php index d7b767f63d5..d985f49e617 100644 --- a/apps/testing/composer/composer/autoload_classmap.php +++ b/apps/testing/composer/composer/autoload_classmap.php @@ -11,7 +11,9 @@ return array( 'OCA\\Testing\\AppInfo\\Application' => $baseDir . '/../lib/AppInfo/Application.php', 'OCA\\Testing\\Controller\\ConfigController' => $baseDir . '/../lib/Controller/ConfigController.php', 'OCA\\Testing\\Controller\\LockingController' => $baseDir . '/../lib/Controller/LockingController.php', + 'OCA\\Testing\\Controller\\PageController' => $baseDir . '/../lib/Controller/PageController.php', 'OCA\\Testing\\Controller\\RateLimitTestController' => $baseDir . '/../lib/Controller/RateLimitTestController.php', + 'OCA\\Testing\\Listener\\DeleteDeclarativeSettingsValueListener' => $baseDir . '/../lib/Listener/DeleteDeclarativeSettingsValueListener.php', 'OCA\\Testing\\Listener\\GetDeclarativeSettingsValueListener' => $baseDir . '/../lib/Listener/GetDeclarativeSettingsValueListener.php', 'OCA\\Testing\\Listener\\RegisterDeclarativeSettingsListener' => $baseDir . '/../lib/Listener/RegisterDeclarativeSettingsListener.php', 'OCA\\Testing\\Listener\\SetDeclarativeSettingsValueListener' => $baseDir . '/../lib/Listener/SetDeclarativeSettingsValueListener.php', diff --git a/apps/testing/composer/composer/autoload_static.php b/apps/testing/composer/composer/autoload_static.php index a147eab93b3..ba18e6ba4f4 100644 --- a/apps/testing/composer/composer/autoload_static.php +++ b/apps/testing/composer/composer/autoload_static.php @@ -26,7 +26,9 @@ class ComposerStaticInitTesting 'OCA\\Testing\\AppInfo\\Application' => __DIR__ . '/..' . '/../lib/AppInfo/Application.php', 'OCA\\Testing\\Controller\\ConfigController' => __DIR__ . '/..' . '/../lib/Controller/ConfigController.php', 'OCA\\Testing\\Controller\\LockingController' => __DIR__ . '/..' . '/../lib/Controller/LockingController.php', + 'OCA\\Testing\\Controller\\PageController' => __DIR__ . '/..' . '/../lib/Controller/PageController.php', 'OCA\\Testing\\Controller\\RateLimitTestController' => __DIR__ . '/..' . '/../lib/Controller/RateLimitTestController.php', + 'OCA\\Testing\\Listener\\DeleteDeclarativeSettingsValueListener' => __DIR__ . '/..' . '/../lib/Listener/DeleteDeclarativeSettingsValueListener.php', 'OCA\\Testing\\Listener\\GetDeclarativeSettingsValueListener' => __DIR__ . '/..' . '/../lib/Listener/GetDeclarativeSettingsValueListener.php', 'OCA\\Testing\\Listener\\RegisterDeclarativeSettingsListener' => __DIR__ . '/..' . '/../lib/Listener/RegisterDeclarativeSettingsListener.php', 'OCA\\Testing\\Listener\\SetDeclarativeSettingsValueListener' => __DIR__ . '/..' . '/../lib/Listener/SetDeclarativeSettingsValueListener.php', diff --git a/apps/testing/lib/Controller/PageController.php b/apps/testing/lib/Controller/PageController.php new file mode 100644 index 00000000000..fb9e46381a6 --- /dev/null +++ b/apps/testing/lib/Controller/PageController.php @@ -0,0 +1,38 @@ +<?php + +namespace OCA\Testing\Controller; + +use OCA\Testing\Settings\DeclarativeSettingsFormTyped; +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\Attribute\FrontpageRoute; +use OCP\AppFramework\Http\Attribute\NoAdminRequired; +use OCP\AppFramework\Http\Attribute\NoCSRFRequired; +use OCP\AppFramework\Http\JSONResponse; +use OCP\IRequest; + +class PageController extends Controller { + public function __construct( + string $appName, + IRequest $request, + private DeclarativeSettingsFormTyped $form, + ) { + parent::__construct($appName, $request); + } + + #[NoAdminRequired] + #[NoCSRFRequired] + #[FrontpageRoute(verb: 'GET', url: '/declarative-settings-typed')] + public function declarativeSettingsTyped(): JSONResponse { + $this->form->test->setValue(1); + $value1 = $this->form->test->getValue(); + $this->form->test->setValue(8); + $value2 = $this->form->test->getValue(); + $this->form->test->deleteValue(); + $value3 = $this->form->test->getValue(); + return new JSONResponse([ + 'value1' => $value1, + 'value2' => $value2, + 'value3' => $value3, + ]); + } +} diff --git a/apps/testing/lib/Listener/DeleteDeclarativeSettingsValueListener.php b/apps/testing/lib/Listener/DeleteDeclarativeSettingsValueListener.php new file mode 100644 index 00000000000..8fbf8e60025 --- /dev/null +++ b/apps/testing/lib/Listener/DeleteDeclarativeSettingsValueListener.php @@ -0,0 +1,35 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\Testing\Listener; + +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; +use OCP\IConfig; +use OCP\Settings\Events\DeclarativeSettingsDeleteValueEvent; + +/** + * @template-implements IEventListener<DeclarativeSettingsDeleteValueEvent> + */ +class DeleteDeclarativeSettingsValueListener implements IEventListener { + + public function __construct(private IConfig $config) { + } + + public function handle(Event $event): void { + if (!$event instanceof DeclarativeSettingsDeleteValueEvent) { + return; + } + + if ($event->getApp() !== 'testing') { + return; + } + + error_log('Testing app wants to delete field ' . $event->getFieldId() . ' for user ' . $event->getUser()->getUID()); + $this->config->deleteUserValue($event->getUser()->getUID(), $event->getApp(), $event->getFieldId()); + } +} diff --git a/apps/testing/lib/Settings/DeclarativeSettingsFormTyped.php b/apps/testing/lib/Settings/DeclarativeSettingsFormTyped.php index 1dff17e3684..7aaa1cba33d 100644 --- a/apps/testing/lib/Settings/DeclarativeSettingsFormTyped.php +++ b/apps/testing/lib/Settings/DeclarativeSettingsFormTyped.php @@ -8,6 +8,7 @@ declare(strict_types=1); namespace OCA\Testing\Settings; +use OCA\Testing\AppInfo\Application; use OCP\Settings\DeclarativeSettingsForm; use OCP\Settings\DeclarativeSettingsOptionInt; use OCP\Settings\DeclarativeSettingsTypes; @@ -17,6 +18,8 @@ class DeclarativeSettingsFormTyped extends DeclarativeSettingsForm { public function __construct() { $this->test = new DeclarativeSettingsOptionInt( + appName: Application::APP_ID, + form: $this, id: 'test_ex_app_field_8', title: 'Multi-selection', default: 2, diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 36748d2fc83..6911692a7c0 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -692,6 +692,7 @@ return array( 'OCP\\Settings\\DeclarativeSettingsForm' => $baseDir . '/lib/public/Settings/DeclarativeSettingsForm.php', 'OCP\\Settings\\DeclarativeSettingsOptionInt' => $baseDir . '/lib/public/Settings/DeclarativeSettingsOptionInt.php', 'OCP\\Settings\\DeclarativeSettingsTypes' => $baseDir . '/lib/public/Settings/DeclarativeSettingsTypes.php', + 'OCP\\Settings\\Events\\DeclarativeSettingsDeleteValueEvent' => $baseDir . '/lib/public/Settings/Events/DeclarativeSettingsDeleteValueEvent.php', 'OCP\\Settings\\Events\\DeclarativeSettingsGetValueEvent' => $baseDir . '/lib/public/Settings/Events/DeclarativeSettingsGetValueEvent.php', 'OCP\\Settings\\Events\\DeclarativeSettingsRegisterFormEvent' => $baseDir . '/lib/public/Settings/Events/DeclarativeSettingsRegisterFormEvent.php', 'OCP\\Settings\\Events\\DeclarativeSettingsSetValueEvent' => $baseDir . '/lib/public/Settings/Events/DeclarativeSettingsSetValueEvent.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 0c65fae8e19..25cbd6c1632 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -725,6 +725,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\Settings\\DeclarativeSettingsForm' => __DIR__ . '/../../..' . '/lib/public/Settings/DeclarativeSettingsForm.php', 'OCP\\Settings\\DeclarativeSettingsOptionInt' => __DIR__ . '/../../..' . '/lib/public/Settings/DeclarativeSettingsOptionInt.php', 'OCP\\Settings\\DeclarativeSettingsTypes' => __DIR__ . '/../../..' . '/lib/public/Settings/DeclarativeSettingsTypes.php', + 'OCP\\Settings\\Events\\DeclarativeSettingsDeleteValueEvent' => __DIR__ . '/../../..' . '/lib/public/Settings/Events/DeclarativeSettingsDeleteValueEvent.php', 'OCP\\Settings\\Events\\DeclarativeSettingsGetValueEvent' => __DIR__ . '/../../..' . '/lib/public/Settings/Events/DeclarativeSettingsGetValueEvent.php', 'OCP\\Settings\\Events\\DeclarativeSettingsRegisterFormEvent' => __DIR__ . '/../../..' . '/lib/public/Settings/Events/DeclarativeSettingsRegisterFormEvent.php', 'OCP\\Settings\\Events\\DeclarativeSettingsSetValueEvent' => __DIR__ . '/../../..' . '/lib/public/Settings/Events/DeclarativeSettingsSetValueEvent.php', diff --git a/lib/private/Settings/DeclarativeManager.php b/lib/private/Settings/DeclarativeManager.php index 29557187175..89a75b43b66 100644 --- a/lib/private/Settings/DeclarativeManager.php +++ b/lib/private/Settings/DeclarativeManager.php @@ -5,6 +5,7 @@ declare(strict_types=1); * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ + namespace OC\Settings; use Exception; @@ -17,6 +18,7 @@ use OCP\IGroupManager; use OCP\IUser; use OCP\Server; use OCP\Settings\DeclarativeSettingsTypes; +use OCP\Settings\Events\DeclarativeSettingsDeleteValueEvent; use OCP\Settings\Events\DeclarativeSettingsGetValueEvent; use OCP\Settings\Events\DeclarativeSettingsRegisterFormEvent; use OCP\Settings\Events\DeclarativeSettingsSetValueEvent; @@ -36,11 +38,11 @@ class DeclarativeManager implements IDeclarativeManager { public function __construct( private IEventDispatcher $eventDispatcher, - private IGroupManager $groupManager, - private Coordinator $coordinator, - private IConfig $config, - private IAppConfig $appConfig, - private LoggerInterface $logger, + private IGroupManager $groupManager, + private Coordinator $coordinator, + private IConfig $config, + private IAppConfig $appConfig, + private LoggerInterface $logger, ) { } @@ -65,8 +67,8 @@ class DeclarativeManager implements IDeclarativeManager { } } - $fieldIDs = array_map(fn ($field) => $field['id'], $schema['fields']); - $otherFieldIDs = array_merge(...array_map(fn ($schema) => array_map(fn ($field) => $field['id'], $schema['fields']), $this->appSchemas[$app])); + $fieldIDs = array_map(fn($field) => $field['id'], $schema['fields']); + $otherFieldIDs = array_merge(...array_map(fn($schema) => array_map(fn($field) => $field['id'], $schema['fields']), $this->appSchemas[$app])); $intersectionFieldIDs = array_intersect($fieldIDs, $otherFieldIDs); if (count($intersectionFieldIDs) > 0) { throw new Exception('Non unique field IDs detected: ' . join(', ', $intersectionFieldIDs)); @@ -220,11 +222,33 @@ class DeclarativeManager implements IDeclarativeManager { } /** + * @throws Exception + * @throws NotAdminException + */ + public function deleteValue(IUser $user, string $app, string $formId, string $fieldId): void { + $sectionType = $this->getSectionType($app, $fieldId); + $this->assertAuthorized($user, $sectionType); + + $storageType = $this->getStorageType($app, $fieldId); + switch ($storageType) { + case DeclarativeSettingsTypes::STORAGE_TYPE_EXTERNAL: + $event = new DeclarativeSettingsDeleteValueEvent($user, $app, $formId, $fieldId); + $this->eventDispatcher->dispatchTyped($event); + break; + case DeclarativeSettingsTypes::STORAGE_TYPE_INTERNAL: + $this->deleteInternalValue($user, $app, $fieldId); + break; + default: + throw new Exception('Unknown storage type "' . $storageType . '"'); + } + } + + /** * @return DeclarativeSettingsValueTypes * @throws Exception * @throws NotAdminException */ - private function getValue(IUser $user, string $app, string $formId, string $fieldId): mixed { + public function getValue(IUser $user, string $app, string $formId, string $fieldId): mixed { $sectionType = $this->getSectionType($app, $fieldId); $this->assertAuthorized($user, $sectionType); @@ -254,7 +278,7 @@ class DeclarativeManager implements IDeclarativeManager { $this->eventDispatcher->dispatchTyped(new DeclarativeSettingsSetValueEvent($user, $app, $formId, $fieldId, $value)); break; case DeclarativeSettingsTypes::STORAGE_TYPE_INTERNAL: - $this->saveInternalValue($user, $app, $fieldId, $value); + $this->setInternalValue($user, $app, $fieldId, $value); break; default: throw new Exception('Unknown storage type "' . $storageType . '"'); @@ -274,11 +298,11 @@ class DeclarativeManager implements IDeclarativeManager { } } - private function saveInternalValue(IUser $user, string $app, string $fieldId, mixed $value): void { + private function setInternalValue(IUser $user, string $app, string $fieldId, mixed $value): void { $sectionType = $this->getSectionType($app, $fieldId); switch ($sectionType) { case DeclarativeSettingsTypes::SECTION_TYPE_ADMIN: - $this->appConfig->setValueString($app, $fieldId, $value); + $this->config->setAppValue($app, $fieldId, $value); break; case DeclarativeSettingsTypes::SECTION_TYPE_PERSONAL: $this->config->setUserValue($user->getUID(), $app, $fieldId, $value); @@ -288,16 +312,25 @@ class DeclarativeManager implements IDeclarativeManager { } } + private function deleteInternalValue(IUser $user, string $app, string $fieldId): void { + $sectionType = $this->getSectionType($app, $fieldId); + switch ($sectionType) { + case DeclarativeSettingsTypes::SECTION_TYPE_ADMIN: + $this->appConfig->deleteKey($app, $fieldId); + break; + case DeclarativeSettingsTypes::SECTION_TYPE_PERSONAL: + $this->config->deleteUserValue($user->getUID(), $app, $fieldId); + break; + default: + throw new Exception('Unknown section type "' . $sectionType . '"'); + } + } + private function getDefaultValue(string $app, string $fieldId): mixed { foreach ($this->appSchemas[$app] as $schema) { foreach ($schema['fields'] as $field) { - if ($field['id'] === $fieldId) { - if (isset($field['default'])) { - if (is_array($field['default'])) { - return json_encode($field['default']); - } - return $field['default']; - } + if ($field['id'] === $fieldId && isset($field['default'])) { + return $field['default']; } } } diff --git a/lib/private/Settings/DeclarativeSettingsOption.php b/lib/private/Settings/DeclarativeSettingsOption.php new file mode 100644 index 00000000000..e7982924d64 --- /dev/null +++ b/lib/private/Settings/DeclarativeSettingsOption.php @@ -0,0 +1,108 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OC\Settings; + +use OC\AppFramework\Middleware\Security\Exceptions\NotAdminException; +use OCP\Common\Exception\NotFoundException; +use OCP\IUser; +use OCP\IUserSession; +use OCP\Server; +use OCP\Settings\DeclarativeSettingsForm; +use OCP\Settings\IDeclarativeManager; +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; + +class DeclarativeSettingsOption { + private ?IUser $user = null; + private ?IDeclarativeManager $manager = null; + + public function __construct( + public string $appName, + public DeclarativeSettingsForm $form, + public string $id, + ) { + } + + /** + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + * @throws NotFoundException + */ + private function getUser(): IUser { + if ($this->user === null) { + $userSession = Server::get(IUserSession::class); + $this->user = $userSession->getUser(); + if ($this->user === null) { + throw new NotFoundException('User not found'); + } + } + + return $this->user; + } + + /** + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + private function getDeclarativeManager(): IDeclarativeManager { + if ($this->manager === null) { + $this->manager = Server::get(IDeclarativeManager::class); + } + + $this->manager->loadSchemas(); + return $this->manager; + } + + /** + * @throws NotAdminException + * @throws NotFoundExceptionInterface + * @throws NotFoundException + * @throws ContainerExceptionInterface + */ + public function setValue(mixed $value): void { + $this->getDeclarativeManager()->setValue( + $this->getUser(), + $this->appName, + $this->form->getId(), + $this->id, + $value, + ); + } + + /** + * @throws NotAdminException + * @throws NotFoundExceptionInterface + * @throws NotFoundException + * @throws ContainerExceptionInterface + */ + public function getValue(): mixed { + return $this->getDeclarativeManager()->getValue( + $this->getUser(), + $this->appName, + $this->form->getId(), + $this->id, + ); + } + + /** + * @throws NotAdminException + * @throws NotFoundExceptionInterface + * @throws NotFoundException + * @throws ContainerExceptionInterface + */ + public function deleteValue(): void { + $this->getDeclarativeManager()->deleteValue( + $this->getUser(), + $this->appName, + $this->form->getId(), + $this->id, + ); + } +} diff --git a/lib/public/Settings/DeclarativeSettingsOptionInt.php b/lib/public/Settings/DeclarativeSettingsOptionInt.php index 5e947c8a0fe..dbeef3413e6 100644 --- a/lib/public/Settings/DeclarativeSettingsOptionInt.php +++ b/lib/public/Settings/DeclarativeSettingsOptionInt.php @@ -9,15 +9,21 @@ declare(strict_types=1); namespace OCP\Settings; +use OC\Settings\DeclarativeSettingsOption; + /** * @psalm-import-type DeclarativeSettingsFormFieldType from IDeclarativeSettingsForm * @psalm-import-type DeclarativeSettingsFormFieldOptions from IDeclarativeSettingsForm */ class DeclarativeSettingsOptionInt implements IDeclarativeSettingsOption { + private DeclarativeSettingsOption $internal; + /** * @param DeclarativeSettingsFormFieldOptions $options */ public function __construct( + public readonly string $appName, + public readonly DeclarativeSettingsForm $form, public readonly string $id, public readonly string $title, public readonly int $default, @@ -26,6 +32,7 @@ class DeclarativeSettingsOptionInt implements IDeclarativeSettingsOption { public readonly ?string $placeholder = null, public readonly ?string $label = null, ) { + $this->internal = new DeclarativeSettingsOption($this->appName, $this->form, $this->id); } public function getId(): string { @@ -59,4 +66,16 @@ class DeclarativeSettingsOptionInt implements IDeclarativeSettingsOption { public function getOptions(): mixed { return $this->options; } + + public function setValue(int $value): void { + $this->internal->setValue($value); + } + + public function getValue(): int { + return $this->internal->getValue(); + } + + public function deleteValue(): void { + $this->internal->deleteValue(); + } } diff --git a/lib/public/Settings/Events/DeclarativeSettingsDeleteValueEvent.php b/lib/public/Settings/Events/DeclarativeSettingsDeleteValueEvent.php new file mode 100644 index 00000000000..9a50aae29c5 --- /dev/null +++ b/lib/public/Settings/Events/DeclarativeSettingsDeleteValueEvent.php @@ -0,0 +1,58 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Settings\Events; + +use OCP\EventDispatcher\Event; +use OCP\IUser; + +/** + * @since 31.0.0 + */ +class DeclarativeSettingsDeleteValueEvent extends Event { + /** + * @since 31.0.0 + */ + public function __construct( + private IUser $user, + private string $app, + private string $formId, + private string $fieldId, + ) { + parent::__construct(); + } + + /** + * @since 31.0.0 + */ + public function getUser(): IUser { + return $this->user; + } + + /** + * @since 31.0.0 + */ + public function getApp(): string { + return $this->app; + } + + /** + * @since 31.0.0 + */ + public function getFormId(): string { + return $this->formId; + } + + /** + * @since 31.0.0 + */ + public function getFieldId(): string { + return $this->fieldId; + } +} diff --git a/lib/public/Settings/IDeclarativeManager.php b/lib/public/Settings/IDeclarativeManager.php index 5c21ee6750b..a87f846a17a 100644 --- a/lib/public/Settings/IDeclarativeManager.php +++ b/lib/public/Settings/IDeclarativeManager.php @@ -72,4 +72,28 @@ interface IDeclarativeManager { * @since 29.0.0 */ public function setValue(IUser $user, string $app, string $formId, string $fieldId, mixed $value): void; + + /** + * Returns the value of the given field ID. + * + * @param IUser $user Used for storing values in the personal section or for authorization for the admin section. + * + * @throws Exception + * @throws NotAdminException + * + * @since 31.0.0 + */ + public function getValue(IUser $user, string $app, string $formId, string $fieldId): mixed; + + /** + * Deletes the value of the given field ID. + * + * @param IUser $user Used for storing values in the personal section or for authorization for the admin section. + * + * @throws Exception + * @throws NotAdminException + * + * @since 31.0.0 + */ + public function deleteValue(IUser $user, string $app, string $formId, string $fieldId): void; } |