Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] feat: default contact #4301

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'],

Check warning on line 19 in appinfo/routes.php

View check run for this annotation

Codecov / codecov/patch

appinfo/routes.php#L18-L19

Added lines #L18 - L19 were not covered by tests
]
];
1 change: 1 addition & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class Application extends App implements IBootstrap {

public const AVAIL_SETTINGS = [
'allowSocialSync' => 'yes',
'enableDefaultContact' => 'yes',
];

public function __construct() {
Expand Down
91 changes: 91 additions & 0 deletions lib/Controller/DefaultContactController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php
/**
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

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\Files\IAppData;
use OCP\Files\NotFoundException;
use OCP\IConfig;
use OCP\IRequest;

class DefaultContactController extends ApiController {

public function __construct(

Check warning on line 21 in lib/Controller/DefaultContactController.php

View check run for this annotation

Codecov / codecov/patch

lib/Controller/DefaultContactController.php#L21

Added line #L21 was not covered by tests
IRequest $request,
private IConfig $config,
private IAppData $appData,
private IAppManager $appManager,
) {
parent::__construct(Application::APP_ID, $request);

Check warning on line 27 in lib/Controller/DefaultContactController.php

View check run for this annotation

Codecov / codecov/patch

lib/Controller/DefaultContactController.php#L27

Added line #L27 was not covered by tests
}


/**
* update appconfig (admin setting)
*
* @param string allow the value to set
*
* @return JSONResponse an empty JSONResponse with respective http status code
*/
public function setAppConfig($allow) {
$key = 'enableDefaultContact';
if ($allow === 'yes' && !$this->defaultContactExists()) {
$this->setInitialDefaultContact();

Check warning on line 41 in lib/Controller/DefaultContactController.php

View check run for this annotation

Codecov / codecov/patch

lib/Controller/DefaultContactController.php#L38-L41

Added lines #L38 - L41 were not covered by tests
}
$this->config->setAppValue(Application::APP_ID, $key, $allow);
return new JSONResponse([], Http::STATUS_OK);

Check warning on line 44 in lib/Controller/DefaultContactController.php

View check run for this annotation

Codecov / codecov/patch

lib/Controller/DefaultContactController.php#L43-L44

Added lines #L43 - L44 were not covered by tests
}

public function setDefaultContact($contactData) {
if (!$this->config->getAppValue(Application::APP_ID, 'enableDefaultContact', 'yes')) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);

Check warning on line 49 in lib/Controller/DefaultContactController.php

View check run for this annotation

Codecov / codecov/patch

lib/Controller/DefaultContactController.php#L47-L49

Added lines #L47 - L49 were not covered by tests
}
try {
$folder = $this->appData->getFolder('defaultContact');
} catch (NotFoundException $e) {
$folder = $this->appData->newFolder('defaultContact');

Check warning on line 54 in lib/Controller/DefaultContactController.php

View check run for this annotation

Codecov / codecov/patch

lib/Controller/DefaultContactController.php#L52-L54

Added lines #L52 - L54 were not covered by tests
}
$file = (!$folder->fileExists('defaultContact.vcf')) ? $folder->newFile('defaultContact.vcf') : $folder->getFile('defaultContact.vcf');
$file->putContent($contactData);
return new JSONResponse([], Http::STATUS_OK);

Check warning on line 58 in lib/Controller/DefaultContactController.php

View check run for this annotation

Codecov / codecov/patch

lib/Controller/DefaultContactController.php#L56-L58

Added lines #L56 - L58 were not covered by tests
}

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:[email protected]' . 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);

Check warning on line 78 in lib/Controller/DefaultContactController.php

View check run for this annotation

Codecov / codecov/patch

lib/Controller/DefaultContactController.php#L61-L78

Added lines #L61 - L78 were not covered by tests
}

