Signed-off-by: jld3103 <jld3103yt@gmail.com> Signed-off-by: Julien Veyssier <julien-nc@posteo.net> Signed-off-by: Andrey Borysenko <andrey18106x@gmail.com>tags/v29.0.0beta2
['name' => 'WebAuthn#deleteRegistration', 'url' => '/settings/api/personal/webauthn/registration/{id}', 'verb' => 'DELETE' , 'root' => ''], | ['name' => 'WebAuthn#deleteRegistration', 'url' => '/settings/api/personal/webauthn/registration/{id}', 'verb' => 'DELETE' , 'root' => ''], | ||||
['name' => 'Reasons#getPdf', 'url' => '/settings/download/reasons', 'verb' => 'GET', 'root' => ''], | ['name' => 'Reasons#getPdf', 'url' => '/settings/download/reasons', 'verb' => 'GET', 'root' => ''], | ||||
] | |||||
], | |||||
'ocs' => [ | |||||
['name' => 'DeclarativeSettings#setValue', 'url' => '/settings/api/declarative/value', 'verb' => 'POST', 'root' => ''], | |||||
['name' => 'DeclarativeSettings#getForms', 'url' => '/settings/api/declarative/forms', 'verb' => 'GET', 'root' => ''], | |||||
], | |||||
]; | ]; |
'OCA\\Settings\\Controller\\ChangePasswordController' => $baseDir . '/../lib/Controller/ChangePasswordController.php', | 'OCA\\Settings\\Controller\\ChangePasswordController' => $baseDir . '/../lib/Controller/ChangePasswordController.php', | ||||
'OCA\\Settings\\Controller\\CheckSetupController' => $baseDir . '/../lib/Controller/CheckSetupController.php', | 'OCA\\Settings\\Controller\\CheckSetupController' => $baseDir . '/../lib/Controller/CheckSetupController.php', | ||||
'OCA\\Settings\\Controller\\CommonSettingsTrait' => $baseDir . '/../lib/Controller/CommonSettingsTrait.php', | 'OCA\\Settings\\Controller\\CommonSettingsTrait' => $baseDir . '/../lib/Controller/CommonSettingsTrait.php', | ||||
'OCA\\Settings\\Controller\\DeclarativeSettingsController' => $baseDir . '/../lib/Controller/DeclarativeSettingsController.php', | |||||
'OCA\\Settings\\Controller\\HelpController' => $baseDir . '/../lib/Controller/HelpController.php', | 'OCA\\Settings\\Controller\\HelpController' => $baseDir . '/../lib/Controller/HelpController.php', | ||||
'OCA\\Settings\\Controller\\LogSettingsController' => $baseDir . '/../lib/Controller/LogSettingsController.php', | 'OCA\\Settings\\Controller\\LogSettingsController' => $baseDir . '/../lib/Controller/LogSettingsController.php', | ||||
'OCA\\Settings\\Controller\\MailSettingsController' => $baseDir . '/../lib/Controller/MailSettingsController.php', | 'OCA\\Settings\\Controller\\MailSettingsController' => $baseDir . '/../lib/Controller/MailSettingsController.php', | ||||
'OCA\\Settings\\Listener\\UserRemovedFromGroupActivityListener' => $baseDir . '/../lib/Listener/UserRemovedFromGroupActivityListener.php', | 'OCA\\Settings\\Listener\\UserRemovedFromGroupActivityListener' => $baseDir . '/../lib/Listener/UserRemovedFromGroupActivityListener.php', | ||||
'OCA\\Settings\\Mailer\\NewUserMailHelper' => $baseDir . '/../lib/Mailer/NewUserMailHelper.php', | 'OCA\\Settings\\Mailer\\NewUserMailHelper' => $baseDir . '/../lib/Mailer/NewUserMailHelper.php', | ||||
'OCA\\Settings\\Middleware\\SubadminMiddleware' => $baseDir . '/../lib/Middleware/SubadminMiddleware.php', | 'OCA\\Settings\\Middleware\\SubadminMiddleware' => $baseDir . '/../lib/Middleware/SubadminMiddleware.php', | ||||
'OCA\\Settings\\ResponseDefinitions' => $baseDir . '/../lib/ResponseDefinitions.php', | |||||
'OCA\\Settings\\Search\\AppSearch' => $baseDir . '/../lib/Search/AppSearch.php', | 'OCA\\Settings\\Search\\AppSearch' => $baseDir . '/../lib/Search/AppSearch.php', | ||||
'OCA\\Settings\\Search\\SectionSearch' => $baseDir . '/../lib/Search/SectionSearch.php', | 'OCA\\Settings\\Search\\SectionSearch' => $baseDir . '/../lib/Search/SectionSearch.php', | ||||
'OCA\\Settings\\Search\\UserSearch' => $baseDir . '/../lib/Search/UserSearch.php', | 'OCA\\Settings\\Search\\UserSearch' => $baseDir . '/../lib/Search/UserSearch.php', |
'OCA\\Settings\\Controller\\ChangePasswordController' => __DIR__ . '/..' . '/../lib/Controller/ChangePasswordController.php', | 'OCA\\Settings\\Controller\\ChangePasswordController' => __DIR__ . '/..' . '/../lib/Controller/ChangePasswordController.php', | ||||
'OCA\\Settings\\Controller\\CheckSetupController' => __DIR__ . '/..' . '/../lib/Controller/CheckSetupController.php', | 'OCA\\Settings\\Controller\\CheckSetupController' => __DIR__ . '/..' . '/../lib/Controller/CheckSetupController.php', | ||||
'OCA\\Settings\\Controller\\CommonSettingsTrait' => __DIR__ . '/..' . '/../lib/Controller/CommonSettingsTrait.php', | 'OCA\\Settings\\Controller\\CommonSettingsTrait' => __DIR__ . '/..' . '/../lib/Controller/CommonSettingsTrait.php', | ||||
'OCA\\Settings\\Controller\\DeclarativeSettingsController' => __DIR__ . '/..' . '/../lib/Controller/DeclarativeSettingsController.php', | |||||
'OCA\\Settings\\Controller\\HelpController' => __DIR__ . '/..' . '/../lib/Controller/HelpController.php', | 'OCA\\Settings\\Controller\\HelpController' => __DIR__ . '/..' . '/../lib/Controller/HelpController.php', | ||||
'OCA\\Settings\\Controller\\LogSettingsController' => __DIR__ . '/..' . '/../lib/Controller/LogSettingsController.php', | 'OCA\\Settings\\Controller\\LogSettingsController' => __DIR__ . '/..' . '/../lib/Controller/LogSettingsController.php', | ||||
'OCA\\Settings\\Controller\\MailSettingsController' => __DIR__ . '/..' . '/../lib/Controller/MailSettingsController.php', | 'OCA\\Settings\\Controller\\MailSettingsController' => __DIR__ . '/..' . '/../lib/Controller/MailSettingsController.php', | ||||
'OCA\\Settings\\Listener\\UserRemovedFromGroupActivityListener' => __DIR__ . '/..' . '/../lib/Listener/UserRemovedFromGroupActivityListener.php', | 'OCA\\Settings\\Listener\\UserRemovedFromGroupActivityListener' => __DIR__ . '/..' . '/../lib/Listener/UserRemovedFromGroupActivityListener.php', | ||||
'OCA\\Settings\\Mailer\\NewUserMailHelper' => __DIR__ . '/..' . '/../lib/Mailer/NewUserMailHelper.php', | 'OCA\\Settings\\Mailer\\NewUserMailHelper' => __DIR__ . '/..' . '/../lib/Mailer/NewUserMailHelper.php', | ||||
'OCA\\Settings\\Middleware\\SubadminMiddleware' => __DIR__ . '/..' . '/../lib/Middleware/SubadminMiddleware.php', | 'OCA\\Settings\\Middleware\\SubadminMiddleware' => __DIR__ . '/..' . '/../lib/Middleware/SubadminMiddleware.php', | ||||
'OCA\\Settings\\ResponseDefinitions' => __DIR__ . '/..' . '/../lib/ResponseDefinitions.php', | |||||
'OCA\\Settings\\Search\\AppSearch' => __DIR__ . '/..' . '/../lib/Search/AppSearch.php', | 'OCA\\Settings\\Search\\AppSearch' => __DIR__ . '/..' . '/../lib/Search/AppSearch.php', | ||||
'OCA\\Settings\\Search\\SectionSearch' => __DIR__ . '/..' . '/../lib/Search/SectionSearch.php', | 'OCA\\Settings\\Search\\SectionSearch' => __DIR__ . '/..' . '/../lib/Search/SectionSearch.php', | ||||
'OCA\\Settings\\Search\\UserSearch' => __DIR__ . '/..' . '/../lib/Search/UserSearch.php', | 'OCA\\Settings\\Search\\UserSearch' => __DIR__ . '/..' . '/../lib/Search/UserSearch.php', |
'name' => '__root__', | 'name' => '__root__', | ||||
'pretty_version' => 'dev-master', | 'pretty_version' => 'dev-master', | ||||
'version' => 'dev-master', | 'version' => 'dev-master', | ||||
'reference' => 'b1797842784b250fb01ed5e3bf130705eb94751b', | |||||
'reference' => '4ff660ca2e0baa02440ba07296ed7e75fa544c0e', | |||||
'type' => 'library', | 'type' => 'library', | ||||
'install_path' => __DIR__ . '/../', | 'install_path' => __DIR__ . '/../', | ||||
'aliases' => array(), | 'aliases' => array(), | ||||
'__root__' => array( | '__root__' => array( | ||||
'pretty_version' => 'dev-master', | 'pretty_version' => 'dev-master', | ||||
'version' => 'dev-master', | 'version' => 'dev-master', | ||||
'reference' => 'b1797842784b250fb01ed5e3bf130705eb94751b', | |||||
'reference' => '4ff660ca2e0baa02440ba07296ed7e75fa544c0e', | |||||
'type' => 'library', | 'type' => 'library', | ||||
'install_path' => __DIR__ . '/../', | 'install_path' => __DIR__ . '/../', | ||||
'aliases' => array(), | 'aliases' => array(), |
use OCP\AppFramework\Controller; | use OCP\AppFramework\Controller; | ||||
use OCP\AppFramework\Http\Attribute\OpenAPI; | use OCP\AppFramework\Http\Attribute\OpenAPI; | ||||
use OCP\AppFramework\Http\TemplateResponse; | use OCP\AppFramework\Http\TemplateResponse; | ||||
use OCP\AppFramework\Services\IInitialState; | |||||
use OCP\Group\ISubAdmin; | use OCP\Group\ISubAdmin; | ||||
use OCP\IGroupManager; | use OCP\IGroupManager; | ||||
use OCP\INavigationManager; | use OCP\INavigationManager; | ||||
use OCP\IRequest; | use OCP\IRequest; | ||||
use OCP\IUser; | use OCP\IUser; | ||||
use OCP\IUserSession; | use OCP\IUserSession; | ||||
use OCP\Settings\IDeclarativeManager; | |||||
use OCP\Settings\IManager as ISettingsManager; | use OCP\Settings\IManager as ISettingsManager; | ||||
use OCP\Template; | use OCP\Template; | ||||
ISettingsManager $settingsManager, | ISettingsManager $settingsManager, | ||||
IUserSession $userSession, | IUserSession $userSession, | ||||
IGroupManager $groupManager, | IGroupManager $groupManager, | ||||
ISubAdmin $subAdmin | |||||
ISubAdmin $subAdmin, | |||||
IDeclarativeManager $declarativeSettingsManager, | |||||
IInitialState $initialState, | |||||
) { | ) { | ||||
parent::__construct($appName, $request); | parent::__construct($appName, $request); | ||||
$this->navigationManager = $navigationManager; | $this->navigationManager = $navigationManager; | ||||
$this->userSession = $userSession; | $this->userSession = $userSession; | ||||
$this->groupManager = $groupManager; | $this->groupManager = $groupManager; | ||||
$this->subAdmin = $subAdmin; | $this->subAdmin = $subAdmin; | ||||
$this->declarativeSettingsManager = $declarativeSettingsManager; | |||||
$this->initialState = $initialState; | |||||
} | } | ||||
/** | /** | ||||
$user = $this->userSession->getUser(); | $user = $this->userSession->getUser(); | ||||
$isSubAdmin = !$this->groupManager->isAdmin($user->getUID()) && $this->subAdmin->isSubAdmin($user); | $isSubAdmin = !$this->groupManager->isAdmin($user->getUID()) && $this->subAdmin->isSubAdmin($user); | ||||
$settings = $this->settingsManager->getAllowedAdminSettings($section, $user); | $settings = $this->settingsManager->getAllowedAdminSettings($section, $user); | ||||
if (empty($settings)) { | |||||
$declarativeFormIDs = $this->declarativeSettingsManager->getFormIDs($user, 'admin', $section); | |||||
if (empty($settings) && empty($declarativeFormIDs)) { | |||||
throw new NotAdminException("Logged in user doesn't have permission to access these settings."); | throw new NotAdminException("Logged in user doesn't have permission to access these settings."); | ||||
} | } | ||||
$formatted = $this->formatSettings($settings); | $formatted = $this->formatSettings($settings); |
* along with this program. If not, see <http://www.gnu.org/licenses/>. | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||
* | * | ||||
*/ | */ | ||||
namespace OCA\Settings\Controller; | namespace OCA\Settings\Controller; | ||||
use OCA\Settings\AppInfo\Application; | |||||
use OCP\AppFramework\Http\TemplateResponse; | use OCP\AppFramework\Http\TemplateResponse; | ||||
use OCP\AppFramework\Services\IInitialState; | |||||
use OCP\Group\ISubAdmin; | use OCP\Group\ISubAdmin; | ||||
use OCP\IGroupManager; | use OCP\IGroupManager; | ||||
use OCP\INavigationManager; | use OCP\INavigationManager; | ||||
use OCP\IUserSession; | use OCP\IUserSession; | ||||
use OCP\Settings\IDeclarativeManager; | |||||
use OCP\Settings\IDeclarativeSettingsForm; | |||||
use OCP\Settings\IIconSection; | use OCP\Settings\IIconSection; | ||||
use OCP\Settings\IManager as ISettingsManager; | use OCP\Settings\IManager as ISettingsManager; | ||||
use OCP\Settings\ISettings; | use OCP\Settings\ISettings; | ||||
use OCP\Util; | |||||
/** | |||||
* @psalm-import-type DeclarativeSettingsFormField from IDeclarativeSettingsForm | |||||
*/ | |||||
trait CommonSettingsTrait { | trait CommonSettingsTrait { | ||||
/** @var ISettingsManager */ | /** @var ISettingsManager */ | ||||
/** @var ISubAdmin */ | /** @var ISubAdmin */ | ||||
private $subAdmin; | private $subAdmin; | ||||
private IDeclarativeManager $declarativeSettingsManager; | |||||
/** @var IInitialState */ | |||||
private $initialState; | |||||
/** | /** | ||||
* @return array{forms: array{personal: array, admin: array}} | * @return array{forms: array{personal: array, admin: array}} | ||||
*/ | */ | ||||
private function getNavigationParameters(string $currentType, string $currentSection): array { | private function getNavigationParameters(string $currentType, string $currentSection): array { | ||||
$templateParameters = [ | |||||
'personal' => $this->formatPersonalSections($currentType, $currentSection), | |||||
'admin' => [] | |||||
]; | |||||
$templateParameters['admin'] = $this->formatAdminSections( | |||||
$currentType, | |||||
$currentSection | |||||
); | |||||
return [ | return [ | ||||
'forms' => $templateParameters | |||||
'forms' => [ | |||||
'personal' => $this->formatPersonalSections($currentType, $currentSection), | |||||
'admin' => $this->formatAdminSections($currentType, $currentSection), | |||||
], | |||||
]; | ]; | ||||
} | } | ||||
/** | /** | ||||
* @param IIconSection[][] $sections | * @param IIconSection[][] $sections | ||||
* @psam-param 'admin'|'personal' $type | |||||
* @psalm-param 'admin'|'personal' $type | |||||
* @return list<array{anchor: string, section-name: string, active: bool, icon: string}> | * @return list<array{anchor: string, section-name: string, active: bool, icon: string}> | ||||
*/ | */ | ||||
protected function formatSections(array $sections, string $currentSection, string $type, string $currentType): array { | protected function formatSections(array $sections, string $currentSection, string $type, string $currentType): array { | ||||
} elseif ($type === 'personal') { | } elseif ($type === 'personal') { | ||||
$settings = $this->settingsManager->getPersonalSettings($section->getID()); | $settings = $this->settingsManager->getPersonalSettings($section->getID()); | ||||
} | } | ||||
if (empty($settings) && !($section->getID() === 'additional' && count(\OC_App::getForms('admin')) > 0)) { | |||||
/** @psalm-suppress PossiblyNullArgument */ | |||||
$declarativeFormIDs = $this->declarativeSettingsManager->getFormIDs($this->userSession->getUser(), $type, $section->getID()); | |||||
if (empty($settings) && empty($declarativeFormIDs) && !($section->getID() === 'additional' && count(\OC_App::getForms('admin')) > 0)) { | |||||
continue; | continue; | ||||
} | } | ||||
return $templateParameters; | return $templateParameters; | ||||
} | } | ||||
protected function formatPersonalSections(string $currentType, string $currentSections): array { | |||||
protected function formatPersonalSections(string $currentType, string $currentSection): array { | |||||
$sections = $this->settingsManager->getPersonalSections(); | $sections = $this->settingsManager->getPersonalSections(); | ||||
return $this->formatSections($sections, $currentSections, 'personal', $currentType); | |||||
return $this->formatSections($sections, $currentSection, 'personal', $currentType); | |||||
} | } | ||||
protected function formatAdminSections(string $currentType, string $currentSections): array { | |||||
protected function formatAdminSections(string $currentType, string $currentSection): array { | |||||
$sections = $this->settingsManager->getAdminSections(); | $sections = $this->settingsManager->getAdminSections(); | ||||
return $this->formatSections($sections, $currentSections, 'admin', $currentType); | |||||
return $this->formatSections($sections, $currentSection, 'admin', $currentType); | |||||
} | } | ||||
/** | /** | ||||
return ['content' => $html]; | return ['content' => $html]; | ||||
} | } | ||||
/** | |||||
* @psalm-param 'admin'|'personal' $type | |||||
*/ | |||||
private function getIndexResponse(string $type, string $section): TemplateResponse { | private function getIndexResponse(string $type, string $section): TemplateResponse { | ||||
if ($type === 'personal') { | if ($type === 'personal') { | ||||
if ($section === 'theming') { | if ($section === 'theming') { | ||||
$this->navigationManager->setActiveEntry('admin_settings'); | $this->navigationManager->setActiveEntry('admin_settings'); | ||||
} | } | ||||
$this->declarativeSettingsManager->loadSchemas(); | |||||
$templateParams = []; | $templateParams = []; | ||||
$templateParams = array_merge($templateParams, $this->getNavigationParameters($type, $section)); | $templateParams = array_merge($templateParams, $this->getNavigationParameters($type, $section)); | ||||
$templateParams = array_merge($templateParams, $this->getSettings($section)); | $templateParams = array_merge($templateParams, $this->getSettings($section)); | ||||
/** @psalm-suppress PossiblyNullArgument */ | |||||
$declarativeFormIDs = $this->declarativeSettingsManager->getFormIDs($this->userSession->getUser(), $type, $section); | |||||
if (!empty($declarativeFormIDs)) { | |||||
foreach ($declarativeFormIDs as $app => $ids) { | |||||
/** @psalm-suppress PossiblyUndefinedArrayOffset */ | |||||
$templateParams['content'] .= join(array_map(fn (string $id) => '<div id="' . $app . '_' . $id . '"></div>', $ids)); | |||||
} | |||||
Util::addScript(Application::APP_ID, 'declarative-settings-forms'); | |||||
/** @psalm-suppress PossiblyNullArgument */ | |||||
$this->initialState->provideInitialState('declarative-settings-forms', $this->declarativeSettingsManager->getFormsWithValues($this->userSession->getUser(), $type, $section)); | |||||
} | |||||
$activeSection = $this->settingsManager->getSection($type, $section); | $activeSection = $this->settingsManager->getSection($type, $section); | ||||
if ($activeSection) { | if ($activeSection) { | ||||
$templateParams['pageTitle'] = $activeSection->getName(); | $templateParams['pageTitle'] = $activeSection->getName(); |
<?php | |||||
/** | |||||
* @copyright Copyright (c) 2023 Kate Döen <kate.doeen@nextcloud.com> | |||||
* | |||||
* @author Kate Döen <kate.doeen@nextcloud.com> | |||||
* | |||||
* @license GNU AGPL version 3 or any later version | |||||
* | |||||
* This program is free software: you can redistribute it and/or modify | |||||
* it under the terms of the GNU Affero General Public License as | |||||
* published by the Free Software Foundation, either version 3 of the | |||||
* License, or (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
* GNU Affero General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Affero General Public License | |||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||||
* | |||||
*/ | |||||
namespace OCA\Settings\Controller; | |||||
use Exception; | |||||
use OC\AppFramework\Middleware\Security\Exceptions\NotAdminException; | |||||
use OC\AppFramework\Middleware\Security\Exceptions\NotLoggedInException; | |||||
use OCA\Settings\ResponseDefinitions; | |||||
use OCP\AppFramework\Http; | |||||
use OCP\AppFramework\Http\Attribute\NoAdminRequired; | |||||
use OCP\AppFramework\Http\DataResponse; | |||||
use OCP\AppFramework\OCS\OCSBadRequestException; | |||||
use OCP\AppFramework\OCSController; | |||||
use OCP\IRequest; | |||||
use OCP\IUserSession; | |||||
use OCP\Settings\IDeclarativeManager; | |||||
use Psr\Log\LoggerInterface; | |||||
/** | |||||
* @psalm-import-type SettingsDeclarativeForm from ResponseDefinitions | |||||
*/ | |||||
class DeclarativeSettingsController extends OCSController { | |||||
public function __construct( | |||||
string $appName, | |||||
IRequest $request, | |||||
private IUserSession $userSession, | |||||
private IDeclarativeManager $declarativeManager, | |||||
private LoggerInterface $logger, | |||||
) { | |||||
parent::__construct($appName, $request); | |||||
} | |||||
/** | |||||
* Sets a declarative settings value | |||||
* | |||||
* @param string $app ID of the app | |||||
* @param string $formId ID of the form | |||||
* @param string $fieldId ID of the field | |||||
* @param mixed $value Value to be saved | |||||
* @return DataResponse<Http::STATUS_OK, null, array{}> | |||||
* @throws NotLoggedInException Not logged in or not an admin user | |||||
* @throws NotAdminException Not logged in or not an admin user | |||||
* @throws OCSBadRequestException Invalid arguments to save value | |||||
* | |||||
* 200: Value set successfully | |||||
*/ | |||||
#[NoAdminRequired] | |||||
public function setValue(string $app, string $formId, string $fieldId, mixed $value): DataResponse { | |||||
$user = $this->userSession->getUser(); | |||||
if ($user === null) { | |||||
throw new NotLoggedInException(); | |||||
} | |||||
try { | |||||
$this->declarativeManager->loadSchemas(); | |||||
$this->declarativeManager->setValue($user, $app, $formId, $fieldId, $value); | |||||
return new DataResponse(null); | |||||
} catch (NotAdminException $e) { | |||||
throw $e; | |||||
} catch (Exception $e) { | |||||
$this->logger->error('Failed to set declarative settings value: ' . $e->getMessage()); | |||||
throw new OCSBadRequestException(); | |||||
} | |||||
} | |||||
/** | |||||
* Gets all declarative forms with the values prefilled. | |||||
* | |||||
* @return DataResponse<Http::STATUS_OK, list<SettingsDeclarativeForm>, array{}> | |||||
* @throws NotLoggedInException | |||||
* @NoSubAdminRequired | |||||
* | |||||
* 200: Forms returned | |||||
*/ | |||||
#[NoAdminRequired] | |||||
public function getForms(): DataResponse { | |||||
$user = $this->userSession->getUser(); | |||||
if ($user === null) { | |||||
throw new NotLoggedInException(); | |||||
} | |||||
$this->declarativeManager->loadSchemas(); | |||||
return new DataResponse($this->declarativeManager->getFormsWithValues($user, null, null)); | |||||
} | |||||
} |
use OCP\AppFramework\Controller; | use OCP\AppFramework\Controller; | ||||
use OCP\AppFramework\Http\Attribute\OpenAPI; | use OCP\AppFramework\Http\Attribute\OpenAPI; | ||||
use OCP\AppFramework\Http\TemplateResponse; | use OCP\AppFramework\Http\TemplateResponse; | ||||
use OCP\AppFramework\Services\IInitialState; | |||||
use OCP\Group\ISubAdmin; | use OCP\Group\ISubAdmin; | ||||
use OCP\IGroupManager; | use OCP\IGroupManager; | ||||
use OCP\INavigationManager; | use OCP\INavigationManager; | ||||
use OCP\IRequest; | use OCP\IRequest; | ||||
use OCP\IUserSession; | use OCP\IUserSession; | ||||
use OCP\Settings\IDeclarativeManager; | |||||
use OCP\Settings\IManager as ISettingsManager; | use OCP\Settings\IManager as ISettingsManager; | ||||
use OCP\Template; | use OCP\Template; | ||||
ISettingsManager $settingsManager, | ISettingsManager $settingsManager, | ||||
IUserSession $userSession, | IUserSession $userSession, | ||||
IGroupManager $groupManager, | IGroupManager $groupManager, | ||||
ISubAdmin $subAdmin | |||||
ISubAdmin $subAdmin, | |||||
IDeclarativeManager $declarativeSettingsManager, | |||||
IInitialState $initialState, | |||||
) { | ) { | ||||
parent::__construct($appName, $request); | parent::__construct($appName, $request); | ||||
$this->navigationManager = $navigationManager; | $this->navigationManager = $navigationManager; | ||||
$this->userSession = $userSession; | $this->userSession = $userSession; | ||||
$this->subAdmin = $subAdmin; | $this->subAdmin = $subAdmin; | ||||
$this->groupManager = $groupManager; | $this->groupManager = $groupManager; | ||||
$this->declarativeSettingsManager = $declarativeSettingsManager; | |||||
$this->initialState = $initialState; | |||||
} | } | ||||
/** | /** |
<?php | |||||
declare(strict_types=1); | |||||
/** | |||||
* @copyright Copyright (c) 2024 Kate Döen <kate.doeen@nextcloud.com> | |||||
* | |||||
* @author Kate Döen <kate.doeen@nextcloud.com> | |||||
* | |||||
* @license GNU AGPL version 3 or any later version | |||||
* | |||||
* This program is free software: you can redistribute it and/or modify | |||||
* it under the terms of the GNU Affero General Public License as | |||||
* published by the Free Software Foundation, either version 3 of the | |||||
* License, or (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
* GNU Affero General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Affero General Public License | |||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||||
* | |||||
*/ | |||||
namespace OCA\Settings; | |||||
/** | |||||
* @psalm-type SettingsDeclarativeFormField = array{ | |||||
* id: string, | |||||
* title: string, | |||||
* description?: string, | |||||
* type: 'text'|'password'|'email'|'tel'|'url'|'number'|'checkbox'|'multi-checkbox'|'radio'|'select'|'multi-select', | |||||
* placeholder?: string, | |||||
* label?: string, | |||||
* default: mixed, | |||||
* options?: list<string|array{name: string, value: mixed}>, | |||||
* value: string|int|float|bool|list<string>, | |||||
* } | |||||
* | |||||
* @psalm-type SettingsDeclarativeForm = array{ | |||||
* id: string, | |||||
* priority: int, | |||||
* section_type: 'admin'|'personal', | |||||
* section_id: string, | |||||
* storage_type: 'internal'|'external', | |||||
* title: string, | |||||
* description?: string, | |||||
* doc_url?: string, | |||||
* app: string, | |||||
* fields: list<SettingsDeclarativeFormField>, | |||||
* } | |||||
*/ | |||||
class ResponseDefinitions { | |||||
} |
{ | |||||
"openapi": "3.0.3", | |||||
"info": { | |||||
"title": "settings-administration", | |||||
"version": "0.0.1", | |||||
"description": "Nextcloud settings", | |||||
"license": { | |||||
"name": "agpl" | |||||
} | |||||
}, | |||||
"components": { | |||||
"securitySchemes": { | |||||
"basic_auth": { | |||||
"type": "http", | |||||
"scheme": "basic" | |||||
}, | |||||
"bearer_auth": { | |||||
"type": "http", | |||||
"scheme": "bearer" | |||||
} | |||||
}, | |||||
"schemas": {} | |||||
}, | |||||
"paths": { | |||||
"/index.php/settings/admin/log/download": { | |||||
"get": { | |||||
"operationId": "log_settings-download", | |||||
"summary": "download logfile", | |||||
"description": "This endpoint requires admin access", | |||||
"tags": [ | |||||
"log_settings" | |||||
], | |||||
"security": [ | |||||
{ | |||||
"bearer_auth": [] | |||||
}, | |||||
{ | |||||
"basic_auth": [] | |||||
} | |||||
], | |||||
"responses": { | |||||
"200": { | |||||
"description": "Logfile returned", | |||||
"headers": { | |||||
"Content-Disposition": { | |||||
"schema": { | |||||
"type": "string" | |||||
} | |||||
} | |||||
}, | |||||
"content": { | |||||
"application/octet-stream": { | |||||
"schema": { | |||||
"type": "string", | |||||
"format": "binary" | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
}, | |||||
"tags": [] | |||||
} |
{ | |||||
"openapi": "3.0.3", | |||||
"info": { | |||||
"title": "settings-full", | |||||
"version": "0.0.1", | |||||
"description": "Nextcloud settings", | |||||
"license": { | |||||
"name": "agpl" | |||||
} | |||||
}, | |||||
"components": { | |||||
"securitySchemes": { | |||||
"basic_auth": { | |||||
"type": "http", | |||||
"scheme": "basic" | |||||
}, | |||||
"bearer_auth": { | |||||
"type": "http", | |||||
"scheme": "bearer" | |||||
} | |||||
}, | |||||
"schemas": { | |||||
"DeclarativeForm": { | |||||
"type": "object", | |||||
"required": [ | |||||
"id", | |||||
"priority", | |||||
"section_type", | |||||
"section_id", | |||||
"storage_type", | |||||
"title", | |||||
"app", | |||||
"fields" | |||||
], | |||||
"properties": { | |||||
"id": { | |||||
"type": "string" | |||||
}, | |||||
"priority": { | |||||
"type": "integer", | |||||
"format": "int64" | |||||
}, | |||||
"section_type": { | |||||
"type": "string", | |||||
"enum": [ | |||||
"admin", | |||||
"personal" | |||||
] | |||||
}, | |||||
"section_id": { | |||||
"type": "string" | |||||
}, | |||||
"storage_type": { | |||||
"type": "string", | |||||
"enum": [ | |||||
"internal", | |||||
"external" | |||||
] | |||||
}, | |||||
"title": { | |||||
"type": "string" | |||||
}, | |||||
"description": { | |||||
"type": "string" | |||||
}, | |||||
"doc_url": { | |||||
"type": "string" | |||||
}, | |||||
"app": { | |||||
"type": "string" | |||||
}, | |||||
"fields": { | |||||
"type": "array", | |||||
"items": { | |||||
"$ref": "#/components/schemas/DeclarativeFormField" | |||||
} | |||||
} | |||||
} | |||||
}, | |||||
"DeclarativeFormField": { | |||||
"type": "object", | |||||
"required": [ | |||||
"id", | |||||
"title", | |||||
"type", | |||||
"default", | |||||
"value" | |||||
], | |||||
"properties": { | |||||
"id": { | |||||
"type": "string" | |||||
}, | |||||
"title": { | |||||
"type": "string" | |||||
}, | |||||
"description": { | |||||
"type": "string" | |||||
}, | |||||
"type": { | |||||
"type": "string", | |||||
"enum": [ | |||||
"text", | |||||
"password", | |||||
"email", | |||||
"tel", | |||||
"url", | |||||
"number", | |||||
"checkbox", | |||||
"multi-checkbox", | |||||
"radio", | |||||
"select", | |||||
"multi-select" | |||||
] | |||||
}, | |||||
"placeholder": { | |||||
"type": "string" | |||||
}, | |||||
"label": { | |||||
"type": "string" | |||||
}, | |||||
"default": { | |||||
"type": "object" | |||||
}, | |||||
"options": { | |||||
"type": "array", | |||||
"items": { | |||||
"oneOf": [ | |||||
{ | |||||
"type": "string" | |||||
}, | |||||
{ | |||||
"type": "object", | |||||
"required": [ | |||||
"name", | |||||
"value" | |||||
], | |||||
"properties": { | |||||
"name": { | |||||
"type": "string" | |||||
}, | |||||
"value": { | |||||
"type": "object" | |||||
} | |||||
} | |||||
} | |||||
] | |||||
} | |||||
}, | |||||
"value": { | |||||
"oneOf": [ | |||||
{ | |||||
"type": "string" | |||||
}, | |||||
{ | |||||
"type": "integer", | |||||
"format": "int64" | |||||
}, | |||||
{ | |||||
"type": "number", | |||||
"format": "float" | |||||
}, | |||||
{ | |||||
"type": "boolean" | |||||
}, | |||||
{ | |||||
"type": "array", | |||||
"items": { | |||||
"type": "string" | |||||
} | |||||
} | |||||
] | |||||
} | |||||
} | |||||
}, | |||||
"OCSMeta": { | |||||
"type": "object", | |||||
"required": [ | |||||
"status", | |||||
"statuscode" | |||||
], | |||||
"properties": { | |||||
"status": { | |||||
"type": "string" | |||||
}, | |||||
"statuscode": { | |||||
"type": "integer" | |||||
}, | |||||
"message": { | |||||
"type": "string" | |||||
}, | |||||
"totalitems": { | |||||
"type": "string" | |||||
}, | |||||
"itemsperpage": { | |||||
"type": "string" | |||||
} | |||||
} | |||||
} | |||||
} | |||||
}, | |||||
"paths": { | |||||
"/index.php/settings/admin/log/download": { | |||||
"get": { | |||||
"operationId": "log_settings-download", | |||||
"summary": "download logfile", | |||||
"description": "This endpoint requires admin access", | |||||
"tags": [ | |||||
"log_settings" | |||||
], | |||||
"security": [ | |||||
{ | |||||
"bearer_auth": [] | |||||
}, | |||||
{ | |||||
"basic_auth": [] | |||||
} | |||||
], | |||||
"responses": { | |||||
"200": { | |||||
"description": "Logfile returned", | |||||
"headers": { | |||||
"Content-Disposition": { | |||||
"schema": { | |||||
"type": "string" | |||||
} | |||||
} | |||||
}, | |||||
"content": { | |||||
"application/octet-stream": { | |||||
"schema": { | |||||
"type": "string", | |||||
"format": "binary" | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
}, | |||||
"/ocs/v2.php/settings/api/declarative/value": { | |||||
"post": { | |||||
"operationId": "declarative_settings-set-value", | |||||
"summary": "Sets a declarative settings value", | |||||
"tags": [ | |||||
"declarative_settings" | |||||
], | |||||
"security": [ | |||||
{ | |||||
"bearer_auth": [] | |||||
}, | |||||
{ | |||||
"basic_auth": [] | |||||
} | |||||
], | |||||
"parameters": [ | |||||
{ | |||||
"name": "app", | |||||
"in": "query", | |||||
"description": "ID of the app", | |||||
"required": true, | |||||
"schema": { | |||||
"type": "string" | |||||
} | |||||
}, | |||||
{ | |||||
"name": "formId", | |||||
"in": "query", | |||||
"description": "ID of the form", | |||||
"required": true, | |||||
"schema": { | |||||
"type": "string" | |||||
} | |||||
}, | |||||
{ | |||||
"name": "fieldId", | |||||
"in": "query", | |||||
"description": "ID of the field", | |||||
"required": true, | |||||
"schema": { | |||||
"type": "string" | |||||
} | |||||
}, | |||||
{ | |||||
"name": "value", | |||||
"in": "query", | |||||
"description": "Value to be saved", | |||||
"required": true, | |||||
"schema": { | |||||
"type": "string" | |||||
} | |||||
}, | |||||
{ | |||||
"name": "OCS-APIRequest", | |||||
"in": "header", | |||||
"description": "Required to be true for the API request to pass", | |||||
"required": true, | |||||
"schema": { | |||||
"type": "boolean", | |||||
"default": true | |||||
} | |||||
} | |||||
], | |||||
"responses": { | |||||
"200": { | |||||
"description": "Value set successfully", | |||||
"content": { | |||||
"application/json": { | |||||
"schema": { | |||||
"type": "object", | |||||
"required": [ | |||||
"ocs" | |||||
], | |||||
"properties": { | |||||
"ocs": { | |||||
"type": "object", | |||||
"required": [ | |||||
"meta", | |||||
"data" | |||||
], | |||||
"properties": { | |||||
"meta": { | |||||
"$ref": "#/components/schemas/OCSMeta" | |||||
}, | |||||
"data": { | |||||
"nullable": true | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
}, | |||||
"500": { | |||||
"description": "Not logged in or not an admin user", | |||||
"content": { | |||||
"text/plain": { | |||||
"schema": { | |||||
"type": "string" | |||||
} | |||||
} | |||||
} | |||||
}, | |||||
"400": { | |||||
"description": "Invalid arguments to save value", | |||||
"content": { | |||||
"text/plain": { | |||||
"schema": { | |||||
"type": "string" | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
}, | |||||
"/ocs/v2.php/settings/api/declarative/forms": { | |||||
"get": { | |||||
"operationId": "declarative_settings-get-forms", | |||||
"summary": "Gets all declarative forms with the values prefilled.", | |||||
"tags": [ | |||||
"declarative_settings" | |||||
], | |||||
"security": [ | |||||
{ | |||||
"bearer_auth": [] | |||||
}, | |||||
{ | |||||
"basic_auth": [] | |||||
} | |||||
], | |||||
"parameters": [ | |||||
{ | |||||
"name": "OCS-APIRequest", | |||||
"in": "header", | |||||
"description": "Required to be true for the API request to pass", | |||||
"required": true, | |||||
"schema": { | |||||
"type": "boolean", | |||||
"default": true | |||||
} | |||||
} | |||||
], | |||||
"responses": { | |||||
"200": { | |||||
"description": "Forms returned", | |||||
"content": { | |||||
"application/json": { | |||||
"schema": { | |||||
"type": "object", | |||||
"required": [ | |||||
"ocs" | |||||
], | |||||
"properties": { | |||||
"ocs": { | |||||
"type": "object", | |||||
"required": [ | |||||
"meta", | |||||
"data" | |||||
], | |||||
"properties": { | |||||
"meta": { | |||||
"$ref": "#/components/schemas/OCSMeta" | |||||
}, | |||||
"data": { | |||||
"type": "array", | |||||
"items": { | |||||
"$ref": "#/components/schemas/DeclarativeForm" | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
}, | |||||
"500": { | |||||
"description": "", | |||||
"content": { | |||||
"text/plain": { | |||||
"schema": { | |||||
"type": "string" | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
}, | |||||
"tags": [] | |||||
} |
"scheme": "bearer" | "scheme": "bearer" | ||||
} | } | ||||
}, | }, | ||||
"schemas": {} | |||||
"schemas": { | |||||
"DeclarativeForm": { | |||||
"type": "object", | |||||
"required": [ | |||||
"id", | |||||
"priority", | |||||
"section_type", | |||||
"section_id", | |||||
"storage_type", | |||||
"title", | |||||
"app", | |||||
"fields" | |||||
], | |||||
"properties": { | |||||
"id": { | |||||
"type": "string" | |||||
}, | |||||
"priority": { | |||||
"type": "integer", | |||||
"format": "int64" | |||||
}, | |||||
"section_type": { | |||||
"type": "string", | |||||
"enum": [ | |||||
"admin", | |||||
"personal" | |||||
] | |||||
}, | |||||
"section_id": { | |||||
"type": "string" | |||||
}, | |||||
"storage_type": { | |||||
"type": "string", | |||||
"enum": [ | |||||
"internal", | |||||
"external" | |||||
] | |||||
}, | |||||
"title": { | |||||
"type": "string" | |||||
}, | |||||
"description": { | |||||
"type": "string" | |||||
}, | |||||
"doc_url": { | |||||
"type": "string" | |||||
}, | |||||
"app": { | |||||
"type": "string" | |||||
}, | |||||
"fields": { | |||||
"type": "array", | |||||
"items": { | |||||
"$ref": "#/components/schemas/DeclarativeFormField" | |||||
} | |||||
} | |||||
} | |||||
}, | |||||
"DeclarativeFormField": { | |||||
"type": "object", | |||||
"required": [ | |||||
"id", | |||||
"title", | |||||
"type", | |||||
"default", | |||||
"value" | |||||
], | |||||
"properties": { | |||||
"id": { | |||||
"type": "string" | |||||
}, | |||||
"title": { | |||||
"type": "string" | |||||
}, | |||||
"description": { | |||||
"type": "string" | |||||
}, | |||||
"type": { | |||||
"type": "string", | |||||
"enum": [ | |||||
"text", | |||||
"password", | |||||
"email", | |||||
"tel", | |||||
"url", | |||||
"number", | |||||
"checkbox", | |||||
"multi-checkbox", | |||||
"radio", | |||||
"select", | |||||
"multi-select" | |||||
] | |||||
}, | |||||
"placeholder": { | |||||
"type": "string" | |||||
}, | |||||
"label": { | |||||
"type": "string" | |||||
}, | |||||
"default": { | |||||
"type": "object" | |||||
}, | |||||
"options": { | |||||
"type": "array", | |||||
"items": { | |||||
"oneOf": [ | |||||
{ | |||||
"type": "string" | |||||
}, | |||||
{ | |||||
"type": "object", | |||||
"required": [ | |||||
"name", | |||||
"value" | |||||
], | |||||
"properties": { | |||||
"name": { | |||||
"type": "string" | |||||
}, | |||||
"value": { | |||||
"type": "object" | |||||
} | |||||
} | |||||
} | |||||
] | |||||
} | |||||
}, | |||||
"value": { | |||||
"oneOf": [ | |||||
{ | |||||
"type": "string" | |||||
}, | |||||
{ | |||||
"type": "integer", | |||||
"format": "int64" | |||||
}, | |||||
{ | |||||
"type": "number", | |||||
"format": "float" | |||||
}, | |||||
{ | |||||
"type": "boolean" | |||||
}, | |||||
{ | |||||
"type": "array", | |||||
"items": { | |||||
"type": "string" | |||||
} | |||||
} | |||||
] | |||||
} | |||||
} | |||||
}, | |||||
"OCSMeta": { | |||||
"type": "object", | |||||
"required": [ | |||||
"status", | |||||
"statuscode" | |||||
], | |||||
"properties": { | |||||
"status": { | |||||
"type": "string" | |||||
}, | |||||
"statuscode": { | |||||
"type": "integer" | |||||
}, | |||||
"message": { | |||||
"type": "string" | |||||
}, | |||||
"totalitems": { | |||||
"type": "string" | |||||
}, | |||||
"itemsperpage": { | |||||
"type": "string" | |||||
} | |||||
} | |||||
} | |||||
} | |||||
}, | }, | ||||
"paths": { | "paths": { | ||||
"/index.php/settings/admin/log/download": { | |||||
"get": { | |||||
"operationId": "log_settings-download", | |||||
"summary": "download logfile", | |||||
"description": "This endpoint requires admin access", | |||||
"/ocs/v2.php/settings/api/declarative/value": { | |||||
"post": { | |||||
"operationId": "declarative_settings-set-value", | |||||
"summary": "Sets a declarative settings value", | |||||
"tags": [ | "tags": [ | ||||
"log_settings" | |||||
"declarative_settings" | |||||
], | ], | ||||
"security": [ | "security": [ | ||||
{ | { | ||||
"basic_auth": [] | "basic_auth": [] | ||||
} | } | ||||
], | ], | ||||
"parameters": [ | |||||
{ | |||||
"name": "app", | |||||
"in": "query", | |||||
"description": "ID of the app", | |||||
"required": true, | |||||
"schema": { | |||||
"type": "string" | |||||
} | |||||
}, | |||||
{ | |||||
"name": "formId", | |||||
"in": "query", | |||||
"description": "ID of the form", | |||||
"required": true, | |||||
"schema": { | |||||
"type": "string" | |||||
} | |||||
}, | |||||
{ | |||||
"name": "fieldId", | |||||
"in": "query", | |||||
"description": "ID of the field", | |||||
"required": true, | |||||
"schema": { | |||||
"type": "string" | |||||
} | |||||
}, | |||||
{ | |||||
"name": "value", | |||||
"in": "query", | |||||
"description": "Value to be saved", | |||||
"required": true, | |||||
"schema": { | |||||
"type": "string" | |||||
} | |||||
}, | |||||
{ | |||||
"name": "OCS-APIRequest", | |||||
"in": "header", | |||||
"description": "Required to be true for the API request to pass", | |||||
"required": true, | |||||
"schema": { | |||||
"type": "boolean", | |||||
"default": true | |||||
} | |||||
} | |||||
], | |||||
"responses": { | "responses": { | ||||
"200": { | "200": { | ||||
"description": "Logfile returned", | |||||
"headers": { | |||||
"Content-Disposition": { | |||||
"description": "Value set successfully", | |||||
"content": { | |||||
"application/json": { | |||||
"schema": { | |||||
"type": "object", | |||||
"required": [ | |||||
"ocs" | |||||
], | |||||
"properties": { | |||||
"ocs": { | |||||
"type": "object", | |||||
"required": [ | |||||
"meta", | |||||
"data" | |||||
], | |||||
"properties": { | |||||
"meta": { | |||||
"$ref": "#/components/schemas/OCSMeta" | |||||
}, | |||||
"data": { | |||||
"nullable": true | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
}, | |||||
"500": { | |||||
"description": "Not logged in or not an admin user", | |||||
"content": { | |||||
"text/plain": { | |||||
"schema": { | |||||
"type": "string" | |||||
} | |||||
} | |||||
} | |||||
}, | |||||
"400": { | |||||
"description": "Invalid arguments to save value", | |||||
"content": { | |||||
"text/plain": { | |||||
"schema": { | "schema": { | ||||
"type": "string" | "type": "string" | ||||
} | } | ||||
} | } | ||||
}, | |||||
} | |||||
} | |||||
} | |||||
} | |||||
}, | |||||
"/ocs/v2.php/settings/api/declarative/forms": { | |||||
"get": { | |||||
"operationId": "declarative_settings-get-forms", | |||||
"summary": "Gets all declarative forms with the values prefilled.", | |||||
"tags": [ | |||||
"declarative_settings" | |||||
], | |||||
"security": [ | |||||
{ | |||||
"bearer_auth": [] | |||||
}, | |||||
{ | |||||
"basic_auth": [] | |||||
} | |||||
], | |||||
"parameters": [ | |||||
{ | |||||
"name": "OCS-APIRequest", | |||||
"in": "header", | |||||
"description": "Required to be true for the API request to pass", | |||||
"required": true, | |||||
"schema": { | |||||
"type": "boolean", | |||||
"default": true | |||||
} | |||||
} | |||||
], | |||||
"responses": { | |||||
"200": { | |||||
"description": "Forms returned", | |||||
"content": { | |||||
"application/json": { | |||||
"schema": { | |||||
"type": "object", | |||||
"required": [ | |||||
"ocs" | |||||
], | |||||
"properties": { | |||||
"ocs": { | |||||
"type": "object", | |||||
"required": [ | |||||
"meta", | |||||
"data" | |||||
], | |||||
"properties": { | |||||
"meta": { | |||||
"$ref": "#/components/schemas/OCSMeta" | |||||
}, | |||||
"data": { | |||||
"type": "array", | |||||
"items": { | |||||
"$ref": "#/components/schemas/DeclarativeForm" | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
}, | |||||
"500": { | |||||
"description": "", | |||||
"content": { | "content": { | ||||
"application/octet-stream": { | |||||
"text/plain": { | |||||
"schema": { | "schema": { | ||||
"type": "string", | |||||
"format": "binary" | |||||
"type": "string" | |||||
} | } | ||||
} | } | ||||
} | } |
<template> | |||||
<NcSettingsSection | |||||
class="declarative-settings-section" | |||||
:name="t(formApp, form.title)" | |||||
:description="t(formApp, form.description)" | |||||
:doc-url="form.doc_url || ''"> | |||||
<div v-for="formField in formFields" | |||||
:key="formField.id" | |||||
class="declarative-form-field" | |||||
:aria-label="t('settings', '{app}\'s declarative setting field: {name}', { app: formApp, name: t(formApp, formField.title) })" | |||||
:class="{ | |||||
'declarative-form-field-text': isTextFormField(formField), | |||||
'declarative-form-field-select': formField.type === 'select', | |||||
'declarative-form-field-multi-select': formField.type === 'multi-select', | |||||
'declarative-form-field-checkbox': formField.type === 'checkbox', | |||||
'declarative-form-field-multi_checkbox': formField.type === 'multi-checkbox', | |||||
'declarative-form-field-radio': formField.type === 'radio' | |||||
}"> | |||||
<template v-if="isTextFormField(formField)"> | |||||
<div class="input-wrapper"> | |||||
<NcInputField | |||||
:type="formField.type" | |||||
:label="t(formApp, formField.title)" | |||||
:value.sync="formFieldsData[formField.id].value" | |||||
:placeholder="t(formApp, formField.placeholder)" | |||||
@update:value="onChangeDebounced(formField)" | |||||
@submit="updateDeclarativeSettingsValue(formField)"/> | |||||
</div> | |||||
<span class="hint">{{ t(formApp, formField.description) }}</span> | |||||
</template> | |||||
<template v-if="formField.type === 'select'"> | |||||
<label :for="formField.id + '_field'">{{ t(formApp, formField.title) }}</label> | |||||
<div class="input-wrapper"> | |||||
<NcSelect | |||||
:id="formField.id + '_field'" | |||||
:options="formField.options" | |||||
:placeholder="t(formApp, formField.placeholder)" | |||||
:label-outside="true" | |||||
:value="formFieldsData[formField.id].value" | |||||
@input="(value) => updateFormFieldDataValue(value, formField, true)"/> | |||||
</div> | |||||
<span class="hint">{{ t(formApp, formField.description) }}</span> | |||||
</template> | |||||
<template v-if="formField.type === 'multi-select'"> | |||||
<label :for="formField.id + '_field'">{{ t(formApp, formField.title) }}</label> | |||||
<div class="input-wrapper"> | |||||
<NcSelect | |||||
:id="formField.id + '_field'" | |||||
:options="formField.options" | |||||
:placeholder="t(formApp, formField.placeholder)" | |||||
:multiple="true" | |||||
:label-outside="true" | |||||
:value="formFieldsData[formField.id].value" | |||||
@input="(value) => { | |||||
formFieldsData[formField.id].value = value | |||||
updateDeclarativeSettingsValue(formField, JSON.stringify(formFieldsData[formField.id].value)) | |||||
} | |||||
"/> | |||||
</div> | |||||
<span class="hint">{{ t(formApp, formField.description) }}</span> | |||||
</template> | |||||
<template v-if="formField.type === 'checkbox'"> | |||||
<label :for="formField.id + '_field'">{{ t(formApp, formField.title) }}</label> | |||||
<NcCheckboxRadioSwitch | |||||
:id="formField.id + '_field'" | |||||
:checked="Boolean(formFieldsData[formField.id].value)" | |||||
@update:checked="(value) => { | |||||
formField.value = value | |||||
updateFormFieldDataValue(+value, formField, true) | |||||
} | |||||
"> | |||||
{{ t(formApp, formField.label) }} | |||||
</NcCheckboxRadioSwitch> | |||||
<span class="hint">{{ t(formApp, formField.description) }}</span> | |||||
</template> | |||||
<template v-if="formField.type === 'multi-checkbox'"> | |||||
<label :for="formField.id + '_field'">{{ t(formApp, formField.title) }}</label> | |||||
<NcCheckboxRadioSwitch | |||||
v-for="option in formField.options" | |||||
:id="formField.id + '_field_' + option.value" | |||||
:key="option.value" | |||||
:checked="formFieldsData[formField.id].value[option.value]" | |||||
@update:checked="(value) => { | |||||
formFieldsData[formField.id].value[option.value] = value | |||||
// Update without re-generating initial formFieldsData.value object as the link to components are lost | |||||
updateDeclarativeSettingsValue(formField, JSON.stringify(formFieldsData[formField.id].value)) | |||||
} | |||||
"> | |||||
{{ t(formApp, option.name) }} | |||||
</NcCheckboxRadioSwitch> | |||||
<span class="hint">{{ t(formApp, formField.description) }}</span> | |||||
</template> | |||||
<template v-if="formField.type === 'radio'"> | |||||
<label :for="formField.id + '_field'">{{ t(formApp, formField.title) }}</label> | |||||
<NcCheckboxRadioSwitch | |||||
v-for="option in formField.options" | |||||
:key="option.value" | |||||
:value="option.value" | |||||
type="radio" | |||||
:checked="formFieldsData[formField.id].value" | |||||
@update:checked="(value) => updateFormFieldDataValue(value, formField, true)"> | |||||
{{ t(formApp, option.name) }} | |||||
</NcCheckboxRadioSwitch> | |||||
<span class="hint">{{ t(formApp, formField.description) }}</span> | |||||
</template> | |||||
</div> | |||||
</NcSettingsSection> | |||||
</template> | |||||
<script> | |||||
import axios from '@nextcloud/axios' | |||||
import { generateOcsUrl } from '@nextcloud/router' | |||||
import { showError } from '@nextcloud/dialogs' | |||||
import debounce from 'debounce' | |||||
import NcSettingsSection from '@nextcloud/vue/dist/Components/NcSettingsSection.js' | |||||
import NcInputField from '@nextcloud/vue/dist/Components/NcInputField.js' | |||||
import NcSelect from '@nextcloud/vue/dist/Components/NcSelect.js' | |||||
import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js' | |||||
export default { | |||||
name: 'DeclarativeSection', | |||||
components: { | |||||
NcSettingsSection, | |||||
NcInputField, | |||||
NcSelect, | |||||
NcCheckboxRadioSwitch, | |||||
}, | |||||
props: { | |||||
form: { | |||||
type: Object, | |||||
required: true, | |||||
}, | |||||
}, | |||||
data() { | |||||
return { | |||||
formFieldsData: {}, | |||||
} | |||||
}, | |||||
beforeMount() { | |||||
this.initFormFieldsData() | |||||
}, | |||||
computed: { | |||||
formApp() { | |||||
return this.form.app || '' | |||||
}, | |||||
formFields() { | |||||
return this.form.fields || [] | |||||
}, | |||||
}, | |||||
methods: { | |||||
initFormFieldsData() { | |||||
this.form.fields.forEach((formField) => { | |||||
if (formField.type === 'checkbox') { | |||||
// convert bool to number using unary plus (+) operator | |||||
this.$set(formField, 'value', +formField.value) | |||||
} | |||||
if (formField.type === 'multi-checkbox') { | |||||
if (formField.value === '') { | |||||
// Init formFieldsData from options | |||||
this.$set(formField, 'value', {}) | |||||
formField.options.forEach(option => { | |||||
this.$set(formField.value, option.value, false) | |||||
}) | |||||
} else { | |||||
this.$set(formField, 'value', JSON.parse(formField.value)) | |||||
// Merge possible new options | |||||
formField.options.forEach(option => { | |||||
if (!formField.value.hasOwnProperty(option.value)) { | |||||
this.$set(formField.value, option.value, false) | |||||
} | |||||
}) | |||||
// Remove options that are not in the form anymore | |||||
Object.keys(formField.value).forEach(key => { | |||||
if (!formField.options.find(option => option.value === key)) { | |||||
delete formField.value[key] | |||||
} | |||||
}) | |||||
} | |||||
} | |||||
if (formField.type === 'multi-select') { | |||||
if (formField.value === '') { | |||||
// Init empty array for multi-select | |||||
this.$set(formField, 'value', []) | |||||
} else { | |||||
// JSON decode an array of multiple values set | |||||
this.$set(formField, 'value', JSON.parse(formField.value)) | |||||
} | |||||
} | |||||
this.$set(this.formFieldsData, formField.id, { | |||||
value: formField.value, | |||||
}) | |||||
}) | |||||
}, | |||||
updateFormFieldDataValue(value, formField, update = false) { | |||||
this.formFieldsData[formField.id].value = value | |||||
if (update) { | |||||
this.updateDeclarativeSettingsValue(formField) | |||||
} | |||||
}, | |||||
updateDeclarativeSettingsValue(formField, value = null) { | |||||
try { | |||||
return axios.post(generateOcsUrl('settings/api/declarative/value'), { | |||||
app: this.formApp, | |||||
formId: this.form.id.replace(this.formApp + '_', ''), // Remove app prefix to send clean form id | |||||
fieldId: formField.id, | |||||
value: value === null ? this.formFieldsData[formField.id].value : value, | |||||
}); | |||||
} catch (err) { | |||||
console.debug(err) | |||||
showError(t('settings', 'Failed to save setting')) | |||||
} | |||||
}, | |||||
onChangeDebounced: debounce(function(formField) { | |||||
this.updateDeclarativeSettingsValue(formField) | |||||
}, 1000), | |||||
isTextFormField(formField) { | |||||
return ['text', 'password', 'email', 'tel', 'url', 'number'].includes(formField.type) | |||||
}, | |||||
}, | |||||
} | |||||
</script> | |||||
<style lang="scss" scoped> | |||||
.declarative-form-field { | |||||
margin: 20px 0; | |||||
padding: 10px 0; | |||||
.input-wrapper { | |||||
width: 100%; | |||||
max-width: 400px; | |||||
} | |||||
&:last-child { | |||||
border-bottom: none; | |||||
} | |||||
.hint { | |||||
display: inline-block; | |||||
color: var(--color-text-maxcontrast); | |||||
margin-left: 8px; | |||||
padding-top: 5px; | |||||
} | |||||
&-radio, &-multi_checkbox { | |||||
max-height: 250px; | |||||
overflow-y: auto; | |||||
} | |||||
&-multi-select, &-select { | |||||
display: flex; | |||||
flex-direction: column; | |||||
label { | |||||
margin-bottom: 5px; | |||||
} | |||||
} | |||||
} | |||||
</style> |
import Vue from 'vue'; | |||||
import { loadState } from '@nextcloud/initial-state'; | |||||
import { translate as t, translatePlural as n } from '@nextcloud/l10n'; | |||||
import DeclarativeSection from './components/DeclarativeSettings/DeclarativeSection.vue'; | |||||
interface DeclarativeFormField { | |||||
id: string, | |||||
title: string, | |||||
description: string, | |||||
type: string, | |||||
placeholder: string, | |||||
label: string, | |||||
options: Array<any>|null, | |||||
value: any, | |||||
default: any, | |||||
} | |||||
interface DeclarativeForm { | |||||
id: number, | |||||
priority: number, | |||||
section_type: string, | |||||
section_id: string, | |||||
storage_type: string, | |||||
title: string, | |||||
description: string, | |||||
doc_url: string, | |||||
app: string, | |||||
fields: Array<DeclarativeFormField>, | |||||
} | |||||
const forms = loadState('settings', 'declarative-settings-forms', []) as Array<DeclarativeForm>; | |||||
console.debug('Loaded declarative forms:', forms); | |||||
function renderDeclarativeSettingsSections(forms: Array<DeclarativeForm>): void { | |||||
Vue.mixin({ methods: { t, n } }) | |||||
const DeclarativeSettingsSection = Vue.extend(<any>DeclarativeSection); | |||||
for (const form of forms) { | |||||
const el = `#${form.app}_${form.id}` | |||||
new DeclarativeSettingsSection({ | |||||
el: el, | |||||
propsData: { | |||||
form, | |||||
}, | |||||
}) | |||||
} | |||||
} | |||||
document.addEventListener('DOMContentLoaded', () => { | |||||
renderDeclarativeSettingsSections(forms); | |||||
}); |
use OCA\Settings\Controller\AdminSettingsController; | use OCA\Settings\Controller\AdminSettingsController; | ||||
use OCA\Settings\Settings\Personal\ServerDevNotice; | use OCA\Settings\Settings\Personal\ServerDevNotice; | ||||
use OCP\AppFramework\Http\TemplateResponse; | use OCP\AppFramework\Http\TemplateResponse; | ||||
use OCP\AppFramework\Services\IInitialState; | |||||
use OCP\Group\ISubAdmin; | use OCP\Group\ISubAdmin; | ||||
use OCP\IGroupManager; | use OCP\IGroupManager; | ||||
use OCP\INavigationManager; | use OCP\INavigationManager; | ||||
use OCP\IRequest; | use OCP\IRequest; | ||||
use OCP\IUser; | use OCP\IUser; | ||||
use OCP\IUserSession; | use OCP\IUserSession; | ||||
use OCP\Settings\IDeclarativeManager; | |||||
use OCP\Settings\IManager; | use OCP\Settings\IManager; | ||||
use PHPUnit\Framework\MockObject\MockObject; | use PHPUnit\Framework\MockObject\MockObject; | ||||
use Test\TestCase; | use Test\TestCase; | ||||
private $groupManager; | private $groupManager; | ||||
/** @var ISubAdmin|MockObject */ | /** @var ISubAdmin|MockObject */ | ||||
private $subAdmin; | private $subAdmin; | ||||
/** @var IDeclarativeManager|MockObject */ | |||||
private $declarativeSettingsManager; | |||||
/** @var IInitialState|MockObject */ | |||||
private $initialState; | |||||
/** @var string */ | /** @var string */ | ||||
private $adminUid = 'lololo'; | private $adminUid = 'lololo'; | ||||
$this->userSession = $this->createMock(IUserSession::class); | $this->userSession = $this->createMock(IUserSession::class); | ||||
$this->groupManager = $this->createMock(IGroupManager::class); | $this->groupManager = $this->createMock(IGroupManager::class); | ||||
$this->subAdmin = $this->createMock(ISubAdmin::class); | $this->subAdmin = $this->createMock(ISubAdmin::class); | ||||
$this->declarativeSettingsManager = $this->createMock(IDeclarativeManager::class); | |||||
$this->initialState = $this->createMock(IInitialState::class); | |||||
$this->adminSettingsController = new AdminSettingsController( | $this->adminSettingsController = new AdminSettingsController( | ||||
'settings', | 'settings', | ||||
$this->settingsManager, | $this->settingsManager, | ||||
$this->userSession, | $this->userSession, | ||||
$this->groupManager, | $this->groupManager, | ||||
$this->subAdmin | |||||
$this->subAdmin, | |||||
$this->declarativeSettingsManager, | |||||
$this->initialState, | |||||
); | ); | ||||
$user = \OC::$server->getUserManager()->createUser($this->adminUid, 'mylongrandompassword'); | $user = \OC::$server->getUserManager()->createUser($this->adminUid, 'mylongrandompassword'); | ||||
->method('getAllowedAdminSettings') | ->method('getAllowedAdminSettings') | ||||
->with('test') | ->with('test') | ||||
->willReturn([5 => $this->createMock(ServerDevNotice::class)]); | ->willReturn([5 => $this->createMock(ServerDevNotice::class)]); | ||||
$this->declarativeSettingsManager | |||||
->expects($this->any()) | |||||
->method('getFormIDs') | |||||
->with($user, 'admin', 'test') | |||||
->willReturn([]); | |||||
$idx = $this->adminSettingsController->index('test'); | $idx = $this->adminSettingsController->index('test'); | ||||
'OCA\\Testing\\Controller\\ConfigController' => $baseDir . '/../lib/Controller/ConfigController.php', | 'OCA\\Testing\\Controller\\ConfigController' => $baseDir . '/../lib/Controller/ConfigController.php', | ||||
'OCA\\Testing\\Controller\\LockingController' => $baseDir . '/../lib/Controller/LockingController.php', | 'OCA\\Testing\\Controller\\LockingController' => $baseDir . '/../lib/Controller/LockingController.php', | ||||
'OCA\\Testing\\Controller\\RateLimitTestController' => $baseDir . '/../lib/Controller/RateLimitTestController.php', | 'OCA\\Testing\\Controller\\RateLimitTestController' => $baseDir . '/../lib/Controller/RateLimitTestController.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', | |||||
'OCA\\Testing\\Locking\\FakeDBLockingProvider' => $baseDir . '/../lib/Locking/FakeDBLockingProvider.php', | 'OCA\\Testing\\Locking\\FakeDBLockingProvider' => $baseDir . '/../lib/Locking/FakeDBLockingProvider.php', | ||||
'OCA\\Testing\\Provider\\FakeText2ImageProvider' => $baseDir . '/../lib/Provider/FakeText2ImageProvider.php', | 'OCA\\Testing\\Provider\\FakeText2ImageProvider' => $baseDir . '/../lib/Provider/FakeText2ImageProvider.php', | ||||
'OCA\\Testing\\Provider\\FakeTextProcessingProvider' => $baseDir . '/../lib/Provider/FakeTextProcessingProvider.php', | 'OCA\\Testing\\Provider\\FakeTextProcessingProvider' => $baseDir . '/../lib/Provider/FakeTextProcessingProvider.php', | ||||
'OCA\\Testing\\Provider\\FakeTextProcessingProviderSync' => $baseDir . '/../lib/Provider/FakeTextProcessingProviderSync.php', | 'OCA\\Testing\\Provider\\FakeTextProcessingProviderSync' => $baseDir . '/../lib/Provider/FakeTextProcessingProviderSync.php', | ||||
'OCA\\Testing\\Provider\\FakeTranslationProvider' => $baseDir . '/../lib/Provider/FakeTranslationProvider.php', | 'OCA\\Testing\\Provider\\FakeTranslationProvider' => $baseDir . '/../lib/Provider/FakeTranslationProvider.php', | ||||
'OCA\\Testing\\Settings\\DeclarativeSettingsForm' => $baseDir . '/../lib/Settings/DeclarativeSettingsForm.php', | |||||
); | ); |
'OCA\\Testing\\Controller\\ConfigController' => __DIR__ . '/..' . '/../lib/Controller/ConfigController.php', | 'OCA\\Testing\\Controller\\ConfigController' => __DIR__ . '/..' . '/../lib/Controller/ConfigController.php', | ||||
'OCA\\Testing\\Controller\\LockingController' => __DIR__ . '/..' . '/../lib/Controller/LockingController.php', | 'OCA\\Testing\\Controller\\LockingController' => __DIR__ . '/..' . '/../lib/Controller/LockingController.php', | ||||
'OCA\\Testing\\Controller\\RateLimitTestController' => __DIR__ . '/..' . '/../lib/Controller/RateLimitTestController.php', | 'OCA\\Testing\\Controller\\RateLimitTestController' => __DIR__ . '/..' . '/../lib/Controller/RateLimitTestController.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', | |||||
'OCA\\Testing\\Locking\\FakeDBLockingProvider' => __DIR__ . '/..' . '/../lib/Locking/FakeDBLockingProvider.php', | 'OCA\\Testing\\Locking\\FakeDBLockingProvider' => __DIR__ . '/..' . '/../lib/Locking/FakeDBLockingProvider.php', | ||||
'OCA\\Testing\\Provider\\FakeText2ImageProvider' => __DIR__ . '/..' . '/../lib/Provider/FakeText2ImageProvider.php', | 'OCA\\Testing\\Provider\\FakeText2ImageProvider' => __DIR__ . '/..' . '/../lib/Provider/FakeText2ImageProvider.php', | ||||
'OCA\\Testing\\Provider\\FakeTextProcessingProvider' => __DIR__ . '/..' . '/../lib/Provider/FakeTextProcessingProvider.php', | 'OCA\\Testing\\Provider\\FakeTextProcessingProvider' => __DIR__ . '/..' . '/../lib/Provider/FakeTextProcessingProvider.php', | ||||
'OCA\\Testing\\Provider\\FakeTextProcessingProviderSync' => __DIR__ . '/..' . '/../lib/Provider/FakeTextProcessingProviderSync.php', | 'OCA\\Testing\\Provider\\FakeTextProcessingProviderSync' => __DIR__ . '/..' . '/../lib/Provider/FakeTextProcessingProviderSync.php', | ||||
'OCA\\Testing\\Provider\\FakeTranslationProvider' => __DIR__ . '/..' . '/../lib/Provider/FakeTranslationProvider.php', | 'OCA\\Testing\\Provider\\FakeTranslationProvider' => __DIR__ . '/..' . '/../lib/Provider/FakeTranslationProvider.php', | ||||
'OCA\\Testing\\Settings\\DeclarativeSettingsForm' => __DIR__ . '/..' . '/../lib/Settings/DeclarativeSettingsForm.php', | |||||
); | ); | ||||
public static function getInitializer(ClassLoader $loader) | public static function getInitializer(ClassLoader $loader) |
'name' => '__root__', | 'name' => '__root__', | ||||
'pretty_version' => 'dev-master', | 'pretty_version' => 'dev-master', | ||||
'version' => 'dev-master', | 'version' => 'dev-master', | ||||
'reference' => 'b1797842784b250fb01ed5e3bf130705eb94751b', | |||||
'reference' => '4ff660ca2e0baa02440ba07296ed7e75fa544c0e', | |||||
'type' => 'library', | 'type' => 'library', | ||||
'install_path' => __DIR__ . '/../', | 'install_path' => __DIR__ . '/../', | ||||
'aliases' => array(), | 'aliases' => array(), | ||||
'__root__' => array( | '__root__' => array( | ||||
'pretty_version' => 'dev-master', | 'pretty_version' => 'dev-master', | ||||
'version' => 'dev-master', | 'version' => 'dev-master', | ||||
'reference' => 'b1797842784b250fb01ed5e3bf130705eb94751b', | |||||
'reference' => '4ff660ca2e0baa02440ba07296ed7e75fa544c0e', | |||||
'type' => 'library', | 'type' => 'library', | ||||
'install_path' => __DIR__ . '/../', | 'install_path' => __DIR__ . '/../', | ||||
'aliases' => array(), | 'aliases' => array(), |
namespace OCA\Testing\AppInfo; | namespace OCA\Testing\AppInfo; | ||||
use OCA\Testing\AlternativeHomeUserBackend; | use OCA\Testing\AlternativeHomeUserBackend; | ||||
use OCA\Testing\Listener\GetDeclarativeSettingsValueListener; | |||||
use OCA\Testing\Listener\RegisterDeclarativeSettingsListener; | |||||
use OCA\Testing\Listener\SetDeclarativeSettingsValueListener; | |||||
use OCA\Testing\Provider\FakeText2ImageProvider; | use OCA\Testing\Provider\FakeText2ImageProvider; | ||||
use OCA\Testing\Provider\FakeTextProcessingProvider; | use OCA\Testing\Provider\FakeTextProcessingProvider; | ||||
use OCA\Testing\Provider\FakeTextProcessingProviderSync; | use OCA\Testing\Provider\FakeTextProcessingProviderSync; | ||||
use OCA\Testing\Provider\FakeTranslationProvider; | use OCA\Testing\Provider\FakeTranslationProvider; | ||||
use OCA\Testing\Settings\DeclarativeSettingsForm; | |||||
use OCP\AppFramework\App; | use OCP\AppFramework\App; | ||||
use OCP\AppFramework\Bootstrap\IBootContext; | use OCP\AppFramework\Bootstrap\IBootContext; | ||||
use OCP\AppFramework\Bootstrap\IBootstrap; | use OCP\AppFramework\Bootstrap\IBootstrap; | ||||
use OCP\AppFramework\Bootstrap\IRegistrationContext; | use OCP\AppFramework\Bootstrap\IRegistrationContext; | ||||
use OCP\Settings\Events\DeclarativeSettingsGetValueEvent; | |||||
use OCP\Settings\Events\DeclarativeSettingsRegisterFormEvent; | |||||
use OCP\Settings\Events\DeclarativeSettingsSetValueEvent; | |||||
class Application extends App implements IBootstrap { | class Application extends App implements IBootstrap { | ||||
public function __construct(array $urlParams = []) { | public function __construct(array $urlParams = []) { | ||||
$context->registerTextProcessingProvider(FakeTextProcessingProvider::class); | $context->registerTextProcessingProvider(FakeTextProcessingProvider::class); | ||||
$context->registerTextProcessingProvider(FakeTextProcessingProviderSync::class); | $context->registerTextProcessingProvider(FakeTextProcessingProviderSync::class); | ||||
$context->registerTextToImageProvider(FakeText2ImageProvider::class); | $context->registerTextToImageProvider(FakeText2ImageProvider::class); | ||||
$context->registerDeclarativeSettings(DeclarativeSettingsForm::class); | |||||
$context->registerEventListener(DeclarativeSettingsRegisterFormEvent::class, RegisterDeclarativeSettingsListener::class); | |||||
$context->registerEventListener(DeclarativeSettingsGetValueEvent::class, GetDeclarativeSettingsValueListener::class); | |||||
$context->registerEventListener(DeclarativeSettingsSetValueEvent::class, SetDeclarativeSettingsValueListener::class); | |||||
} | } | ||||
public function boot(IBootContext $context): void { | public function boot(IBootContext $context): void { |
<?php | |||||
declare(strict_types=1); | |||||
namespace OCA\Testing\Listener; | |||||
use OCP\EventDispatcher\Event; | |||||
use OCP\EventDispatcher\IEventListener; | |||||
use OCP\IConfig; | |||||
use OCP\Settings\Events\DeclarativeSettingsGetValueEvent; | |||||
/** | |||||
* @template-implements IEventListener<DeclarativeSettingsGetValueEvent> | |||||
*/ | |||||
class GetDeclarativeSettingsValueListener implements IEventListener { | |||||
public function __construct(private IConfig $config) { | |||||
} | |||||
public function handle(Event $event): void { | |||||
if (!$event instanceof DeclarativeSettingsGetValueEvent) { | |||||
return; | |||||
} | |||||
if ($event->getApp() !== 'testing') { | |||||
return; | |||||
} | |||||
$value = $this->config->getUserValue($event->getUser()->getUID(), $event->getApp(), $event->getFieldId()); | |||||
$event->setValue($value); | |||||
} | |||||
} |
<?php | |||||
declare(strict_types=1); | |||||
namespace OCA\Testing\Listener; | |||||
use OCP\EventDispatcher\Event; | |||||
use OCP\EventDispatcher\IEventListener; | |||||
use OCP\Settings\DeclarativeSettingsTypes; | |||||
use OCP\Settings\Events\DeclarativeSettingsRegisterFormEvent; | |||||
/** | |||||
* @template-implements IEventListener<DeclarativeSettingsRegisterFormEvent> | |||||
*/ | |||||
class RegisterDeclarativeSettingsListener implements IEventListener { | |||||
public function __construct() { | |||||
} | |||||
public function handle(Event $event): void { | |||||
if (!($event instanceof DeclarativeSettingsRegisterFormEvent)) { | |||||
// Unrelated | |||||
return; | |||||
} | |||||
$event->registerSchema('testing', [ | |||||
'id' => 'test_declarative_form_event', | |||||
'priority' => 20, | |||||
'section_type' => DeclarativeSettingsTypes::SECTION_TYPE_ADMIN, | |||||
'section_id' => 'additional', | |||||
'storage_type' => DeclarativeSettingsTypes::STORAGE_TYPE_INTERNAL, | |||||
'title' => 'Test declarative settings event', // NcSettingsSection name | |||||
'description' => 'This form is registered via the RegisterDeclarativeSettingsFormEvent', // NcSettingsSection description | |||||
'fields' => [ | |||||
[ | |||||
'id' => 'event_field_1', | |||||
'title' => 'Why is 42 this answer to all questions?', | |||||
'description' => 'Hint: It\'s not', | |||||
'type' => DeclarativeSettingsTypes::TEXT, | |||||
'placeholder' => 'Enter your answer', | |||||
'default' => 'Because it is', | |||||
], | |||||
[ | |||||
'id' => 'feature_rating', | |||||
'title' => 'How would you rate this feature?', | |||||
'description' => 'Your vote is not anonymous', | |||||
'type' => DeclarativeSettingsTypes::RADIO, // radio, radio-button (NcCheckboxRadioSwitch button-variant) | |||||
'label' => 'Select single toggle', | |||||
'default' => '3', | |||||
'options' => [ | |||||
[ | |||||
'name' => 'Awesome', // NcCheckboxRadioSwitch display name | |||||
'value' => '1' // NcCheckboxRadioSwitch value | |||||
], | |||||
[ | |||||
'name' => 'Very awesome', | |||||
'value' => '2' | |||||
], | |||||
[ | |||||
'name' => 'Super awesome', | |||||
'value' => '3' | |||||
], | |||||
], | |||||
], | |||||
], | |||||
]); | |||||
} | |||||
} |
<?php | |||||
declare(strict_types=1); | |||||
namespace OCA\Testing\Listener; | |||||
use OCP\EventDispatcher\Event; | |||||
use OCP\EventDispatcher\IEventListener; | |||||
use OCP\IConfig; | |||||
use OCP\Settings\Events\DeclarativeSettingsSetValueEvent; | |||||
/** | |||||
* @template-implements IEventListener<DeclarativeSettingsSetValueEvent> | |||||
*/ | |||||
class SetDeclarativeSettingsValueListener implements IEventListener { | |||||
public function __construct(private IConfig $config) { | |||||
} | |||||
public function handle(Event $event): void { | |||||
if (!$event instanceof DeclarativeSettingsSetValueEvent) { | |||||
return; | |||||
} | |||||
if ($event->getApp() !== 'testing') { | |||||
return; | |||||
} | |||||
error_log('Testing app wants to store ' . $event->getValue() . ' for field ' . $event->getFieldId() . ' for user ' . $event->getUser()->getUID()); | |||||
$this->config->setUserValue($event->getUser()->getUID(), $event->getApp(), $event->getFieldId(), $event->getValue()); | |||||
} | |||||
} |
<?php | |||||
declare(strict_types=1); | |||||
namespace OCA\Testing\Settings; | |||||
use OCP\Settings\DeclarativeSettingsTypes; | |||||
use OCP\Settings\IDeclarativeSettingsForm; | |||||
class DeclarativeSettingsForm implements IDeclarativeSettingsForm { | |||||
public function getSchema(): array { | |||||
return [ | |||||
'id' => 'test_declarative_form', | |||||
'priority' => 10, | |||||
'section_type' => DeclarativeSettingsTypes::SECTION_TYPE_ADMIN, // admin, personal | |||||
'section_id' => 'additional', | |||||
'storage_type' => DeclarativeSettingsTypes::STORAGE_TYPE_INTERNAL, // external, internal (handled by core to store in appconfig and preferences) | |||||
'title' => 'Test declarative settings class', // NcSettingsSection name | |||||
'description' => 'This form is registered with a DeclarativeSettingsForm class', // NcSettingsSection description | |||||
'doc_url' => '', // NcSettingsSection doc_url for documentation or help page, empty string if not needed | |||||
'fields' => [ | |||||
[ | |||||
'id' => 'test_ex_app_field_7', // configkey | |||||
'title' => 'Multi-selection', // name or label | |||||
'description' => 'Select some option setting', // hint | |||||
'type' => DeclarativeSettingsTypes::MULTI_SELECT, // select, radio, multi-select | |||||
'options' => ['foo', 'bar', 'baz'], // simple options for select, radio, multi-select | |||||
'placeholder' => 'Select some multiple options', // input placeholder | |||||
'default' => ['foo', 'bar'], | |||||
], | |||||
[ | |||||
'id' => 'some_real_setting', | |||||
'title' => 'Choose init status check background job interval', | |||||
'description' => 'How often AppAPI should check for initialization status', | |||||
'type' => DeclarativeSettingsTypes::RADIO, // radio (NcCheckboxRadioSwitch type radio) | |||||
'placeholder' => 'Choose init status check background job interval', | |||||
'default' => '40m', | |||||
'options' => [ | |||||
[ | |||||
'name' => 'Each 40 minutes', // NcCheckboxRadioSwitch display name | |||||
'value' => '40m' // NcCheckboxRadioSwitch value | |||||
], | |||||
[ | |||||
'name' => 'Each 60 minutes', | |||||
'value' => '60m' | |||||
], | |||||
[ | |||||
'name' => 'Each 120 minutes', | |||||
'value' => '120m' | |||||
], | |||||
[ | |||||
'name' => 'Each day', | |||||
'value' => 60 * 24 . 'm' | |||||
], | |||||
], | |||||
], | |||||
[ | |||||
'id' => 'test_ex_app_field_1', // configkey | |||||
'title' => 'Default text field', // label | |||||
'description' => 'Set some simple text setting', // hint | |||||
'type' => DeclarativeSettingsTypes::TEXT, // text, password, email, tel, url, number | |||||
'placeholder' => 'Enter text setting', // placeholder | |||||
'default' => 'foo', | |||||
], | |||||
[ | |||||
'id' => 'test_ex_app_field_1_1', | |||||
'title' => 'Email field', | |||||
'description' => 'Set email config', | |||||
'type' => DeclarativeSettingsTypes::EMAIL, | |||||
'placeholder' => 'Enter email', | |||||
'default' => '', | |||||
], | |||||
[ | |||||
'id' => 'test_ex_app_field_1_2', | |||||
'title' => 'Tel field', | |||||
'description' => 'Set tel config', | |||||
'type' => DeclarativeSettingsTypes::TEL, | |||||
'placeholder' => 'Enter your tel', | |||||
'default' => '', | |||||
], | |||||
[ | |||||
'id' => 'test_ex_app_field_1_3', | |||||
'title' => 'Url (website) field', | |||||
'description' => 'Set url config', | |||||
'type' => 'url', | |||||
'placeholder' => 'Enter url', | |||||
'default' => '', | |||||
], | |||||
[ | |||||
'id' => 'test_ex_app_field_1_4', | |||||
'title' => 'Number field', | |||||
'description' => 'Set number config', | |||||
'type' => DeclarativeSettingsTypes::NUMBER, | |||||
'placeholder' => 'Enter number value', | |||||
'default' => 0, | |||||
], | |||||
[ | |||||
'id' => 'test_ex_app_field_2', | |||||
'title' => 'Password', | |||||
'description' => 'Set some secure value setting', | |||||
'type' => 'password', | |||||
'placeholder' => 'Set secure value', | |||||
'default' => '', | |||||
], | |||||
[ | |||||
'id' => 'test_ex_app_field_3', | |||||
'title' => 'Selection', | |||||
'description' => 'Select some option setting', | |||||
'type' => DeclarativeSettingsTypes::SELECT, // select, radio, multi-select | |||||
'options' => ['foo', 'bar', 'baz'], | |||||
'placeholder' => 'Select some option setting', | |||||
'default' => 'foo', | |||||
], | |||||
[ | |||||
'id' => 'test_ex_app_field_4', | |||||
'title' => 'Toggle something', | |||||
'description' => 'Select checkbox option setting', | |||||
'type' => DeclarativeSettingsTypes::CHECKBOX, // checkbox, multiple-checkbox | |||||
'label' => 'Verify something if enabled', | |||||
'default' => false, | |||||
], | |||||
[ | |||||
'id' => 'test_ex_app_field_5', | |||||
'title' => 'Multiple checkbox toggles, describing one setting, checked options are saved as an JSON object {foo: true, bar: false}', | |||||
'description' => 'Select checkbox option setting', | |||||
'type' => DeclarativeSettingsTypes::MULTI_CHECKBOX, // checkbox, multi-checkbox | |||||
'default' => ['foo' => true, 'bar' => true, 'baz' => true], | |||||
'options' => [ | |||||
[ | |||||
'name' => 'Foo', | |||||
'value' => 'foo', // multiple-checkbox configkey | |||||
], | |||||
[ | |||||
'name' => 'Bar', | |||||
'value' => 'bar', | |||||
], | |||||
[ | |||||
'name' => 'Baz', | |||||
'value' => 'baz', | |||||
], | |||||
[ | |||||
'name' => 'Qux', | |||||
'value' => 'qux', | |||||
], | |||||
], | |||||
], | |||||
[ | |||||
'id' => 'test_ex_app_field_6', | |||||
'title' => 'Radio toggles, describing one setting like single select', | |||||
'description' => 'Select radio option setting', | |||||
'type' => DeclarativeSettingsTypes::RADIO, // radio (NcCheckboxRadioSwitch type radio) | |||||
'label' => 'Select single toggle', | |||||
'default' => 'foo', | |||||
'options' => [ | |||||
[ | |||||
'name' => 'First radio', // NcCheckboxRadioSwitch display name | |||||
'value' => 'foo' // NcCheckboxRadioSwitch value | |||||
], | |||||
[ | |||||
'name' => 'Second radio', | |||||
'value' => 'bar' | |||||
], | |||||
[ | |||||
'name' => 'Third radio', | |||||
'value' => 'baz' | |||||
], | |||||
], | |||||
], | |||||
], | |||||
]; | |||||
} | |||||
} |
Copyright (c) Nils Adermann, Jordi Boggiano | Copyright (c) Nils Adermann, Jordi Boggiano | ||||
Permission is hereby granted, free of charge, to any person obtaining a copy | Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||
THE SOFTWARE. | THE SOFTWARE. | ||||
'OCP\\Security\\VerificationToken\\InvalidTokenException' => $baseDir . '/lib/public/Security/VerificationToken/InvalidTokenException.php', | 'OCP\\Security\\VerificationToken\\InvalidTokenException' => $baseDir . '/lib/public/Security/VerificationToken/InvalidTokenException.php', | ||||
'OCP\\Server' => $baseDir . '/lib/public/Server.php', | 'OCP\\Server' => $baseDir . '/lib/public/Server.php', | ||||
'OCP\\Session\\Exceptions\\SessionNotAvailableException' => $baseDir . '/lib/public/Session/Exceptions/SessionNotAvailableException.php', | 'OCP\\Session\\Exceptions\\SessionNotAvailableException' => $baseDir . '/lib/public/Session/Exceptions/SessionNotAvailableException.php', | ||||
'OCP\\Settings\\DeclarativeSettingsTypes' => $baseDir . '/lib/public/Settings/DeclarativeSettingsTypes.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', | |||||
'OCP\\Settings\\IDeclarativeManager' => $baseDir . '/lib/public/Settings/IDeclarativeManager.php', | |||||
'OCP\\Settings\\IDeclarativeSettingsForm' => $baseDir . '/lib/public/Settings/IDeclarativeSettingsForm.php', | |||||
'OCP\\Settings\\IDelegatedSettings' => $baseDir . '/lib/public/Settings/IDelegatedSettings.php', | 'OCP\\Settings\\IDelegatedSettings' => $baseDir . '/lib/public/Settings/IDelegatedSettings.php', | ||||
'OCP\\Settings\\IIconSection' => $baseDir . '/lib/public/Settings/IIconSection.php', | 'OCP\\Settings\\IIconSection' => $baseDir . '/lib/public/Settings/IIconSection.php', | ||||
'OCP\\Settings\\IManager' => $baseDir . '/lib/public/Settings/IManager.php', | 'OCP\\Settings\\IManager' => $baseDir . '/lib/public/Settings/IManager.php', | ||||
'OC\\Session\\Session' => $baseDir . '/lib/private/Session/Session.php', | 'OC\\Session\\Session' => $baseDir . '/lib/private/Session/Session.php', | ||||
'OC\\Settings\\AuthorizedGroup' => $baseDir . '/lib/private/Settings/AuthorizedGroup.php', | 'OC\\Settings\\AuthorizedGroup' => $baseDir . '/lib/private/Settings/AuthorizedGroup.php', | ||||
'OC\\Settings\\AuthorizedGroupMapper' => $baseDir . '/lib/private/Settings/AuthorizedGroupMapper.php', | 'OC\\Settings\\AuthorizedGroupMapper' => $baseDir . '/lib/private/Settings/AuthorizedGroupMapper.php', | ||||
'OC\\Settings\\DeclarativeManager' => $baseDir . '/lib/private/Settings/DeclarativeManager.php', | |||||
'OC\\Settings\\Manager' => $baseDir . '/lib/private/Settings/Manager.php', | 'OC\\Settings\\Manager' => $baseDir . '/lib/private/Settings/Manager.php', | ||||
'OC\\Settings\\Section' => $baseDir . '/lib/private/Settings/Section.php', | 'OC\\Settings\\Section' => $baseDir . '/lib/private/Settings/Section.php', | ||||
'OC\\Setup' => $baseDir . '/lib/private/Setup.php', | 'OC\\Setup' => $baseDir . '/lib/private/Setup.php', |
'OCP\\Security\\VerificationToken\\InvalidTokenException' => __DIR__ . '/../../..' . '/lib/public/Security/VerificationToken/InvalidTokenException.php', | 'OCP\\Security\\VerificationToken\\InvalidTokenException' => __DIR__ . '/../../..' . '/lib/public/Security/VerificationToken/InvalidTokenException.php', | ||||
'OCP\\Server' => __DIR__ . '/../../..' . '/lib/public/Server.php', | 'OCP\\Server' => __DIR__ . '/../../..' . '/lib/public/Server.php', | ||||
'OCP\\Session\\Exceptions\\SessionNotAvailableException' => __DIR__ . '/../../..' . '/lib/public/Session/Exceptions/SessionNotAvailableException.php', | 'OCP\\Session\\Exceptions\\SessionNotAvailableException' => __DIR__ . '/../../..' . '/lib/public/Session/Exceptions/SessionNotAvailableException.php', | ||||
'OCP\\Settings\\DeclarativeSettingsTypes' => __DIR__ . '/../../..' . '/lib/public/Settings/DeclarativeSettingsTypes.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', | |||||
'OCP\\Settings\\IDeclarativeManager' => __DIR__ . '/../../..' . '/lib/public/Settings/IDeclarativeManager.php', | |||||
'OCP\\Settings\\IDeclarativeSettingsForm' => __DIR__ . '/../../..' . '/lib/public/Settings/IDeclarativeSettingsForm.php', | |||||
'OCP\\Settings\\IDelegatedSettings' => __DIR__ . '/../../..' . '/lib/public/Settings/IDelegatedSettings.php', | 'OCP\\Settings\\IDelegatedSettings' => __DIR__ . '/../../..' . '/lib/public/Settings/IDelegatedSettings.php', | ||||
'OCP\\Settings\\IIconSection' => __DIR__ . '/../../..' . '/lib/public/Settings/IIconSection.php', | 'OCP\\Settings\\IIconSection' => __DIR__ . '/../../..' . '/lib/public/Settings/IIconSection.php', | ||||
'OCP\\Settings\\IManager' => __DIR__ . '/../../..' . '/lib/public/Settings/IManager.php', | 'OCP\\Settings\\IManager' => __DIR__ . '/../../..' . '/lib/public/Settings/IManager.php', | ||||
'OC\\Session\\Session' => __DIR__ . '/../../..' . '/lib/private/Session/Session.php', | 'OC\\Session\\Session' => __DIR__ . '/../../..' . '/lib/private/Session/Session.php', | ||||
'OC\\Settings\\AuthorizedGroup' => __DIR__ . '/../../..' . '/lib/private/Settings/AuthorizedGroup.php', | 'OC\\Settings\\AuthorizedGroup' => __DIR__ . '/../../..' . '/lib/private/Settings/AuthorizedGroup.php', | ||||
'OC\\Settings\\AuthorizedGroupMapper' => __DIR__ . '/../../..' . '/lib/private/Settings/AuthorizedGroupMapper.php', | 'OC\\Settings\\AuthorizedGroupMapper' => __DIR__ . '/../../..' . '/lib/private/Settings/AuthorizedGroupMapper.php', | ||||
'OC\\Settings\\DeclarativeManager' => __DIR__ . '/../../..' . '/lib/private/Settings/DeclarativeManager.php', | |||||
'OC\\Settings\\Manager' => __DIR__ . '/../../..' . '/lib/private/Settings/Manager.php', | 'OC\\Settings\\Manager' => __DIR__ . '/../../..' . '/lib/private/Settings/Manager.php', | ||||
'OC\\Settings\\Section' => __DIR__ . '/../../..' . '/lib/private/Settings/Section.php', | 'OC\\Settings\\Section' => __DIR__ . '/../../..' . '/lib/private/Settings/Section.php', | ||||
'OC\\Setup' => __DIR__ . '/../../..' . '/lib/private/Setup.php', | 'OC\\Setup' => __DIR__ . '/../../..' . '/lib/private/Setup.php', |
'name' => '__root__', | 'name' => '__root__', | ||||
'pretty_version' => 'dev-master', | 'pretty_version' => 'dev-master', | ||||
'version' => 'dev-master', | 'version' => 'dev-master', | ||||
'reference' => 'b6abfc4cba2d1ef4fdd8f2c22bbff46796b9485e', | |||||
'reference' => '4ff660ca2e0baa02440ba07296ed7e75fa544c0e', | |||||
'type' => 'library', | 'type' => 'library', | ||||
'install_path' => __DIR__ . '/../../../', | 'install_path' => __DIR__ . '/../../../', | ||||
'aliases' => array(), | 'aliases' => array(), | ||||
'__root__' => array( | '__root__' => array( | ||||
'pretty_version' => 'dev-master', | 'pretty_version' => 'dev-master', | ||||
'version' => 'dev-master', | 'version' => 'dev-master', | ||||
'reference' => 'b6abfc4cba2d1ef4fdd8f2c22bbff46796b9485e', | |||||
'reference' => '4ff660ca2e0baa02440ba07296ed7e75fa544c0e', | |||||
'type' => 'library', | 'type' => 'library', | ||||
'install_path' => __DIR__ . '/../../../', | 'install_path' => __DIR__ . '/../../../', | ||||
'aliases' => array(), | 'aliases' => array(), |
use OCP\Notification\INotifier; | use OCP\Notification\INotifier; | ||||
use OCP\Profile\ILinkAction; | use OCP\Profile\ILinkAction; | ||||
use OCP\Search\IProvider; | use OCP\Search\IProvider; | ||||
use OCP\Settings\IDeclarativeSettingsForm; | |||||
use OCP\SetupCheck\ISetupCheck; | use OCP\SetupCheck\ISetupCheck; | ||||
use OCP\Share\IPublicShareTemplateProvider; | use OCP\Share\IPublicShareTemplateProvider; | ||||
use OCP\SpeechToText\ISpeechToTextProvider; | use OCP\SpeechToText\ISpeechToTextProvider; | ||||
/** @var ServiceRegistration<\OCP\TextToImage\IProvider>[] */ | /** @var ServiceRegistration<\OCP\TextToImage\IProvider>[] */ | ||||
private $textToImageProviders = []; | private $textToImageProviders = []; | ||||
/** @var ParameterRegistration[] */ | /** @var ParameterRegistration[] */ | ||||
private $sensitiveMethods = []; | private $sensitiveMethods = []; | ||||
/** @var PreviewProviderRegistration[] */ | /** @var PreviewProviderRegistration[] */ | ||||
private array $previewProviders = []; | private array $previewProviders = []; | ||||
/** @var ServiceRegistration<IDeclarativeSettingsForm>[] */ | |||||
private array $declarativeSettings = []; | |||||
/** @var ServiceRegistration<ITeamResourceProvider>[] */ | /** @var ServiceRegistration<ITeamResourceProvider>[] */ | ||||
private array $teamResourceProviders = []; | private array $teamResourceProviders = []; | ||||
$setupCheckClass | $setupCheckClass | ||||
); | ); | ||||
} | } | ||||
public function registerDeclarativeSettings(string $declarativeSettingsClass): void { | |||||
$this->context->registerDeclarativeSettings( | |||||
$this->appId, | |||||
$declarativeSettingsClass | |||||
); | |||||
} | |||||
}; | }; | ||||
} | } | ||||
); | ); | ||||
} | } | ||||
/** | /** | ||||
* @psalm-param class-string<ITeamResourceProvider> $class | * @psalm-param class-string<ITeamResourceProvider> $class | ||||
*/ | */ | ||||
$this->setupChecks[] = new ServiceRegistration($appId, $setupCheckClass); | $this->setupChecks[] = new ServiceRegistration($appId, $setupCheckClass); | ||||
} | } | ||||
/** | |||||
* @psalm-param class-string<IDeclarativeSettingsForm> $declarativeSettingsClass | |||||
*/ | |||||
public function registerDeclarativeSettings(string $appId, string $declarativeSettingsClass): void { | |||||
$this->declarativeSettings[] = new ServiceRegistration($appId, $declarativeSettingsClass); | |||||
} | |||||
/** | /** | ||||
* @param App[] $apps | * @param App[] $apps | ||||
*/ | */ | ||||
return $this->setupChecks; | return $this->setupChecks; | ||||
} | } | ||||
/** | /** | ||||
* @return ServiceRegistration<ITeamResourceProvider>[] | |||||
* @return ServiceRegistration<IDeclarativeSettingsForm>[] | |||||
*/ | */ | ||||
public function getTeamResourceProviders(): array { | |||||
return $this->teamResourceProviders; | |||||
public function getDeclarativeSettings(): array { | |||||
return $this->declarativeSettings; | |||||
} | } | ||||
} | } |
use OC\Security\TrustedDomainHelper; | use OC\Security\TrustedDomainHelper; | ||||
use OC\Security\VerificationToken\VerificationToken; | use OC\Security\VerificationToken\VerificationToken; | ||||
use OC\Session\CryptoWrapper; | use OC\Session\CryptoWrapper; | ||||
use OC\Settings\DeclarativeManager; | |||||
use OC\SetupCheck\SetupCheckManager; | use OC\SetupCheck\SetupCheckManager; | ||||
use OC\Share20\ProviderFactory; | use OC\Share20\ProviderFactory; | ||||
use OC\Share20\ShareHelper; | use OC\Share20\ShareHelper; | ||||
use OCP\Security\ITrustedDomainHelper; | use OCP\Security\ITrustedDomainHelper; | ||||
use OCP\Security\RateLimiting\ILimiter; | use OCP\Security\RateLimiting\ILimiter; | ||||
use OCP\Security\VerificationToken\IVerificationToken; | use OCP\Security\VerificationToken\IVerificationToken; | ||||
use OCP\Settings\IDeclarativeManager; | |||||
use OCP\SetupCheck\ISetupCheckManager; | use OCP\SetupCheck\ISetupCheckManager; | ||||
use OCP\Share\IProviderFactory; | use OCP\Share\IProviderFactory; | ||||
use OCP\Share\IShareHelper; | use OCP\Share\IShareHelper; | ||||
$this->registerAlias(IAvailabilityCoordinator::class, AvailabilityCoordinator::class); | $this->registerAlias(IAvailabilityCoordinator::class, AvailabilityCoordinator::class); | ||||
$this->registerAlias(IDeclarativeManager::class, DeclarativeManager::class); | |||||
$this->connectDispatcher(); | $this->connectDispatcher(); | ||||
} | } | ||||
<?php | |||||
/** | |||||
* @copyright Copyright (c) 2023 Kate Döen <kate.doeen@nextcloud.com> | |||||
* | |||||
* @author Kate Döen <kate.doeen@nextcloud.com> | |||||
* | |||||
* @license GNU AGPL version 3 or any later version | |||||
* | |||||
* This program is free software: you can redistribute it and/or modify | |||||
* it under the terms of the GNU Affero General Public License as | |||||
* published by the Free Software Foundation, either version 3 of the | |||||
* License, or (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
* GNU Affero General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Affero General Public License | |||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||||
* | |||||
*/ | |||||
namespace OC\Settings; | |||||
use Exception; | |||||
use OC\AppFramework\Bootstrap\Coordinator; | |||||
use OC\AppFramework\Middleware\Security\Exceptions\NotAdminException; | |||||
use OCP\EventDispatcher\IEventDispatcher; | |||||
use OCP\IAppConfig; | |||||
use OCP\IConfig; | |||||
use OCP\IGroupManager; | |||||
use OCP\IUser; | |||||
use OCP\Server; | |||||
use OCP\Settings\DeclarativeSettingsTypes; | |||||
use OCP\Settings\Events\DeclarativeSettingsGetValueEvent; | |||||
use OCP\Settings\Events\DeclarativeSettingsRegisterFormEvent; | |||||
use OCP\Settings\Events\DeclarativeSettingsSetValueEvent; | |||||
use OCP\Settings\IDeclarativeManager; | |||||
use OCP\Settings\IDeclarativeSettingsForm; | |||||
use Psr\Log\LoggerInterface; | |||||
/** | |||||
* @psalm-import-type DeclarativeSettingsValueTypes from IDeclarativeSettingsForm | |||||
* @psalm-import-type DeclarativeSettingsStorageType from IDeclarativeSettingsForm | |||||
* @psalm-import-type DeclarativeSettingsSectionType from IDeclarativeSettingsForm | |||||
* @psalm-import-type DeclarativeSettingsFormSchemaWithValues from IDeclarativeSettingsForm | |||||
* @psalm-import-type DeclarativeSettingsFormSchemaWithoutValues from IDeclarativeSettingsForm | |||||
*/ | |||||
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, | |||||
) { | |||||
} | |||||
/** | |||||
* @var array<string, list<DeclarativeSettingsFormSchemaWithoutValues>> | |||||
*/ | |||||
private array $appSchemas = []; | |||||
/** | |||||
* @inheritdoc | |||||
*/ | |||||
public function registerSchema(string $app, array $schema): void { | |||||
$this->appSchemas[$app] ??= []; | |||||
if (!$this->validateSchema($app, $schema)) { | |||||
throw new Exception('Invalid schema. Please check the logs for more details.'); | |||||
} | |||||
foreach ($this->appSchemas[$app] as $otherSchema) { | |||||
if ($otherSchema['id'] === $schema['id']) { | |||||
throw new Exception('Duplicate form IDs detected: ' . $schema['id']); | |||||
} | |||||
} | |||||
$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)); | |||||
} | |||||
$this->appSchemas[$app][] = $schema; | |||||
} | |||||
/** | |||||
* @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()); | |||||
} | |||||
$this->eventDispatcher->dispatchTyped(new DeclarativeSettingsRegisterFormEvent($this)); | |||||
} | |||||
/** | |||||
* @inheritdoc | |||||
*/ | |||||
public function getFormIDs(IUser $user, string $type, string $section): array { | |||||
$isAdmin = $this->groupManager->isAdmin($user->getUID()); | |||||
/** @var array<string, list<string>> $formIds */ | |||||
$formIds = []; | |||||
foreach ($this->appSchemas as $app => $schemas) { | |||||
$ids = []; | |||||
usort($schemas, [$this, 'sortSchemasByPriorityCallback']); | |||||
foreach ($schemas as $schema) { | |||||
if ($schema['section_type'] === DeclarativeSettingsTypes::SECTION_TYPE_ADMIN && !$isAdmin) { | |||||
continue; | |||||
} | |||||
if ($schema['section_type'] === $type && $schema['section_id'] === $section) { | |||||
$ids[] = $schema['id']; | |||||
} | |||||
} | |||||
if (!empty($ids)) { | |||||
$formIds[$app] = array_merge($formIds[$app] ?? [], $ids); | |||||
} | |||||
} | |||||
return $formIds; | |||||
} | |||||
/** | |||||
* @inheritdoc | |||||
* @throws Exception | |||||
*/ | |||||
public function getFormsWithValues(IUser $user, ?string $type, ?string $section): array { | |||||
$isAdmin = $this->groupManager->isAdmin($user->getUID()); | |||||
$forms = []; | |||||
foreach ($this->appSchemas as $app => $schemas) { | |||||
foreach ($schemas as $schema) { | |||||
if ($type !== null && $schema['section_type'] !== $type) { | |||||
continue; | |||||
} | |||||
if ($section !== null && $schema['section_id'] !== $section) { | |||||
continue; | |||||
} | |||||
// If listing all fields skip the admin fields which a non-admin user has no access to | |||||
if ($type === null && $schema['section_type'] === 'admin' && !$isAdmin) { | |||||
continue; | |||||
} | |||||
$s = $schema; | |||||
$s['app'] = $app; | |||||
foreach ($s['fields'] as &$field) { | |||||
$field['value'] = $this->getValue($user, $app, $schema['id'], $field['id']); | |||||
} | |||||
unset($field); | |||||
/** @var DeclarativeSettingsFormSchemaWithValues $s */ | |||||
$forms[] = $s; | |||||
} | |||||
} | |||||
usort($forms, [$this, 'sortSchemasByPriorityCallback']); | |||||
return $forms; | |||||
} | |||||
private function sortSchemasByPriorityCallback(mixed $a, mixed $b): int { | |||||
if ($a['priority'] === $b['priority']) { | |||||
return 0; | |||||
} | |||||
return $a['priority'] > $b['priority'] ? -1 : 1; | |||||
} | |||||
/** | |||||
* @return DeclarativeSettingsStorageType | |||||
*/ | |||||
private function getStorageType(string $app, string $fieldId): string { | |||||
if (array_key_exists($app, $this->appSchemas)) { | |||||
foreach ($this->appSchemas[$app] as $schema) { | |||||
foreach ($schema['fields'] as $field) { | |||||
if ($field['id'] == $fieldId) { | |||||
if (array_key_exists('storage_type', $field)) { | |||||
return $field['storage_type']; | |||||
} | |||||
} | |||||
} | |||||
if (array_key_exists('storage_type', $schema)) { | |||||
return $schema['storage_type']; | |||||
} | |||||
} | |||||
} | |||||
return DeclarativeSettingsTypes::STORAGE_TYPE_INTERNAL; | |||||
} | |||||
/** | |||||
* @return DeclarativeSettingsSectionType | |||||
* @throws Exception | |||||
*/ | |||||
private function getSectionType(string $app, string $fieldId): string { | |||||
if (array_key_exists($app, $this->appSchemas)) { | |||||
foreach ($this->appSchemas[$app] as $schema) { | |||||
foreach ($schema['fields'] as $field) { | |||||
if ($field['id'] == $fieldId) { | |||||
return $schema['section_type']; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
throw new Exception('Unknown fieldId "' . $fieldId . '"'); | |||||
} | |||||
/** | |||||
* @psalm-param DeclarativeSettingsSectionType $sectionType | |||||
* @throws NotAdminException | |||||
*/ | |||||
private function assertAuthorized(IUser $user, string $sectionType): void { | |||||
if ($sectionType === 'admin' && !$this->groupManager->isAdmin($user->getUID())) { | |||||
throw new NotAdminException('Logged in user does not have permission to access these settings.'); | |||||
} | |||||
} | |||||
/** | |||||
* @return DeclarativeSettingsValueTypes | |||||
* @throws Exception | |||||
* @throws NotAdminException | |||||
*/ | |||||
private function getValue(IUser $user, string $app, string $formId, string $fieldId): mixed { | |||||
$sectionType = $this->getSectionType($app, $fieldId); | |||||
$this->assertAuthorized($user, $sectionType); | |||||
$storageType = $this->getStorageType($app, $fieldId); | |||||
switch ($storageType) { | |||||
case DeclarativeSettingsTypes::STORAGE_TYPE_EXTERNAL: | |||||
$event = new DeclarativeSettingsGetValueEvent($user, $app, $formId, $fieldId); | |||||
$this->eventDispatcher->dispatchTyped($event); | |||||
return $event->getValue(); | |||||
case DeclarativeSettingsTypes::STORAGE_TYPE_INTERNAL: | |||||
return $this->getInternalValue($user, $app, $formId, $fieldId); | |||||
default: | |||||
throw new Exception('Unknown storage type "' . $storageType . '"'); | |||||
} | |||||
} | |||||
/** | |||||
* @inheritdoc | |||||
*/ | |||||
public function setValue(IUser $user, string $app, string $formId, string $fieldId, mixed $value): void { | |||||
$sectionType = $this->getSectionType($app, $fieldId); | |||||
$this->assertAuthorized($user, $sectionType); | |||||
$storageType = $this->getStorageType($app, $fieldId); | |||||
switch ($storageType) { | |||||
case DeclarativeSettingsTypes::STORAGE_TYPE_EXTERNAL: | |||||
$this->eventDispatcher->dispatchTyped(new DeclarativeSettingsSetValueEvent($user, $app, $formId, $fieldId, $value)); | |||||
break; | |||||
case DeclarativeSettingsTypes::STORAGE_TYPE_INTERNAL: | |||||
$this->saveInternalValue($user, $app, $fieldId, $value); | |||||
break; | |||||
default: | |||||
throw new Exception('Unknown storage type "' . $storageType . '"'); | |||||
} | |||||
} | |||||
private function getInternalValue(IUser $user, string $app, string $formId, string $fieldId): mixed { | |||||
$sectionType = $this->getSectionType($app, $fieldId); | |||||
$defaultValue = $this->getDefaultValue($app, $formId, $fieldId); | |||||
switch ($sectionType) { | |||||
case DeclarativeSettingsTypes::SECTION_TYPE_ADMIN: | |||||
return $this->config->getAppValue($app, $fieldId, $defaultValue); | |||||
case DeclarativeSettingsTypes::SECTION_TYPE_PERSONAL: | |||||
return $this->config->getUserValue($user->getUID(), $app, $fieldId, $defaultValue); | |||||
default: | |||||
throw new Exception('Unknown section type "' . $sectionType . '"'); | |||||
} | |||||
} | |||||
private function saveInternalValue(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); | |||||
break; | |||||
case DeclarativeSettingsTypes::SECTION_TYPE_PERSONAL: | |||||
$this->config->setUserValue($user->getUID(), $app, $fieldId, $value); | |||||
break; | |||||
default: | |||||
throw new Exception('Unknown section type "' . $sectionType . '"'); | |||||
} | |||||
} | |||||
private function getDefaultValue(string $app, string $formId, string $fieldId): mixed { | |||||
foreach ($this->appSchemas[$app] as $schema) { | |||||
if ($schema['id'] === $formId) { | |||||
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']; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
return null; | |||||
} | |||||
private function validateSchema(string $appId, array $schema): bool { | |||||
if (!isset($schema['id'])) { | |||||
$this->logger->warning('Attempt to register a declarative settings schema with no id', ['app' => $appId]); | |||||
return false; | |||||
} | |||||
$formId = $schema['id']; | |||||
if (!isset($schema['section_type'])) { | |||||
$this->logger->warning('Declarative settings: missing section_type', ['app' => $appId, 'form_id' => $formId]); | |||||
return false; | |||||
} | |||||
if (!in_array($schema['section_type'], [DeclarativeSettingsTypes::SECTION_TYPE_ADMIN, DeclarativeSettingsTypes::SECTION_TYPE_PERSONAL])) { | |||||
$this->logger->warning('Declarative settings: invalid section_type', ['app' => $appId, 'form_id' => $formId, 'section_type' => $schema['section_type']]); | |||||
return false; | |||||
} | |||||
if (!isset($schema['section_id'])) { | |||||
$this->logger->warning('Declarative settings: missing section_id', ['app' => $appId, 'form_id' => $formId]); | |||||
return false; | |||||
} | |||||
if (!isset($schema['storage_type'])) { | |||||
$this->logger->warning('Declarative settings: missing storage_type', ['app' => $appId, 'form_id' => $formId]); | |||||
return false; | |||||
} | |||||
if (!in_array($schema['storage_type'], [DeclarativeSettingsTypes::STORAGE_TYPE_EXTERNAL, DeclarativeSettingsTypes::STORAGE_TYPE_INTERNAL])) { | |||||
$this->logger->warning('Declarative settings: invalid storage_type', ['app' => $appId, 'form_id' => $formId, 'storage_type' => $schema['storage_type']]); | |||||
return false; | |||||
} | |||||
if (!isset($schema['title'])) { | |||||
$this->logger->warning('Declarative settings: missing title', ['app' => $appId, 'form_id' => $formId]); | |||||
return false; | |||||
} | |||||
if (!isset($schema['fields']) || !is_array($schema['fields'])) { | |||||
$this->logger->warning('Declarative settings: missing or invalid fields', ['app' => $appId, 'form_id' => $formId]); | |||||
return false; | |||||
} | |||||
foreach ($schema['fields'] as $field) { | |||||
if (!isset($field['id'])) { | |||||
$this->logger->warning('Declarative settings: missing field id', ['app' => $appId, 'form_id' => $formId, 'field' => $field]); | |||||
return false; | |||||
} | |||||
$fieldId = $field['id']; | |||||
if (!isset($field['title'])) { | |||||
$this->logger->warning('Declarative settings: missing field title', ['app' => $appId, 'form_id' => $formId, 'field_id' => $fieldId]); | |||||
return false; | |||||
} | |||||
if (!isset($field['type'])) { | |||||
$this->logger->warning('Declarative settings: missing field type', ['app' => $appId, 'form_id' => $formId, 'field_id' => $fieldId]); | |||||
return false; | |||||
} | |||||
if (!in_array($field['type'], [ | |||||
DeclarativeSettingsTypes::MULTI_SELECT, DeclarativeSettingsTypes::MULTI_CHECKBOX, DeclarativeSettingsTypes::RADIO, | |||||
DeclarativeSettingsTypes::SELECT, DeclarativeSettingsTypes::CHECKBOX, | |||||
DeclarativeSettingsTypes::URL, DeclarativeSettingsTypes::EMAIL, DeclarativeSettingsTypes::NUMBER, | |||||
DeclarativeSettingsTypes::TEL, DeclarativeSettingsTypes::TEXT, DeclarativeSettingsTypes::PASSWORD, | |||||
])) { | |||||
$this->logger->warning('Declarative settings: invalid field type', [ | |||||
'app' => $appId, 'form_id' => $formId, 'field_id' => $fieldId, 'type' => $field['type'], | |||||
]); | |||||
return false; | |||||
} | |||||
if (!$this->validateField($appId, $formId, $field)) { | |||||
return false; | |||||
} | |||||
} | |||||
return true; | |||||
} | |||||
private function validateField(string $appId, string $formId, array $field): bool { | |||||
$fieldId = $field['id']; | |||||
if (in_array($field['type'], [ | |||||
DeclarativeSettingsTypes::MULTI_SELECT, DeclarativeSettingsTypes::MULTI_CHECKBOX, DeclarativeSettingsTypes::RADIO, | |||||
DeclarativeSettingsTypes::SELECT | |||||
])) { | |||||
if (!isset($field['options'])) { | |||||
$this->logger->warning('Declarative settings: missing field options', ['app' => $appId, 'form_id' => $formId, 'field_id' => $fieldId]); | |||||
return false; | |||||
} | |||||
if (!is_array($field['options'])) { | |||||
$this->logger->warning('Declarative settings: field options should be an array', ['app' => $appId, 'form_id' => $formId, 'field_id' => $fieldId]); | |||||
return false; | |||||
} | |||||
} | |||||
return true; | |||||
} | |||||
} |
* @since 28.0.0 | * @since 28.0.0 | ||||
*/ | */ | ||||
public function registerSetupCheck(string $setupCheckClass): void; | public function registerSetupCheck(string $setupCheckClass): void; | ||||
/** | |||||
* Register an implementation of \OCP\Settings\IDeclarativeSettings that | |||||
* will handle the implementation of declarative settings | |||||
* | |||||
* @param string $declarativeSettingsClass | |||||
* @psalm-param class-string<\OCP\Settings\IDeclarativeSettingsForm> $declarativeSettingsClass | |||||
* @return void | |||||
* @since 29.0.0 | |||||
*/ | |||||
public function registerDeclarativeSettings(string $declarativeSettingsClass): void; | |||||
} | } |
<?php | |||||
declare(strict_types=1); | |||||
/** | |||||
* @copyright Copyright (c) 2023 Andrey Borysenko <andrey.borysenko@nextcloud.com> | |||||
* | |||||
* @author Andrey Borysenko <andrey.borysenko@nextcloud.com> | |||||
* | |||||
* @license GNU AGPL version 3 or any later version | |||||
* | |||||
* This program is free software: you can redistribute it and/or modify | |||||
* it under the terms of the GNU Affero General Public License as | |||||
* published by the Free Software Foundation, either version 3 of the | |||||
* License, or (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
* GNU Affero General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Affero General Public License | |||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||||
* | |||||
*/ | |||||
namespace OCP\Settings; | |||||
/** | |||||
* Declarative settings types supported in the IDeclarativeSettingsForm forms | |||||
* | |||||
* @since 29.0.0 | |||||
*/ | |||||
final class DeclarativeSettingsTypes { | |||||
/** | |||||
* IDeclarativeSettingsForm section_type which is determines where the form is displayed | |||||
* | |||||
* @since 29.0.0 | |||||
*/ | |||||
public const SECTION_TYPE_ADMIN = 'admin'; | |||||
/** | |||||
* IDeclarativeSettingsForm section_type which is determines where the form is displayed | |||||
* | |||||
* @since 29.0.0 | |||||
*/ | |||||
public const SECTION_TYPE_PERSONAL = 'personal'; | |||||
/** | |||||
* 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. | |||||
* | |||||
* @since 29.0.0 | |||||
*/ | |||||
public const STORAGE_TYPE_EXTERNAL = 'external'; | |||||
/** | |||||
* 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 | |||||
*/ | |||||
public const STORAGE_TYPE_INTERNAL = 'internal'; | |||||
/** | |||||
* NcInputField type text | |||||
* | |||||
* @since 29.0.0 | |||||
*/ | |||||
public const TEXT = 'text'; | |||||
/** | |||||
* NcInputField type password | |||||
* | |||||
* @since 29.0.0 | |||||
*/ | |||||
public const PASSWORD = 'password'; | |||||
/** | |||||
* NcInputField type email | |||||
* | |||||
* @since 29.0.0 | |||||
*/ | |||||
public const EMAIL = 'email'; | |||||
/** | |||||
* NcInputField type tel | |||||
* | |||||
* @since 29.0.0 | |||||
*/ | |||||
public const TEL = 'tel'; | |||||
/** | |||||
* NcInputField type url | |||||
* | |||||
* @since 29.0.0 | |||||
*/ | |||||
public const URL = 'url'; | |||||
/** | |||||
* NcInputField type number | |||||
* | |||||
* @since 29.0.0 | |||||
*/ | |||||
public const NUMBER = 'number'; | |||||
/** | |||||
* NcCheckboxRadioSwitch type checkbox | |||||
* | |||||
* @since 29.0.0 | |||||
*/ | |||||
public const CHECKBOX = 'checkbox'; | |||||
/** | |||||
* Multiple NcCheckboxRadioSwitch type checkbox representing a one config value (saved as JSON object) | |||||
* | |||||
* @since 29.0.0 | |||||
*/ | |||||
public const MULTI_CHECKBOX = 'multi-checkbox'; | |||||
/** | |||||
* NcCheckboxRadioSwitch type radio | |||||
* | |||||
* @since 29.0.0 | |||||
*/ | |||||
public const RADIO = 'radio'; | |||||
/** | |||||
* NcSelect | |||||
* | |||||
* @since 29.0.0 | |||||
*/ | |||||
public const SELECT = 'select'; | |||||
/** | |||||
* Multiple NcSelect representing a one config value (saved as JSON array) | |||||
* | |||||
* @since 29.0.0 | |||||
*/ | |||||
public const MULTI_SELECT = 'multi-select'; | |||||
} |
<?php | |||||
namespace OCP\Settings\Events; | |||||
use Exception; | |||||
use OCP\EventDispatcher\Event; | |||||
use OCP\IUser; | |||||
use OCP\Settings\IDeclarativeSettingsForm; | |||||
/** | |||||
* @psalm-import-type DeclarativeSettingsValueTypes from IDeclarativeSettingsForm | |||||
* | |||||
* @since 29.0.0 | |||||
*/ | |||||
class DeclarativeSettingsGetValueEvent extends Event { | |||||
/** | |||||
* @var ?DeclarativeSettingsValueTypes | |||||
*/ | |||||
private mixed $value = null; | |||||
/** | |||||
* @since 29.0.0 | |||||
*/ | |||||
public function __construct( | |||||
private IUser $user, | |||||
private string $app, | |||||
private string $formId, | |||||
private string $fieldId, | |||||
) { | |||||
parent::__construct(); | |||||
} | |||||
/** | |||||
* @since 29.0.0 | |||||
*/ | |||||
public function getUser(): IUser { | |||||
return $this->user; | |||||
} | |||||
/** | |||||
* @since 29.0.0 | |||||
*/ | |||||
public function getApp(): string { | |||||
return $this->app; | |||||
} | |||||
/** | |||||
* @since 29.0.0 | |||||
*/ | |||||
public function getFormId(): string { | |||||
return $this->formId; | |||||
} | |||||
/** | |||||
* @since 29.0.0 | |||||
*/ | |||||
public function getFieldId(): string { | |||||
return $this->fieldId; | |||||
} | |||||
/** | |||||
* @since 29.0.0 | |||||
*/ | |||||
public function setValue(mixed $value): void { | |||||
$this->value = $value; | |||||
} | |||||
/** | |||||
* @return DeclarativeSettingsValueTypes | |||||
* @throws Exception | |||||
* | |||||
* @since 29.0.0 | |||||
*/ | |||||
public function getValue(): mixed { | |||||
if ($this->value === null) { | |||||
throw new Exception('Value not set'); | |||||
} | |||||
return $this->value; | |||||
} | |||||
} |
<?php | |||||
namespace OCP\Settings\Events; | |||||
use OCP\EventDispatcher\Event; | |||||
use OCP\Settings\IDeclarativeManager; | |||||
use OCP\Settings\IDeclarativeSettingsForm; | |||||
/** | |||||
* @psalm-import-type DeclarativeSettingsFormSchemaWithoutValues from IDeclarativeSettingsForm | |||||
* | |||||
* @since 29.0.0 | |||||
*/ | |||||
class DeclarativeSettingsRegisterFormEvent extends Event { | |||||
/** | |||||
* @since 29.0.0 | |||||
*/ | |||||
public function __construct(private IDeclarativeManager $manager) { | |||||
parent::__construct(); | |||||
} | |||||
/** | |||||
* @param DeclarativeSettingsFormSchemaWithoutValues $schema | |||||
* @since 29.0.0 | |||||
*/ | |||||
public function registerSchema(string $app, array $schema): void { | |||||
$this->manager->registerSchema($app, $schema); | |||||
} | |||||
} |
<?php | |||||
namespace OCP\Settings\Events; | |||||
use OCP\EventDispatcher\Event; | |||||
use OCP\IUser; | |||||
use OCP\Settings\IDeclarativeSettingsForm; | |||||
/** | |||||
* @psalm-import-type DeclarativeSettingsValueTypes from IDeclarativeSettingsForm | |||||
* | |||||
* @since 29.0.0 | |||||
*/ | |||||
class DeclarativeSettingsSetValueEvent extends Event { | |||||
/** | |||||
* @param DeclarativeSettingsValueTypes $value | |||||
* @since 29.0.0 | |||||
*/ | |||||
public function __construct( | |||||
private IUser $user, | |||||
private string $app, | |||||
private string $formId, | |||||
private string $fieldId, | |||||
private mixed $value, | |||||
) { | |||||
parent::__construct(); | |||||
} | |||||
/** | |||||
* @since 29.0.0 | |||||
*/ | |||||
public function getUser(): IUser { | |||||
return $this->user; | |||||
} | |||||
/** | |||||
* @since 29.0.0 | |||||
*/ | |||||
public function getApp(): string { | |||||
return $this->app; | |||||
} | |||||
/** | |||||
* @since 29.0.0 | |||||
*/ | |||||
public function getFormId(): string { | |||||
return $this->formId; | |||||
} | |||||
/** | |||||
* @since 29.0.0 | |||||
*/ | |||||
public function getFieldId(): string { | |||||
return $this->fieldId; | |||||
} | |||||
/** | |||||
* @since 29.0.0 | |||||
*/ | |||||
public function getValue(): mixed { | |||||
return $this->value; | |||||
} | |||||
} |
<?php | |||||
/** | |||||
* @copyright Copyright (c) 2023 Kate Döen <kate.doeen@nextcloud.com> | |||||
* | |||||
* @author Kate Döen <kate.doeen@nextcloud.com> | |||||
* | |||||
* @license GNU AGPL version 3 or any later version | |||||
* | |||||
* This program is free software: you can redistribute it and/or modify | |||||
* it under the terms of the GNU Affero General Public License as | |||||
* published by the Free Software Foundation, either version 3 of the | |||||
* License, or (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
* GNU Affero General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Affero General Public License | |||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||||
* | |||||
*/ | |||||
namespace OCP\Settings; | |||||
use Exception; | |||||
use OC\AppFramework\Middleware\Security\Exceptions\NotAdminException; | |||||
use OCP\IUser; | |||||
/** | |||||
* @since 29.0.0 | |||||
* | |||||
* @psalm-import-type DeclarativeSettingsValueTypes from IDeclarativeSettingsForm | |||||
* @psalm-import-type DeclarativeSettingsSectionType from IDeclarativeSettingsForm | |||||
* @psalm-import-type DeclarativeSettingsFormSchemaWithValues from IDeclarativeSettingsForm | |||||
* @psalm-import-type DeclarativeSettingsFormSchemaWithoutValues from IDeclarativeSettingsForm | |||||
*/ | |||||
interface IDeclarativeManager { | |||||
/** | |||||
* Registers a new declarative settings schema. | |||||
* | |||||
* @param DeclarativeSettingsFormSchemaWithoutValues $schema | |||||
* @since 29.0.0 | |||||
*/ | |||||
public function registerSchema(string $app, array $schema): void; | |||||
/** | |||||
* Load all schemas from the registration context and events. | |||||
* | |||||
* @since 29.0.0 | |||||
*/ | |||||
public function loadSchemas(): void; | |||||
/** | |||||
* Gets the IDs of the forms for the given type and section. | |||||
* | |||||
* @param DeclarativeSettingsSectionType $type | |||||
* @param string $section | |||||
* @return array<string, list<string>> | |||||
* | |||||
* @since 29.0.0 | |||||
*/ | |||||
public function getFormIDs(IUser $user, string $type, string $section): array; | |||||
/** | |||||
* Gets the forms including the field values for the given type and section. | |||||
* | |||||
* @param IUser $user Used for reading values from the personal section or for authorization for the admin section. | |||||
* @param ?DeclarativeSettingsSectionType $type If it is null the forms will not be filtered by type. | |||||
* @param ?string $section If it is null the forms will not be filtered by section. | |||||
* @return list<DeclarativeSettingsFormSchemaWithValues> | |||||
* | |||||
* @since 29.0.0 | |||||
*/ | |||||
public function getFormsWithValues(IUser $user, ?string $type, ?string $section): array; | |||||
/** | |||||
* Sets a value for the given field ID. | |||||
* | |||||
* @param IUser $user Used for storing values in the personal section or for authorization for the admin section. | |||||
* @param DeclarativeSettingsValueTypes $value | |||||
* | |||||
* @throws Exception | |||||
* @throws NotAdminException | |||||
* | |||||
* @since 29.0.0 | |||||
*/ | |||||
public function setValue(IUser $user, string $app, string $formId, string $fieldId, mixed $value): void; | |||||
} |
<?php | |||||
/** | |||||
* @copyright Copyright (c) 2023 Kate Döen <kate.doeen@nextcloud.com> | |||||
* | |||||
* @author Kate Döen <kate.doeen@nextcloud.com> | |||||
* | |||||
* @license GNU AGPL version 3 or any later version | |||||
* | |||||
* This program is free software: you can redistribute it and/or modify | |||||
* it under the terms of the GNU Affero General Public License as | |||||
* published by the Free Software Foundation, either version 3 of the | |||||
* License, or (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
* GNU Affero General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Affero General Public License | |||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||||
* | |||||
*/ | |||||
namespace OCP\Settings; | |||||
/** | |||||
* @since 29.0.0 | |||||
* | |||||
* @psalm-type DeclarativeSettingsSectionType = 'admin'|'personal' | |||||
* | |||||
* @psalm-type DeclarativeSettingsStorageType = 'internal'|'external' | |||||
* | |||||
* @psalm-type DeclarativeSettingsValueTypes = string|int|float|bool|list<string> | |||||
* | |||||
* @psalm-type DeclarativeSettingsFormField = array{ | |||||
* id: string, | |||||
* title: string, | |||||
* description?: string, | |||||
* type: 'text'|'password'|'email'|'tel'|'url'|'number'|'checkbox'|'multi-checkbox'|'radio'|'select'|'multi-select', | |||||
* placeholder?: string, | |||||
* label?: string, | |||||
* default: mixed, | |||||
* options?: list<string|array{name: string, value: mixed}>, | |||||
* } | |||||
* | |||||
* @psalm-type DeclarativeSettingsFormFieldWithValue = DeclarativeSettingsFormField&array{ | |||||
* value: DeclarativeSettingsValueTypes, | |||||
* } | |||||
* | |||||
* @psalm-type DeclarativeSettingsFormSchema = array{ | |||||
* id: string, | |||||
* priority: int, | |||||
* section_type: DeclarativeSettingsSectionType, | |||||
* section_id: string, | |||||
* storage_type: DeclarativeSettingsStorageType, | |||||
* title: string, | |||||
* description?: string, | |||||
* doc_url?: string, | |||||
* } | |||||
* | |||||
* @psalm-type DeclarativeSettingsFormSchemaWithValues = DeclarativeSettingsFormSchema&array{ | |||||
* app: string, | |||||
* fields: list<DeclarativeSettingsFormFieldWithValue>, | |||||
* } | |||||
* | |||||
* @psalm-type DeclarativeSettingsFormSchemaWithoutValues = DeclarativeSettingsFormSchema&array{ | |||||
* fields: list<DeclarativeSettingsFormField>, | |||||
* } | |||||
*/ | |||||
interface IDeclarativeSettingsForm { | |||||
/** | |||||
* Gets the schema that defines the declarative settings form | |||||
* | |||||
* @return DeclarativeSettingsFormSchemaWithoutValues | |||||
* @since 29.0.0 | |||||
*/ | |||||
public function getSchema(): array; | |||||
} |
<?php | |||||
/** | |||||
* @copyright Copyright (c) 2023 Andrey Borysenko <andrey.borysenko@nextcloud.com> | |||||
* | |||||
* @author Andrey Borysenko <andrey.borysenko@nextcloud.com> | |||||
* | |||||
* @license GNU AGPL version 3 or any later version | |||||
* | |||||
* This program is free software: you can redistribute it and/or modify | |||||
* it under the terms of the GNU Affero General Public License as | |||||
* published by the Free Software Foundation, either version 3 of the | |||||
* License, or (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
* GNU Affero General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Affero General Public License | |||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||||
* | |||||
*/ | |||||
namespace Test\Settings; | |||||
use OC\AppFramework\Bootstrap\Coordinator; | |||||
use OC\Settings\DeclarativeManager; | |||||
use OCP\EventDispatcher\IEventDispatcher; | |||||
use OCP\IAppConfig; | |||||
use OCP\IConfig; | |||||
use OCP\IGroupManager; | |||||
use OCP\IUser; | |||||
use OCP\Settings\DeclarativeSettingsTypes; | |||||
use OCP\Settings\Events\DeclarativeSettingsSetValueEvent; | |||||
use OCP\Settings\IDeclarativeManager; | |||||
use PHPUnit\Framework\MockObject\MockObject; | |||||
use Psr\Log\LoggerInterface; | |||||
use Test\TestCase; | |||||
class DeclarativeManagerTest extends TestCase { | |||||
/** @var IDeclarativeManager|MockObject */ | |||||
private $declarativeManager; | |||||
/** @var IEventDispatcher|MockObject */ | |||||
private $eventDispatcher; | |||||
/** @var IGroupManager|MockObject */ | |||||
private $groupManager; | |||||
/** @var Coordinator|MockObject */ | |||||
private $coordinator; | |||||
/** @var IConfig|MockObject */ | |||||
private $config; | |||||
/** @var IAppConfig|MockObject */ | |||||
private $appConfig; | |||||
/** @var LoggerInterface|MockObject */ | |||||
private $logger; | |||||
/** @var IUser|MockObject */ | |||||
private $user; | |||||
/** @var IUser|MockObject */ | |||||
private $adminUser; | |||||
public const validSchemaAllFields = [ | |||||
'id' => 'test_form_1', | |||||
'priority' => 10, | |||||
'section_type' => DeclarativeSettingsTypes::SECTION_TYPE_ADMIN, // admin, personal | |||||
'section_id' => 'additional', | |||||
'storage_type' => DeclarativeSettingsTypes::STORAGE_TYPE_INTERNAL, // external, internal (handled by core to store in appconfig and preferences) | |||||
'title' => 'Test declarative settings', // NcSettingsSection name | |||||
'description' => 'These fields are rendered dynamically from declarative schema', // NcSettingsSection description | |||||
'doc_url' => '', // NcSettingsSection doc_url for documentation or help page, empty string if not needed | |||||
'fields' => [ | |||||
[ | |||||
'id' => 'test_field_7', // configkey | |||||
'title' => 'Multi-selection', // name or label | |||||
'description' => 'Select some option setting', // hint | |||||
'type' => DeclarativeSettingsTypes::MULTI_SELECT, | |||||
'options' => ['foo', 'bar', 'baz'], // simple options for select, radio, multi-select | |||||
'placeholder' => 'Select some multiple options', // input placeholder | |||||
'default' => ['foo', 'bar'], | |||||
], | |||||
[ | |||||
'id' => 'some_real_setting', | |||||
'title' => 'Select single option', | |||||
'description' => 'Single option radio buttons', | |||||
'type' => DeclarativeSettingsTypes::RADIO, // radio (NcCheckboxRadioSwitch type radio) | |||||
'placeholder' => 'Select single option, test interval', | |||||
'default' => '40m', | |||||
'options' => [ | |||||
[ | |||||
'name' => 'Each 40 minutes', // NcCheckboxRadioSwitch display name | |||||
'value' => '40m' // NcCheckboxRadioSwitch value | |||||
], | |||||
[ | |||||
'name' => 'Each 60 minutes', | |||||
'value' => '60m' | |||||
], | |||||
[ | |||||
'name' => 'Each 120 minutes', | |||||
'value' => '120m' | |||||
], | |||||
[ | |||||
'name' => 'Each day', | |||||
'value' => 60 * 24 . 'm' | |||||
], | |||||
], | |||||
], | |||||
[ | |||||
'id' => 'test_field_1', // configkey | |||||
'title' => 'Default text field', // label | |||||
'description' => 'Set some simple text setting', // hint | |||||
'type' => DeclarativeSettingsTypes::TEXT, | |||||
'placeholder' => 'Enter text setting', // placeholder | |||||
'default' => 'foo', | |||||
], | |||||
[ | |||||
'id' => 'test_field_1_1', | |||||
'title' => 'Email field', | |||||
'description' => 'Set email config', | |||||
'type' => DeclarativeSettingsTypes::EMAIL, | |||||
'placeholder' => 'Enter email', | |||||
'default' => '', | |||||
], | |||||
[ | |||||
'id' => 'test_field_1_2', | |||||
'title' => 'Tel field', | |||||
'description' => 'Set tel config', | |||||
'type' => DeclarativeSettingsTypes::TEL, | |||||
'placeholder' => 'Enter your tel', | |||||
'default' => '', | |||||
], | |||||
[ | |||||
'id' => 'test_field_1_3', | |||||
'title' => 'Url (website) field', | |||||
'description' => 'Set url config', | |||||
'type' => 'url', | |||||
'placeholder' => 'Enter url', | |||||
'default' => '', | |||||
], | |||||
[ | |||||
'id' => 'test_field_1_4', | |||||
'title' => 'Number field', | |||||
'description' => 'Set number config', | |||||
'type' => DeclarativeSettingsTypes::NUMBER, | |||||
'placeholder' => 'Enter number value', | |||||
'default' => 0, | |||||
], | |||||
[ | |||||
'id' => 'test_field_2', | |||||
'title' => 'Password', | |||||
'description' => 'Set some secure value setting', | |||||
'type' => 'password', | |||||
'placeholder' => 'Set secure value', | |||||
'default' => '', | |||||
], | |||||
[ | |||||
'id' => 'test_field_3', | |||||
'title' => 'Selection', | |||||
'description' => 'Select some option setting', | |||||
'type' => DeclarativeSettingsTypes::SELECT, | |||||
'options' => ['foo', 'bar', 'baz'], | |||||
'placeholder' => 'Select some option setting', | |||||
'default' => 'foo', | |||||
], | |||||
[ | |||||
'id' => 'test_field_4', | |||||
'title' => 'Toggle something', | |||||
'description' => 'Select checkbox option setting', | |||||
'type' => DeclarativeSettingsTypes::CHECKBOX, | |||||
'label' => 'Verify something if enabled', | |||||
'default' => false, | |||||
], | |||||
[ | |||||
'id' => 'test_field_5', | |||||
'title' => 'Multiple checkbox toggles, describing one setting, checked options are saved as an JSON object {foo: true, bar: false}', | |||||
'description' => 'Select checkbox option setting', | |||||
'type' => DeclarativeSettingsTypes::MULTI_CHECKBOX, | |||||
'default' => ['foo' => true, 'bar' => true], | |||||
'options' => [ | |||||
[ | |||||
'name' => 'Foo', | |||||
'value' => 'foo', // multiple-checkbox configkey | |||||
], | |||||
[ | |||||
'name' => 'Bar', | |||||
'value' => 'bar', | |||||
], | |||||
[ | |||||
'name' => 'Baz', | |||||
'value' => 'baz', | |||||
], | |||||
[ | |||||
'name' => 'Qux', | |||||
'value' => 'qux', | |||||
], | |||||
], | |||||
], | |||||
[ | |||||
'id' => 'test_field_6', | |||||
'title' => 'Radio toggles, describing one setting like single select', | |||||
'description' => 'Select radio option setting', | |||||
'type' => DeclarativeSettingsTypes::RADIO, // radio (NcCheckboxRadioSwitch type radio) | |||||
'label' => 'Select single toggle', | |||||
'default' => 'foo', | |||||
'options' => [ | |||||
[ | |||||
'name' => 'First radio', // NcCheckboxRadioSwitch display name | |||||
'value' => 'foo' // NcCheckboxRadioSwitch value | |||||
], | |||||
[ | |||||
'name' => 'Second radio', | |||||
'value' => 'bar' | |||||
], | |||||
[ | |||||
'name' => 'Second radio', | |||||
'value' => 'baz' | |||||
], | |||||
], | |||||
], | |||||
], | |||||
]; | |||||
public static bool $testSetInternalValueAfterChange = false; | |||||
protected function setUp(): void { | |||||
parent::setUp(); | |||||
$this->eventDispatcher = $this->createMock(IEventDispatcher::class); | |||||
$this->groupManager = $this->createMock(IGroupManager::class); | |||||
$this->coordinator = $this->createMock(Coordinator::class); | |||||
$this->config = $this->createMock(IConfig::class); | |||||
$this->appConfig = $this->createMock(IAppConfig::class); | |||||
$this->logger = $this->createMock(LoggerInterface::class); | |||||
$this->declarativeManager = new DeclarativeManager( | |||||
$this->eventDispatcher, | |||||
$this->groupManager, | |||||
$this->coordinator, | |||||
$this->config, | |||||
$this->appConfig, | |||||
$this->logger | |||||
); | |||||
$this->user = $this->createMock(IUser::class); | |||||
$this->user->expects($this->any()) | |||||
->method('getUID') | |||||
->willReturn('test_user'); | |||||
$this->adminUser = $this->createMock(IUser::class); | |||||
$this->adminUser->expects($this->any()) | |||||
->method('getUID') | |||||
->willReturn('admin_test_user'); | |||||
$this->groupManager->expects($this->any()) | |||||
->method('isAdmin') | |||||
->willReturnCallback(function ($userId) { | |||||
return $userId === 'admin_test_user'; | |||||
}); | |||||
} | |||||
public function testRegisterSchema(): void { | |||||
$app = 'testing'; | |||||
$schema = self::validSchemaAllFields; | |||||
$this->declarativeManager->registerSchema($app, $schema); | |||||
$formIds = $this->declarativeManager->getFormIDs($this->adminUser, $schema['section_type'], $schema['section_id']); | |||||
$this->assertTrue(isset($formIds[$app]) && in_array($schema['id'], $formIds[$app])); | |||||
} | |||||
/** | |||||
* Simple test to verify that exception is thrown when trying to register schema with duplicate id | |||||
*/ | |||||
public function testRegisterDuplicateSchema(): void { | |||||
$this->declarativeManager->registerSchema('testing', self::validSchemaAllFields); | |||||
$this->expectException(\Exception::class); | |||||
$this->declarativeManager->registerSchema('testing', self::validSchemaAllFields); | |||||
} | |||||
/** | |||||
* It's not allowed to register schema with duplicate fields ids for the same app | |||||
*/ | |||||
public function testRegisterSchemaWithDuplicateFields(): void { | |||||
// Register first valid schema | |||||
$this->declarativeManager->registerSchema('testing', self::validSchemaAllFields); | |||||
// Register second schema with duplicate fields, but different schema id | |||||
$this->expectException(\Exception::class); | |||||
$schema = self::validSchemaAllFields; | |||||
$schema['id'] = 'test_form_2'; | |||||
$this->declarativeManager->registerSchema('testing', $schema); | |||||
} | |||||
public function testRegisterMultipleSchemasAndDuplicate(): void { | |||||
$app = 'testing'; | |||||
$schema = self::validSchemaAllFields; | |||||
$this->declarativeManager->registerSchema($app, $schema); | |||||
$formIds = $this->declarativeManager->getFormIDs($this->adminUser, $schema['section_type'], $schema['section_id']); | |||||
// 1. Check that form is registered for the app | |||||
$this->assertTrue(isset($formIds[$app]) && in_array($schema['id'], $formIds[$app])); | |||||
$app = 'testing2'; | |||||
$this->declarativeManager->registerSchema($app, $schema); | |||||
$formIds = $this->declarativeManager->getFormIDs($this->adminUser, $schema['section_type'], $schema['section_id']); | |||||
// 2. Check that form is registered for the second app | |||||
$this->assertTrue(isset($formIds[$app]) && in_array($schema['id'], $formIds[$app])); | |||||
$app = 'testing'; | |||||
$this->expectException(\Exception::class); // expecting duplicate form id and duplicate fields ids exception | |||||
$this->declarativeManager->registerSchema($app, $schema); | |||||
$schemaDuplicateFields = self::validSchemaAllFields; | |||||
$schemaDuplicateFields['id'] = 'test_form_2'; // change form id to test duplicate fields | |||||
$this->declarativeManager->registerSchema($app, $schemaDuplicateFields); | |||||
// 3. Check that not valid form with duplicate fields is not registered | |||||
$formIds = $this->declarativeManager->getFormIDs($this->adminUser, $schemaDuplicateFields['section_type'], $schemaDuplicateFields['section_id']); | |||||
$this->assertFalse(isset($formIds[$app]) && in_array($schemaDuplicateFields['id'], $formIds[$app])); | |||||
} | |||||
/** | |||||
* @dataProvider dataValidateSchema | |||||
*/ | |||||
public function testValidateSchema(bool $expected, bool $expectException, string $app, array $schema): void { | |||||
if ($expectException) { | |||||
$this->expectException(\Exception::class); | |||||
} | |||||
$this->declarativeManager->registerSchema($app, $schema); | |||||
$formIds = $this->declarativeManager->getFormIDs($this->adminUser, $schema['section_type'], $schema['section_id']); | |||||
$this->assertEquals($expected, isset($formIds[$app]) && in_array($schema['id'], $formIds[$app])); | |||||
} | |||||
public static function dataValidateSchema(): array { | |||||
return [ | |||||
'valid schema with all supported fields' => [ | |||||
true, | |||||
false, | |||||
'testing', | |||||
self::validSchemaAllFields, | |||||
], | |||||
'invalid schema with missing id' => [ | |||||
false, | |||||
true, | |||||
'testing', | |||||
[ | |||||
'priority' => 10, | |||||
'section_type' => DeclarativeSettingsTypes::SECTION_TYPE_ADMIN, | |||||
'section_id' => 'additional', | |||||
'storage_type' => DeclarativeSettingsTypes::STORAGE_TYPE_INTERNAL, | |||||
'title' => 'Test declarative settings', | |||||
'description' => 'These fields are rendered dynamically from declarative schema', | |||||
'doc_url' => '', | |||||
'fields' => [ | |||||
[ | |||||
'id' => 'test_field_7', | |||||
'title' => 'Multi-selection', | |||||
'description' => 'Select some option setting', | |||||
'type' => DeclarativeSettingsTypes::MULTI_SELECT, | |||||
'options' => ['foo', 'bar', 'baz'], | |||||
'placeholder' => 'Select some multiple options', | |||||
'default' => ['foo', 'bar'], | |||||
], | |||||
], | |||||
], | |||||
], | |||||
'invalid schema with invalid field' => [ | |||||
false, | |||||
true, | |||||
'testing', | |||||
[ | |||||
'id' => 'test_form_1', | |||||
'priority' => 10, | |||||
'section_type' => DeclarativeSettingsTypes::SECTION_TYPE_ADMIN, | |||||
'section_id' => 'additional', | |||||
'storage_type' => DeclarativeSettingsTypes::STORAGE_TYPE_INTERNAL, | |||||
'title' => 'Test declarative settings', | |||||
'description' => 'These fields are rendered dynamically from declarative schema', | |||||
'doc_url' => '', | |||||
'fields' => [ | |||||
[ | |||||
'id' => 'test_invalid_field', | |||||
'title' => 'Invalid field', | |||||
'description' => 'Some invalid setting description', | |||||
'type' => 'some_invalid_type', | |||||
'placeholder' => 'Some invalid field placeholder', | |||||
'default' => null, | |||||
], | |||||
], | |||||
], | |||||
], | |||||
]; | |||||
} | |||||
public function testGetFormIDs(): void { | |||||
$app = 'testing'; | |||||
$schema = self::validSchemaAllFields; | |||||
$this->declarativeManager->registerSchema($app, $schema); | |||||
$formIds = $this->declarativeManager->getFormIDs($this->adminUser, $schema['section_type'], $schema['section_id']); | |||||
$this->assertTrue(isset($formIds[$app]) && in_array($schema['id'], $formIds[$app])); | |||||
$app = 'testing2'; | |||||
$this->declarativeManager->registerSchema($app, $schema); | |||||
$formIds = $this->declarativeManager->getFormIDs($this->adminUser, $schema['section_type'], $schema['section_id']); | |||||
$this->assertTrue(isset($formIds[$app]) && in_array($schema['id'], $formIds[$app])); | |||||
} | |||||
/** | |||||
* Check that form with default values is returned with internal storage_type | |||||
*/ | |||||
public function testGetFormsWithDefaultValues(): void { | |||||
$app = 'testing'; | |||||
$schema = self::validSchemaAllFields; | |||||
$this->declarativeManager->registerSchema($app, $schema); | |||||
$this->config->expects($this->any()) | |||||
->method('getAppValue') | |||||
->willReturnCallback(fn ($app, $configkey, $default) => $default); | |||||
$forms = $this->declarativeManager->getFormsWithValues($this->adminUser, $schema['section_type'], $schema['section_id']); | |||||
$this->assertNotEmpty($forms); | |||||
$this->assertTrue(array_search($schema['id'], array_column($forms, 'id')) !== false); | |||||
// Check some_real_setting field default value | |||||
$someRealSettingField = array_values(array_filter(array_filter($forms, fn ($form) => $form['id'] === $schema['id'])[0]['fields'], fn ($field) => $field['id'] === 'some_real_setting'))[0]; | |||||
$schemaSomeRealSettingField = array_values(array_filter($schema['fields'], fn ($field) => $field['id'] === 'some_real_setting'))[0]; | |||||
$this->assertEquals($schemaSomeRealSettingField['default'], $someRealSettingField['default']); | |||||
} | |||||
/** | |||||
* Check values in json format to ensure that they are properly encoded | |||||
*/ | |||||
public function testGetFormsWithDefaultValuesJson(): void { | |||||
$app = 'testing'; | |||||
$schema = [ | |||||
'id' => 'test_form_1', | |||||
'priority' => 10, | |||||
'section_type' => DeclarativeSettingsTypes::SECTION_TYPE_PERSONAL, | |||||
'section_id' => 'additional', | |||||
'storage_type' => DeclarativeSettingsTypes::STORAGE_TYPE_INTERNAL, | |||||
'title' => 'Test declarative settings', | |||||
'description' => 'These fields are rendered dynamically from declarative schema', | |||||
'doc_url' => '', | |||||
'fields' => [ | |||||
[ | |||||
'id' => 'test_field_json', | |||||
'title' => 'Multi-selection', | |||||
'description' => 'Select some option setting', | |||||
'type' => DeclarativeSettingsTypes::MULTI_SELECT, | |||||
'options' => ['foo', 'bar', 'baz'], | |||||
'placeholder' => 'Select some multiple options', | |||||
'default' => ['foo', 'bar'], | |||||
], | |||||
], | |||||
]; | |||||
$this->declarativeManager->registerSchema($app, $schema); | |||||
// config->getUserValue() should be called with json encoded default value | |||||
$this->config->expects($this->once()) | |||||
->method('getUserValue') | |||||
->with($this->adminUser->getUID(), $app, 'test_field_json', json_encode($schema['fields'][0]['default'])) | |||||
->willReturn(json_encode($schema['fields'][0]['default'])); | |||||
$forms = $this->declarativeManager->getFormsWithValues($this->adminUser, $schema['section_type'], $schema['section_id']); | |||||
$this->assertNotEmpty($forms); | |||||
$this->assertTrue(array_search($schema['id'], array_column($forms, 'id')) !== false); | |||||
$testFieldJson = array_values(array_filter(array_filter($forms, fn ($form) => $form['id'] === $schema['id'])[0]['fields'], fn ($field) => $field['id'] === 'test_field_json'))[0]; | |||||
$this->assertEquals(json_encode($schema['fields'][0]['default']), $testFieldJson['value']); | |||||
} | |||||
/** | |||||
* Check that saving value for field with internal storage_type is handled by core | |||||
*/ | |||||
public function testSetInternalValue(): void { | |||||
$app = 'testing'; | |||||
$schema = self::validSchemaAllFields; | |||||
$this->declarativeManager->registerSchema($app, $schema); | |||||
self::$testSetInternalValueAfterChange = false; | |||||
$this->config->expects($this->any()) | |||||
->method('getAppValue') | |||||
->willReturnCallback(function ($app, $configkey, $default) { | |||||
if ($configkey === 'some_real_setting' && self::$testSetInternalValueAfterChange) { | |||||
return '120m'; | |||||
} | |||||
return $default; | |||||
}); | |||||
$this->appConfig->expects($this->once()) | |||||
->method('setValueString') | |||||
->with($app, 'some_real_setting', '120m'); | |||||
$forms = $this->declarativeManager->getFormsWithValues($this->adminUser, $schema['section_type'], $schema['section_id']); | |||||
$someRealSettingField = array_values(array_filter(array_filter($forms, fn ($form) => $form['id'] === $schema['id'])[0]['fields'], fn ($field) => $field['id'] === 'some_real_setting'))[0]; | |||||
$this->assertEquals('40m', $someRealSettingField['value']); // first check that default value (40m) is returned | |||||
// Set new value for some_real_setting field | |||||
$this->declarativeManager->setValue($this->adminUser, $app, $schema['id'], 'some_real_setting', '120m'); | |||||
self::$testSetInternalValueAfterChange = true; | |||||
$forms = $this->declarativeManager->getFormsWithValues($this->adminUser, $schema['section_type'], $schema['section_id']); | |||||
$this->assertNotEmpty($forms); | |||||
$this->assertTrue(array_search($schema['id'], array_column($forms, 'id')) !== false); | |||||
// Check some_real_setting field default value | |||||
$someRealSettingField = array_values(array_filter(array_filter($forms, fn ($form) => $form['id'] === $schema['id'])[0]['fields'], fn ($field) => $field['id'] === 'some_real_setting'))[0]; | |||||
$this->assertEquals('120m', $someRealSettingField['value']); | |||||
} | |||||
public function testSetExternalValue(): void { | |||||
$app = 'testing'; | |||||
$schema = self::validSchemaAllFields; | |||||
// Change storage_type to external and section_type to personal | |||||
$schema['storage_type'] = DeclarativeSettingsTypes::STORAGE_TYPE_EXTERNAL; | |||||
$schema['section_type'] = DeclarativeSettingsTypes::SECTION_TYPE_PERSONAL; | |||||
$this->declarativeManager->registerSchema($app, $schema); | |||||
$setDeclarativeSettingsValueEvent = new DeclarativeSettingsSetValueEvent( | |||||
$this->adminUser, | |||||
$app, | |||||
$schema['id'], | |||||
'some_real_setting', | |||||
'120m' | |||||
); | |||||
$this->eventDispatcher->expects($this->once()) | |||||
->method('dispatchTyped') | |||||
->with($setDeclarativeSettingsValueEvent); | |||||
$this->declarativeManager->setValue($this->adminUser, $app, $schema['id'], 'some_real_setting', '120m'); | |||||
} | |||||
public function testAdminFormUserUnauthorized(): void { | |||||
$app = 'testing'; | |||||
$schema = self::validSchemaAllFields; | |||||
$this->declarativeManager->registerSchema($app, $schema); | |||||
$this->expectException(\Exception::class); | |||||
$this->declarativeManager->getFormsWithValues($this->user, $schema['section_type'], $schema['section_id']); | |||||
} | |||||
} |
'vue-settings-personal-password': path.join(__dirname, 'apps/settings/src', 'main-personal-password.js'), | 'vue-settings-personal-password': path.join(__dirname, 'apps/settings/src', 'main-personal-password.js'), | ||||
'vue-settings-personal-security': path.join(__dirname, 'apps/settings/src', 'main-personal-security.js'), | 'vue-settings-personal-security': path.join(__dirname, 'apps/settings/src', 'main-personal-security.js'), | ||||
'vue-settings-personal-webauthn': path.join(__dirname, 'apps/settings/src', 'main-personal-webauth.js'), | 'vue-settings-personal-webauthn': path.join(__dirname, 'apps/settings/src', 'main-personal-webauth.js'), | ||||
'declarative-settings-forms': path.join(__dirname, 'apps/settings/src', 'main-declarative-settings-forms.ts'), | |||||
}, | }, | ||||
sharebymail: { | sharebymail: { | ||||
'vue-settings-admin-sharebymail': path.join(__dirname, 'apps/sharebymail/src', 'main-admin.js'), | 'vue-settings-admin-sharebymail': path.join(__dirname, 'apps/sharebymail/src', 'main-admin.js'), |