From 8c0276631ba084ab860024e86a7ca47fc225759e Mon Sep 17 00:00:00 2001 From: RainLoop Team Date: Wed, 15 Jan 2014 00:02:30 +0400 Subject: [PATCH] Import contacts (close #44) + Some small improvements and fixes --- dev/Common/LinkBuilder.js | 8 + dev/Common/Selector.js | 9 +- dev/Styles/Contacts.less | 2 +- dev/Styles/Ui.less | 4 +- dev/Styles/_FontasticToBoot.less | 3 +- dev/ViewModels/PopupsContactsViewModel.js | 45 ++++ package.json | 2 +- .../0.0.0/app/libraries/RainLoop/Actions.php | 231 +++++++++++++++++- .../Providers/PersonalAddressBook.php | 97 ++++++++ .../PersonalAddressBook/Classes/Contact.php | 36 ++- .../PersonalAddressBook/Classes/Property.php | 8 +- .../Enumerations/PropertyType.php | 8 + .../PdoPersonalAddressBook.php | 27 +- .../libraries/RainLoop/SabreDAV/CardDAV.php | 12 + .../app/libraries/RainLoop/ServiceActions.php | 8 + .../app/templates/Views/PopupsContacts.html | 67 ++--- rainloop/v/0.0.0/langs/de.ini | 2 + rainloop/v/0.0.0/langs/en.ini | 2 + rainloop/v/0.0.0/langs/es.ini | 2 + rainloop/v/0.0.0/langs/fr.ini | 2 + rainloop/v/0.0.0/langs/is.ini | 2 + rainloop/v/0.0.0/langs/ko-kr.ini | 2 + rainloop/v/0.0.0/langs/lv.ini | 2 + rainloop/v/0.0.0/langs/nl.ini | 2 + rainloop/v/0.0.0/langs/no.ini | 2 + rainloop/v/0.0.0/langs/pl.ini | 2 + rainloop/v/0.0.0/langs/pt-br.ini | 2 + rainloop/v/0.0.0/langs/pt-pt.ini | 2 + rainloop/v/0.0.0/langs/ru.ini | 2 + rainloop/v/0.0.0/langs/zh-cn.ini | 2 + rainloop/v/0.0.0/static/css/app.css | 9 +- rainloop/v/0.0.0/static/css/app.min.css | 2 +- rainloop/v/0.0.0/static/js/admin.js | 8 + rainloop/v/0.0.0/static/js/admin.min.js | 4 +- rainloop/v/0.0.0/static/js/app.js | 62 ++++- rainloop/v/0.0.0/static/js/app.min.js | 14 +- 36 files changed, 628 insertions(+), 66 deletions(-) diff --git a/dev/Common/LinkBuilder.js b/dev/Common/LinkBuilder.js index 4b9c5e502..dbf9b4992 100644 --- a/dev/Common/LinkBuilder.js +++ b/dev/Common/LinkBuilder.js @@ -58,6 +58,14 @@ LinkBuilder.prototype.upload = function () return this.sServer + '/Upload/' + this.sSpecSuffix + '/'; }; +/** + * @return {string} + */ +LinkBuilder.prototype.uploadContacts = function () +{ + return this.sServer + '/UploadContacts/' + this.sSpecSuffix + '/'; +}; + /** * @return {string} */ diff --git a/dev/Common/Selector.js b/dev/Common/Selector.js index 891425824..b79cf2fc9 100644 --- a/dev/Common/Selector.js +++ b/dev/Common/Selector.js @@ -149,9 +149,16 @@ Selector.prototype.init = function (oContentVisible, oContentScrollable) ; $(this.oContentVisible) + .on('selectstart', function (oEvent) { + if (oEvent && oEvent.preventDefault) + { + oEvent.preventDefault(); + } + }) .on('click', this.sItemSelector, function (oEvent) { self.actionClick(ko.dataFor(this), oEvent); - }).on('click', this.sItemCheckedSelector, function (oEvent) { + }) + .on('click', this.sItemCheckedSelector, function (oEvent) { var oItem = ko.dataFor(this); if (oItem) { diff --git a/dev/Styles/Contacts.less b/dev/Styles/Contacts.less index 2824fe01a..d5acf289a 100644 --- a/dev/Styles/Contacts.less +++ b/dev/Styles/Contacts.less @@ -299,7 +299,7 @@ .box-shadow(none); border-color: #fff; font-size: 18px; - width: 250px; + width: 300px; &:hover { .box-shadow(inset 0 1px 1px rgba(0, 0, 0, 0.075)); diff --git a/dev/Styles/Ui.less b/dev/Styles/Ui.less index fdc0991ac..36d5d4686 100644 --- a/dev/Styles/Ui.less +++ b/dev/Styles/Ui.less @@ -1,11 +1,13 @@ .g-ui-user-select-none { -webkit-user-select: none; + -webkit-touch-callout: none; -khtml-user-select: none; -moz-user-select: none; + -ms-user-select: none; -o-user-select: none; user-select: none; - -webkit-touch-callout: none; + standard-user-select: none; } .g-ui-clearfix { diff --git a/dev/Styles/_FontasticToBoot.less b/dev/Styles/_FontasticToBoot.less index dbdc000e7..76531c30c 100644 --- a/dev/Styles/_FontasticToBoot.less +++ b/dev/Styles/_FontasticToBoot.less @@ -13,7 +13,8 @@ line-height: 18px; font-size: 18px; margin-top: -2px; - margin-left: -2px; + margin-left: -1px; + width: 17px; } .iconsize24 { diff --git a/dev/ViewModels/PopupsContactsViewModel.js b/dev/ViewModels/PopupsContactsViewModel.js index bf4e1f0bb..30cbff180 100644 --- a/dev/ViewModels/PopupsContactsViewModel.js +++ b/dev/ViewModels/PopupsContactsViewModel.js @@ -32,8 +32,11 @@ function PopupsContactsViewModel() this.contactsCount = ko.observable(0); this.contacts = ko.observableArray([]); this.contacts.loading = ko.observable(false).extend({'throttle': 200}); + this.contacts.importing = ko.observable(false).extend({'throttle': 200}); this.currentContact = ko.observable(null); + this.importUploaderButton = ko.observable(null); + this.contactsSharingIsAllowed = !!RL.settingsGet('ContactsSharingIsAllowed'); this.contactsPage = ko.observable(1); @@ -336,6 +339,46 @@ PopupsContactsViewModel.prototype.addNewPhone = function () this.addNewProperty(Enums.ContactPropertyType.MobilePersonal); }; +PopupsContactsViewModel.prototype.initUploader = function () +{ + if (this.importUploaderButton()) + { + var + oJua = new Jua({ + 'action': RL.link().uploadContacts(), + 'name': 'uploader', + 'queueSize': 1, + 'multipleSizeLimit': 1, + 'disableFolderDragAndDrop': true, + 'disableDragAndDrop': true, + 'disableMultiple': true, + 'disableDocumentDropPrevent': true, + 'clickElement': this.importUploaderButton() + }) + ; + + if (oJua) + { + oJua + .on('onStart', _.bind(function () { + this.contacts.importing(true); + }, this)) + .on('onComplete', _.bind(function (sId, bResult, oData) { + + this.contacts.importing(false); + this.reloadContactList(); + + if (!sId || !bResult || !oData || !oData.Result) + { + window.alert(Utils.i18n('CONTACTS/ERROR_IMPORT_FILE')); + } + + }, this)) + ; + } + } +}; + PopupsContactsViewModel.prototype.removeCheckedOrSelectedContactsFromList = function () { var @@ -575,6 +618,8 @@ PopupsContactsViewModel.prototype.onBuild = function (oDom) return bResult; }); + + this.initUploader(); }; PopupsContactsViewModel.prototype.onShow = function () diff --git a/package.json b/package.json index 6ca83c16e..1a9d7afd9 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "RainLoop", "title": "RainLoop Webmail", "version": "1.6.1", - "release": "630", + "release": "642", "description": "Simple, modern & fast web-based email client", "homepage": "http://rainloop.net", "main": "Gruntfile.js", 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 c4c50707a..612ff20aa 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php @@ -5122,6 +5122,235 @@ class Actions return $this->DefaultResponse(__FUNCTION__, $aResponse); } + /** + * @param \RainLoop\Account $oAccount + * @param resource $rFile + * @param string $sFileStart + * + * @return int + */ + private function importContactsFromCsvFile($oAccount, $rFile, $sFileStart) + { + $this->Logger()->Write('Import Csv'); + + $iCount = 0; + $aHeaders = null; + $aData = array(); + + if ($oAccount && \is_resource($rFile)) + { + $oPab = $this->PersonalAddressBookProvider($oAccount); + if ($oPab) + { + $sDelimiter = ((int) \strpos($sFileStart, ',') > (int) \strpos($sFileStart, ';')) ? ',' : ';'; + + @\setlocale(LC_CTYPE, 'en_US.UTF-8'); + while (false !== ($mRow = \fgetcsv($rFile, 5000, $sDelimiter, '"'))) + { + if (null === $aHeaders) + { + if (3 >= \count($mRow)) + { + return 0; + } + + $aHeaders = $mRow; + } + else + { + $aNewItem = array(); + foreach ($aHeaders as $iIndex => $sHeaderValue) + { + $aNewItem[@\iconv('utf-8', 'utf-8//IGNORE', $sHeaderValue)] = + isset($mRow[$iIndex]) ? $mRow[$iIndex] : ''; + } + + $aData[] = $aNewItem; + } + } + + if (\is_array($aData) && 0 < \count($aData)) + { + $this->Logger()->Write('Start to import '.\count($aData).' contacts from csv file'); + $oPab->ImportCsvArray($oAccount->ParentEmailHelper(), $aData); + + $iCount = \count($aData); + } + } + } + + return $iCount; + } + + /** + * @param \RainLoop\Account $oAccount + * @param resource $rFile + * @param string $sFileStart + * + * + * @return int + */ + private function importContactsFromVcfFile($oAccount, $rFile, $sFileStart) + { + $this->Logger()->Write('Import Vcf'); + + $iCount = 0; + if ($oAccount && \is_resource($rFile)) + { + $oPab = $this->PersonalAddressBookProvider($oAccount); + if ($oPab) + { + $sFile = \stream_get_contents($rFile); + if (\is_resource($rFile)) + { + @\fclose($rFile); + } + + if (is_string($sFile) && 5 < \strlen($sFile)) + { + $sFile = \trim($sFile); + if ("\xef\xbb\xbf" === \substr($sFile, 0, 3)) + { + $sFile = \substr($sFile, 3); + } + + $oVCard = null; + try + { + $oVCardSplitter = new \Sabre\VObject\Splitter\VCard($sFile); + } + catch (\Exception $oExc) + { + $this->Logger()->WriteException($oExc); + }; + + if ($oVCardSplitter) + { + $oContact = new \RainLoop\Providers\PersonalAddressBook\Classes\Contact(); + + $oVCard = null; + $sEmail = $oAccount->ParentEmailHelper(); + + $this->Logger()->Write('Start to import contacts from vcf'); + while ($oVCard = $oVCardSplitter->getNext()) + { + if ($oVCard instanceof \Sabre\VObject\Component\VCard) + { + if (empty($oVCard->UID)) + { + $oVCard->UID = \Sabre\DAV\UUIDUtil::getUUID(); + } + + $oContact->ParseVCard($oVCard, $oVCard->serialize()); + if ($oPab->ContactSave($sEmail, $oContact)) + { + $iCount++; + } + + $oContact->Clear(); + } + } + } + } + } + } + + return $iCount; + } + + /** + * @return array + */ + public function UploadContacts() + { + $oAccount = $this->getAccountFromToken(); + $oConfig = $this->Config(); + + $sInputName = 'uploader'; + $mResponse = false; + + $iError = UploadError::UNKNOWN; + $iSizeLimit = ((int) $oConfig->Get('webmail', 'attachment_size_limit', 0)) * 1024 * 1024; + + if ($oAccount) + { + $oPab = $this->PersonalAddressBookProvider($oAccount); + if ($oPab) + { + $iError = UPLOAD_ERR_OK; + $_FILES = isset($_FILES) ? $_FILES : null; + if (isset($_FILES, $_FILES[$sInputName], $_FILES[$sInputName]['name'], $_FILES[$sInputName]['tmp_name'], $_FILES[$sInputName]['size'])) + { + $iError = (isset($_FILES[$sInputName]['error'])) ? (int) $_FILES[$sInputName]['error'] : UPLOAD_ERR_OK; + + if (UPLOAD_ERR_OK === $iError && 0 < $iSizeLimit && $iSizeLimit < (int) $_FILES[$sInputName]['size']) + { + $iError = UploadError::CONFIG_SIZE; + } + + $sSavedName = 'upload-post-'.md5($_FILES[$sInputName]['name'].$_FILES[$sInputName]['tmp_name']); + + if (UPLOAD_ERR_OK === $iError) + { + if (!$this->FilesProvider()->MoveUploadedFile($oAccount, $sSavedName, $_FILES[$sInputName]['tmp_name'])) + { + $iError = UploadError::ON_SAVING; + } + + $mData = $this->FilesProvider()->GetFile($oAccount, $sSavedName); + if ($mData) + { + $sFileStart = @\fread($mData, 20); + \rewind($mData); + + if (false !== $sFileStart) + { + $sFileStart = \trim($sFileStart); + if (false !== \strpos($sFileStart, 'BEGIN:VCARD')) + { + $mResponse = $this->importContactsFromVcfFile($oAccount, $mData, $sFileStart); + } + else if (false !== \strpos($sFileStart, ',') || false !== \strpos($sFileStart, ';')) + { + $mResponse = $this->importContactsFromCsvFile($oAccount, $mData, $sFileStart); + } + } + } + + if (\is_resource($mData)) + { + @\fclose($mData); + } + + unset($mData); + $this->FilesProvider()->Clear($oAccount, $sSavedName); + } + } + else if (!isset($_FILES) || !is_array($_FILES) || 0 === count($_FILES)) + { + $iError = UPLOAD_ERR_INI_SIZE; + } + else + { + $iError = UploadError::EMPTY_FILES_DATA; + } + } + } + + if (UPLOAD_ERR_OK !== $iError) + { + $iClientError = UploadClientError::NORMAL; + $sError = $this->getUploadErrorMessageByCode($iError, $iClientError); + + if (!empty($sError)) + { + return $this->FalseResponse(__FUNCTION__, $iClientError, $sError); + } + } + + return $this->DefaultResponse(__FUNCTION__, $mResponse); + } + /** * @return array */ @@ -5200,7 +5429,7 @@ class Actions $sError = $this->getUploadErrorMessageByCode($iError, $iClientError); if (!empty($sError)) { - return $this->FalseResponse($sSavedName, $iClientError, $sError); + return $this->FalseResponse(__FUNCTION__, $iClientError, $sError); } } diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/PersonalAddressBook.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/PersonalAddressBook.php index 761c0540f..4b6ea1671 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/PersonalAddressBook.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/PersonalAddressBook.php @@ -2,6 +2,8 @@ namespace RainLoop\Providers; +use \RainLoop\Providers\PersonalAddressBook\Enumerations\PropertyType as PropertyType; + class PersonalAddressBook extends \RainLoop\Providers\AbstractProvider { /** @@ -181,4 +183,99 @@ class PersonalAddressBook extends \RainLoop\Providers\AbstractProvider { return $this->IsActive() ? $this->oDriver->IncFrec($sEmail, $aEmails, $bCreateAuto) : false; } + + /** + * @param string $sCsvName + * + * @return int + */ + private function csvNameToTypeConvertor($sCsvName) + { + static $aMap = null; + if (null === $aMap) + { + $aMap = array( + 'Title' => PropertyType::FULLNAME, + 'First Name' => PropertyType::FIRST_NAME, + 'Middle Name' => PropertyType::MIDDLE_NAME, + 'Last Name' => PropertyType::LAST_NAME, + 'Suffix' => PropertyType::NAME_SUFFIX, + 'Business Fax' => PropertyType::FAX_BUSSINES, + 'Business Phone' => PropertyType::PHONE_BUSSINES, + 'Business Phone 2' => PropertyType::PHONE_BUSSINES, + 'Company Main Phone' => PropertyType::PHONE_BUSSINES, + 'Home Fax' => PropertyType::FAX_PERSONAL, + 'Home Phone' => PropertyType::PHONE_PERSONAL, + 'Home Phone 2' => PropertyType::PHONE_PERSONAL, + 'Mobile Phone' => PropertyType::MOBILE_PERSONAL, + 'Other Fax' => PropertyType::FAX_OTHER, + 'Other Phone' => PropertyType::PHONE_OTHER, +// 'Primary Phone' => PropertyType::PHONE_PERSONAL, + 'E-mail Address' => PropertyType::EMAIl_PERSONAL, + 'E-mail 2 Address' => PropertyType::EMAIl_OTHER, + 'E-mail 3 Address' => PropertyType::EMAIl_OTHER, + 'E-mail Display Name' => PropertyType::FULLNAME, + 'E-mail 2 Display Name' => PropertyType::FULLNAME, + 'E-mail 3 Display Name' => PropertyType::FULLNAME, + 'Notes' => PropertyType::NOTE, + 'Web Page' => PropertyType::WEB_PAGE_PERSONAL, + 'WebPage' => PropertyType::WEB_PAGE_PERSONAL, + ); + + $aMap = array_change_key_case($aMap, CASE_LOWER); + } + + $sCsvNameLower = \MailSo\Base\Utils::IsAscii($sCsvName) ? \strtolower($sCsvName) : ''; + return isset($aMap[$sCsvNameLower]) ? $aMap[$sCsvNameLower] : PropertyType::UNKNOWN; + } + + /** + * @param string $sEmail + * @param array $aCsvData + * + * @return int + */ + public function ImportCsvArray($sEmail, $aCsvData) + { + $iCount = 0; + if ($this->IsActive() && \is_array($aCsvData) && 0 < \count($aCsvData)) + { + $oContact = new \RainLoop\Providers\PersonalAddressBook\Classes\Contact(); + foreach ($aCsvData as $aItem) + { + foreach ($aItem as $sItemName => $sItemValue) + { + $sItemName = \trim($sItemName); + $sItemValue = \trim($sItemValue); + + if (!empty($sItemName) && !empty($sItemValue)) + { + $iType = $this->csvNameToTypeConvertor($sItemName); + if (PropertyType::UNKNOWN !== $iType) + { + $oProp = new \RainLoop\Providers\PersonalAddressBook\Classes\Property(); + $oProp->Type = $iType; + $oProp->Value = $sItemValue; + + $oContact->Properties[] = $oProp; + } + } + } + + if ($oContact && 0 < \count($oContact->Properties)) + { + if ($this->ContactSave($sEmail, $oContact)) + { + $iCount++; + } + } + + $oContact->Clear(); + } + + unset($oContact); + } + + return $iCount; + } } diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/PersonalAddressBook/Classes/Contact.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/PersonalAddressBook/Classes/Contact.php index 8c28f5417..3cde85ecb 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/PersonalAddressBook/Classes/Contact.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/PersonalAddressBook/Classes/Contact.php @@ -122,8 +122,8 @@ class Contact $sFirstName = $oProperty->Value; } else if (\in_array($oProperty->Type, array(PropertyType::FULLNAME, - PropertyType::PHONE_PERSONAL, PropertyType::PHONE_BUSSINES, - PropertyType::MOBILE_PERSONAL, PropertyType::MOBILE_BUSSINES, + PropertyType::PHONE_PERSONAL, PropertyType::PHONE_BUSSINES, PropertyType::PHONE_OTHER, + PropertyType::MOBILE_PERSONAL, PropertyType::MOBILE_BUSSINES, PropertyType::MOBILE_OTHER ))) { $sOther = $oProperty->Value; @@ -287,6 +287,21 @@ class Contact } } } + + if (isset($oVCard->URL)) + { + foreach($oVCard->URL as $oUrl) + { + $oTypes = $oEmail ? $oEmail['TYPE'] : null; + $sUrl = $oTypes ? \trim((string) $oUrl) : ''; + + if ($oTypes && 0 < \strlen($sUrl)) + { + $oProp = new Property($oTypes->has('WORK') ? PropertyType::WEB_PAGE_BUSSINES : PropertyType::WEB_PAGE_PERSONAL, $sEmail); + \array_push($aProperties, $oProp); + } + } + } if (isset($oVCard->TEL)) { @@ -402,6 +417,7 @@ class Contact break; case PropertyType::EMAIl_PERSONAL: case PropertyType::EMAIl_BUSSINES: + case PropertyType::EMAIl_OTHER: $aParams = array('TYPE' => array('INTERNET')); $aParams['TYPE'][] = PropertyType::EMAIl_BUSSINES === $oProperty->Type ? 'WORK' : 'HOME'; @@ -412,23 +428,33 @@ class Contact } $oVCard->add('EMAIL', $oProperty->Value, $aParams); break; + case PropertyType::WEB_PAGE_PERSONAL: + case PropertyType::WEB_PAGE_BUSSINES: + case PropertyType::WEB_PAGE_OTHER: + $aParams = array('TYPE' => array()); + $aParams['TYPE'][] = PropertyType::WEB_PAGE_BUSSINES === $oProperty->Type ? 'WORK' : 'HOME'; + $oVCard->add('URL', $oProperty->Value, $aParams); + break; case PropertyType::PHONE_PERSONAL: case PropertyType::PHONE_BUSSINES: + case PropertyType::PHONE_OTHER: case PropertyType::MOBILE_PERSONAL: case PropertyType::MOBILE_BUSSINES: + case PropertyType::MOBILE_OTHER: case PropertyType::FAX_PERSONAL: case PropertyType::FAX_BUSSINES: + case PropertyType::FAX_OTHER: $aParams = array('TYPE' => array()); $sType = ''; - if (\in_array($oProperty->Type, array(PropertyType::PHONE_PERSONAL, PropertyType::PHONE_BUSSINES))) + if (\in_array($oProperty->Type, array(PropertyType::PHONE_PERSONAL, PropertyType::PHONE_BUSSINES, PropertyType::PHONE_OTHER))) { $sType = 'VOICE'; } - else if (\in_array($oProperty->Type, array(PropertyType::MOBILE_PERSONAL, PropertyType::MOBILE_BUSSINES))) + else if (\in_array($oProperty->Type, array(PropertyType::MOBILE_PERSONAL, PropertyType::MOBILE_BUSSINES, PropertyType::MOBILE_OTHER))) { $sType = 'CELL'; } - else if (\in_array($oProperty->Type, array(PropertyType::FAX_PERSONAL, PropertyType::FAX_BUSSINES))) + else if (\in_array($oProperty->Type, array(PropertyType::FAX_PERSONAL, PropertyType::FAX_BUSSINES, PropertyType::FAX_OTHER))) { $sType = 'FAX'; } diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/PersonalAddressBook/Classes/Property.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/PersonalAddressBook/Classes/Property.php index 6c94b7f59..b0aa97c98 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/PersonalAddressBook/Classes/Property.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/PersonalAddressBook/Classes/Property.php @@ -70,7 +70,7 @@ class Property public function IsEmail() { return \in_array($this->Type, array( - PropertyType::EMAIl_PERSONAL, PropertyType::EMAIl_BUSSINES + PropertyType::EMAIl_PERSONAL, PropertyType::EMAIl_BUSSINES, PropertyType::EMAIl_OTHER )); } @@ -80,9 +80,9 @@ class Property public function IsPhone() { return \in_array($this->Type, array( - PropertyType::PHONE_PERSONAL, PropertyType::PHONE_BUSSINES, - PropertyType::MOBILE_PERSONAL, PropertyType::MOBILE_BUSSINES, - PropertyType::FAX_BUSSINES + PropertyType::PHONE_PERSONAL, PropertyType::PHONE_BUSSINES, PropertyType::PHONE_OTHER, + PropertyType::MOBILE_PERSONAL, PropertyType::MOBILE_BUSSINES, PropertyType::MOBILE_OTHER, + PropertyType::FAX_PERSONAL, PropertyType::FAX_BUSSINES, PropertyType::FAX_OTHER )); } diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/PersonalAddressBook/Enumerations/PropertyType.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/PersonalAddressBook/Enumerations/PropertyType.php index d090ae9bc..1428e6ff7 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/PersonalAddressBook/Enumerations/PropertyType.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/PersonalAddressBook/Enumerations/PropertyType.php @@ -18,15 +18,19 @@ class PropertyType const EMAIl_PERSONAL = 30; const EMAIl_BUSSINES = 31; + const EMAIl_OTHER = 32; const PHONE_PERSONAL = 50; const PHONE_BUSSINES = 51; + const PHONE_OTHER = 52; const MOBILE_PERSONAL = 60; const MOBILE_BUSSINES = 61; + const MOBILE_OTHER = 62; const FAX_PERSONAL = 70; const FAX_BUSSINES = 71; + const FAX_OTHER = 72; const FACEBOOK = 90; const SKYPE = 91; @@ -34,5 +38,9 @@ class PropertyType const NOTE = 110; + const WEB_PAGE_PERSONAL = 220; + const WEB_PAGE_BUSSINES = 221; + const WEB_PAGE_OTHER = 222; + const CUSTOM = 250; } diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/PersonalAddressBook/PdoPersonalAddressBook.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/PersonalAddressBook/PdoPersonalAddressBook.php index 0e0a6d2d4..1b39c54ce 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/PersonalAddressBook/PdoPersonalAddressBook.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/PersonalAddressBook/PdoPersonalAddressBook.php @@ -403,9 +403,9 @@ class PdoPersonalAddressBook ($this->bConsiderShare ? ' OR scope_type = :scope_type_share_all' : ''). ') AND (prop_value LIKE :search ESCAPE \'=\''. (0 < \strlen($sCustomSearch) ? ' OR (prop_type IN ('.\implode(',', array( - PropertyType::PHONE_PERSONAL, PropertyType::PHONE_BUSSINES, - PropertyType::MOBILE_PERSONAL, PropertyType::MOBILE_BUSSINES, - PropertyType::FAX_PERSONAL, PropertyType::FAX_BUSSINES + PropertyType::PHONE_PERSONAL, PropertyType::PHONE_BUSSINES, PropertyType::PHONE_OTHER, + PropertyType::MOBILE_PERSONAL, PropertyType::MOBILE_BUSSINES, PropertyType::MOBILE_OTHER, + PropertyType::FAX_PERSONAL, PropertyType::FAX_BUSSINES, PropertyType::FAX_OTHER )).') AND prop_value_custom <> \'\' AND prop_value_custom LIKE :search_custom_phone)' : ''). ') GROUP BY id_contact, id_prop'; @@ -729,7 +729,7 @@ class PdoPersonalAddressBook $iUserID = $this->getUserId($sEmail); $sTypes = implode(',', array( - PropertyType::EMAIl_PERSONAL, PropertyType::EMAIl_BUSSINES, PropertyType::FIRST_NAME, PropertyType::LAST_NAME + PropertyType::EMAIl_PERSONAL, PropertyType::EMAIl_BUSSINES, PropertyType::EMAIl_OTHER, PropertyType::FIRST_NAME, PropertyType::LAST_NAME )); $sSql = 'SELECT id_contact, id_prop, prop_type, prop_value FROM rainloop_pab_properties '. @@ -759,6 +759,7 @@ class PdoPersonalAddressBook { $aIdContacts = array(); $aIdProps = array(); + $aContactAllAccess = array(); $aFetch = $oStmt->fetchAll(\PDO::FETCH_ASSOC); if (\is_array($aFetch) && 0 < \count($aFetch)) @@ -767,10 +768,17 @@ class PdoPersonalAddressBook { $iIdContact = $aItem && isset($aItem['id_contact']) ? (int) $aItem['id_contact'] : 0; $iIdProp = $aItem && isset($aItem['id_prop']) ? (int) $aItem['id_prop'] : 0; + $iType = $aItem && isset($aItem['prop_type']) ? (int) $aItem['prop_type'] : 0; + if (0 < $iIdContact && 0 < $iIdProp) { $aIdContacts[$iIdContact] = $iIdContact; $aIdProps[$iIdProp] = $iIdProp; + + if (\in_array($iType, array(PropertyType::LAST_NAME, PropertyType::FIRST_NAME))) + { + $aContactAllAccess[$iIdContact] = $iIdContact; + } } } } @@ -783,7 +791,7 @@ class PdoPersonalAddressBook $oStmt->closeCursor(); $sTypes = \implode(',', array( - PropertyType::EMAIl_PERSONAL, PropertyType::EMAIl_BUSSINES, PropertyType::FIRST_NAME, PropertyType::LAST_NAME + PropertyType::EMAIl_PERSONAL, PropertyType::EMAIl_BUSSINES, PropertyType::EMAIl_OTHER, PropertyType::FIRST_NAME, PropertyType::LAST_NAME )); $sSql = 'SELECT id_prop, id_contact, prop_type, prop_value FROM rainloop_pab_properties '. @@ -815,7 +823,8 @@ class PdoPersonalAddressBook $aNames[$iIdContact][PropertyType::LAST_NAME === $iType ? 0 : 1] = $aItem['prop_value']; } - else if (isset($aIdProps[$iIdProp]) && \in_array($iType, array(PropertyType::EMAIl_PERSONAL, PropertyType::EMAIl_BUSSINES))) + else if ((isset($aIdProps[$iIdProp]) || isset($aContactAllAccess[$iIdContact]))&& + \in_array($iType, array(PropertyType::EMAIl_PERSONAL, PropertyType::EMAIl_BUSSINES))) { if (!isset($aEmails[$iIdContact])) { @@ -827,8 +836,8 @@ class PdoPersonalAddressBook } } - $this->writeLog($aNames); - $this->writeLog($aEmails); +// $this->writeLog($aNames); +// $this->writeLog($aEmails); foreach ($aEmails as $iId => $aItems) { @@ -886,7 +895,7 @@ class PdoPersonalAddressBook $iUserID = $this->getUserId($sEmail); $sTypes = \implode(',', array( - PropertyType::EMAIl_PERSONAL, PropertyType::EMAIl_BUSSINES + PropertyType::EMAIl_PERSONAL, PropertyType::EMAIl_BUSSINES, PropertyType::EMAIl_OTHER )); $aExists = array(); diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/SabreDAV/CardDAV.php b/rainloop/v/0.0.0/app/libraries/RainLoop/SabreDAV/CardDAV.php index 07f8fb2bb..57c0ae7cb 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/SabreDAV/CardDAV.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/SabreDAV/CardDAV.php @@ -290,6 +290,12 @@ class CardDAV implements \Sabre\CardDAV\Backend\BackendInterface $sEmail = $this->getAuthEmail('', $mAddressBookID); if (!empty($sEmail)) { + if (empty($oVCard->UID)) + { + $oVCard->UID = \Sabre\DAV\UUIDUtil::getUUID(); + $sCardData = $oVCard->serialize(); + } + $oContact = new \RainLoop\Providers\PersonalAddressBook\Classes\Contact(); $oContact->ParseVCard($oVCard, $sCardData); @@ -358,6 +364,12 @@ class CardDAV implements \Sabre\CardDAV\Backend\BackendInterface $oContact = $this->oPersonalAddressBook->GetContactByID($sEmail, \substr($sCardUri, 0, -4), true); if ($oContact && (0 === $iRev || $oContact->Changed < $iRev)) { + if (empty($oVCard->UID)) + { + $oVCard->UID = \Sabre\DAV\UUIDUtil::getUUID(); + $sCardData = $oVCard->serialize(); + } + $oContact->ParseVCard($oVCard, $sCardData); if ($this->oPersonalAddressBook->ContactSave($sEmail, $oContact) && !empty($oContact->CardDavHash)) { diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/ServiceActions.php b/rainloop/v/0.0.0/app/libraries/RainLoop/ServiceActions.php index cbe65256e..86211e357 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/ServiceActions.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/ServiceActions.php @@ -313,6 +313,14 @@ class ServiceActions { return $this->privateUpload('Upload'); } + + /** + * @return string + */ + public function ServiceUploadContacts() + { + return $this->privateUpload('UploadContacts'); + } /** * @return string diff --git a/rainloop/v/0.0.0/app/templates/Views/PopupsContacts.html b/rainloop/v/0.0.0/app/templates/Views/PopupsContacts.html index a75dc11f1..d4cc5e680 100644 --- a/rainloop/v/0.0.0/app/templates/Views/PopupsContacts.html +++ b/rainloop/v/0.0.0/app/templates/Views/PopupsContacts.html @@ -2,18 +2,44 @@