From 22163c60d42bbd60181818d315442f98fbbe89d0 Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Thu, 16 Nov 2023 21:53:14 +0100 Subject: [PATCH] enh(settings): Migrate admin settings for sharing to vue This is required to get the fixes for a11y from `@nextcloud/vue`. Signed-off-by: Ferdinand Thiessen --- apps/settings/lib/Settings/Admin/Sharing.php | 133 +++---- apps/settings/src/.jshintrc | 3 - apps/settings/src/admin-settings-sharing.ts | 30 ++ apps/settings/src/admin.js | 133 ------- .../components/AdminSettingsSharingForm.vue | 355 ++++++++++++++++++ .../components/SelectSharingPermissions.vue | 100 +++++ .../src/views/AdminSettingsSharing.vue | 62 +++ .../templates/settings/admin/sharing.php | 265 +------------ webpack.modules.js | 1 + 9 files changed, 603 insertions(+), 479 deletions(-) delete mode 100644 apps/settings/src/.jshintrc create mode 100644 apps/settings/src/admin-settings-sharing.ts create mode 100644 apps/settings/src/components/AdminSettingsSharingForm.vue create mode 100644 apps/settings/src/components/SelectSharingPermissions.vue create mode 100644 apps/settings/src/views/AdminSettingsSharing.vue diff --git a/apps/settings/lib/Settings/Admin/Sharing.php b/apps/settings/lib/Settings/Admin/Sharing.php index 32b09f333a9..f4d3a5c107b 100644 --- a/apps/settings/lib/Settings/Admin/Sharing.php +++ b/apps/settings/lib/Settings/Admin/Sharing.php @@ -13,8 +13,9 @@ * @author Sascha Wiswedel * @author Vincent Petry * @author Thomas Citharel + * @author Ferdinand Thiessen * - * @license GNU AGPL version 3 or any later version + * @license AGPL-3.0-or-later * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -34,31 +35,25 @@ namespace OCA\Settings\Settings\Admin; use OCP\App\IAppManager; use OCP\AppFramework\Http\TemplateResponse; +use OCP\AppFramework\Services\IInitialState; use OCP\Constants; use OCP\IConfig; use OCP\IL10N; +use OCP\IURLGenerator; use OCP\Settings\IDelegatedSettings; use OCP\Share\IManager; use OCP\Util; class Sharing implements IDelegatedSettings { - /** @var IConfig */ - private $config; - - /** @var IL10N */ - private $l; - - /** @var IManager */ - private $shareManager; - - /** @var IAppManager */ - private $appManager; - - public function __construct(IConfig $config, IL10N $l, IManager $shareManager, IAppManager $appManager) { - $this->config = $config; - $this->l = $l; - $this->shareManager = $shareManager; - $this->appManager = $appManager; + public function __construct( + private IConfig $config, + private IL10N $l, + private IManager $shareManager, + private IAppManager $appManager, + private IURLGenerator $urlGenerator, + private IInitialState $initialState, + private string $appName, + ) { } /** @@ -66,85 +61,57 @@ class Sharing implements IDelegatedSettings { */ public function getForm() { $excludedGroups = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', ''); - $excludeGroupsList = !is_null(json_decode($excludedGroups)) - ? implode('|', json_decode($excludedGroups, true)) : ''; $linksExcludedGroups = $this->config->getAppValue('core', 'shareapi_allow_links_exclude_groups', ''); - $linksExcludeGroupsList = !is_null(json_decode($linksExcludedGroups)) - ? implode('|', json_decode($linksExcludedGroups, true)) : ''; - $excludedPasswordGroups = $this->config->getAppValue('core', 'shareapi_enforce_links_password_excluded_groups', ''); - $excludedPasswordGroupsList = !is_null(json_decode($excludedPasswordGroups)) - ? implode('|', json_decode($excludedPasswordGroups, true)) : ''; - $parameters = [ // Built-In Sharing - 'sharingAppEnabled' => $this->appManager->isEnabledForUser('files_sharing'), - 'allowGroupSharing' => $this->config->getAppValue('core', 'shareapi_allow_group_sharing', 'yes'), - 'allowLinks' => $this->config->getAppValue('core', 'shareapi_allow_links', 'yes'), - 'allowLinksExcludeGroups' => $linksExcludeGroupsList, - 'allowPublicUpload' => $this->config->getAppValue('core', 'shareapi_allow_public_upload', 'yes'), - 'allowResharing' => $this->config->getAppValue('core', 'shareapi_allow_resharing', 'yes'), - 'allowShareDialogUserEnumeration' => $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes'), - 'restrictUserEnumerationToGroup' => $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no'), - 'restrictUserEnumerationToPhone' => $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no'), - 'restrictUserEnumerationFullMatch' => $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match', 'yes'), - 'restrictUserEnumerationFullMatchUserId' => $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_userid', 'yes'), - 'restrictUserEnumerationFullMatchEmail' => $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_email', 'yes'), - 'restrictUserEnumerationFullMatchIgnoreSecondDN' => $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_ignore_second_dn', 'no'), - 'enforceLinkPassword' => Util::isPublicLinkPasswordRequired(false), - 'passwordExcludedGroups' => $excludedPasswordGroupsList, + 'enabled' => $this->getHumanBooleanConfig('core', 'shareapi_enabled', true), + 'allowGroupSharing' => $this->getHumanBooleanConfig('core', 'shareapi_allow_group_sharing', true), + 'allowLinks' => $this->getHumanBooleanConfig('core', 'shareapi_allow_links', true), + 'allowLinksExcludeGroups' => json_decode($linksExcludedGroups, true) ?? [], + 'allowPublicUpload' => $this->getHumanBooleanConfig('core', 'shareapi_allow_public_upload', true), + 'allowResharing' => $this->getHumanBooleanConfig('core', 'shareapi_allow_resharing', true), + 'allowShareDialogUserEnumeration' => $this->getHumanBooleanConfig('core', 'shareapi_allow_share_dialog_user_enumeration', true), + 'restrictUserEnumerationToGroup' => $this->getHumanBooleanConfig('core', 'shareapi_restrict_user_enumeration_to_group'), + 'restrictUserEnumerationToPhone' => $this->getHumanBooleanConfig('core', 'shareapi_restrict_user_enumeration_to_phone'), + 'restrictUserEnumerationFullMatch' => $this->getHumanBooleanConfig('core', 'shareapi_restrict_user_enumeration_full_match', true), + 'restrictUserEnumerationFullMatchUserId' => $this->getHumanBooleanConfig('core', 'shareapi_restrict_user_enumeration_full_match_userid', true), + 'restrictUserEnumerationFullMatchEmail' => $this->getHumanBooleanConfig('core', 'shareapi_restrict_user_enumeration_full_match_email', true), + 'restrictUserEnumerationFullMatchIgnoreSecondDN' => $this->getHumanBooleanConfig('core', 'shareapi_restrict_user_enumeration_full_match_ignore_second_dn'), + 'enforceLinksPassword' => Util::isPublicLinkPasswordRequired(false), + 'passwordExcludedGroups' => json_decode($excludedPasswordGroups) ?? [], 'passwordExcludedGroupsFeatureEnabled' => $this->config->getSystemValueBool('sharing.allow_disabled_password_enforcement_groups', false), 'onlyShareWithGroupMembers' => $this->shareManager->shareWithGroupMembersOnly(), - 'shareAPIEnabled' => $this->config->getAppValue('core', 'shareapi_enabled', 'yes'), - 'shareDefaultExpireDateSet' => $this->config->getAppValue('core', 'shareapi_default_expire_date', 'no'), - 'shareExpireAfterNDays' => $this->config->getAppValue('core', 'shareapi_expire_after_n_days', '7'), - 'shareEnforceExpireDate' => $this->config->getAppValue('core', 'shareapi_enforce_expire_date', 'no'), - 'shareExcludeGroups' => $this->config->getAppValue('core', 'shareapi_exclude_groups', 'no') === 'yes', - 'shareExcludedGroupsList' => $excludeGroupsList, + 'defaultExpireDate' => $this->getHumanBooleanConfig('core', 'shareapi_default_expire_date'), + 'expireAfterNDays' => $this->config->getAppValue('core', 'shareapi_expire_after_n_days', '7'), + 'enforceExpireDate' => $this->getHumanBooleanConfig('core', 'shareapi_enforce_expire_date'), + 'excludeGroups' => $this->getHumanBooleanConfig('core', 'shareapi_exclude_groups'), + 'excludeGroupsList' => json_decode($excludedGroups, true) ?? [], 'publicShareDisclaimerText' => $this->config->getAppValue('core', 'shareapi_public_link_disclaimertext', null), - 'enableLinkPasswordByDefault' => $this->config->getAppValue('core', 'shareapi_enable_link_password_by_default', 'no'), - 'shareApiDefaultPermissions' => (int)$this->config->getAppValue('core', 'shareapi_default_permissions', (string)Constants::PERMISSION_ALL), - 'shareApiDefaultPermissionsCheckboxes' => $this->getSharePermissionList(), - 'shareDefaultInternalExpireDateSet' => $this->config->getAppValue('core', 'shareapi_default_internal_expire_date', 'no'), - 'shareInternalExpireAfterNDays' => $this->config->getAppValue('core', 'shareapi_internal_expire_after_n_days', '7'), - 'shareInternalEnforceExpireDate' => $this->config->getAppValue('core', 'shareapi_enforce_internal_expire_date', 'no'), - 'shareDefaultRemoteExpireDateSet' => $this->config->getAppValue('core', 'shareapi_default_remote_expire_date', 'no'), - 'shareRemoteExpireAfterNDays' => $this->config->getAppValue('core', 'shareapi_remote_expire_after_n_days', '7'), - 'shareRemoteEnforceExpireDate' => $this->config->getAppValue('core', 'shareapi_enforce_remote_expire_date', 'no'), + 'enableLinkPasswordByDefault' => $this->getHumanBooleanConfig('core', 'shareapi_enable_link_password_by_default'), + 'defaultPermissions' => (int)$this->config->getAppValue('core', 'shareapi_default_permissions', (string)Constants::PERMISSION_ALL), + 'defaultInternalExpireDate' => $this->getHumanBooleanConfig('core', 'shareapi_default_internal_expire_date'), + 'internalExpireAfterNDays' => $this->config->getAppValue('core', 'shareapi_internal_expire_after_n_days', '7'), + 'enforceInternalExpireDate' => $this->getHumanBooleanConfig('core', 'shareapi_enforce_internal_expire_date'), + 'defaultRemoteExpireDate' => $this->getHumanBooleanConfig('core', 'shareapi_default_remote_expire_date'), + 'remoteExpireAfterNDays' => $this->config->getAppValue('core', 'shareapi_remote_expire_after_n_days', '7'), + 'enforceRemoteExpireDate' => $this->getHumanBooleanConfig('core', 'shareapi_enforce_remote_expire_date'), ]; - return new TemplateResponse('settings', 'settings/admin/sharing', $parameters, ''); + $this->initialState->provideInitialState('sharingAppEnabled', $this->appManager->isEnabledForUser('files_sharing')); + $this->initialState->provideInitialState('sharingDocumentation', $this->urlGenerator->linkToDocs('admin-sharing')); + $this->initialState->provideInitialState('sharingSettings', $parameters); + + \OCP\Util::addScript($this->appName, 'vue-settings-admin-sharing'); + return new TemplateResponse($this->appName, 'settings/admin/sharing', [], ''); } /** - * get share permission list for template - * - * @return array + * Helper function to retrive boolean values from human readable strings ('yes' / 'no') */ - private function getSharePermissionList() { - return [ - [ - 'id' => 'cancreate', - 'label' => $this->l->t('Create'), - 'value' => Constants::PERMISSION_CREATE - ], - [ - 'id' => 'canupdate', - 'label' => $this->l->t('Change'), - 'value' => Constants::PERMISSION_UPDATE - ], - [ - 'id' => 'candelete', - 'label' => $this->l->t('Delete'), - 'value' => Constants::PERMISSION_DELETE - ], - [ - 'id' => 'canshare', - 'label' => $this->l->t('Reshare'), - 'value' => Constants::PERMISSION_SHARE - ], - ]; + private function getHumanBooleanConfig(string $app, string $key, bool $default = false): bool { + return $this->config->getAppValue($app, $key, $default ? 'yes' : 'no') === 'yes'; } /** diff --git a/apps/settings/src/.jshintrc b/apps/settings/src/.jshintrc deleted file mode 100644 index fc024bea970..00000000000 --- a/apps/settings/src/.jshintrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "esversion": 6 -} diff --git a/apps/settings/src/admin-settings-sharing.ts b/apps/settings/src/admin-settings-sharing.ts new file mode 100644 index 00000000000..2cb269f9a5d --- /dev/null +++ b/apps/settings/src/admin-settings-sharing.ts @@ -0,0 +1,30 @@ +/** + * @copyright Copyright (c) 2023 Ferdinand Thiessen + * + * @author Ferdinand Thiessen + * + * @license AGPL-3.0-or-later + * + * 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 . + * + */ + +import Vue from 'vue' +import AdminSettingsSharing from './views/AdminSettingsSharing.vue' + +export default new Vue({ + name: 'AdminSettingsSharingSection', + el: '#vue-admin-settings-sharing', + render: (h) => h(AdminSettingsSharing), +}) diff --git a/apps/settings/src/admin.js b/apps/settings/src/admin.js index c8d04049ded..35f5266acba 100644 --- a/apps/settings/src/admin.js +++ b/apps/settings/src/admin.js @@ -1,133 +1,10 @@ window.addEventListener('DOMContentLoaded', () => { - $('#linksExcludedGroups,#passwordsExcludedGroups').each(function(index, element) { - OC.Settings.setupGroupsSelect($(element)) - $(element).change(function(ev) { - let groups = ev.val || [] - groups = JSON.stringify(groups) - OCP.AppConfig.setValue('core', $(this).attr('name'), groups) - }) - }) - $('#loglevel').change(function() { $.post(OC.generateUrl('/settings/admin/log/level'), { level: $(this).val() }, () => { OC.Log.reload() }) }) - $('#shareAPIEnabled').change(function() { - $('#shareAPI p:not(#enable)').toggleClass('hidden', !this.checked) - }) - - $('#shareapiExpireAfterNDays').on('input', function() { - this.value = this.value.replace(/\D/g, '') - }) - - $('#shareAPI input:not(.noJSAutoUpdate)').change(function() { - let value = $(this).val() - if ($(this).attr('type') === 'checkbox') { - if (this.checked) { - value = 'yes' - } else { - value = 'no' - } - } - OCP.AppConfig.setValue('core', $(this).attr('name'), value) - }) - - $('#shareapiDefaultExpireDate').change(function() { - $('#setDefaultExpireDate').toggleClass('hidden', !this.checked) - }) - - $('#shareapiDefaultInternalExpireDate').change(function() { - $('#setDefaultInternalExpireDate').toggleClass('hidden', !this.checked) - }) - - $('#shareapiDefaultRemoteExpireDate').change(function() { - $('#setDefaultRemoteExpireDate').toggleClass('hidden', !this.checked) - }) - - $('#enableLinkPasswordByDefault').change(function() { - if (this.checked) { - $('#enforceLinkPassword').removeAttr('disabled') - $('#passwordsExcludedGroups').removeAttr('disabled') - } else { - $('#enforceLinkPassword').attr('disabled', '') - $('#passwordsExcludedGroups').attr('disabled', '') - - // Uncheck "Enforce password protection" when "Always asks for a - // password" is unchecked; the change event needs to be explicitly - // triggered so it behaves like a change done by the user. - $('#enforceLinkPassword').removeAttr('checked').trigger('change') - } - }) - - $('#enforceLinkPassword').change(function() { - $('#selectPasswordsExcludedGroups').toggleClass('hidden', !this.checked) - }) - - $('#publicShareDisclaimer').change(function() { - $('#publicShareDisclaimerText').toggleClass('hidden', !this.checked) - if (!this.checked) { - savePublicShareDisclaimerText('') - } - }) - - $('#shareApiDefaultPermissionsSection input').change(function(ev) { - const $el = $('#shareApiDefaultPermissions') - const $target = $(ev.target) - - let value = $el.val() - if ($target.is(':checked')) { - value = value | $target.val() - } else { - value = value & ~$target.val() - } - - // always set read permission - value |= OC.PERMISSION_READ - - // this will trigger the field's change event and will save it - $el.val(value).change() - - ev.preventDefault() - - return false - }) - - const savePublicShareDisclaimerText = _.debounce(function(value) { - const options = { - success: () => { - OC.msg.finishedSuccess('#publicShareDisclaimerStatus', t('settings', 'Saved')) - }, - error: () => { - OC.msg.finishedError('#publicShareDisclaimerStatus', t('settings', 'Not saved')) - }, - } - - OC.msg.startSaving('#publicShareDisclaimerStatus') - if (_.isString(value) && value !== '') { - OCP.AppConfig.setValue('core', 'shareapi_public_link_disclaimertext', value, options) - } else { - $('#publicShareDisclaimerText').val('') - OCP.AppConfig.deleteKey('core', 'shareapi_public_link_disclaimertext', options) - } - }, 500) - - $('#publicShareDisclaimerText').on('change, keyup', function() { - savePublicShareDisclaimerText(this.value) - }) - - $('#shareapi_allow_share_dialog_user_enumeration').on('change', function() { - $('#shareapi_restrict_user_enumeration_to_group_setting').toggleClass('hidden', !this.checked) - $('#shareapi_restrict_user_enumeration_to_phone_setting').toggleClass('hidden', !this.checked) - $('#shareapi_restrict_user_enumeration_combinewarning_setting').toggleClass('hidden', !this.checked) - }) - - $('#allowLinks').change(function() { - $('#publicLinkSettings').toggleClass('hidden', !this.checked) - $('#setDefaultExpireDate').toggleClass('hidden', !(this.checked && $('#shareapiDefaultExpireDate')[0].checked)) - }) - $('#mail_smtpauth').change(function() { if (!this.checked) { $('#mail_credentials').addClass('hidden') @@ -221,14 +98,6 @@ window.addEventListener('DOMContentLoaded', () => { }) }) - $('#allowGroupSharing').change(function() { - $('#allowGroupSharing').toggleClass('hidden', !this.checked) - }) - - $('#shareapiExcludeGroups').change(function() { - $('#selectExcludedGroups').toggleClass('hidden', !this.checked) - }) - const setupChecks = () => { // run setup checks then gather error messages $.when( @@ -301,6 +170,4 @@ window.addEventListener('DOMContentLoaded', () => { if (document.getElementById('security-warning') !== null) { setupChecks() } - - $('#shareAPI').removeClass('loading') }) diff --git a/apps/settings/src/components/AdminSettingsSharingForm.vue b/apps/settings/src/components/AdminSettingsSharingForm.vue new file mode 100644 index 00000000000..de23adf67d2 --- /dev/null +++ b/apps/settings/src/components/AdminSettingsSharingForm.vue @@ -0,0 +1,355 @@ + + + + + + diff --git a/apps/settings/src/components/SelectSharingPermissions.vue b/apps/settings/src/components/SelectSharingPermissions.vue new file mode 100644 index 00000000000..278b7b623df --- /dev/null +++ b/apps/settings/src/components/SelectSharingPermissions.vue @@ -0,0 +1,100 @@ + + + + + + diff --git a/apps/settings/src/views/AdminSettingsSharing.vue b/apps/settings/src/views/AdminSettingsSharing.vue new file mode 100644 index 00000000000..c0846969a11 --- /dev/null +++ b/apps/settings/src/views/AdminSettingsSharing.vue @@ -0,0 +1,62 @@ + + + + diff --git a/apps/settings/templates/settings/admin/sharing.php b/apps/settings/templates/settings/admin/sharing.php index 2918a764d89..4ddc9a8ada0 100644 --- a/apps/settings/templates/settings/admin/sharing.php +++ b/apps/settings/templates/settings/admin/sharing.php @@ -1,11 +1,10 @@ + * @copyright Copyright (c) 2023 Ferdinand Thiessen * - * @author Arthur Schiwon - * @author Thomas Citharel + * @author Ferdinand Thiessen * - * @license GNU AGPL version 3 or any later version + * @license AGPL-3.0-or-later * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -18,263 +17,9 @@ * 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 . + * along with this program. If not, see . * */ - -/** @var \OCP\IL10N $l */ -/** @var array $_ */ - ?> -
-

t('Sharing'));?>

- -

t('You need to enable the File sharing App.')); ?>

- - -
-

t('As admin you can fine-tune the sharing behavior. Please see the documentation for more information.'));?>

-

- /> -
-

- -

- /> -
-

-

- t('Expire after') . ' '); ?> - ' /> - t('day(s)')); ?> - /> -
-

- -

- /> -
-

-

- t('Expire after'). ' '); ?> - ' /> - t('day(s)')); ?> - /> -
-

- -

- /> -
-

- -

- /> -
- /> -
- /> -
- - - -
- /> -

- - - /> -
- -

-

- t('Expire after'). ' '); ?> - ' /> - t('day(s)')); ?> - /> -
-

