From 7733438a03f6b55f9e6c37c6914bf03d4efbaa93 Mon Sep 17 00:00:00 2001
From: Hamza Mahjoubi
Date: Fri, 17 Jan 2025 17:56:01 +0700
Subject: [PATCH 1/4] feat: default contact
Signed-off-by: Hamza Mahjoubi
---
appinfo/routes.php | 2 +
lib/AppInfo/Application.php | 1 +
lib/Controller/DefaultContactController.php | 62 +++++++++++++
lib/Controller/PageController.php | 3 +
src/components/AdminSettings.vue | 96 ++++++++++++++++++++-
src/services/defaultContactService.js | 14 +++
6 files changed, 174 insertions(+), 4 deletions(-)
create mode 100644 lib/Controller/DefaultContactController.php
create mode 100644 src/services/defaultContactService.js
diff --git a/appinfo/routes.php b/appinfo/routes.php
index 3ead33df2..cf2106550 100644
--- a/appinfo/routes.php
+++ b/appinfo/routes.php
@@ -15,5 +15,7 @@
['name' => 'social_api#set_app_config', 'url' => '/api/v1/social/config/global/{key}', 'verb' => 'PUT'],
['name' => 'social_api#set_user_config', 'url' => '/api/v1/social/config/user/{key}', 'verb' => 'PUT'],
['name' => 'social_api#get_user_config', 'url' => '/api/v1/social/config/user/{key}', 'verb' => 'GET'],
+ ['name' => 'default_contact#set_app_config', 'url' => '/api/defaultcontact/config', 'verb' => 'PUT'],
+ ['name' => 'default_contact#set_default_contact', 'url' => '/api/defaultcontact/contact', 'verb' => 'PUT'],
]
];
diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php
index aebba7b70..4934626a5 100644
--- a/lib/AppInfo/Application.php
+++ b/lib/AppInfo/Application.php
@@ -22,6 +22,7 @@ class Application extends App implements IBootstrap {
public const AVAIL_SETTINGS = [
'allowSocialSync' => 'yes',
+ 'enableDefaultContact' => 'yes',
];
public function __construct() {
diff --git a/lib/Controller/DefaultContactController.php b/lib/Controller/DefaultContactController.php
new file mode 100644
index 000000000..da6ab5229
--- /dev/null
+++ b/lib/Controller/DefaultContactController.php
@@ -0,0 +1,62 @@
+config->setAppValue(Application::APP_ID, $key, $allow);
+ return new JSONResponse([], Http::STATUS_OK);
+ }
+
+ public function setDefaultContact($contactData){
+ if(!$this->config->getAppValue(Application::APP_ID, 'enableDefaultContact', 'yes')){
+ return new JSONResponse([], Http::STATUS_FORBIDDEN);
+ }
+ try{
+ $folder = $this->appData->getFolder('defaultContact');
+ }
+ catch(NotFoundException $e){
+ $folder = $this->appData->newFolder('defaultContact');
+ }
+ if(!$folder->fileExists('defaultContact.vcf')){
+ $file = $folder->newFile('defaultContact.vcf');
+ }
+ else {
+ $file = $folder->getFile('defaultContact.vcf');
+ }
+ $file->putContent($contactData);
+ return new JSONResponse([], Http::STATUS_OK);
+ }
+
+}
\ No newline at end of file
diff --git a/lib/Controller/PageController.php b/lib/Controller/PageController.php
index efbf03704..751a200f5 100644
--- a/lib/Controller/PageController.php
+++ b/lib/Controller/PageController.php
@@ -52,6 +52,8 @@ public function index(): TemplateResponse {
$supportedNetworks = $this->socialApiService->getSupportedNetworks();
// allow users to retrieve avatars from social networks (default: yes)
$syncAllowedByAdmin = $this->config->getAppValue(Application::APP_ID, 'allowSocialSync', 'yes');
+ // add a default contact to the user's address book on first login (default: yes)
+ $enableDefaultContact = $this->config->getAppValue(Application::APP_ID, 'enableDefaultContact', 'yes');
// automated background syncs for social avatars (default: no)
$bgSyncEnabledByUser = $this->config->getUserValue($userId, Application::APP_ID, 'enableSocialSync', 'no');
@@ -72,6 +74,7 @@ public function index(): TemplateResponse {
$this->initialStateService->provideInitialState(Application::APP_ID, 'defaultProfile', $defaultProfile);
$this->initialStateService->provideInitialState(Application::APP_ID, 'supportedNetworks', $supportedNetworks);
$this->initialStateService->provideInitialState(Application::APP_ID, 'allowSocialSync', $syncAllowedByAdmin);
+ $this->initialStateService->provideInitialState(Application::APP_ID, 'enableDefaultContact', $enableDefaultContact);
$this->initialStateService->provideInitialState(Application::APP_ID, 'enableSocialSync', $bgSyncEnabledByUser);
$this->initialStateService->provideInitialState(Application::APP_ID, 'isContactsInteractionEnabled', $isContactsInteractionEnabled);
$this->initialStateService->provideInitialState(Application::APP_ID, 'isCirclesEnabled', $isCirclesEnabled && $isCircleVersionCompatible);
diff --git a/src/components/AdminSettings.vue b/src/components/AdminSettings.vue
index 6975a8a53..a6eb83fda 100644
--- a/src/components/AdminSettings.vue
+++ b/src/components/AdminSettings.vue
@@ -11,9 +11,46 @@
v-model="allowSocialSync"
type="checkbox"
class="checkbox"
- @change="updateSetting('allowSocialSync')">
+ @change="updateAllowSocialSync">
+
+
+
+
+
+
+
+ {{ t('contacts', 'Import contact') }}
+
+
+
+
+
@@ -21,19 +58,70 @@
import axios from '@nextcloud/axios'
import { generateUrl } from '@nextcloud/router'
import { loadState } from '@nextcloud/initial-state'
+import { NcDialog, NcButton } from '@nextcloud/vue'
+import Contact from '../models/contact.js'
+import validate from '../services/validate.js'
+import { showError, showSuccess } from '@nextcloud/dialogs'
+import IconUpload from 'vue-material-design-icons/Upload.vue'
+
export default {
name: 'AdminSettings',
+ components: {
+ NcDialog,
+ NcButton,
+ IconUpload,
+ },
data() {
return {
allowSocialSync: loadState('contacts', 'allowSocialSync') === 'yes',
+ enableDefaultContact: loadState('contacts', 'enableDefaultContact') === 'yes',
+ isModalOpen: false,
+ loading: false,
}
},
methods: {
- updateSetting(setting) {
- axios.put(generateUrl('apps/contacts/api/v1/social/config/global/' + setting), {
- allow: this[setting] ? 'yes' : 'no',
+ updateAllowSocialSync() {
+ axios.put(generateUrl('apps/contacts/api/v1/social/config/global/allowSocialSync'), {
+ allow: this.allowSocialSync ? 'yes' : 'no',
+ })
+ },
+ updateEnableDefaultContact() {
+ axios.put(generateUrl('apps/contacts/api/defaultcontact/config'), {
+ allow: this.enableDefaultContact ? 'yes' : 'no',
})
},
+ toggleModal() {
+ this.isModalOpen = !this.isModalOpen
+ },
+ clickImportInput() {
+ this.$refs['contact-import-input'].click()
+ },
+
+ /**
+ * Process input type file change
+ *
+ * @param {Event} event the input change event
+ */
+ processFile(event) {
+ this.loading = true
+
+ const file = event.target.files[0]
+ const reader = new FileReader()
+
+ reader.onload = () => {
+ this.isModalOpen = false
+ const contact = new Contact(reader.result)
+ /* if (!validate(contact)) {
+ showError(t('contacts', 'Invalid VCF file'))
+ event.target.value = ''
+ return
+ } */
+ axios.put(generateUrl('/apps/contacts/api/defaultcontact/contact'), { contactData: reader.result })
+ event.target.value = ''
+ }
+ reader.readAsText(file)
+ showSuccess(this.t('contacts', 'Contact imported successfully'))
+ },
},
}
diff --git a/src/services/defaultContactService.js b/src/services/defaultContactService.js
new file mode 100644
index 000000000..938386eb6
--- /dev/null
+++ b/src/services/defaultContactService.js
@@ -0,0 +1,14 @@
+/**
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import axios from '@nextcloud/axios'
+import { generateOcsUrl } from '@nextcloud/router'
+
+export const setDefaultContact = async function(contactId) {
+ const request = await axios.post(generateOcsUrl('apps/contacts/api/v1/default'), {
+ contactId,
+ })
+ return request.data
+}
From fa9ad1d349d0aca88a31f3f33141b07fa7f0dc94 Mon Sep 17 00:00:00 2001
From: Hamza Mahjoubi
Date: Mon, 20 Jan 2025 22:38:42 +0700
Subject: [PATCH 2/4] fixup! feat: default contact
Signed-off-by: Hamza Mahjoubi
---
lib/Controller/DefaultContactController.php | 43 ++++++++++++++++---
src/components/AdminSettings.vue | 46 ++++++++++++---------
src/models/contact.js | 2 +-
3 files changed, 65 insertions(+), 26 deletions(-)
diff --git a/lib/Controller/DefaultContactController.php b/lib/Controller/DefaultContactController.php
index da6ab5229..b69817329 100644
--- a/lib/Controller/DefaultContactController.php
+++ b/lib/Controller/DefaultContactController.php
@@ -10,6 +10,7 @@
use OCP\AppFramework\ApiController;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\JSONResponse;
+use OCP\App\IAppManager;
use OCP\Files\NotFoundException;
use OCP\Files\IAppData;
use OCP\IConfig;
@@ -21,6 +22,7 @@ public function __construct(
IRequest $request,
private IConfig $config,
private IAppData $appData,
+ private IAppManager $appManager
) {
parent::__construct(Application::APP_ID, $request);
}
@@ -35,6 +37,9 @@ public function __construct(
*/
public function setAppConfig($allow) {
$key ='enableDefaultContact';
+ if($allow ==='yes' && !$this->defaultContactExists()){
+ $this->setInitialDefaultContact();
+ }
$this->config->setAppValue(Application::APP_ID, $key, $allow);
return new JSONResponse([], Http::STATUS_OK);
}
@@ -49,14 +54,40 @@ public function setDefaultContact($contactData){
catch(NotFoundException $e){
$folder = $this->appData->newFolder('defaultContact');
}
- if(!$folder->fileExists('defaultContact.vcf')){
- $file = $folder->newFile('defaultContact.vcf');
- }
- else {
- $file = $folder->getFile('defaultContact.vcf');
- }
+ $file = (!$folder->fileExists('defaultContact.vcf')) ? $folder->newFile('defaultContact.vcf') : $folder->getFile('defaultContact.vcf');
$file->putContent($contactData);
return new JSONResponse([], Http::STATUS_OK);
}
+ private function setInitialDefaultContact(){
+ $cardData = 'BEGIN:VCARD' . PHP_EOL .
+ 'VERSION:3.0' . PHP_EOL .
+ 'PRODID:-//Nextcloud Contacts v' . $this->appManager->getAppVersion('contacts') . PHP_EOL .
+ 'UID: janeDoe' . PHP_EOL .
+ 'FN:Jane Doe' . PHP_EOL .
+ 'ADR;TYPE=HOME:;;123 Street Street;City;State;;Country' . PHP_EOL .
+ 'EMAIL;TYPE=WORK:example@example.com' . PHP_EOL .
+ 'TEL;TYPE=HOME,VOICE:+999999999999' . PHP_EOL .
+ 'TITLE:Manager' . PHP_EOL .
+ 'ORG:Company' . PHP_EOL .
+ 'BDAY;VALUE=DATE:20000101' . PHP_EOL .
+ 'URL;VALUE=URI:https://example.com/' . PHP_EOL .
+ 'REV;VALUE=DATE-AND-OR-TIME:20241227T144820Z' . PHP_EOL .
+ 'END:VCARD';
+ $folder = $this->appData->getFolder('defaultContact');
+ $file = $folder->newFile('defaultContact.vcf');
+ $file->putContent($cardData);
+ }
+
+ private function defaultContactExists(): bool {
+ try{
+ $folder = $this->appData->getFolder('defaultContact');
+ }
+ catch(NotFoundException $e){
+ $this->appData->newFolder('defaultContact');
+ return false;
+ }
+ return $folder->fileExists('defaultContact.vcf');
+ }
+
}
\ No newline at end of file
diff --git a/src/components/AdminSettings.vue b/src/components/AdminSettings.vue
index a6eb83fda..76a3c2342 100644
--- a/src/components/AdminSettings.vue
+++ b/src/components/AdminSettings.vue
@@ -22,6 +22,7 @@
@change="updateEnableDefaultContact">
@@ -29,26 +30,18 @@
{{ t('contacts', 'Import contact') }}
-
-
@@ -63,6 +56,8 @@ import Contact from '../models/contact.js'
import validate from '../services/validate.js'
import { showError, showSuccess } from '@nextcloud/dialogs'
import IconUpload from 'vue-material-design-icons/Upload.vue'
+import IconCancel from '@mdi/svg/svg/cancel.svg'
+import IconCheck from '@mdi/svg/svg/check.svg'
export default {
name: 'AdminSettings',
@@ -77,6 +72,19 @@ export default {
enableDefaultContact: loadState('contacts', 'enableDefaultContact') === 'yes',
isModalOpen: false,
loading: false,
+ buttons: [
+ {
+ label: t('contacts', 'Cancel'),
+ icon: IconCancel,
+ callback: () => { this.isModalOpen = false },
+ },
+ {
+ label: t('contacts', 'Import'),
+ type: 'primary',
+ icon: IconCheck,
+ callback: () => { this.clickImportInput() },
+ },
+ ],
}
},
methods: {
@@ -97,12 +105,7 @@ export default {
this.$refs['contact-import-input'].click()
},
- /**
- * Process input type file change
- *
- * @param {Event} event the input change event
- */
- processFile(event) {
+ processFile(event) {
this.loading = true
const file = event.target.files[0]
@@ -125,3 +128,8 @@ export default {
},
}
+
diff --git a/src/models/contact.js b/src/models/contact.js
index 0a84440f5..cfb275eff 100644
--- a/src/models/contact.js
+++ b/src/models/contact.js
@@ -214,7 +214,7 @@ export default class Contact {
*
* @readonly
* @memberof Contact
- */
+ */
get hasPhoto() {
return this.dav && this.dav.hasphoto
}
From 27d815c77671798f6a8d5303f8d5706065dfb763 Mon Sep 17 00:00:00 2001
From: Hamza Mahjoubi
Date: Mon, 20 Jan 2025 22:41:09 +0700
Subject: [PATCH 3/4] fixup! feat: default contact
Signed-off-by: Hamza Mahjoubi
---
lib/Controller/DefaultContactController.php | 102 ++++++++++----------
1 file changed, 50 insertions(+), 52 deletions(-)
diff --git a/lib/Controller/DefaultContactController.php b/lib/Controller/DefaultContactController.php
index b69817329..9e5396d61 100644
--- a/lib/Controller/DefaultContactController.php
+++ b/lib/Controller/DefaultContactController.php
@@ -7,12 +7,12 @@
namespace OCA\Contacts\Controller;
use OCA\Contacts\AppInfo\Application;
+use OCP\App\IAppManager;
use OCP\AppFramework\ApiController;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\JSONResponse;
-use OCP\App\IAppManager;
-use OCP\Files\NotFoundException;
use OCP\Files\IAppData;
+use OCP\Files\NotFoundException;
use OCP\IConfig;
use OCP\IRequest;
@@ -21,8 +21,8 @@ class DefaultContactController extends ApiController {
public function __construct(
IRequest $request,
private IConfig $config,
- private IAppData $appData,
- private IAppManager $appManager
+ private IAppData $appData,
+ private IAppManager $appManager,
) {
parent::__construct(Application::APP_ID, $request);
}
@@ -36,58 +36,56 @@ public function __construct(
* @return JSONResponse an empty JSONResponse with respective http status code
*/
public function setAppConfig($allow) {
- $key ='enableDefaultContact';
- if($allow ==='yes' && !$this->defaultContactExists()){
- $this->setInitialDefaultContact();
- }
+ $key = 'enableDefaultContact';
+ if ($allow === 'yes' && !$this->defaultContactExists()) {
+ $this->setInitialDefaultContact();
+ }
$this->config->setAppValue(Application::APP_ID, $key, $allow);
return new JSONResponse([], Http::STATUS_OK);
}
- public function setDefaultContact($contactData){
- if(!$this->config->getAppValue(Application::APP_ID, 'enableDefaultContact', 'yes')){
- return new JSONResponse([], Http::STATUS_FORBIDDEN);
- }
- try{
- $folder = $this->appData->getFolder('defaultContact');
- }
- catch(NotFoundException $e){
- $folder = $this->appData->newFolder('defaultContact');
- }
- $file = (!$folder->fileExists('defaultContact.vcf')) ? $folder->newFile('defaultContact.vcf') : $folder->getFile('defaultContact.vcf');
- $file->putContent($contactData);
- return new JSONResponse([], Http::STATUS_OK);
- }
+ public function setDefaultContact($contactData) {
+ if (!$this->config->getAppValue(Application::APP_ID, 'enableDefaultContact', 'yes')) {
+ return new JSONResponse([], Http::STATUS_FORBIDDEN);
+ }
+ try {
+ $folder = $this->appData->getFolder('defaultContact');
+ } catch (NotFoundException $e) {
+ $folder = $this->appData->newFolder('defaultContact');
+ }
+ $file = (!$folder->fileExists('defaultContact.vcf')) ? $folder->newFile('defaultContact.vcf') : $folder->getFile('defaultContact.vcf');
+ $file->putContent($contactData);
+ return new JSONResponse([], Http::STATUS_OK);
+ }
- private function setInitialDefaultContact(){
- $cardData = 'BEGIN:VCARD' . PHP_EOL .
- 'VERSION:3.0' . PHP_EOL .
- 'PRODID:-//Nextcloud Contacts v' . $this->appManager->getAppVersion('contacts') . PHP_EOL .
- 'UID: janeDoe' . PHP_EOL .
- 'FN:Jane Doe' . PHP_EOL .
- 'ADR;TYPE=HOME:;;123 Street Street;City;State;;Country' . PHP_EOL .
- 'EMAIL;TYPE=WORK:example@example.com' . PHP_EOL .
- 'TEL;TYPE=HOME,VOICE:+999999999999' . PHP_EOL .
- 'TITLE:Manager' . PHP_EOL .
- 'ORG:Company' . PHP_EOL .
- 'BDAY;VALUE=DATE:20000101' . PHP_EOL .
- 'URL;VALUE=URI:https://example.com/' . PHP_EOL .
- 'REV;VALUE=DATE-AND-OR-TIME:20241227T144820Z' . PHP_EOL .
- 'END:VCARD';
- $folder = $this->appData->getFolder('defaultContact');
- $file = $folder->newFile('defaultContact.vcf');
- $file->putContent($cardData);
- }
+ private function setInitialDefaultContact() {
+ $cardData = 'BEGIN:VCARD' . PHP_EOL .
+ 'VERSION:3.0' . PHP_EOL .
+ 'PRODID:-//Nextcloud Contacts v' . $this->appManager->getAppVersion('contacts') . PHP_EOL .
+ 'UID: janeDoe' . PHP_EOL .
+ 'FN:Jane Doe' . PHP_EOL .
+ 'ADR;TYPE=HOME:;;123 Street Street;City;State;;Country' . PHP_EOL .
+ 'EMAIL;TYPE=WORK:example@example.com' . PHP_EOL .
+ 'TEL;TYPE=HOME,VOICE:+999999999999' . PHP_EOL .
+ 'TITLE:Manager' . PHP_EOL .
+ 'ORG:Company' . PHP_EOL .
+ 'BDAY;VALUE=DATE:20000101' . PHP_EOL .
+ 'URL;VALUE=URI:https://example.com/' . PHP_EOL .
+ 'REV;VALUE=DATE-AND-OR-TIME:20241227T144820Z' . PHP_EOL .
+ 'END:VCARD';
+ $folder = $this->appData->getFolder('defaultContact');
+ $file = $folder->newFile('defaultContact.vcf');
+ $file->putContent($cardData);
+ }
- private function defaultContactExists(): bool {
- try{
- $folder = $this->appData->getFolder('defaultContact');
- }
- catch(NotFoundException $e){
- $this->appData->newFolder('defaultContact');
- return false;
- }
- return $folder->fileExists('defaultContact.vcf');
- }
+ private function defaultContactExists(): bool {
+ try {
+ $folder = $this->appData->getFolder('defaultContact');
+ } catch (NotFoundException $e) {
+ $this->appData->newFolder('defaultContact');
+ return false;
+ }
+ return $folder->fileExists('defaultContact.vcf');
+ }
-}
\ No newline at end of file
+}
From 206d2470a73794565ee8b5aa485cce63a7927b46 Mon Sep 17 00:00:00 2001
From: Hamza Mahjoubi
Date: Tue, 21 Jan 2025 21:41:22 +0700
Subject: [PATCH 4/4] fixup! feat: default contact
Signed-off-by: Hamza Mahjoubi
---
src/components/AdminSettings.vue | 10 +---------
1 file changed, 1 insertion(+), 9 deletions(-)
diff --git a/src/components/AdminSettings.vue b/src/components/AdminSettings.vue
index 76a3c2342..6118b8cfb 100644
--- a/src/components/AdminSettings.vue
+++ b/src/components/AdminSettings.vue
@@ -52,9 +52,7 @@ import axios from '@nextcloud/axios'
import { generateUrl } from '@nextcloud/router'
import { loadState } from '@nextcloud/initial-state'
import { NcDialog, NcButton } from '@nextcloud/vue'
-import Contact from '../models/contact.js'
-import validate from '../services/validate.js'
-import { showError, showSuccess } from '@nextcloud/dialogs'
+import { showSuccess } from '@nextcloud/dialogs'
import IconUpload from 'vue-material-design-icons/Upload.vue'
import IconCancel from '@mdi/svg/svg/cancel.svg'
import IconCheck from '@mdi/svg/svg/check.svg'
@@ -113,12 +111,6 @@ export default {
reader.onload = () => {
this.isModalOpen = false
- const contact = new Contact(reader.result)
- /* if (!validate(contact)) {
- showError(t('contacts', 'Invalid VCF file'))
- event.target.value = ''
- return
- } */
axios.put(generateUrl('/apps/contacts/api/defaultcontact/contact'), { contactData: reader.result })
event.target.value = ''
}