l10n = $l10nFactory->get(Application::APP_ID); } /** * Import the given vCard file (by id) into the given address book of the current user. * If no address book URI is posted as a payload, an attempt will be made to determine the * user's default address book. * * @param int $fileId The id of a vCard file to import * @param ?string $addressBookUri Optional URI of the address book to import into - {@see \OCP\IAddressBook::getUri} * @return DataResponse A translated string indicating the outcome of the import action. * * 200: Contacts were processed (check the response data for stats) * 400: Not a vCard file or given both $addressBookKey and $addressBookUri * 401: User is not logged in * 404: File or address book was not found */ #[NoAdminRequired] #[ApiRoute('POST', '/api/v1/import/{fileId}')] public function import(int $fileId, ?string $addressBookUri = null): DataResponse { if ($this->userId === null) { return new DataResponse('Not logged in', Http::STATUS_UNAUTHORIZED); } try { $addressBook = $this->findUserAddressBook($addressBookUri); } catch (InsufficientPermissionsException $e) { return new DataResponse($e->getMessage(), Http::STATUS_FORBIDDEN); } if ($addressBook === null) { return new DataResponse('Address book not found', Http::STATUS_NOT_FOUND); } $userRoot = $this->rootFolder->getUserFolder($this->userId); $file = $userRoot->getFirstNodeById($fileId); if ($file === null) { return new DataResponse('File not found', Http::STATUS_NOT_FOUND); } if (!($file instanceof File)) { return new DataResponse('Not a file', Http::STATUS_BAD_REQUEST); } if ($file->getMimetype() !== 'text/vcard' && $file->getExtension() !== 'vcf') { return new DataResponse('Not a vCard file', Http::STATUS_BAD_REQUEST); } /** @var array{uid: ?string, data: string}[] $contacts */ $contacts = []; $currentContact = null; $currentContactUid = null; // The vcf file might contain multiple contacts -> split each vcard $vcf = $file->getContent(); $vcfLines = explode("\n", $vcf); foreach ($vcfLines as $line) { $line = rtrim($line, "\r"); if ($line === 'BEGIN:VCARD') { $currentContact = [$line]; continue; } if ($line === 'END:VCARD') { $currentContact[] = $line; $contacts[] = [ 'uid' => $currentContactUid, 'data' => implode("\n", $currentContact), ]; $currentContact = null; $currentContactUid = null; continue; } if ($currentContact === null) { continue; } if (str_starts_with($line, self::UID_PREFIX)) { $currentContactUid = substr($line, strlen(self::UID_PREFIX)); } $currentContact[] = $line; } $skipped = 0; $errors = []; $imported = []; foreach ($contacts as $contact) { $uid = $contact['uid']; $vcf = $contact['data']; if ($uid !== null) { $existingContacts = $addressBook->search($uid, ['UID'], [ 'limit' => 1, 'wildcard' => false, ]); if (!empty($existingContacts) && $existingContacts[0]['UID'] === $uid) { $skipped++; continue; } } $uri = $this->random->generate( 32, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', ); $uri = "$uri.vcf"; try { $addressBook->createFromString($uri, $vcf); } catch (\Exception $e) { $errors[] = $e->getMessage(); $skipped++; continue; } $imported[] = $uri; } if (!empty($errors)) { $this->logger->error('Failed to import ' . count($errors) . ' contacts via OCS', [ 'errors' => $errors, 'fileId' => $fileId, ]); } if ($skipped === 0) { $message = $this->l10n->n( 'Imported %n contact', 'Imported %n contacts', count($imported), ); } else { $message = $this->l10n->n( 'Imported %n contact (skipped %d)', 'Imported %n contacts (skipped %d)', count($imported), [$skipped], ); } return new DataResponse($message); } private function findUserAddressBook(?string $uri): ?ICreateContactFromString { /** @var ICreateContactFromString[] $addressBooks */ $addressBooks = array_filter( $this->contactsManager->getUserAddressBooks(), static fn ($addressBook) => $addressBook instanceof ICreateContactFromString, ); // Try the given address book by URI first if ($uri !== null) { foreach ($addressBooks as $addressBook) { if ($addressBook->getUri() === $uri) { if (! ($addressBook->getPermissions() & Constants::PERMISSION_UPDATE)) { throw new InsufficientPermissionsException('Insufficient permissions to import into the address book ' . $uri); } return $addressBook; } } return null; } // Try to find the user's default address book foreach ($addressBooks as $addressBook) { if ($addressBook->getUri() === CardDavBackend::PERSONAL_ADDRESSBOOK_URI) { return $addressBook; } } // Otherwise, use the first writable, owned address book foreach ($addressBooks as $addressBook) { if ($addressBook->isShared()) { continue; } if (($addressBook->getPermissions() & Constants::PERMISSION_CREATE) === 0) { continue; } return $addressBook; } return null; } /** * Get a list of all available address books of the currently logged in user. * * @return DataResponse A list of address books with. Each one has an id and a display name. * * 200: List of address book options * 401: User is not logged in */ #[NoAdminRequired] #[ApiRoute('POST', '/api/v1/address-book-options')] public function addressBookOptions(): DataResponse { if ($this->userId === null) { return new DataResponse('Not logged in', Http::STATUS_UNAUTHORIZED); } $addressBooks = $this->contactsManager->getUserAddressBooks(); $options = array_map(static fn (IAddressBook $addressBook) => [ 'id' => $addressBook->getKey(), 'displayName' => $addressBook->getDisplayName(), ], $addressBooks); return new DataResponse($options); } }