mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-08-29 20:19:22 +03:00
Move to plugin #385
This commit is contained in:
parent
86ac71ea2e
commit
9789a2509f
13 changed files with 197 additions and 67 deletions
320
plugins/kolab/KolabAddressBook.php
Normal file
320
plugins/kolab/KolabAddressBook.php
Normal file
|
|
@ -0,0 +1,320 @@
|
|||
<?php
|
||||
|
||||
use RainLoop\Providers\AddressBook\Classes\Contact;
|
||||
use RainLoop\Providers\AddressBook\Classes\Property;
|
||||
use RainLoop\Providers\AddressBook\Enumerations\PropertyType;
|
||||
|
||||
class KolabAddressBook implements \RainLoop\Providers\AddressBook\AddressBookInterface
|
||||
{
|
||||
use \RainLoop\Providers\AddressBook\CardDAV;
|
||||
|
||||
protected
|
||||
$oImapClient,
|
||||
$sFolderName;
|
||||
|
||||
protected function MailClient() : \MailSo\Mail\MailClient
|
||||
{
|
||||
$oActions = \RainLoop\Api::Actions();
|
||||
$oMailClient = $oActions->MailClient();
|
||||
if (!$oMailClient->IsLoggined()) {
|
||||
$oActions->getAccountFromToken()->IncConnectAndLoginHelper($oActions->Plugins(), $oMailClient, $oActions->Config());
|
||||
}
|
||||
return $oMailClient;
|
||||
}
|
||||
|
||||
protected function ImapClient() : \MailSo\Imap\ImapClient
|
||||
{
|
||||
if (!$this->oImapClient) {
|
||||
$this->oImapClient = $this->MailClient()->ImapClient();
|
||||
}
|
||||
return $this->oImapClient;
|
||||
}
|
||||
|
||||
protected function FolderName() : string
|
||||
{
|
||||
if (!\is_string($this->sFolderName)) {
|
||||
$oActions = \RainLoop\Api::Actions();
|
||||
$oAccount = $oActions->getAccountFromToken();
|
||||
$sFolderName = (string) $oActions->SettingsProvider(true)->Load($oAccount)->GetConf('KolabContactFolder', '');
|
||||
|
||||
$metadata = $this->ImapClient()->FolderGetMetadata($sFolderName, [\MailSo\Imap\Enumerations\MetadataKeys::KOLAB_CTYPE]);
|
||||
if (!$metadata || 'contact' !== \array_shift($metadata)) {
|
||||
$sFolderName = '';
|
||||
// throw new \Exception("Invalid kolab contact folder: {$sFolderName}");
|
||||
}
|
||||
|
||||
$this->sFolderName = $sFolderName;
|
||||
}
|
||||
return $this->sFolderName;
|
||||
}
|
||||
|
||||
protected function SelectFolder() : bool
|
||||
{
|
||||
try {
|
||||
$sFolderName = $this->FolderName();
|
||||
if ($sFolderName) {
|
||||
$this->ImapClient()->FolderSelect($sFolderName);
|
||||
return true;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
\trigger_error("KolabAddressBook {$sFolderName} error: {$e->getMessage()}");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function fetchXCardFromMessage(\MailSo\Mail\Message $oMessage) : ?\Sabre\VObject\Component\VCard
|
||||
{
|
||||
$xCard = null;
|
||||
foreach ($oMessage->Attachments() ?: [] as $oAttachment) {
|
||||
if ('application/vcard+xml' === $oAttachment->MimeType()) {
|
||||
$result = $this->MailClient()->MessageMimeStream(function ($rResource) use (&$xCard) {
|
||||
if (\is_resource($rResource)) {
|
||||
$xCard = \Sabre\VObject\Reader::readXML($rResource);
|
||||
}
|
||||
}, $this->FolderName(), $oMessage->Uid(), $oAttachment->MimeIndex());
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $xCard;
|
||||
}
|
||||
|
||||
protected function MessageAsContact(\MailSo\Mail\Message $oMessage) : ?Contact
|
||||
{
|
||||
$oContact = new Contact;
|
||||
$oContact->IdContact = $oMessage->Uid();
|
||||
// $oContact->Display = isset($aItem['display']) ? (string) $aItem['display'] : '';
|
||||
$oContact->Changed = $oMessage->HeaderTimeStampInUTC();
|
||||
|
||||
$oFrom = $oMessage->From();
|
||||
if ($oFrom) {
|
||||
$oMail = $oFrom[0];
|
||||
$oProperty = new Property(PropertyType::EMAIl, $oMail->GetEmail());
|
||||
$oContact->Properties[] = $oProperty;
|
||||
$oProperty = new Property(PropertyType::FULLNAME, $oMail->GetDisplayName());
|
||||
// $oProperty = new Property(PropertyType::FULLNAME, $oMail->ToString());
|
||||
$oContact->Properties[] = $oProperty;
|
||||
// $oProperty = new Property(PropertyType::NICK_NAME, $oMail->GetDisplayName());
|
||||
// $oContact->Properties[] = $oProperty;
|
||||
}
|
||||
|
||||
// Fetch xCard attachment and populate $oContact with it
|
||||
$xCard = $this->fetchXCardFromMessage($oMessage);
|
||||
if ($xCard instanceof \Sabre\VObject\Component\VCard) {
|
||||
$oContact->PopulateByVCard($xCard);
|
||||
}
|
||||
|
||||
// Reset, else it is 'urn:uuid:01234567-89AB-CDEF-0123-456789ABCDEF'
|
||||
// $oContact->IdContactStr = $oMessage->Subject();
|
||||
|
||||
$oContact->UpdateDependentValues();
|
||||
|
||||
return $oContact;
|
||||
}
|
||||
|
||||
public function IsSupported() : bool
|
||||
{
|
||||
// Check $this->ImapClient()->IsSupported('METADATA')
|
||||
return true;
|
||||
}
|
||||
|
||||
public function Sync(array $oConfig) : bool
|
||||
{
|
||||
// TODO
|
||||
return false;
|
||||
}
|
||||
|
||||
public function Export(string $sEmail, string $sType = 'vcf') : bool
|
||||
{
|
||||
// TODO
|
||||
return false;
|
||||
}
|
||||
|
||||
public function ContactSave(string $sEmail, Contact $oContact) : bool
|
||||
{
|
||||
// TODO
|
||||
// $emails = $oContact->GetEmails();
|
||||
|
||||
if (!$this->SelectFolder()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$oContact->PopulateDisplayAndFullNameValue();
|
||||
|
||||
$iUID = $oContact->IdContact;
|
||||
|
||||
$oPrevMessage = $this->MailClient()->Message($this->FolderName(), $iUID);
|
||||
if ($oPrevMessage) {
|
||||
$oVCard = $this->fetchXCardFromMessage($oPrevMessage);
|
||||
} else {
|
||||
$oVCard = null;
|
||||
$iUID = 0;
|
||||
}
|
||||
|
||||
$oMessage = new \MailSo\Mime\Message();
|
||||
|
||||
$sEmail = '';
|
||||
if (isset($oVCard->EMAIL)) {
|
||||
foreach ($oVCard->EMAIL as $oProp) {
|
||||
$oTypes = $oProp ? $oProp['TYPE'] : null;
|
||||
$sValue = $oProp ? \trim($oProp->getValue()) : '';
|
||||
if ($sValue && (!$sEmail || ($oTypes && $oTypes->has('PREF')))) {
|
||||
$sEmail = $sValue;
|
||||
}
|
||||
}
|
||||
if ($sEmail) {
|
||||
$oMessage->SetFrom(new \MailSo\Mime\Email($sEmail, $oContact->Display));
|
||||
}
|
||||
}
|
||||
|
||||
$oMessage->SetSubject($oContact->GetUID());
|
||||
// $oMessage->SetDate(\time());
|
||||
$oMessage->Headers->AddByName('X-Kolab-Type', 'application/x-vnd.kolab.contact');
|
||||
$oMessage->Headers->AddByName('X-Kolab-Mime-Version', '3.0');
|
||||
// $oMessage->Headers->AddByName('User-Agent', 'SnappyMail');
|
||||
|
||||
$oPart = new \MailSo\Mime\Part;
|
||||
$oPart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, 'text/plain');
|
||||
$oPart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TRANSFER_ENCODING, '7Bit');
|
||||
$oPart->Body = "This is a Kolab Groupware object.\r\n"
|
||||
. "To view this object you will need an email client that can understand the Kolab Groupware format.\r\n"
|
||||
. "For a list of such email clients please visit\r\n"
|
||||
. "http://www.kolab.org/get-kolab";
|
||||
$oMessage->SubParts->append($oPart);
|
||||
|
||||
// Now the vCard
|
||||
$oPart = new \MailSo\Mime\Part;
|
||||
$oPart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, 'application/vcard+xml; name="kolab.xml"');
|
||||
// $oPart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TRANSFER_ENCODING, 'quoted-printable');
|
||||
$oPart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_DISPOSITION, 'attachment; filename="kolab.xml"');
|
||||
$oPart->Body = $oContact->ToXCard($oVCard/*, $oLogger*/);
|
||||
$oMessage->SubParts->append($oPart);
|
||||
|
||||
// Store Message
|
||||
$rMessageStream = \MailSo\Base\ResourceRegistry::CreateMemoryResource();
|
||||
$iMessageStreamSize = \MailSo\Base\Utils::MultipleStreamWriter(
|
||||
$oMessage->ToStream(false), array($rMessageStream), 8192, true, true);
|
||||
if (false !== $iMessageStreamSize) {
|
||||
\rewind($rMessageStream);
|
||||
$this->ImapClient()->MessageReplaceStream($this->sFolderName, $iUID, $rMessageStream, $iMessageStreamSize);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function DeleteContacts(string $sEmail, array $aContactIds) : bool
|
||||
{
|
||||
try {
|
||||
$this->MailClient()->MessageDelete(
|
||||
$this->FolderName(),
|
||||
new \MailSo\Imap\SequenceSet($aContactIds)
|
||||
);
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function DeleteAllContacts(string $sEmail) : bool
|
||||
{
|
||||
// Called by \RainLoop\Api::ClearUserData()
|
||||
// Not needed as the contacts are inside IMAP mailbox
|
||||
// $this->MailClient()->FolderClear($this->FolderName());
|
||||
return false;
|
||||
}
|
||||
|
||||
public function GetContacts(string $sEmail, int $iOffset = 0, int $iLimit = 20, string $sSearch = '', int &$iResultCount = 0) : array
|
||||
{
|
||||
$oParams = new \MailSo\Mail\MessageListParams;
|
||||
$oParams->sFolderName = $this->FolderName();
|
||||
$oParams->iOffset = $iOffset;
|
||||
$oParams->iLimit = $iLimit;
|
||||
if ($sSearch) {
|
||||
$oParams->sSearch = 'from='.$sSearch;
|
||||
}
|
||||
$oParams->sSort = 'FROM';
|
||||
// $oParams->iPrevUidNext = $this->GetActionParam('UidNext', 0);
|
||||
// $oParams->bUseThreads = false;
|
||||
|
||||
if (!\strlen($oParams->sFolderName)) {
|
||||
// return [];
|
||||
throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantGetMessageList);
|
||||
}
|
||||
|
||||
$this->ImapClient();
|
||||
|
||||
$aResult = [];
|
||||
|
||||
try
|
||||
{
|
||||
$oMessageList = $this->MailClient()->MessageList($oParams);
|
||||
foreach ($oMessageList as $oMessage) {
|
||||
$aResult[] = $this->MessageAsContact($oMessage);
|
||||
}
|
||||
}
|
||||
catch (\Throwable $oException)
|
||||
{
|
||||
throw $oException;
|
||||
throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantGetMessageList, $oException);
|
||||
}
|
||||
|
||||
return $aResult;
|
||||
}
|
||||
|
||||
public function GetContactByID(string $sEmail, $mID, bool $bIsStrID = false) : ?Contact
|
||||
{
|
||||
if ($bIsStrID) {
|
||||
$oMessage = null;
|
||||
} else {
|
||||
$oMessage = $this->MailClient()->Message($this->FolderName(), $mID);
|
||||
}
|
||||
return $oMessage ? $this->MessageAsContact($oMessage) : null;
|
||||
}
|
||||
|
||||
public function GetSuggestions(string $sEmail, string $sSearch, int $iLimit = 20) : array
|
||||
{
|
||||
$sSearch = \trim($sSearch);
|
||||
if (2 > \strlen($sSearch) || !$this->SelectFolder()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$sSearch = \MailSo\Imap\SearchCriterias::escapeSearchString($this->ImapClient(), $sSearch);
|
||||
$aUids = \array_slice(
|
||||
$this->ImapClient()->MessageSimpleSearch("FROM {$sSearch}"),
|
||||
0, $iLimit
|
||||
);
|
||||
|
||||
$aResult = [];
|
||||
foreach ($this->ImapClient()->Fetch(['BODY.PEEK[HEADER.FIELDS (FROM)]'], \implode(',', $aUids), true) as $oFetchResponse) {
|
||||
$oHeaders = new \MailSo\Mime\HeaderCollection($oFetchResponse->GetHeaderFieldsValue());
|
||||
$oFrom = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::FROM_, true);
|
||||
foreach ($oFrom as $oMail) {
|
||||
$aResult[] = [$oMail->GetEmail(), $oMail->GetDisplayName()];
|
||||
}
|
||||
}
|
||||
|
||||
return $aResult;
|
||||
}
|
||||
|
||||
public function IncFrec(string $sEmail, array $aEmails, bool $bCreateAuto = true) : bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function Test() : string
|
||||
{
|
||||
$sResult = '';
|
||||
try
|
||||
{
|
||||
// $sResult = 'Unknown error';
|
||||
}
|
||||
catch (\Throwable $oException)
|
||||
{
|
||||
$sResult = $oException->getMessage();
|
||||
if (!\is_string($sResult) || empty($sResult)) {
|
||||
$sResult = 'Unknown error';
|
||||
}
|
||||
}
|
||||
|
||||
return $sResult;
|
||||
}
|
||||
}
|
||||
20
plugins/kolab/LICENSE
Normal file
20
plugins/kolab/LICENSE
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 RainLoop Team
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
89
plugins/kolab/index.php
Normal file
89
plugins/kolab/index.php
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
class KolabPlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||
{
|
||||
const
|
||||
NAME = 'Kolab',
|
||||
VERSION = '0.1',
|
||||
RELEASE = '2022-05-13',
|
||||
CATEGORY = 'Security',
|
||||
DESCRIPTION = 'Get contacts suggestions from Kolab.',
|
||||
REQUIRED = '2.15.4';
|
||||
|
||||
public function Init() : void
|
||||
{
|
||||
// \RainLoop\Api::Config()->Set('contacts', 'enable', true);
|
||||
if (\RainLoop\Api::Config()->Get('contacts', 'enable', false)) {
|
||||
$this->addHook('filter.app-data', 'FilterAppData');
|
||||
$this->addHook('main.fabrica', 'MainFabrica');
|
||||
|
||||
$this->addJs('js/settings.js');
|
||||
$this->addTemplate('templates/KolabSettings.html');
|
||||
$this->addJsonHook('KolabFolder', 'DoKolabFolder');
|
||||
}
|
||||
}
|
||||
|
||||
public function Supported() : 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());
|
||||
}
|
||||
|
||||
public function DoKolabFolder() : array
|
||||
{
|
||||
// \error_log(\print_r($this->Manager()->Actions()->GetActionParams(), 1));
|
||||
$sValue = $this->jsonParam('contact');
|
||||
$oSettings = $this->Settings();
|
||||
if (\is_string($sValue)) {
|
||||
$oSettings->SetConf('KolabContactFolder', $sValue);
|
||||
$this->SettingsProvider()->Save($this->Account(), $oSettings);
|
||||
}
|
||||
return $this->jsonResponse(__FUNCTION__, true);
|
||||
}
|
||||
|
||||
public function FilterAppData($bAdmin, &$aResult) : void
|
||||
{
|
||||
// if ImapClient->IsSupported('METADATA')
|
||||
if (\is_array($aResult) && !empty($aResult['Auth'])) {
|
||||
$aResult['Capa']['Kolab'] = true;
|
||||
$aResult['KolabContactFolder'] = (string) $this->Settings()->GetConf('KolabContactFolder', '');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $mResult
|
||||
*/
|
||||
public function MainFabrica(string $sName, &$mResult)
|
||||
{
|
||||
/*
|
||||
if ('suggestions' === $sName) {
|
||||
if (!\is_array($mResult)) {
|
||||
$mResult = array();
|
||||
}
|
||||
// $sFolder = \trim($this->Config()->Get('plugin', 'mailbox', ''));
|
||||
// if ($sFolder) {
|
||||
require_once __DIR__ . '/KolabContactsSuggestions.php';
|
||||
$mResult[] = new KolabContactsSuggestions();
|
||||
// }
|
||||
}
|
||||
*/
|
||||
if ('address-book' === $sName) {
|
||||
require_once __DIR__ . '/KolabAddressBook.php';
|
||||
$mResult = new KolabAddressBook();
|
||||
}
|
||||
}
|
||||
}
|
||||
54
plugins/kolab/js/settings.js
Normal file
54
plugins/kolab/js/settings.js
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
(rl => {
|
||||
|
||||
const getFolders = type => {
|
||||
const
|
||||
aResult = [{
|
||||
id: '',
|
||||
name: '',
|
||||
}],
|
||||
foldersWalk = folders => {
|
||||
folders.forEach(oItem => {
|
||||
if (type === oItem.kolabType()) {
|
||||
aResult.push({
|
||||
id: oItem.fullName,
|
||||
name: oItem.fullName
|
||||
});
|
||||
}
|
||||
if (oItem.subFolders.length) {
|
||||
foldersWalk(oItem.subFolders());
|
||||
}
|
||||
});
|
||||
};
|
||||
foldersWalk(rl.app.folderList());
|
||||
return aResult;
|
||||
};
|
||||
|
||||
|
||||
class KolabSettings /* extends AbstractViewSettings */
|
||||
{
|
||||
constructor() {
|
||||
this.contactFolder = ko.observable(rl.settings.get('KolabContactFolder'));
|
||||
// rl.app.FolderUserStore.hasCapability('METADATA');
|
||||
this.contactFolder.subscribe(value => {
|
||||
rl.pluginRemoteRequest(()=>{}, 'KolabFolder', {
|
||||
contact: value
|
||||
});
|
||||
});
|
||||
this.contactFoldersList = ko.computed(() => getFolders('contact'), {'pure':true});
|
||||
// this.eventFoldersList = ko.computed(() => getFolders('event'), {'pure':true});
|
||||
// this.taskFoldersList = ko.computed(() => getFolders('task'), {'pure':true});
|
||||
// this.noteFoldersList = ko.computed(() => getFolders('note'), {'pure':true});
|
||||
// this.fileFoldersList = ko.computed(() => getFolders('file'), {'pure':true});
|
||||
// this.journalFoldersList = ko.computed(() => getFolders('journal'), {'pure':true});
|
||||
// this.configFoldersList = ko.computed(() => getFolders('configuration'), {'pure':true});
|
||||
}
|
||||
}
|
||||
|
||||
rl.addSettingsViewModel(
|
||||
KolabSettings,
|
||||
'KolabSettings',
|
||||
'Kolab',
|
||||
'kolab'
|
||||
);
|
||||
|
||||
})(window.rl);
|
||||
12
plugins/kolab/templates/KolabSettings.html
Normal file
12
plugins/kolab/templates/KolabSettings.html
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<div class="b-settings-kolab form-horizontal">
|
||||
<div class="legend">Kolab</div>
|
||||
<div class="form-horizontal">
|
||||
|
||||
<div class="control-group">
|
||||
<label data-i18n="KOLAB/CONTACTS_FOLDER"></label>
|
||||
<select data-bind="options: contactFoldersList, value: contactFolder,
|
||||
optionsText: 'name', optionsValue: 'id'"></select>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
Loading…
Add table
Add a link
Reference in a new issue