-

-

- t('Exclude groups from creating link shares:'));?> -

-

- -

- /> -
-

-

- /> -
-

-

- /> -
-

-

- /> -
-

-

- -
- t('These groups will still be able to receive shares, but not to initiate them.')); ?> -

- -

- /> -
-

- -

- /> -
-

- -

- /> -
-

-

- t('If autocompletion "same group" and "phone number integration" are enabled a match in either is enough to show the user.'));?>
-

-

- /> -
-

- -

- /> - - -
- -

- -

t('Default share permissions'));?>

- -

- - /> - - -

-
- -
+
\ No newline at end of file diff --git a/webpack.modules.js b/webpack.modules.js index d749f13b81f..677d0b17aaa 100644 --- a/webpack.modules.js +++ b/webpack.modules.js @@ -91,6 +91,7 @@ module.exports = { 'vue-settings-admin-ai': path.join(__dirname, 'apps/settings/src', 'main-admin-ai.js'), 'vue-settings-admin-delegation': path.join(__dirname, 'apps/settings/src', 'main-admin-delegation.js'), 'vue-settings-admin-security': path.join(__dirname, 'apps/settings/src', 'main-admin-security.js'), + 'vue-settings-admin-sharing': path.join(__dirname, 'apps/settings/src', 'admin-settings-sharing.ts'), 'vue-settings-apps-users-management': path.join(__dirname, 'apps/settings/src', 'main-apps-users-management.js'), 'vue-settings-nextcloud-pdf': path.join(__dirname, 'apps/settings/src', 'main-nextcloud-pdf.js'), 'vue-settings-personal-info': path.join(__dirname, 'apps/settings/src', 'main-personal-info.js'),