private function defaultContactExists(): bool {

Check warning on line 81 in lib/Controller/DefaultContactController.php

View check run for this annotation

Codecov / codecov/patch

lib/Controller/DefaultContactController.php#L81

Added line #L81 was not covered by tests
try {
$folder = $this->appData->getFolder('defaultContact');
} catch (NotFoundException $e) {
$this->appData->newFolder('defaultContact');
return false;

Check warning on line 86 in lib/Controller/DefaultContactController.php

View check run for this annotation

Codecov / codecov/patch

lib/Controller/DefaultContactController.php#L83-L86

Added lines #L83 - L86 were not covered by tests
}
return $folder->fileExists('defaultContact.vcf');

Check warning on line 88 in lib/Controller/DefaultContactController.php

View check run for this annotation

Codecov / codecov/patch

lib/Controller/DefaultContactController.php#L88

Added line #L88 was not covered by tests
}

}
3 changes: 3 additions & 0 deletions lib/Controller/PageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand All @@ -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);
Expand Down
96 changes: 92 additions & 4 deletions src/components/AdminSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,117 @@
v-model="allowSocialSync"
type="checkbox"
class="checkbox"
@change="updateSetting('allowSocialSync')">
@change="updateAllowSocialSync">
<label for="allow-social-sync">{{ t('contacts', 'Allow updating avatars from social media') }}</label>
</p>
<p>

Check warning on line 17 in src/components/AdminSettings.vue

View check run for this annotation

Codecov / codecov/patch

src/components/AdminSettings.vue#L17

Added line #L17 was not covered by tests
<input id="enable-default-contact"
v-model="enableDefaultContact"
type="checkbox"
class="checkbox"
@change="updateEnableDefaultContact">
<label for="enable-default-contact"> {{ t('mail',"Default contact is added to the user's own address book on user's first login.") }} </label>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<label for="enable-default-contact"> {{ t('mail',"Default contact is added to the user's own address book on user's first login.") }} </label>
<label for="enable-default-contact"> {{ t('contacts', "Default contact is added to the user's own address book on user's first login.") }} </label>

<NcButton v-if="enableDefaultContact"
class="import-button"
type="primary"

Check warning on line 26 in src/components/AdminSettings.vue

View check run for this annotation

Codecov / codecov/patch

src/components/AdminSettings.vue#L25-L26

Added lines #L25 - L26 were not covered by tests
@click="toggleModal">
<template #icon>
<IconUpload :size="20" />
</template>
{{ t('contacts', 'Import contact') }}
</NcButton>
<NcDialog :open.sync="isModalOpen"

Check warning on line 33 in src/components/AdminSettings.vue

View check run for this annotation

Codecov / codecov/patch

src/components/AdminSettings.vue#L32-L33

Added lines #L32 - L33 were not covered by tests
:name="t('contacts', 'Import contacts')"
:buttons="buttons">
<div>
<p>{{ t('contacts', 'Importing a new .vcf file will delete the existing default contact and replace it with the new one. Do you want to continue?') }}</p>
<input id="contact-import"
ref="contact-import-input"
:disabled="loading"

Check warning on line 40 in src/components/AdminSettings.vue

View check run for this annotation

Codecov / codecov/patch

src/components/AdminSettings.vue#L39-L40

Added lines #L39 - L40 were not covered by tests
type="file"
class="hidden-visually"
@change="processFile">
</div>
</NcDialog>

Check warning on line 45 in src/components/AdminSettings.vue

View check run for this annotation

Codecov / codecov/patch

src/components/AdminSettings.vue#L44-L45

Added lines #L44 - L45 were not covered by tests
</p>
</div>
</template>

<script>
import axios from '@nextcloud/axios'
import { generateUrl } from '@nextcloud/router'
import { loadState } from '@nextcloud/initial-state'
import { NcDialog, NcButton } from '@nextcloud/vue'
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'

Check warning on line 59 in src/components/AdminSettings.vue

View check run for this annotation

Codecov / codecov/patch

src/components/AdminSettings.vue#L55-L59

