diff --git a/plugins/nextcloud/NextcloudAddressBook.php b/plugins/nextcloud/NextcloudAddressBook.php index 2790e4e5c..4d3d06de6 100644 --- a/plugins/nextcloud/NextcloudAddressBook.php +++ b/plugins/nextcloud/NextcloudAddressBook.php @@ -1,38 +1,44 @@ GetSavedAddressBookKey(); + } + + private function GetSavedAddressBookKey(): string { $this->contactsManager = \OC::$server->getContactsManager(); - foreach($this->contactsManager->getUserAddressBooks() as $addressBook) { - if($addressBook->isSystemAddressBook()) { - $this->contactsManager->unregisterAddressBook($addressBook); + foreach ($this->contactsManager->getUserAddressBooks() as $addressBook) { + if ($addressBook->isSystemAddressBook()) { + $this->contactsManager->unregisterAddressBook($addressBook); } } $uid = \OC::$server->getUserSession()->getUser()->getUID(); $cardDavBackend = \OC::$server->get(\OCA\DAV\CardDAV\CardDavBackend::class); - $principalUri = 'principals/users/'. $uid; - $addressBookId = $cardDavBackend->getAddressBooksByUri($principalUri, self::URI); + $principalUri = 'principals/users/' . $uid; + $uri = $this->GetSavedUri(); + $addressBookId = $cardDavBackend->getAddressBooksByUri($principalUri, $uri); if ($addressBookId === null) { - $addressBookId = $cardDavBackend->createAddressBook($principalUri, self::URI, array_filter([ - '{DAV:}displayname' => 'Webmail', - '{urn:ietf:params:xml:ns:carddav}addressbook-description' => 'Recipients from snappymail', - ])); + $addressBookId = $cardDavBackend->createAddressBook($principalUri, $uri, array_filter([ + '{DAV:}displayname' => 'WebMail', + '{urn:ietf:params:xml:ns:carddav}addressbook-description' => 'Recipients from snappymail', + ])); } else { $addressBookId = $addressBookId['id']; } - $this->key = $addressBookId; + return $addressBookId; } - public function IsSupported() : bool { + public function IsSupported(): bool + { // Maybe just return true, contacts app is just a frontend //return \OC::$server->getAppManager()->isEnabledForUser('contacts'); return true; @@ -43,53 +49,63 @@ class NextcloudAddressBook implements \RainLoop\Providers\AddressBook\AddressBoo return true; } - public function SetEmail(string $sEmail) : bool { + public function SetEmail(string $sEmail): bool + { return true; } - public function Sync() : bool { + public function Sync(): bool + { return false; } - public function Export(string $sType = 'vcf') : bool { + public function Export(string $sType = 'vcf'): bool + { return false; } - public function ContactSave(Contact $oContact) : bool { + public function ContactSave(Contact $oContact): bool + { return false; } - public function DeleteContacts(array $aContactIds) : bool { + public function DeleteContacts(array $aContactIds): bool + { return false; } - public function DeleteAllContacts(string $sEmail) : bool { + public function DeleteAllContacts(string $sEmail): bool + { return false; } - public function GetContacts(int $iOffset = 0, int $iLimit = 20, string $sSearch = '', int &$iResultCount = 0) : array { + public function GetContacts(int $iOffset = 0, int $iLimit = 20, string $sSearch = '', int &$iResultCount = 0): array + { return []; } - public function GetContactByEmail(string $sEmail) : ?Contact { + public function GetContactByEmail(string $sEmail): ?Contact + { return null; } - public function GetContactByID($mID, bool $bIsStrID = false) : ?Contact { + public function GetContactByID($mID, bool $bIsStrID = false): ?Contact + { return null; } - public function GetSuggestions(string $sSearch, int $iLimit = 20) : array { + public function GetSuggestions(string $sSearch, int $iLimit = 20): array + { return []; } - private function GetEmailObjects(array $aEmails) : array { + private function GetEmailObjects(array $aEmails): array + { $aEmailsObjects = \array_map(function ($mItem) { $oResult = null; try { $oResult = \MailSo\Mime\Email::Parse(\trim($mItem)); - } - catch (\Throwable $oException) { + } catch (\Throwable $oException) { unset($oException); } return $oResult; @@ -105,20 +121,21 @@ class NextcloudAddressBook implements \RainLoop\Providers\AddressBook\AddressBoo * Add/increment email address usage * Handy for "most used" sorting suggestions in PdoAddressBook */ - public function IncFrec(array $aEmails, bool $bCreateAuto = true) : bool { + public function IncFrec(array $aEmails, bool $bCreateAuto = true): bool + { if ($bCreateAuto) { $aEmailsObjects = $this->GetEmailObjects($aEmails); if (!count($aEmailsObjects)) { return false; } - foreach ($aEmailsObjects as $oEmail) { + foreach ($aEmailsObjects as $oEmail) { if ('' === \trim($oEmail->GetEmail())) { continue; } $sEmail = \trim($oEmail->GetEmail(true)); $existingResults = $this->contactsManager->search($sEmail, ['EMAIL'], ['strict_search' => true]); - + if (!empty($existingResults)) { continue; } @@ -127,20 +144,39 @@ class NextcloudAddressBook implements \RainLoop\Providers\AddressBook\AddressBoo 'EMAIL' => $sEmail, 'FN' => $sEmail ]; - + if ('' !== \trim($oEmail->GetDisplayName())) { $properties['FN'] = $oEmail->GetDisplayName(); } - $this->contactsManager->createOrUpdate($properties, $this->key); + $this->contactsManager->createOrUpdate($properties, $this->GetSavedAddressBookKey()); } return true; } return false; } - public function Test() : string { + public function Test(): string + { return ''; } + private function Account(): \RainLoop\Model\Account + { + return \RainLoop\Api::Actions()->getAccountFromToken(); + } + private function SettingsProvider(): \RainLoop\Providers\Settings + { + return \RainLoop\Api::Actions()->SettingsProvider(true); + } + + private function Settings(): \RainLoop\Settings + { + return $this->SettingsProvider()->Load($this->Account()); + } + + private function GetSavedUri(): string + { + return $this->Settings()->GetConf('NextCloudAddressBookUri', 'webmail'); + } } diff --git a/plugins/nextcloud/index.php b/plugins/nextcloud/index.php index 614f1bcfb..04bc3a084 100644 --- a/plugins/nextcloud/index.php +++ b/plugins/nextcloud/index.php @@ -10,7 +10,7 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin DESCRIPTION = 'Integrate with Nextcloud v20+', REQUIRED = '2.36.2'; - public function Init() : void + public function Init(): void { if (static::IsIntegrated()) { \SnappyMail\Log::debug('Nextcloud', 'integrated'); @@ -36,11 +36,16 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin $this->addTemplate('templates/PopupsNextcloudFiles.html'); $this->addTemplate('templates/PopupsNextcloudCalendars.html'); -// $this->addHook('login.credentials.step-2', 'loginCredentials2'); -// $this->addHook('login.credentials', 'loginCredentials'); + // $this->addHook('login.credentials.step-2', 'loginCredentials2'); + // $this->addHook('login.credentials', 'loginCredentials'); $this->addHook('imap.before-login', 'beforeLogin'); $this->addHook('smtp.before-login', 'beforeLogin'); $this->addHook('sieve.before-login', 'beforeLogin'); + + $this->addJs('js/addressbook.js'); // add js file + $this->addTemplate('templates/AddressBookSettings.html'); + $this->addJsonHook('JsonGetAddressbooks', 'GetAddressBooks'); + $this->addJsonHook('NextcloudUpdateAddressBook', 'UpdateAddressBook'); } else { \SnappyMail\Log::debug('Nextcloud', 'NOT integrated'); // \OC::$server->getConfig()->getAppValue('snappymail', 'snappymail-no-embed'); @@ -48,6 +53,48 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin } } + public function GetAddressBooks() + { + $addressBooks = []; + + $contactsManager = \OC::$server->getContactsManager(); + + $selectedUri = $this->Settings()->GetConf('NextCloudAddressBookUri', 'webmail'); + + foreach ($contactsManager->getUserAddressBooks() as $addressBook) { + if ($addressBook->isSystemAddressBook()) { + $contactsManager->unregisterAddressBook($addressBook); + continue; + } + + $book = new AddressBook(); + $book->uri = $addressBook->getUri(); + $book->name = $addressBook->getDisplayName(); + if (strcmp($selectedUri, $book->uri) === 0) { + $book->selected = true; + } + + $addressBooks[] = $book; + } + + + return $this->jsonResponse(__FUNCTION__, array( + 'addressbooks' => json_encode($addressBooks) + )); + } + + public function UpdateAddressBook(): array + { + $uri = $this->jsonParam('uri'); + $oSettings = $this->Settings(); + if (\is_string($uri)) { + $oSettings->SetConf('NextCloudAddressBookUri', $uri); + $this->SettingsProvider()->Save($this->Account(), $oSettings); + } + return $this->jsonResponse(__FUNCTION__, true); + } + + public function ContentSecurityPolicy(\SnappyMail\HTTP\CSP $CSP) { if (\method_exists($CSP, 'add')) { @@ -55,7 +102,7 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin } } - public function Supported() : string + public function Supported(): string { return static::IsIntegrated() ? '' : 'Nextcloud not found to use this plugin'; } @@ -70,24 +117,24 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin return static::IsIntegrated() && \OC::$server->getUserSession()->isLoggedIn(); } - public function loginCredentials(string &$sEmail, string &$sLogin, ?string &$sPassword = null) : void + public function loginCredentials(string &$sEmail, string &$sLogin, ?string &$sPassword = null): void { /** * This has an issue. * When user changes email address, all settings are gone as the new * _data_/_default_/storage/{domain}/{local-part} is used */ -// $ocUser = \OC::$server->getUserSession()->getUser(); -// $sEmail = $ocUser->getEMailAddress() ?: $ocUser->getPrimaryEMailAddress() ?: $sEmail; + // $ocUser = \OC::$server->getUserSession()->getUser(); + // $sEmail = $ocUser->getEMailAddress() ?: $ocUser->getPrimaryEMailAddress() ?: $sEmail; } - public function loginCredentials2(string &$sEmail, ?string &$sPassword = null) : void + public function loginCredentials2(string &$sEmail, ?string &$sPassword = null): void { $ocUser = \OC::$server->getUserSession()->getUser(); $sEmail = $ocUser->getEMailAddress() ?: $ocUser->getPrimaryEMailAddress() ?: $sEmail; } - public function beforeLogin(\RainLoop\Model\Account $oAccount, \MailSo\Net\NetClient $oClient, \MailSo\Net\ConnectSettings $oSettings) : void + public function beforeLogin(\RainLoop\Model\Account $oAccount, \MailSo\Net\NetClient $oClient, \MailSo\Net\ConnectSettings $oSettings): void { // https://apps.nextcloud.com/apps/oidc_login $config = \OC::$server->getConfig(); @@ -104,11 +151,12 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin // Only login with OIDC access token if // it is enabled in config, the user is currently logged in with OIDC, // the current snappymail account is the OIDC account and no account defined explicitly - if (\OC::$server->getConfig()->getAppValue('snappymail', 'snappymail-autologin-oidc', false) - && \OC::$server->getSession()->get('is_oidc') - && $sNcEmail === $oSettings->username - && !$bAccountDefinedExplicitly -// && $oClient->supportsAuthType('OAUTHBEARER') // v2.28 + if ( + \OC::$server->getConfig()->getAppValue('snappymail', 'snappymail-autologin-oidc', false) + && \OC::$server->getSession()->get('is_oidc') + && $sNcEmail === $oSettings->username + && !$bAccountDefinedExplicitly + // && $oClient->supportsAuthType('OAUTHBEARER') // v2.28 ) { $sAccessToken = \OC::$server->getSession()->get('oidc_access_token'); if ($sAccessToken) { @@ -123,7 +171,7 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin \OC::$server->getLDAPProvider(); */ - public function NextcloudAttachFile() : array + public function NextcloudAttachFile(): array { $aResult = [ 'success' => false, @@ -147,10 +195,10 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin return $this->jsonResponse(__FUNCTION__, $aResult); } - public function NextcloudSaveMsg() : array + public function NextcloudSaveMsg(): array { $sSaveFolder = \ltrim($this->jsonParam('folder', ''), '/'); -// $aValues = \RainLoop\Api::Actions()->decodeRawKey($this->jsonParam('msgHash', '')); + // $aValues = \RainLoop\Api::Actions()->decodeRawKey($this->jsonParam('msgHash', '')); $msgHash = $this->jsonParam('msgHash', ''); $aValues = \json_decode(\MailSo\Base\Utils::UrlSafeBase64Decode($msgHash), true); $aResult = [ @@ -204,14 +252,14 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin foreach ($data->items as $aItem) { $sSavedFileName = empty($aItem['fileName']) ? 'file.dat' : $aItem['fileName']; if (!empty($aItem['data'])) { - $sSavedFileNameFull = static::SmartFileExists($sSaveFolder.'/'.$sSavedFileName, $oFiles); + $sSavedFileNameFull = static::SmartFileExists($sSaveFolder . '/' . $sSavedFileName, $oFiles); if (!$oFiles->file_put_contents($sSavedFileNameFull, $aItem['data'])) { $data->result = false; } } else if (!empty($aItem['fileHash'])) { $fFile = $data->filesProvider->GetFile($data->account, $aItem['fileHash'], 'rb'); if (\is_resource($fFile)) { - $sSavedFileNameFull = static::SmartFileExists($sSaveFolder.'/'.$sSavedFileName, $oFiles); + $sSavedFileNameFull = static::SmartFileExists($sSaveFolder . '/' . $sSavedFileName, $oFiles); if (!$oFiles->file_put_contents($sSavedFileNameFull, $fFile)) { $data->result = false; } @@ -225,19 +273,19 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin } } - public function FilterAppData($bAdmin, &$aResult) : void + public function FilterAppData($bAdmin, &$aResult): void { if (!$bAdmin && \is_array($aResult)) { $ocUser = \OC::$server->getUserSession()->getUser(); $sUID = $ocUser->getUID(); $oUrlGen = \OC::$server->getURLGenerator(); $sWebDAV = $oUrlGen->getAbsoluteURL($oUrlGen->linkTo('', 'remote.php') . '/dav'); -// $sWebDAV = \OCP\Util::linkToRemote('dav'); + // $sWebDAV = \OCP\Util::linkToRemote('dav'); $aResult['Nextcloud'] = [ 'UID' => $sUID, 'WebDAV' => $sWebDAV, 'CalDAV' => $this->Config()->Get('plugin', 'calendar', false) -// 'WebDAV_files' => $sWebDAV . '/files/' . $sUID + // 'WebDAV_files' => $sWebDAV . '/files/' . $sUID ]; if (empty($aResult['Auth'])) { $config = \OC::$server->getConfig(); @@ -259,9 +307,9 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin } if (!$sEmail) { $sEmail = $ocUser->getEMailAddress(); -// ?: $ocUser->getPrimaryEMailAddress(); + // ?: $ocUser->getPrimaryEMailAddress(); } -/* + /* if ($config->getAppValue('snappymail', 'snappymail-autologin-oidc', false)) { if (\OC::$server->getSession()->get('is_oidc')) { $sEmail = "{$sUID}@nextcloud"; @@ -308,7 +356,7 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin } } - public function FilterLanguage(&$sLanguage, $bAdmin) : void + public function FilterLanguage(&$sLanguage, $bAdmin): void { if (!\RainLoop\Api::Config()->Get('webmail', 'allow_languages_on_settings', true)) { $aResultLang = \SnappyMail\L10n::getLanguages($bAdmin); @@ -331,7 +379,7 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin * * @return string return locale */ - private function determineLocale(string $langCode, array $languagesArray) : ?string + private function determineLocale(string $langCode, array $languagesArray): ?string { // Direct check for the language code if (\in_array($langCode, $languagesArray)) { @@ -378,7 +426,7 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin include_once __DIR__ . '/NextcloudAddressBook.php'; $mResult = new NextcloudAddressBook(); } -/* + /* if ($this->Config()->Get('plugin', 'storage', false) && ('storage' === $sName || 'storage-local' === $sName)) { require_once __DIR__ . '/storage.php'; $oDriver = new \NextcloudStorage(APP_PRIVATE_DATA.'storage', $sName === 'storage-local'); @@ -387,7 +435,7 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin } } - protected function configMapping() : array + protected function configMapping(): array { return array( \RainLoop\Plugins\Property::NewInstance('suggestions')->SetLabel('Suggestions') @@ -396,7 +444,7 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin \RainLoop\Plugins\Property::NewInstance('ignoreSystemAddressbook')->SetLabel('Ignore system addressbook') ->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL) ->SetDefaultValue(true), -/* + /* \RainLoop\Plugins\Property::NewInstance('storage')->SetLabel('Use Nextcloud user ID in config storage path') ->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL) ->SetDefaultValue(false) @@ -407,7 +455,7 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin ); } - private static function SmartFileExists(string $sFilePath, $oFiles) : string + private static function SmartFileExists(string $sFilePath, $oFiles): string { $sFilePath = \str_replace('\\', '/', \trim($sFilePath)); @@ -421,11 +469,10 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin while (true) { ++$iIndex; - $sFilePathNew = $aFileInfo['dirname'].'/'. - \preg_replace('/\(\d{1,2}\)$/', '', $aFileInfo['filename']). - ' ('.$iIndex.')'. - (empty($aFileInfo['extension']) ? '' : '.'.$aFileInfo['extension']) - ; + $sFilePathNew = $aFileInfo['dirname'] . '/' . + \preg_replace('/\(\d{1,2}\)$/', '', $aFileInfo['filename']) . + ' (' . $iIndex . ')' . + (empty($aFileInfo['extension']) ? '' : '.' . $aFileInfo['extension']); if (!$oFiles->file_exists($sFilePathNew)) { return $sFilePathNew; } @@ -435,4 +482,26 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin } return $sFilePath; } + + private function Account(): \RainLoop\Model\Account + { + return \RainLoop\Api::Actions()->getAccountFromToken(); + } + + private function SettingsProvider(): \RainLoop\Providers\Settings + { + return \RainLoop\Api::Actions()->SettingsProvider(true); + } + + private function Settings(): \RainLoop\Settings + { + return $this->SettingsProvider()->Load($this->Account()); + } +} + +class AddressBook +{ + public string $uri; + public string $name; + public bool $selected = false; } diff --git a/plugins/nextcloud/js/addressbook.js b/plugins/nextcloud/js/addressbook.js new file mode 100644 index 000000000..1ce7bfc79 --- /dev/null +++ b/plugins/nextcloud/js/addressbook.js @@ -0,0 +1,36 @@ +(rl => { + if (rl) { + class AddressBookSettings /* extends AbstractViewSettings */ { + constructor() { + this.addressBookList = ko.observableArray(); + this.selectedAddressBook = ko.observable(); + + rl.pluginRemoteRequest((iError, oData) => { + if (!iError) { + const books = JSON.parse(oData.Result.addressbooks); + books.forEach(book => { + this.addressBookList.push(book); + + if (book.selected) { + this.selectedAddressBook(book.uri); + } + }); + + this.selectedAddressBook.subscribe(value => { + rl.pluginRemoteRequest(() => { }, 'NextcloudUpdateAddressBook', { + uri: value + }); + }); + } + }, "JsonGetAddressbooks"); + } + } + + rl.addSettingsViewModel( + AddressBookSettings, + 'AddressBookSettings', + 'NEXTCLOUD/ADDRESS_BOOK', + 'addressbook' + ); + } +})(window.rl); diff --git a/plugins/nextcloud/langs/de.json b/plugins/nextcloud/langs/de.json index 5f43f1357..a92d94b3c 100644 --- a/plugins/nextcloud/langs/de.json +++ b/plugins/nextcloud/langs/de.json @@ -9,6 +9,7 @@ "SELECT_CALENDAR": "Kalender auswählen", "FILE_ATTACH": "anfügen", "FILE_INTERNAL": "intern", - "FILE_PUBLIC": "öffentlich" + "FILE_PUBLIC": "öffentlich", + "ADDRESS_BOOK": "Address Book" } } diff --git a/plugins/nextcloud/langs/en.json b/plugins/nextcloud/langs/en.json index 2c19d11c2..b4fb55a97 100644 --- a/plugins/nextcloud/langs/en.json +++ b/plugins/nextcloud/langs/en.json @@ -9,6 +9,7 @@ "SELECT_CALENDAR": "Select calendar", "FILE_ATTACH": "attach", "FILE_INTERNAL": "internal", - "FILE_PUBLIC": "public" + "FILE_PUBLIC": "public", + "ADDRESS_BOOK": "Address Book" } } diff --git a/plugins/nextcloud/langs/pl.json b/plugins/nextcloud/langs/pl.json index 82e0d4f18..57f28aa50 100644 --- a/plugins/nextcloud/langs/pl.json +++ b/plugins/nextcloud/langs/pl.json @@ -9,6 +9,7 @@ "SELECT_CALENDAR": "Wybierz kalendarz", "FILE_ATTACH": "dołącz", "FILE_INTERNAL": "link wewnętrzny", - "FILE_PUBLIC": "link publiczny" + "FILE_PUBLIC": "link publiczny", + "ADDRESS_BOOK": "Address Book" } } diff --git a/plugins/nextcloud/langs/ru.json b/plugins/nextcloud/langs/ru.json index a8b28372d..4e7125770 100644 --- a/plugins/nextcloud/langs/ru.json +++ b/plugins/nextcloud/langs/ru.json @@ -9,6 +9,7 @@ "SELECT_CALENDAR": "Выбрать календарь", "FILE_ATTACH": "Прикрепить с ПК", "FILE_INTERNAL": "Внутреняя", - "FILE_PUBLIC": "Публичная" + "FILE_PUBLIC": "Публичная", + "ADDRESS_BOOK": "Address Book" } } diff --git a/plugins/nextcloud/langs/zh-TW.json b/plugins/nextcloud/langs/zh-TW.json index 292d02286..14f227645 100644 --- a/plugins/nextcloud/langs/zh-TW.json +++ b/plugins/nextcloud/langs/zh-TW.json @@ -9,6 +9,7 @@ "SELECT_CALENDAR": "選擇日曆", "FILE_ATTACH": "附加", "FILE_INTERNAL": "內部", - "FILE_PUBLIC": "公開" + "FILE_PUBLIC": "公開", + "ADDRESS_BOOK": "Address Book" } } diff --git a/plugins/nextcloud/langs/zh.json b/plugins/nextcloud/langs/zh.json index 39b20ac2e..491221392 100644 --- a/plugins/nextcloud/langs/zh.json +++ b/plugins/nextcloud/langs/zh.json @@ -9,6 +9,7 @@ "SELECT_CALENDAR": "选择日历", "FILE_ATTACH": "附加", "FILE_INTERNAL": "内部", - "FILE_PUBLIC": "公开" + "FILE_PUBLIC": "公开", + "ADDRESS_BOOK": "Address Book" } } diff --git a/plugins/nextcloud/templates/AddressBookSettings.html b/plugins/nextcloud/templates/AddressBookSettings.html new file mode 100644 index 000000000..e33682515 --- /dev/null +++ b/plugins/nextcloud/templates/AddressBookSettings.html @@ -0,0 +1,12 @@ +