From 58c1e893c1c4c9ef538399f9a146447891e2cd33 Mon Sep 17 00:00:00 2001 From: Christopher Ng Date: Tue, 8 Jun 2021 14:31:25 +0000 Subject: [PATCH] [WIP] Vuetiful emails Signed-off-by: Christopher Ng --- .../lib/Controller/UsersController.php | 15 ++- apps/settings/js/federationsettingsview.js | 2 +- .../lib/Settings/Personal/PersonalInfo.php | 26 ++++- .../components/PersonalInfo/EmailSection.vue | 110 ++++++++++++++++++ apps/settings/src/main-personal-info.js | 39 +++++++ apps/settings/src/mixins/ErrorHandler.js | 31 +++++ .../settings/personal/personal.info.php | 4 + apps/settings/webpack.js | 1 + lib/private/Accounts/AccountManager.php | 19 ++- lib/private/Accounts/TAccountsHelper.php | 4 +- lib/public/Accounts/IAccountManager.php | 4 + 11 files changed, 244 insertions(+), 11 deletions(-) create mode 100644 apps/settings/src/components/PersonalInfo/EmailSection.vue create mode 100644 apps/settings/src/main-personal-info.js create mode 100644 apps/settings/src/mixins/ErrorHandler.js diff --git a/apps/provisioning_api/lib/Controller/UsersController.php b/apps/provisioning_api/lib/Controller/UsersController.php index 256077e9ae99b..777d7a43d7531 100644 --- a/apps/provisioning_api/lib/Controller/UsersController.php +++ b/apps/provisioning_api/lib/Controller/UsersController.php @@ -740,12 +740,19 @@ public function editUser(string $userId, string $key, string $value): DataRespon $this->config->setUserValue($targetUser->getUID(), 'core', 'locale', $value); break; case IAccountManager::PROPERTY_EMAIL: - if (filter_var($value, FILTER_VALIDATE_EMAIL) || $value === '') { - $targetUser->setEMailAddress($value); - } else { - throw new OCSException('', 102); + $userAccount = $this->accountManager->getAccount($targetUser); + $userProperty = $userAccount->getProperty($key); + if ($userProperty->getValue() !== $value) { + try { + $userProperty->setValue($value); + $this->accountManager->updateAccount($userAccount); + } catch (\InvalidArgumentException $e) { + throw new OCSException('Invalid ' . $e->getMessage(), 102); + } } break; + case IAccountManager::COLLECTION_EMAIL: + break; case IAccountManager::PROPERTY_PHONE: case IAccountManager::PROPERTY_ADDRESS: case IAccountManager::PROPERTY_WEBSITE: diff --git a/apps/settings/js/federationsettingsview.js b/apps/settings/js/federationsettingsview.js index cf2865be1b0a1..b8d2b406fbad7 100644 --- a/apps/settings/js/federationsettingsview.js +++ b/apps/settings/js/federationsettingsview.js @@ -36,7 +36,7 @@ this._inputFields = [ 'displayname', 'phone', - 'email', + // 'email', 'website', 'twitter', 'address', diff --git a/apps/settings/lib/Settings/Personal/PersonalInfo.php b/apps/settings/lib/Settings/Personal/PersonalInfo.php index 94ff867dabac4..098a7282657a2 100644 --- a/apps/settings/lib/Settings/Personal/PersonalInfo.php +++ b/apps/settings/lib/Settings/Personal/PersonalInfo.php @@ -48,6 +48,8 @@ use OCP\IUserManager; use OCP\L10N\IFactory; use OCP\Settings\ISettings; +use OCP\Accounts\IAccountProperty; +use OCP\AppFramework\Services\IInitialState; class PersonalInfo implements ISettings { @@ -65,6 +67,8 @@ class PersonalInfo implements ISettings { private $l10nFactory; /** @var IL10N */ private $l; + /** @var IInitialState */ + private $initialStateService; public function __construct( IConfig $config, @@ -73,7 +77,8 @@ public function __construct( IAccountManager $accountManager, IAppManager $appManager, IFactory $l10nFactory, - IL10N $l + IL10N $l, + IInitialState $initialStateService ) { $this->config = $config; $this->userManager = $userManager; @@ -82,6 +87,7 @@ public function __construct( $this->appManager = $appManager; $this->l10nFactory = $l10nFactory; $this->l = $l; + $this->initialStateService = $initialStateService; } public function getForm(): TemplateResponse { @@ -138,6 +144,24 @@ public function getForm(): TemplateResponse { 'groups' => $this->getGroups($user), ] + $messageParameters + $languageParameters + $localeParameters; + $this->initialStateService->provideInitialState( + 'emails', + [ + [ + 'value' => $account->getProperty(IAccountManager::PROPERTY_EMAIL)->getValue(), + 'scope' => $account->getProperty(IAccountManager::PROPERTY_EMAIL)->getScope(), + 'verified' => $account->getProperty(IAccountManager::PROPERTY_EMAIL)->getVerified(), + ], + ...array_map( + fn(IAccountProperty $property) => [ + 'value' => $property->getValue(), + 'scope' => $property->getScope(), + 'verified' => $property->getVerified(), + ], + $account->getPropertyCollection(IAccountManager::COLLECTION_EMAIL)->getProperties() + ) + ] + ); return new TemplateResponse('settings', 'settings/personal/personal.info', $parameters, ''); } diff --git a/apps/settings/src/components/PersonalInfo/EmailSection.vue b/apps/settings/src/components/PersonalInfo/EmailSection.vue new file mode 100644 index 0000000000000..b57461e8758eb --- /dev/null +++ b/apps/settings/src/components/PersonalInfo/EmailSection.vue @@ -0,0 +1,110 @@ + + + + + + + diff --git a/apps/settings/src/main-personal-info.js b/apps/settings/src/main-personal-info.js new file mode 100644 index 0000000000000..c8e37056d9df4 --- /dev/null +++ b/apps/settings/src/main-personal-info.js @@ -0,0 +1,39 @@ +/** + * @copyright 2021, Christopher Ng + * + * @author Christopher Ng + * + * @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 . + * + */ + +import Vue from 'vue' +import { loadState } from '@nextcloud/initial-state' + +import EmailSection from './components/PersonalInfo/EmailSection' + +// eslint-disable-next-line camelcase +__webpack_nonce__ = btoa(OC.requestToken) + +Vue.prototype.t = t + +const View = Vue.extend(EmailSection) +new View({ + propsData: { + initialEmails: loadState('settings', 'emails'), + // ...more initial props + }, +}).$mount('#vue-emailform') diff --git a/apps/settings/src/mixins/ErrorHandler.js b/apps/settings/src/mixins/ErrorHandler.js new file mode 100644 index 0000000000000..80b853d017f59 --- /dev/null +++ b/apps/settings/src/mixins/ErrorHandler.js @@ -0,0 +1,31 @@ +/** + * @copyright Copyright (c) 2021 Christopher Ng + * + * @author Christopher Ng + * + * @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 . + * + */ + +export default { + methods: { + handleError(promise) { + promise + .then(response => response) + .catch(error => ({ data: null, error })) + } + } +} diff --git a/apps/settings/templates/settings/personal/personal.info.php b/apps/settings/templates/settings/personal/personal.info.php index 6f8516e6437f9..8c6d37890bc4d 100644 --- a/apps/settings/templates/settings/personal/personal.info.php +++ b/apps/settings/templates/settings/personal/personal.info.php @@ -31,6 +31,7 @@ 'federationsettingsview', 'federationscopemenu', 'settings/personalInfo', + 'vue-settings-personal-info', ]); ?> @@ -125,6 +126,9 @@ +
+
+

diff --git a/apps/settings/webpack.js b/apps/settings/webpack.js index 756a748ae1b4b..12ac464fed740 100644 --- a/apps/settings/webpack.js +++ b/apps/settings/webpack.js @@ -32,6 +32,7 @@ module.exports = { 'settings-personal-security': path.join(__dirname, 'src', 'main-personal-security'), 'settings-personal-webauthn': path.join(__dirname, 'src', 'main-personal-webauth'), 'settings-nextcloud-pdf': path.join(__dirname, 'src', 'main-nextcloud-pdf'), + 'settings-personal-info': path.join(__dirname, 'src', 'main-personal-info'), }, output: { path: path.resolve(__dirname, './js'), diff --git a/lib/private/Accounts/AccountManager.php b/lib/private/Accounts/AccountManager.php index 4d75c94346b42..6440d18d6271a 100644 --- a/lib/private/Accounts/AccountManager.php +++ b/lib/private/Accounts/AccountManager.php @@ -405,6 +405,12 @@ protected function addMissingDefaultValues(array $userData) { } } + foreach ($this::COLLECTION_PROPERTIES as $property) { + if (!isset($userData[$property])) { + $userData[$property] = new AccountPropertyCollection($property); + } + } + return $userData; } @@ -566,6 +572,11 @@ protected function buildDefaultUserRecord(IUser $user) { 'scope' => self::SCOPE_FEDERATED, 'verified' => self::NOT_VERIFIED, ], + self::COLLECTION_EMAIL => + [ + // TODO implement the correct way + 'properties' => [], + ], self::PROPERTY_AVATAR => [ 'scope' => self::SCOPE_FEDERATED @@ -588,7 +599,11 @@ protected function buildDefaultUserRecord(IUser $user) { private function parseAccountData(IUser $user, $data): Account { $account = new Account($user); foreach ($data as $property => $accountData) { - $account->setProperty($property, $accountData['value'] ?? '', $accountData['scope'] ?? self::SCOPE_LOCAL, $accountData['verified'] ?? self::NOT_VERIFIED); + if (!$this->isCollection($property)) { + $account->setProperty($property, $accountData['value'] ?? '', $accountData['scope'] ?? self::SCOPE_LOCAL, $accountData['verified'] ?? self::NOT_VERIFIED); + } else { + $account->setPropertyCollection(new AccountPropertyCollection($property)); + } } return $account; } @@ -600,7 +615,7 @@ public function getAccount(IUser $user): IAccount { public function updateAccount(IAccount $account): void { $data = []; - foreach ($account->getProperties() as $property) { + foreach ($account->getAllProperties() as $property) { $data[$property->getName()] = [ 'value' => $property->getValue(), 'scope' => $property->getScope(), diff --git a/lib/private/Accounts/TAccountsHelper.php b/lib/private/Accounts/TAccountsHelper.php index 530204b451fb7..c6d9b9b0a5a59 100644 --- a/lib/private/Accounts/TAccountsHelper.php +++ b/lib/private/Accounts/TAccountsHelper.php @@ -31,9 +31,7 @@ trait TAccountsHelper { protected function isCollection(string $propertyName): bool { return in_array($propertyName, - [ - IAccountManager::COLLECTION_EMAIL, - ], + IAccountManager::COLLECTION_PROPERTIES, true ); } diff --git a/lib/public/Accounts/IAccountManager.php b/lib/public/Accounts/IAccountManager.php index 9418e07ec972f..b497cf5b054a1 100644 --- a/lib/public/Accounts/IAccountManager.php +++ b/lib/public/Accounts/IAccountManager.php @@ -98,6 +98,10 @@ interface IAccountManager { public const COLLECTION_EMAIL = 'additional_mail'; + public const COLLECTION_PROPERTIES = [ + self::COLLECTION_EMAIL, + ]; + public const NOT_VERIFIED = '0'; public const VERIFICATION_IN_PROGRESS = '1'; public const VERIFIED = '2';