Added lines #L55 - L59 were not covered by tests
export default {
name: 'AdminSettings',
components: {
NcDialog,
NcButton,

Check warning on line 64 in src/components/AdminSettings.vue

View check run for this annotation

Codecov / codecov/patch

src/components/AdminSettings.vue#L64

Added line #L64 was not covered by tests
IconUpload,
},

Check warning on line 66 in src/components/AdminSettings.vue

View check run for this annotation

Codecov / codecov/patch

src/components/AdminSettings.vue#L66

Added line #L66 was not covered by tests
data() {
return {
allowSocialSync: loadState('contacts', 'allowSocialSync') === 'yes',
enableDefaultContact: loadState('contacts', 'enableDefaultContact') === 'yes',
isModalOpen: false,
loading: false,
buttons: [
{
label: t('contacts', 'Cancel'),

Check warning on line 75 in src/components/AdminSettings.vue

View check run for this annotation

Codecov / codecov/patch

src/components/AdminSettings.vue#L72-L75

Added lines #L72 - L75 were not covered by tests
icon: IconCancel,
callback: () => { this.isModalOpen = false },
},
{
label: t('contacts', 'Import'),
type: 'primary',
icon: IconCheck,
callback: () => { this.clickImportInput() },
},
],
}
},
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',
})
},

Check warning on line 93 in src/components/AdminSettings.vue

View check run for this annotation

Codecov / codecov/patch

src/components/AdminSettings.vue#L92-L93

Added lines #L92 - L93 were not covered by tests
updateEnableDefaultContact() {
axios.put(generateUrl('apps/contacts/api/defaultcontact/config'), {
allow: this.enableDefaultContact ? 'yes' : 'no',
})
},
toggleModal() {

Check warning on line 99 in src/components/AdminSettings.vue

View check run for this annotation

Codecov / codecov/patch

src/components/AdminSettings.vue#L99

Added line #L99 was not covered by tests
this.isModalOpen = !this.isModalOpen
},
clickImportInput() {
this.$refs['contact-import-input'].click()

Check warning on line 103 in src/components/AdminSettings.vue

View check run for this annotation

Codecov / codecov/patch

src/components/AdminSettings.vue#L102-L103

Added lines #L102 - L103 were not covered by tests
},

processFile(event) {

Check warning on line 106 in src/components/AdminSettings.vue

View check run for this annotation

Codecov / codecov/patch

src/components/AdminSettings.vue#L106

Added line #L106 was not covered by tests
this.loading = true

Check warning on line 108 in src/components/AdminSettings.vue

View check run for this annotation

Codecov / codecov/patch

src/components/AdminSettings.vue#L108

Added line #L108 was not covered by tests
const file = event.target.files[0]
const reader = new FileReader()

reader.onload = () => {
this.isModalOpen = false

Check warning on line 113 in src/components/AdminSettings.vue

View check run for this annotation

Codecov / codecov/patch

src/components/AdminSettings.vue#L112-L113

Added lines #L112 - L113 were not covered by tests
axios.put(generateUrl('/apps/contacts/api/defaultcontact/contact'), { contactData: reader.result })
event.target.value = ''
}
reader.readAsText(file)
showSuccess(this.t('contacts', 'Contact imported successfully'))
},
},
}
</script>
<style lang="scss" scoped>
.import-button {
margin-top: 1rem;
}
</style>
2 changes: 1 addition & 1 deletion src/models/contact.js
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ export default class Contact {
*
* @readonly
* @memberof Contact
*/
*/
get hasPhoto() {
return this.dav && this.dav.hasphoto
}
Expand Down
14 changes: 14 additions & 0 deletions src/services/defaultContactService.js
Original file line number Diff line number Diff line change
@@ -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'), {

Check warning on line 10 in src/services/defaultContactService.js

View check run for this annotation

Codecov / codecov/patch

src/services/defaultContactService.js#L9-L10

Added lines #L9 - L10 were not covered by tests
contactId,
})
return request.data

Check warning on line 13 in src/services/defaultContactService.js

View check run for this annotation

Codecov / codecov/patch

src/services/defaultContactService.js#L13

Added line #L13 was not covered by tests
}
Loading