Skip to content

Commit 642de86

Browse files
authored
Merge pull request #245 from nextcloud/other-contacts
feat: support other contacts
2 parents 106dc01 + 185848e commit 642de86

4 files changed

Lines changed: 86 additions & 24 deletions

File tree

lib/Controller/ConfigController.php

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class ConfigController extends Controller {
3636

3737
public const DRIVE_SCOPE = 'https://www.googleapis.com/auth/drive.readonly';
3838
public const CONTACTS_SCOPE = 'https://www.googleapis.com/auth/contacts.readonly';
39+
public const CONTACTS_OTHER_SCOPE = 'https://www.googleapis.com/auth/contacts.other.readonly';
3940
public const CALENDAR_SCOPE = 'https://www.googleapis.com/auth/calendar.readonly';
4041
public const CALENDAR_EVENTS_SCOPE = 'https://www.googleapis.com/auth/calendar.events.readonly';
4142
public const PHOTOS_SCOPE = 'https://www.googleapis.com/auth/photoslibrary.readonly';
@@ -162,8 +163,8 @@ public function popupSuccessPage(string $username): TemplateResponse {
162163
public function oauthRedirect(string $code = '', string $state = '', string $scope = '', string $error = ''): RedirectResponse {
163164
if ($this->userId === null) {
164165
return new RedirectResponse(
165-
$this->urlGenerator->linkToRoute('settings.PersonalSettings.index', ['section' => 'migration']) .
166-
'?googleToken=error&message=' . urlencode($this->l->t('No logged in user'))
166+
$this->urlGenerator->linkToRoute('settings.PersonalSettings.index', ['section' => 'migration'])
167+
. '?googleToken=error&message=' . urlencode($this->l->t('No logged in user'))
167168
);
168169
}
169170

@@ -177,6 +178,7 @@ public function oauthRedirect(string $code = '', string $state = '', string $sco
177178
$scopesArray = [
178179
'can_access_drive' => in_array(self::DRIVE_SCOPE, $scopes) ? 1 : 0,
179180
'can_access_contacts' => in_array(self::CONTACTS_SCOPE, $scopes) ? 1 : 0,
181+
'can_access_other_contacts' => in_array(self::CONTACTS_OTHER_SCOPE, $scopes) ? 1 : 0,
180182
'can_access_photos' => in_array(self::PHOTOS_SCOPE, $scopes) ? 1 : 0,
181183
'can_access_calendar' => (in_array(self::CALENDAR_SCOPE, $scopes) && in_array(self::CALENDAR_EVENTS_SCOPE, $scopes)) ? 1 : 0,
182184
];
@@ -214,8 +216,8 @@ public function oauthRedirect(string $code = '', string $state = '', string $sco
214216
);
215217
} else {
216218
return new RedirectResponse(
217-
$this->urlGenerator->linkToRoute('settings.PersonalSettings.index', ['section' => 'migration']) .
218-
'?googleToken=success'
219+
$this->urlGenerator->linkToRoute('settings.PersonalSettings.index', ['section' => 'migration'])
220+
. '?googleToken=success'
219221
);
220222
}
221223
}
@@ -228,8 +230,8 @@ public function oauthRedirect(string $code = '', string $state = '', string $sco
228230
$result = $this->l->t('Error during OAuth exchanges');
229231
}
230232
return new RedirectResponse(
231-
$this->urlGenerator->linkToRoute('settings.PersonalSettings.index', ['section' => 'migration']) .
232-
'?googleToken=error&message=' . urlencode($result)
233+
$this->urlGenerator->linkToRoute('settings.PersonalSettings.index', ['section' => 'migration'])
234+
. '?googleToken=error&message=' . urlencode($result)
233235
);
234236
}
235237

lib/Service/GoogleContactsAPIService.php

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use OCA\DAV\CardDAV\CardDavBackend;
1919
use OCA\Google\AppInfo\Application;
2020
use OCP\Contacts\IManager as IContactManager;
21+
use OCP\IConfig;
2122
use Psr\Log\LoggerInterface;
2223
use Sabre\VObject\Component\VCard;
2324
use Throwable;
@@ -33,6 +34,7 @@ public function __construct(
3334
private IContactManager $contactsManager,
3435
private CardDavBackend $cdBackend,
3536
private GoogleAPIService $googleApiService,
37+
private IConfig $config,
3638
) {
3739
}
3840

@@ -72,29 +74,41 @@ public function getContactGroupsById(string $userId): array {
7274
* @return array
7375
*/
7476
public function getContactNumber(string $userId): array {
75-
$nbContacts = 0;
7677
$params = [
7778
'personFields' => implode(',', [
7879
'names',
7980
]),
80-
'pageSize' => 100,
8181
];
82-
do {
83-
$result = $this->googleApiService->request($userId, 'v1/people/me/connections', $params, 'GET', 'https://people.googleapis.com/');
84-
if (isset($result['error'])) {
85-
return $result;
82+
$result = [];
83+
$contacts = $this->googleApiService->request($userId, 'v1/people/me/connections', $params, 'GET', 'https://people.googleapis.com/');
84+
if (isset($contacts['error'])) {
85+
return $contacts;
86+
}
87+
$result['nbContacts'] = $contacts['totalItems'] ?? 0;
88+
$scopes = $this->config->getUserValue($userId, Application::APP_ID, 'user_scopes', '{}');
89+
$scopes = json_decode($scopes, true);
90+
if (isset($scopes['can_access_other_contacts']) && $scopes['can_access_other_contacts'] === 1) {
91+
$params = [
92+
'readMask' => implode(',', [
93+
'names',
94+
'emailAddresses',
95+
]),
96+
];
97+
$otherContacts = $this->googleApiService->request($userId, 'v1/otherContacts', $params, 'GET', 'https://people.googleapis.com/');
98+
if (isset($otherContacts['error'])) {
99+
return $otherContacts;
86100
}
87-
$nbContacts += count($result['connections'] ?? []);
88-
$params['pageToken'] = $result['nextPageToken'] ?? '';
89-
} while (isset($result['nextPageToken']));
90-
return ['nbContacts' => $nbContacts];
101+
$result['nbOtherContacts'] = $otherContacts['totalSize'] ?? 0;
102+
}
103+
return $result;
91104
}
92105

93106
/**
94107
* @param string $userId
108+
* @param bool $otherContacts
95109
* @return Generator
96110
*/
97-
public function getContactList(string $userId): Generator {
111+
public function getContactList(string $userId, bool $otherContacts = false): Generator {
98112
$params = [
99113
'personFields' => implode(',', [
100114
'addresses',
@@ -127,6 +141,31 @@ public function getContactList(string $userId): Generator {
127141
}
128142
$params['pageToken'] = $result['nextPageToken'] ?? '';
129143
} while (isset($result['nextPageToken']));
144+
if ($otherContacts) {
145+
$params = [
146+
'readMask' => implode(',', [
147+
'emailAddresses',
148+
'metadata',
149+
'names',
150+
'phoneNumbers',
151+
'photos',
152+
]),
153+
'sources' => 'READ_SOURCE_TYPE_CONTACT',
154+
'pageSize' => 100,
155+
];
156+
do {
157+
$result = $this->googleApiService->request($userId, 'v1/otherContacts', $params, 'GET', 'https://people.googleapis.com/');
158+
if (isset($result['error'])) {
159+
return $result;
160+
}
161+
if (isset($result['otherContacts']) && is_array($result['otherContacts'])) {
162+
foreach ($result['otherContacts'] as $contact) {
163+
yield $contact;
164+
}
165+
}
166+
$params['pageToken'] = $result['nextPageToken'] ?? '';
167+
} while (isset($result['nextPageToken']));
168+
}
130169
return [];
131170
}
132171

@@ -166,9 +205,9 @@ public function importContacts(string $userId, ?string $uri, int $key, ?string $
166205
}
167206
$existingAddressBook = $addressBook;
168207
}
169-
208+
$otherContacts = $this->config->getUserValue($userId, Application::APP_ID, 'consider_other_contacts', '0') === '1';
170209
$groupsById = $this->getContactGroupsById($userId);
171-
$contacts = $this->getContactList($userId);
210+
$contacts = $this->getContactList($userId, $otherContacts);
172211
$nbAdded = 0;
173212
$nbUpdated = 0;
174213
$totalContactNumber = 0;

lib/Settings/Personal.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@
88
use OCA\Google\Service\SecretService;
99
use OCP\AppFramework\Http\TemplateResponse;
1010
use OCP\AppFramework\Services\IInitialState;
11+
use OCP\Files\Folder;
1112
use OCP\Files\IRootFolder;
1213
use OCP\Files\NotFoundException;
1314
use OCP\Files\NotPermittedException;
1415
use OCP\IConfig;
1516
use OCP\IUserManager;
1617
use OCP\Settings\ISettings;
18+
use Throwable;
1719

1820
class Personal implements ISettings {
1921

@@ -45,6 +47,7 @@ public function getForm(): TemplateResponse {
4547
$photoOutputDir = $photoOutputDir ?: '/Google Photos';
4648
$considerSharedFiles = $this->config->getUserValue($this->userId, Application::APP_ID, 'consider_shared_files', '0') === '1';
4749
$considerSharedAlbums = $this->config->getUserValue($this->userId, Application::APP_ID, 'consider_shared_albums', '0') === '1';
50+
$considerOtherContacts = $this->config->getUserValue($this->userId, Application::APP_ID, 'consider_other_contacts', '0') === '1';
4851
$documentFormat = $this->config->getUserValue($this->userId, Application::APP_ID, 'document_format', 'openxml');
4952
if (!in_array($documentFormat, ['openxml', 'opendoc'])) {
5053
$documentFormat = 'openxml';
@@ -83,6 +86,7 @@ public function getForm(): TemplateResponse {
8386
'user_quota' => $user === null ? '' : $user->getQuota(),
8487
'consider_shared_files' => $considerSharedFiles,
8588
'consider_shared_albums' => $considerSharedAlbums,
89+
'consider_other_contacts' => $considerOtherContacts,
8690
'document_format' => $documentFormat,
8791
'drive_output_dir' => $driveOutputDir,
8892
'photo_output_dir' => $photoOutputDir,
@@ -101,16 +105,16 @@ public function getPriority(): int {
101105
}
102106

103107
/**
104-
* @param \OCP\Files\Folder $userRoot
108+
* @param Folder $userRoot
105109
* @param string $outputDir
106110
* @return bool|float|int
107111
* @throws NotFoundException
108112
*/
109-
public static function getFreeSpace(\OCP\Files\Folder $userRoot, string $outputDir) {
113+
public static function getFreeSpace(Folder $userRoot, string $outputDir) {
110114
try {
111115
// OutputDir can be on an external storage which can have more free space
112116
$freeSpace = $userRoot->get($outputDir)->getStorage()->free_space('/');
113-
} catch (\Throwable $e) {
117+
} catch (Throwable $e) {
114118
$freeSpace = false;
115119
}
116120
return $freeSpace !== false && $freeSpace > 0 ? $freeSpace : $userRoot->getStorage()->free_space('/');

src/components/PersonalSettings.vue

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,22 @@
2828
</NcButton>
2929
</div>
3030
<br>
31-
<div v-if="nbContacts > 0"
31+
<div v-if="nbContacts + nbOtherContacts >= 0"
3232
id="google-contacts">
3333
<h3>{{ t('integration_google', 'Contacts') }}</h3>
34+
<div class="line">
35+
<NcCheckboxRadioSwitch v-if="!importingContacts && state.user_scopes.can_access_other_contacts"
36+
:model-value="state.consider_other_contacts"
37+
@update:model-value="onContactsConsiderOtherChange">
38+
{{ t('integration_google', 'Include other contacts') }}
39+
</NcCheckboxRadioSwitch>
40+
</div>
3441
<div class="line">
3542
<label>
3643
<AccountGroupIcon />
37-
{{ t('integration_google', '{amount} Google contacts', { amount: nbContacts }) }}
44+
{{ state.consider_other_contacts
45+
? t('integration_google', '{amount} Google + {otherAmount} other contacts', { amount: nbContacts, otherAmount: nbOtherContacts })
46+
: t('integration_google', '{amount} Google contacts', { amount: nbContacts }) }}
3847
</label>
3948
<NcButton @click="onImportContacts">
4049
<template #icon>
@@ -310,8 +319,10 @@ export default {
310319
calendars: [],
311320
importingCalendar: {},
312321
// contacts
322+
considerOtherContacts: false,
313323
addressbooks: [],
314324
nbContacts: 0,
325+
nbOtherContacts: 0,
315326
showAddressBooks: false,
316327
selectedAddressBook: 0,
317328
newAddressBookName: 'Google Contacts import',
@@ -466,6 +477,7 @@ export default {
466477
'https://www.googleapis.com/auth/contacts.readonly',
467478
'https://www.googleapis.com/auth/photoslibrary.readonly',
468479
'https://www.googleapis.com/auth/drive.readonly',
480+
'https://www.googleapis.com/auth/contacts.other.readonly',
469481
]
470482
const requestUrl = 'https://accounts.google.com/o/oauth2/v2/auth?'
471483
+ 'client_id=' + encodeURIComponent(this.state.client_id)
@@ -598,6 +610,7 @@ export default {
598610
.then((response) => {
599611
if (response.data && Object.keys(response.data).length > 0) {
600612
this.nbContacts = response.data.nbContacts
613+
this.nbOtherContacts = response.data.nbOtherContacts ?? 0
601614
}
602615
})
603616
.catch((error) => {
@@ -816,6 +829,10 @@ export default {
816829
myHumanFileSize(bytes, approx = false, si = false, dp = 1) {
817830
return humanFileSize(bytes, approx, si, dp)
818831
},
832+
onContactsConsiderOtherChange(newValue) {
833+
this.state.consider_other_contacts = newValue
834+
this.saveOptions({ consider_other_contacts: this.state.consider_other_contacts ? '1' : '0' }, this.getNbGoogleContacts)
835+
},
819836
onDriveConsiderSharedChange(newValue) {
820837
this.state.consider_shared_files = !newValue
821838
this.saveOptions({ consider_shared_files: this.state.consider_shared_files ? '1' : '0' }, this.getGoogleDriveInfo)

0 commit comments

Comments
 (0)