diff --git a/dev/Stores/User/App.js b/dev/Stores/User/App.js index 80b295ed1..8e8a73695 100644 --- a/dev/Stores/User/App.js +++ b/dev/Stores/User/App.js @@ -53,7 +53,7 @@ this.contactsIsAllowed = ko.observable(false); - this.attahcmentsActions = ko.observableArray([]); + this.attachmentsActions = ko.observableArray([]); this.devEmail = ''; this.devPassword = ''; @@ -70,8 +70,8 @@ this.contactsIsAllowed(!!Settings.settingsGet('ContactsIsAllowed')); - var mAttahcmentsActions = Settings.settingsGet('AttahcmentsActions'); - this.attahcmentsActions(Utils.isNonEmptyArray(mAttahcmentsActions) ? mAttahcmentsActions : []); + var mAttachmentsActions = Settings.settingsGet('AttachmentsActions'); + this.attachmentsActions(Utils.isNonEmptyArray(mAttachmentsActions) ? mAttachmentsActions : []); this.devEmail = Settings.settingsGet('DevEmail'); this.devPassword = Settings.settingsGet('DevPassword'); diff --git a/dev/View/User/MailBox/MessageView.js b/dev/View/User/MailBox/MessageView.js index a611b78a6..2151d24cd 100644 --- a/dev/View/User/MailBox/MessageView.js +++ b/dev/View/User/MailBox/MessageView.js @@ -65,7 +65,7 @@ this.pswp = null; - this.attahcmentsActions = AppStore.attahcmentsActions; + this.attachmentsActions = AppStore.attachmentsActions; this.message = MessageStore.message; this.messageListChecked = MessageStore.messageListChecked; @@ -90,18 +90,18 @@ this.showAttachmnetControls = ko.observable(false); this.allowAttachmnetControls = ko.computed(function () { - return 0 < this.attahcmentsActions().length; + return 0 < this.attachmentsActions().length; }, this); this.downloadAsZipAllowed = ko.computed(function () { - return -1 < Utils.inArray('zip', this.attahcmentsActions()); + return -1 < Utils.inArray('zip', this.attachmentsActions()); }, this); this.downloadAsZipLoading = ko.observable(false); this.downloadAsZipError = ko.observable(false).extend({'falseTimeout': 7000}); this.saveToOwnCloudAllowed = ko.computed(function () { - return -1 < Utils.inArray('owncloud', this.attahcmentsActions()); + return -1 < Utils.inArray('owncloud', this.attachmentsActions()); }, this); this.saveToOwnCloudLoading = ko.observable(false); @@ -123,7 +123,7 @@ }, this); this.saveToDropboxAllowed = ko.computed(function () { - return -1 < Utils.inArray('dropbox', this.attahcmentsActions()); + return -1 < Utils.inArray('dropbox', this.attachmentsActions()); }, this); this.saveToDropboxLoading = ko.observable(false); diff --git a/package.json b/package.json index 2ecee54df..0cce4a40c 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "RainLoop", "title": "RainLoop Webmail", "version": "1.9.0", - "release": "323", + "release": "324", "description": "Simple, modern & fast web-based email client", "homepage": "http://rainloop.net", "main": "gulpfile.js", diff --git a/rainloop/v/0.0.0/app/handle.php b/rainloop/v/0.0.0/app/handle.php index ebb703d10..1de59f78c 100644 --- a/rainloop/v/0.0.0/app/handle.php +++ b/rainloop/v/0.0.0/app/handle.php @@ -22,7 +22,7 @@ if (!\defined('RAINLOOP_APP_LIBRARIES_PATH')) function rainLoopSplAutoloadNamespaces() { return RAINLOOP_INCLUDE_AS_API_DEF ? array('RainLoop') : - array('RainLoop', 'Facebook', 'GuzzleHttp', 'Symfony', 'PHPThumb', 'Sabre'); + array('RainLoop', 'Facebook', 'GuzzleHttp', 'PHPThumb', 'SabreForRainLoop'); } /** @@ -41,7 +41,7 @@ if (!\defined('RAINLOOP_APP_LIBRARIES_PATH')) { if (0 === \strpos($sClassName, $sNamespaceName.'\\')) { - if ('Sabre' === $sNamespaceName && !RAINLOOP_MB_SUPPORTED && !defined('RL_MB_FIXED')) + if ('SabreForRainLoop' === $sNamespaceName && !RAINLOOP_MB_SUPPORTED && !defined('RL_MB_FIXED')) { \define('RL_MB_FIXED', true); include_once RAINLOOP_APP_LIBRARIES_PATH.'RainLoop/Common/MbStringFix.php'; diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Base/DateTimeHelper.php b/rainloop/v/0.0.0/app/libraries/MailSo/Base/DateTimeHelper.php index 5896a536b..caab6bdaf 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Base/DateTimeHelper.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Base/DateTimeHelper.php @@ -86,4 +86,21 @@ class DateTimeHelper $oDateTime = \DateTime::createFromFormat('Y-m-d H:i:s O', \trim($sDateTime), \MailSo\Base\DateTimeHelper::GetUtcTimeZoneObject()); return $oDateTime ? $oDateTime->getTimestamp() : 0; } + + /** + * Parse date string formated as "2015-05-08T14:32:18.483-07:00" + * + * @param string $sDateTime + * + * @return int + */ + public static function TryToParseSpecEtagFormat($sDateTime) + { + $sDateTime = \trim(\preg_replace('/ \([a-zA-Z0-9]+\)$/', '', \trim($sDateTime))); + $sDateTime = \trim(\preg_replace('/(:[\d]{2})\.[\d]{3}/', '$1', \trim($sDateTime))); + $sDateTime = \trim(\preg_replace('/(-[\d]{2})T([\d]{2}:)/', '$1 $2', \trim($sDateTime))); + $sDateTime = \trim(\preg_replace('/([\-+][\d]{2}):([\d]{2})$/', ' $1$2', \trim($sDateTime))); + + return \MailSo\Base\DateTimeHelper::ParseDateStringType1($sDateTime); + } } diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Log/Logger.php b/rainloop/v/0.0.0/app/libraries/MailSo/Log/Logger.php index e5a0a37f4..475d8c39c 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Log/Logger.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Log/Logger.php @@ -165,7 +165,7 @@ class Logger extends \MailSo\Base\Collection */ public function AddSecret($sWord) { - if (0 < \strlen(\trim($sWord))) + if (\is_string($sWord) && 0 < \strlen(\trim($sWord))) { $this->aSecretWords[] = $sWord; $this->aSecretWords = \array_unique($this->aSecretWords); diff --git a/rainloop/v/0.0.0/app/libraries/PHPThumb/PHPThumb.php b/rainloop/v/0.0.0/app/libraries/PHPThumb/PHPThumb.php index 7521b9d90..a86c8c91e 100644 --- a/rainloop/v/0.0.0/app/libraries/PHPThumb/PHPThumb.php +++ b/rainloop/v/0.0.0/app/libraries/PHPThumb/PHPThumb.php @@ -27,11 +27,6 @@ abstract class PHPThumb */ protected $fileName; - /** - * @var \Symfony\Component\Filesystem\Filesystem - */ - protected $filesystem; - /** * What the file format is (mime-type) * @@ -59,7 +54,6 @@ abstract class PHPThumb */ public function __construct($fileName, array $options = array(), array $plugins = array()) { - $this->filesystem = new \Symfony\Component\Filesystem\Filesystem(); $this->fileName = $fileName; $this->remoteImage = false; @@ -89,9 +83,9 @@ abstract class PHPThumb return true; } - if($this->filesystem->exists($filename)) { - return true; - } + if (file_exists($filename)) { + return true; + } return false; } diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php index b2835562f..6e915c727 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php @@ -1276,6 +1276,14 @@ class Actions return $this->GetAccountFromCustomToken($this->getAuthToken(), $bThrowExceptionOnFalse); } + /** + * @return bool + */ + public function IsOpen() + { + return !$this->PremProvider(); + } + /** * @param bool $bAdmin * @param string $sAuthAccountHash = '' @@ -1349,7 +1357,7 @@ class Actions 'PremType' => false, 'Admin' => array(), 'Capa' => array(), - 'AttahcmentsActions' => array(), + 'AttachmentsActions' => array(), 'Plugins' => array() ); @@ -1357,17 +1365,17 @@ class Actions { if (!!\class_exists('ZipArchive')) { - $aResult['AttahcmentsActions'][] = 'zip'; + $aResult['AttachmentsActions'][] = 'zip'; } if (\RainLoop\Utils::IsOwnCloudLoggedIn() && \class_exists('\\OCP\\Files')) { - $aResult['AttahcmentsActions'][] = 'owncloud'; + $aResult['AttachmentsActions'][] = 'owncloud'; } if ($oConfig->Get('social', 'dropbox_enable', false) && 0 < \strlen(\trim($oConfig->Get('social', 'dropbox_api_key', '')))) { - $aResult['AttahcmentsActions'][] = 'dropbox'; + $aResult['AttachmentsActions'][] = 'dropbox'; } } @@ -2833,7 +2841,7 @@ class Actions if (\RainLoop\Utils::IsOwnCloudLoggedIn() && \class_exists('\\OCP\\Files')) { $sSaveFolder = $this->Config()->Get('labs', 'owncloud_save_folder', ''); - if (!empty($sSaveFolder)) + if (empty($sSaveFolder)) { $sSaveFolder = 'Attachments'; } @@ -6776,6 +6784,8 @@ class Actions $oContact->Etag = \md5($oContact->ToVCard()); } + $oContact->PopulateDisplayAndFullNameValue(true); + $bResult = $oAddressBookProvider->ContactSave($oAccount->ParentEmailHelper(), $oContact); } diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook.php index 8ea5a138e..dfe914de1 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook.php @@ -318,7 +318,7 @@ class AddressBook extends \RainLoop\Providers\AbstractProvider { $iCount = 0; - if (\class_exists('Sabre\DAV\Client') && $this->IsActive() && \is_string($sVcfData)) + if (\class_exists('SabreForRainLoop\DAV\Client') && $this->IsActive() && \is_string($sVcfData)) { $sVcfData = \trim($sVcfData); if ("\xef\xbb\xbf" === \substr($sVcfData, 0, 3)) @@ -329,7 +329,7 @@ class AddressBook extends \RainLoop\Providers\AbstractProvider $oVCardSplitter = null; try { - $oVCardSplitter = new \Sabre\VObject\Splitter\VCard($sVcfData); + $oVCardSplitter = new \SabreForRainLoop\VObject\Splitter\VCard($sVcfData); } catch (\Exception $oExc) { @@ -344,13 +344,13 @@ class AddressBook extends \RainLoop\Providers\AbstractProvider while ($oVCard = $oVCardSplitter->getNext()) { - if ($oVCard instanceof \Sabre\VObject\Component\VCard) + if ($oVCard instanceof \SabreForRainLoop\VObject\Component\VCard) { \MailSo\Base\Utils::ResetTimeLimit(); if (empty($oVCard->UID)) { - $oVCard->UID = \Sabre\DAV\UUIDUtil::getUUID(); + $oVCard->UID = \SabreForRainLoop\DAV\UUIDUtil::getUUID(); } $oContact->PopulateByVCard($oVCard->UID, $oVCard->serialize()); diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/Classes/Contact.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/Classes/Contact.php index 7fd425139..41085ff1b 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/Classes/Contact.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/Classes/Contact.php @@ -66,8 +66,9 @@ class Contact $this->Etag = ''; } - public function UpdateDependentValues() + public function PopulateDisplayAndFullNameValue($bForceFullNameReplace = false) { + $sFullName = ''; $sLastName = ''; $sFirstName = ''; $sEmail = ''; @@ -100,8 +101,12 @@ class Contact { $sFirstName = $oProperty->Value; } - else if (\in_array($oProperty->Type, array( - PropertyType::FULLNAME, PropertyType::PHONE + else if ('' === $sFullName && PropertyType::FULLNAME === $oProperty->Type) + { + $sFullName = $oProperty->Value; + } + else if ('' === $sOther && \in_array($oProperty->Type, array( + PropertyType::PHONE ))) { $sOther = $oProperty->Value; @@ -110,25 +115,26 @@ class Contact } } - if (empty($this->IdContactStr)) - { - $this->RegenerateContactStr(); - } + $sDisplay = $bForceFullNameReplace ? '' : \trim($sFullName); - $sDisplay = ''; - if (0 < \strlen($sLastName) || 0 < \strlen($sFirstName)) + if ('' === $sDisplay && (0 < \strlen($sLastName) || 0 < \strlen($sFirstName))) { $sDisplay = \trim($sFirstName.' '.$sLastName); } - if ('' === $sDisplay && 0 < \strlen($sEmail)) + if ('' === $sDisplay) + { + $sDisplay = \trim($sFullName); + } + + if ('' === $sDisplay) { $sDisplay = \trim($sEmail); } if ('' === $sDisplay) { - $sDisplay = $sOther; + $sDisplay = \trim($sOther); } $this->Display = \trim($sDisplay); @@ -145,13 +151,23 @@ class Contact } } + public function UpdateDependentValues() + { + if (empty($this->IdContactStr)) + { + $this->RegenerateContactStr(); + } + + $this->PopulateDisplayAndFullNameValue(); + } + /** * @return array */ public function RegenerateContactStr() { - $this->IdContactStr = \class_exists('Sabre\DAV\Client') ? - \Sabre\DAV\UUIDUtil::getUUID() : \MailSo\Base\Utils::Md5Rand(); + $this->IdContactStr = \class_exists('SabreForRainLoop\DAV\Client') ? + \SabreForRainLoop\DAV\UUIDUtil::getUUID() : \MailSo\Base\Utils::Md5Rand(); } /** @@ -186,7 +202,7 @@ class Contact { $this->UpdateDependentValues(); - if (!\class_exists('Sabre\DAV\Client')) + if (!\class_exists('SabreForRainLoop\DAV\Client')) { return ''; } @@ -201,7 +217,7 @@ class Contact { try { - $oVCard = \Sabre\VObject\Reader::read($sPreVCard); + $oVCard = \SabreForRainLoop\VObject\Reader::read($sPreVCard); } catch (\Exception $oExc) { @@ -213,9 +229,14 @@ class Contact } } +// if ($oLogger) +// { +// $oLogger->WriteDump($sPreVCard); +// } + if (!$oVCard) { - $oVCard = new \Sabre\VObject\Component\VCard(); + $oVCard = new \SabreForRainLoop\VObject\Component\VCard(); } $oVCard->VERSION = '3.0'; @@ -223,7 +244,7 @@ class Contact unset($oVCard->FN, $oVCard->EMAIL, $oVCard->TEL, $oVCard->URL, $oVCard->NICKNAME); - $sFirstName = $sLastName = $sMiddleName = $sSuffix = $sPrefix = ''; + $sUid = $sFirstName = $sLastName = $sMiddleName = $sSuffix = $sPrefix = ''; foreach ($this->Properties as /* @var $oProperty \RainLoop\Providers\AddressBook\Classes\Property */ &$oProperty) { if ($oProperty) @@ -240,6 +261,9 @@ class Contact case PropertyType::NOTE: $oVCard->NOTE = $oProperty->Value; break; + case PropertyType::UID: + $sUid = $oProperty->Value; + break; case PropertyType::FIRST_NAME: $sFirstName = $oProperty->Value; break; @@ -278,7 +302,7 @@ class Contact } } - $oVCard->UID = $this->IdContactStr; + $oVCard->UID = empty($sUid) ? $this->IdContactStr : $sUid; $oVCard->N = array($sLastName, $sFirstName, $sMiddleName, $sPrefix, $sSuffix); $oVCard->REV = \gmdate('Ymd', $this->Changed).'T'.\gmdate('His', $this->Changed).'Z'; @@ -505,7 +529,7 @@ class Contact $this->Properties = array(); - if (!\class_exists('Sabre\DAV\Client')) + if (!\class_exists('SabreForRainLoop\DAV\Client')) { return false; } @@ -515,9 +539,11 @@ class Contact $this->Etag = $sEtag; } + $this->IdContactStr = $sUid; + try { - $oVCard = \Sabre\VObject\Reader::read($sVCard); + $oVCard = \SabreForRainLoop\VObject\Reader::read($sVCard); } catch (\Exception $oExc) { @@ -526,18 +552,24 @@ class Contact $oLogger->WriteException($oExc); $oLogger->WriteDump($sVCard); } - - $this->IdContactStr = $sUid; } +// if ($oLogger) +// { +// $oLogger->WriteDump($sVCard); +// } + + $bOwnCloud = false; $aProperties = array(); + if ($oVCard) { + $bOwnCloud = empty($oVCard->PRODID) ? false : + false !== \strpos(\strtolower($oVCard->PRODID), 'owncloud'); + $bOldVersion = empty($oVCard->VERSION) ? false : \in_array((string) $oVCard->VERSION, array('2.1', '2.0', '1.0')); - $this->IdContactStr = $oVCard->UID ? (string) $oVCard->UID : \Sabre\DAV\UUIDUtil::getUUID(); - if (isset($oVCard->FN) && '' !== \trim($oVCard->FN)) { $sValue = $this->getPropertyValueHelper($oVCard->FN, $bOldVersion); @@ -612,6 +644,14 @@ class Contact $this->addArrayPropertyHelper($aProperties, $oVCard->TEL, PropertyType::PHONE); } + $sUidValue = $oVCard->UID ? (string) $oVCard->UID : \SabreForRainLoop\DAV\UUIDUtil::getUUID(); + $aProperties[] = new Property(PropertyType::UID, $sUidValue); + + if (empty($this->IdContactStr)) + { + $this->IdContactStr = $sUidValue; + } + $this->Properties = $aProperties; } diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/Classes/Property.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/Classes/Property.php index a301ffe2f..8356d5e8d 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/Classes/Property.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/Classes/Property.php @@ -10,7 +10,7 @@ class Property * @var int */ public $IdProperty; - + /** * @var int */ @@ -49,7 +49,7 @@ class Property public function Clear() { $this->IdProperty = 0; - + $this->Type = PropertyType::UNKNOWN; $this->TypeStr = ''; @@ -59,6 +59,15 @@ class Property $this->Frec = 0; } + /** + * @return bool + */ + public function IsName() + { + return \in_array($this->Type, array(PropertyType::FULLNAME, PropertyType::FIRST_NAME, + PropertyType::LAST_NAME, PropertyType::MIDDLE_NAME, PropertyType::NICK_NAME)); + } + /** * @return bool */ @@ -66,7 +75,7 @@ class Property { return PropertyType::EMAIl === $this->Type; } - + /** * @return bool */ @@ -111,7 +120,7 @@ class Property $this->Value = \trim($this->Value); $this->ValueCustom = \trim($this->ValueCustom); $this->TypeStr = \trim($this->TypeStr); - + if (0 < \strlen($this->Value)) { // lower @@ -120,6 +129,11 @@ class Property $this->Value = \MailSo\Base\Utils::StrToLowerIfAscii($this->Value); } + if ($this->IsName()) + { + $this->Value = \preg_replace('/[\s]+/u', ' ', $this->Value); + } + // phones clear value for searching if ($this->IsPhone()) { diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/Enumerations/PropertyType.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/Enumerations/PropertyType.php index 4068e4abc..5b1bdae94 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/Enumerations/PropertyType.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/Enumerations/PropertyType.php @@ -6,8 +6,9 @@ class PropertyType { const UNKNOWN = 0; + const UID = 9; const FULLNAME = 10; - + const FIRST_NAME = 15; const LAST_NAME = 16; const MIDDLE_NAME = 17; @@ -21,7 +22,7 @@ class PropertyType // const MOBILE = 32; // const FAX = 33; const WEB_PAGE = 32; - + const BIRTHDAY = 40; const FACEBOOK = 90; diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/PdoAddressBook.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/PdoAddressBook.php index b2100bc05..21fd6e76d 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/PdoAddressBook.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/PdoAddressBook.php @@ -117,11 +117,14 @@ class PdoAddressBook try { $this->oLogger->Write('PROPFIND '.$sPath, \MailSo\Log\Enumerations\Type::INFO, 'DAV'); + $aResponse = $oClient->propFind($sPath, array( '{DAV:}getlastmodified', + '{DAV:}resourcetype', '{DAV:}getetag' ), 1); +// $this->oLogger->WriteDump($aResponse); } catch (\Exception $oException) { @@ -137,10 +140,11 @@ class PdoAddressBook if (!empty($sKey) && is_array($aItem)) { $aItem = \array_change_key_case($aItem, \CASE_LOWER); - if (isset($aItem['{dav:}getetag'], $aItem['{dav:}getlastmodified'])) + if (isset($aItem['{dav:}getetag'])) { $aMatch = array(); - if (\preg_match('/\/([^\/?]+)$/', $sKey, $aMatch) && !empty($aMatch[1])) + if (\preg_match('/\/([^\/?]+)$/', $sKey, $aMatch) && !empty($aMatch[1]) && + (!$aItem['{dav:}resourcetype'] || !$aItem['{dav:}resourcetype']->is('{DAV:}collection'))) { $sVcfFileName = \urldecode(\urldecode($aMatch[1])); $sKeyID = \preg_replace('/\.vcf$/i', '', $sVcfFileName); @@ -150,10 +154,23 @@ class PdoAddressBook 'uid' => $sKeyID, 'vcf' => $sVcfFileName, 'etag' => \trim(\trim($aItem['{dav:}getetag']), '"\''), - 'lastmodified' => $aItem['{dav:}getlastmodified'], - 'changed' => \MailSo\Base\DateTimeHelper::ParseRFC2822DateString($aItem['{dav:}getlastmodified']) + 'lastmodified' => '', + 'changed' => 0 ); + if (isset($aItem['{dav:}getlastmodified'])) + { + $mResult[$sKeyID]['lastmodified'] = $aItem['{dav:}getlastmodified']; + $mResult[$sKeyID]['changed'] = \MailSo\Base\DateTimeHelper::ParseRFC2822DateString( + $aItem['{dav:}getlastmodified']); + } + else + { + $mResult[$sKeyID]['changed'] = \MailSo\Base\DateTimeHelper::TryToParseSpecEtagFormat($mResult[$sKeyID]['etag']); + $mResult[$sKeyID]['lastmodified'] = 0 < $mResult[$sKeyID]['changed'] ? + \gmdate('c', $mResult[$sKeyID]['changed']) : ''; + } + $mResult[$sKeyID]['changed_'] = \gmdate('c', $mResult[$sKeyID]['changed']); } } @@ -168,10 +185,10 @@ class PdoAddressBook { \MailSo\Base\Utils::ResetTimeLimit(); - $this->oLogger->Write($sCmd.' '.$sUrl.('PUT' === $sCmd && null !== $mData ? ' ('.\strlen($mData).')' : ''), + $this->oLogger->Write($sCmd.' '.$sUrl.(('PUT' === $sCmd || 'POST' === $sCmd) && null !== $mData ? ' ('.\strlen($mData).')' : ''), \MailSo\Log\Enumerations\Type::INFO, 'DAV'); - if ('PUT' === $sCmd) + if ('PUT' === $sCmd || 'POST' === $sCmd) { $this->oLogger->Write($mData, \MailSo\Log\Enumerations\Type::INFO, 'DAV'); } @@ -179,13 +196,21 @@ class PdoAddressBook $oResponse = false; try { - $oResponse = 'PUT' === $sCmd && null !== $mData ? - $oClient->request($sCmd, $sUrl, $mData) : $oClient->request($sCmd, $sUrl); - - if ('GET' === $sCmd && false) + if (('PUT' === $sCmd || 'POST' === $sCmd) && null !== $mData) { - $this->oLogger->WriteDump($oResponse, \MailSo\Log\Enumerations\Type::INFO, 'DAV'); + $oResponse = $oClient->request($sCmd, $sUrl, $mData, array( + 'Content-Type' => 'text/vcard; charset=utf-8' + )); } + else + { + $oResponse = $oClient->request($sCmd, $sUrl); + } + +// if ('GET' === $sCmd) +// { +// $this->oLogger->WriteDump($oResponse, \MailSo\Log\Enumerations\Type::INFO, 'DAV'); +// } } catch (\Exception $oException) { @@ -196,24 +221,297 @@ class PdoAddressBook } /** - * @param string $sEmail - * @param string $sUrl + * @param \SabreForRainLoop\DAV\Client $oClient + * @param string $sPath + * + * @return bool + */ + private function detectionPropFind($oClient, $sPath) + { + $aResponse = null; + + try + { + $this->oLogger->Write('PROPFIND '.$sPath, \MailSo\Log\Enumerations\Type::INFO, 'DAV'); + + $aResponse = $oClient->propFind($sPath, array( + '{DAV:}current-user-principal', + '{DAV:}resourcetype', + '{DAV:}displayname', + '{urn:ietf:params:xml:ns:carddav}addressbook-home-set' + ), 1); + +// $this->oLogger->WriteDump($aResponse); + } + catch (\Exception $oException) + { + $this->oLogger->WriteException($oException); + } + + return $aResponse; + } + + /** + * @param \SabreForRainLoop\DAV\Client $oClient * @param string $sUser * @param string $sPassword * @param string $sProxy = '' * + * @return array + */ + private function getContactsPaths(&$oClient, $sUser, $sPassword, $sProxy = '') + { + $aContactsPaths = array(); + + $sCurrentUserPrincipal = ''; + $sAddressbookHomeSet = ''; + +// [{DAV:}current-user-principal] => /cloud/remote.php/carddav/principals/admin/ +// [{urn:ietf:params:xml:ns:carddav}addressbook-home-set] => /cloud/remote.php/carddav/addressbooks/admin/ + + if (!$oClient) + { + return $aContactsPaths; + } + + $aResponse = $this->detectionPropFind($oClient, '/.well-known/carddav'); + + $sNextPath = ''; + $sFirstNextPath = ''; + if (\is_array($aResponse)) + { + foreach ($aResponse as $sKey => $aItem) + { + if (empty($sAddressbookHomeSet) && !empty($aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set']) && + false === \strpos($aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set'], '/calendar-proxy')) + { + $sAddressbookHomeSet = $aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set']; + continue; + } + + if (empty($sCurrentUserPrincipal) && !empty($aItem['{DAV:}current-user-principal'])) + { + $sCurrentUserPrincipal = $aItem['{DAV:}current-user-principal']; + continue; + } + + if (!empty($sKey)) + { + $sFirstNextPath = $sKey; + + $oResourceType = isset($aItem['{DAV:}resourcetype']) ? $aItem['{DAV:}resourcetype'] : null; + /* @var $oResourceType \SabreForRainLoop\DAV\Property\ResourceType */ + if ($oResourceType && $oResourceType->is('{DAV:}collection')) + { + $sNextPath = $sKey; + continue; + } + } + } + + if (empty($sNextPath) && empty($sCurrentUserPrincipal) && empty($sAddressbookHomeSet) && !empty($sFirstNextPath)) + { + $sNextPath = $sFirstNextPath; + } + } + + if (empty($sCurrentUserPrincipal) && empty($sAddressbookHomeSet)) + { + if (empty($sNextPath)) + { + return $aContactsPaths; + } + else + { + if (\preg_match('/^http[s]?:\/\//i', $sNextPath)) + { + $oClient = $this->getDavClientFromUrl($sNextPath, $sUser, $sPassword, $sProxy); + if ($oClient) + { + $sNextPath = $oClient->__UrlPath__; + } + else + { + return $aContactsPaths; + } + } + + $aResponse = $this->detectionPropFind($oClient, $sNextPath); + if (\is_array($aResponse)) + { + foreach ($aResponse as $sKey => $aItem) + { + if (empty($sAddressbookHomeSet) && !empty($aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set']) && + false === \strpos($aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set'], '/calendar-proxy')) + { + $sAddressbookHomeSet = $aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set']; + continue; + } + + if (empty($sCurrentUserPrincipal) && !empty($aItem['{DAV:}current-user-principal'])) + { + $sCurrentUserPrincipal = $aItem['{DAV:}current-user-principal']; + continue; + } + } + } + } + } + + if (empty($sAddressbookHomeSet)) + { + if (empty($sCurrentUserPrincipal)) + { + return $aContactsPaths; + } + else + { + if (\preg_match('/^http[s]?:\/\//i', $sCurrentUserPrincipal)) + { + $oClient = $this->getDavClientFromUrl($sCurrentUserPrincipal, $sUser, $sPassword, $sProxy); + if ($oClient) + { + $sCurrentUserPrincipal = $oClient->__UrlPath__; + } + else + { + return $aContactsPaths; + } + } + + $aResponse = $this->detectionPropFind($oClient, $sCurrentUserPrincipal); + if (\is_array($aResponse)) + { + foreach ($aResponse as $sKey => $aItem) + { + if (empty($sAddressbookHomeSet) && !empty($aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set']) && + false === \strpos($aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set'], '/calendar-proxy')) + { + $sAddressbookHomeSet = $aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set']; + continue; + } + } + } + } + } + + if (empty($sAddressbookHomeSet)) + { + return $aContactsPaths; + } + else + { + if (\preg_match('/^http[s]?:\/\//i', $sAddressbookHomeSet)) + { + $oClient = $this->getDavClientFromUrl($sAddressbookHomeSet, $sUser, $sPassword, $sProxy); + if ($oClient) + { + $sAddressbookHomeSet = $oClient->__UrlPath__; + } + else + { + return $aContactsPaths; + } + } + + $aResponse = $this->detectionPropFind($oClient, $sAddressbookHomeSet); + if (\is_array($aResponse)) + { + foreach ($aResponse as $sKey => $aItem) + { + if (!empty($sKey) && $aItem && isset($aItem['{DAV:}resourcetype'])) + { + $oResourceType = $aItem['{DAV:}resourcetype']; + /* @var $oResourceType \SabreForRainLoop\DAV\Property\ResourceType */ + + if ($oResourceType && $oResourceType->is('{DAV:}collection')) + { + if ($oResourceType->is('{urn:ietf:params:xml:ns:carddav}addressbook')) + { + $aContactsPaths[$sKey] = isset($aItem['{DAV:}displayname']) ? \trim($aItem['{DAV:}displayname']) : ''; + } + } + } + } + } + } + + return $aContactsPaths; + } + + /** + * @param \SabreForRainLoop\DAV\Client $oClient + * @param string $sPath + * * @return bool */ - public function Sync($sEmail, $sUrl, $sUser, $sPassword, $sProxy = '') + private function checkContactsPath(&$oClient, $sPath) { - $this->SyncDatabase(); - - $iUserID = $this->getUserId($sEmail); - if (0 >= $iUserID) + if (!$oClient) { return false; } + $this->oLogger->Write('PROPFIND '.$sPath, \MailSo\Log\Enumerations\Type::INFO, 'DAV'); + + try + { + $aResponse = $oClient->propFind($sPath, array( + '{DAV:}resourcetype' + ), 1); + +// $this->oLogger->WriteDump($aResponse); + } + catch (\Exception $oException) + { + $this->oLogger->WriteException($oException); + } + + $bGood = false; + if (\is_array($aResponse)) + { + foreach ($aResponse as $sKey => $aItem) + { + if (!empty($sKey) && isset($aItem['{DAV:}resourcetype'])) + { + $oResourceType = $aItem['{DAV:}resourcetype']; + /* @var $oResourceType \SabreForRainLoop\DAV\Property\ResourceType */ + + if ($oResourceType && $oResourceType->is('{DAV:}collection') && + $oResourceType->is('{urn:ietf:params:xml:ns:carddav}addressbook')) + { + $bGood = true; + } + } + } + } + + if ($bGood) + { + $oClient->__UrlPath__ = $sPath; + } + + return $bGood; + } + + public function getDavClientFromUrl($sUrl, $sUser, $sPassword, $sProxy = '') + { + if (!\preg_match('/^http[s]?:\/\//i', $sUrl)) + { + $sUrl = \preg_replace('/^fruux\.com/i', 'dav.fruux.com', $sUrl); + $sUrl = \preg_replace('/^icloud\.com/i', 'contacts.icloud.com', $sUrl); + $sUrl = \preg_replace('/^gmail\.com/i', 'google.com', $sUrl); + + if (\preg_match('/^(google\.|dav\.fruux\.com|contacts\.icloud\.com)/i', $sUrl)) + { + $sUrl = 'https://'.$sUrl; + } + else + { + $sUrl = 'http://'.$sUrl; + } + } + $aUrl = \parse_url($sUrl); if (!\is_array($aUrl)) { @@ -238,17 +536,117 @@ class PdoAddressBook $aSettings['proxy'] = $sProxy; } - $sPath = $aUrl['path']; + $oClient = new \SabreForRainLoop\DAV\Client($aSettings); + $oClient->setVerifyPeer(false); - if (!\class_exists('Sabre\DAV\Client')) + $oClient->__UrlPath__ = $aUrl['path']; + + $this->oLogger->Write('DavClient: User: '.$aSettings['userName'].', Url: '.$sUrl, \MailSo\Log\Enumerations\Type::INFO, 'DAV'); + + return $oClient; + } + + /** + * @param string $sEmail + * @param string $sUrl + * @param string $sUser + * @param string $sPassword + * @param string $sProxy = '' + * + * @return bool + */ + public function getDavClient($sUrl, $sUser, $sPassword, $sProxy = '') + { + if (!\class_exists('SabreForRainLoop\DAV\Client')) { return false; } - $oClient = new \Sabre\DAV\Client($aSettings); - $oClient->setVerifyPeer(false); + $oClient = $this->getDavClientFromUrl($sUrl, $sUser, $sPassword, $sProxy); + if (!$oClient) + { + return false; + } - $this->oLogger->Write('User: '.$aSettings['userName'].', Url: '.$sUrl, \MailSo\Log\Enumerations\Type::INFO, 'DAV'); + $bGood = false; + $sPath = $oClient->__UrlPath__; + + $bGood = false; + if ('' === $sPath || '/' === $sPath || !$this->checkContactsPath($oClient, $sPath)) + { + $sNewPath = ''; + + $aPaths = $this->getContactsPaths($oClient, $sUser, $sPassword, $sProxy); + $this->oLogger->WriteDump($aPaths); + + if (\is_array($aPaths)) + { + if (1 < \count($aPaths)) + { + foreach ($aPaths as $sKey => $sValue) + { + if (\in_array(\strtolower($sValue), array('contacts', 'default', 'addressbook', 'address book'))) + { + $sNewPath = $sKey; + break; + } + } + } + + if (empty($sNewPath)) + { + foreach ($aPaths as $sKey => $sValue) + { + $sNewPath = $sKey; + break; + } + } + } + + + $sPath = $sNewPath; + + $bGood = $this->checkContactsPath($oClient, $sPath); + } + else + { + $bGood = true; + } + + if (!$bGood) + { + $oClient = false; + } + + return $oClient; + } + + /** + * @param string $sEmail + * @param string $sUrl + * @param string $sUser + * @param string $sPassword + * @param string $sProxy = '' + * + * @return bool + */ + public function Sync($sEmail, $sUrl, $sUser, $sPassword, $sProxy = '') + { + $this->SyncDatabase(); + + $iUserID = $this->getUserId($sEmail); + if (0 >= $iUserID) + { + return false; + } + + $oClient = $this->getDavClient($sUrl, $sUser, $sPassword, $sProxy); + if (!$oClient) + { + return false; + } + + $sPath = $oClient->__UrlPath__; $aRemoteSyncData = $this->prepearRemoteSyncData($oClient, $sPath); if (false === $aRemoteSyncData) @@ -258,8 +656,8 @@ class PdoAddressBook $aDatabaseSyncData = $this->prepearDatabaseSyncData($iUserID); -// $this->oLogger->WriteDump($aDatabaseSyncData); // $this->oLogger->WriteDump($aRemoteSyncData); +// $this->oLogger->WriteDump($aDatabaseSyncData); //+++del (from carddav) foreach ($aDatabaseSyncData as $sKey => $aData) @@ -315,10 +713,13 @@ class PdoAddressBook { $sExsistensBody = \trim($oResponse['body']); } + +// $this->oLogger->WriteDump($sExsistensBody); } $oResponse = $this->davClientRequest($oClient, 'PUT', - $sPath.$oContact->CardDavNameUri(), $oContact->ToVCard($sExsistensBody, $this->oLogger)."\r\n\r\n"); + $sPath.(0 < \strlen($mExsistenRemoteID) ? $mExsistenRemoteID : $oContact->CardDavNameUri()), + $oContact->ToVCard($sExsistensBody, $this->oLogger)."\r\n\r\n"); if ($oResponse && isset($oResponse['headers'], $oResponse['headers']['etag'])) { diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Service.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Service.php index 896fbdfa7..70e76b9d8 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Service.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Service.php @@ -193,6 +193,7 @@ class Service $sResult .= '