diff --git a/.idea/contactimporter.iml b/.idea/contactimporter.iml index 0c76797..c956989 100644 --- a/.idea/contactimporter.iml +++ b/.idea/contactimporter.iml @@ -4,15 +4,5 @@ - - - - - - - - - - \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..e7bedf3 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..be9a660 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..0ef7566 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + DEFINITION_ORDER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + project + + + + + + + + + + + + + + + + project + + + true + + + + DIRECTORY + + false + + + + + + + + + 1480428196914 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Unnamed + + + + + + + + \ No newline at end of file diff --git a/config.php b/config.php index 3788267..1570309 100644 --- a/config.php +++ b/config.php @@ -7,4 +7,3 @@ define('PLUGIN_CONTACTIMPORTER_DEFAULT', "Kontakte"); /** Tempory path for uploaded files... */ define('PLUGIN_CONTACTIMPORTER_TMP_UPLOAD', "/var/lib/kopano-webapp/tmp/"); -?> diff --git a/languages/de_DE.UTF-8/LC_MESSAGES/plugin_contactimporter.po b/languages/de_DE.UTF-8/LC_MESSAGES/plugin_contactimporter.po new file mode 100644 index 0000000..e69de29 diff --git a/languages/de_DE.UTF-8/language.txt b/languages/de_DE.UTF-8/language.txt new file mode 100644 index 0000000..1149b6b --- /dev/null +++ b/languages/de_DE.UTF-8/language.txt @@ -0,0 +1,2 @@ +Deutsch +deu_DEU diff --git a/languages/en_US.UTF-8/LC_MESSAGES/plugin_calendarimporter.mo b/languages/en_US.UTF-8/LC_MESSAGES/plugin_calendarimporter.mo new file mode 100644 index 0000000..ab0296d Binary files /dev/null and b/languages/en_US.UTF-8/LC_MESSAGES/plugin_calendarimporter.mo differ diff --git a/languages/en_US.UTF-8/LC_MESSAGES/plugin_calendarimporter.po b/languages/en_US.UTF-8/LC_MESSAGES/plugin_calendarimporter.po new file mode 100644 index 0000000..9711b7c --- /dev/null +++ b/languages/en_US.UTF-8/LC_MESSAGES/plugin_calendarimporter.po @@ -0,0 +1,57 @@ +msgid "" +msgstr "" +"Project-Id-Version: Plugin Contactimporter\n" +"POT-Creation-Date: 2016-11-29 15:23+0100\n" +"PO-Revision-Date: 2016-11-29 15:23+0100\n" +"Last-Translator: \n" +"Language-Team: Christoph Haas \n" +"Language: en_US\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 1.8.7.1\n" +"X-Poedit-Basepath: ../../../php\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Poedit-SearchPath-0: .\n" +"X-Poedit-SearchPath-1: vendor\n" + +#: module.contact.php:245 +msgid "VCF file is empty!" +msgstr "VCF file is empty!" + +#. TRANSLATORS: Filename suffix for exported files +#: module.contact.php:448 +msgid "_contacts.vcf" +msgstr "_contacts.vcf" + +#: module.contact.php:451 +msgid "No contacts found. Export skipped!" +msgstr "No contacts found. Export skipped!" + +#: module.contact.php:728 +msgid "No contacts in vcf file" +msgstr "No contacts in vcf file" + +#: module.contact.php:739 +msgid "File could not be read by server" +msgstr "File could not be read by server" + +#: module.contact.php:1141 +msgid "Store could not be opened!" +msgstr "Store could not be opened!" + +#: module.contact.php:1147 +msgid "Wrong call, store and entryid have to be set!" +msgstr "Wrong call, store and entryid have to be set!" + +#: upload.php:69 +msgid "" +"File could not be moved to TMP path! Check plugin config and folder " +"permissions!" +msgstr "" +"File could not be moved to TMP path! Check plugin config and folder " +"permissions!" + +#: upload.php:72 +msgid "File could not be read by server, upload error!" +msgstr "File could not be read by server, upload error!" diff --git a/languages/en_US.UTF-8/language.txt b/languages/en_US.UTF-8/language.txt new file mode 100644 index 0000000..ea7e1f6 --- /dev/null +++ b/languages/en_US.UTF-8/language.txt @@ -0,0 +1,2 @@ +English (US) +eng_USA diff --git a/manifest.xml b/manifest.xml index 32a0584..b81c4a1 100644 --- a/manifest.xml +++ b/manifest.xml @@ -9,6 +9,9 @@ http://www.sprinternet.at Import or Export a VCF file to/from the zarafa addressbook + + languages + config.php diff --git a/php/download.php b/php/download.php index cbbc2d2..52edb6b 100644 --- a/php/download.php +++ b/php/download.php @@ -1,7 +1,7 @@ * Copyright (C) 2012-2016 Christoph Haas @@ -25,7 +25,6 @@ class DownloadHandler { /** * Download the given vcf file. - * @return bool */ public static function doDownload() { diff --git a/php/module.contact.php b/php/module.contact.php index b653b94..1e471c9 100644 --- a/php/module.contact.php +++ b/php/module.contact.php @@ -1,6 +1,6 @@ * Copyright (C) 2012-2016 Christoph Haas @@ -29,1139 +29,1136 @@ use JeroenDesloovere\VCard\VCardParser; class ContactModule extends Module { - private $DEBUG = false; // enable error_log debugging - - /** - * @constructor - * @param $id - * @param $data - */ - public function __construct($id, $data) - { - parent::__construct($id, $data); - } - - /** - * Executes all the actions in the $data variable. - * Exception part is used for authentication errors also - * @return boolean true on success or false on failure. - */ - public function execute() - { - $result = false; - - if (!$this->DEBUG) { - /* disable error printing - otherwise json communication might break... */ - ini_set('display_errors', '0'); - } - - foreach ($this->data as $actionType => $actionData) { - if (isset($actionType)) { - try { - if ($this->DEBUG) { - error_log("exec: " . $actionType); - } - switch ($actionType) { - case "load": - $result = $this->loadContacts($actionType, $actionData); - break; - case "import": - $result = $this->importContacts($actionType, $actionData); - break; - case "export": - $result = $this->exportContacts($actionType, $actionData); - break; - case "importattachment": - $result = $this->getAttachmentPath($actionType, $actionData); - break; - default: - $this->handleUnknownActionType($actionType); - } - - } catch (MAPIException $e) { - if ($this->DEBUG) { - error_log("mapi exception: " . $e->getMessage()); - } - } catch (Exception $e) { - if ($this->DEBUG) { - error_log("exception: " . $e->getMessage()); - } - } - } - } - - return $result; - } - - /** - * Generates a random string with variable length. - * @param $length the lenght of the generated string - * @return string a random string - */ - private function randomstring($length = 6) - { - // $chars - all allowed charakters - $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; - - srand((double)microtime() * 1000000); - $i = 0; - $pass = ""; - while ($i < $length) { - $num = rand() % strlen($chars); - $tmp = substr($chars, $num, 1); - $pass = $pass . $tmp; - $i++; - } - return $pass; - } - - /** - * Add an attachment to the give contact - * @param $actionType - * @param $actionData - */ - private function importContacts($actionType, $actionData) - { - - // Get uploaded vcf path - $vcffile = false; - if (isset($actionData["vcf_filepath"])) { - $vcffile = $actionData["vcf_filepath"]; - } - - // Get store id - $storeid = false; - if (isset($actionData["storeid"])) { - $storeid = $actionData["storeid"]; - } - - // Get folder entryid - $folderid = false; - if (isset($actionData["folderid"])) { - $folderid = $actionData["folderid"]; - } - - // Get uids - $uids = array(); - if (isset($actionData["uids"])) { - $uids = $actionData["uids"]; - } - - $response = array(); - $error = false; - $error_msg = ""; - - // parse the vcf file a last time... - $parser = null; - try { - $parser = VCardParser::parseFromFile($vcffile); - } catch (Exception $e) { - $error = true; - $error_msg = $e->getMessage(); - } - - $contacts = array(); - - if (!$error && iterator_count($parser) > 0) { - $contacts = $this->parseContactsToArray($parser); - $store = $GLOBALS["mapisession"]->openMessageStore(hex2bin($storeid)); - $folder = mapi_msgstore_openentry($store, hex2bin($folderid)); - - $importall = false; - if (count($uids) == count($contacts)) { - $importall = true; - } - - $propValuesMAPI = array(); - $properties = $this->getProperties(); - $properties = $this->replaceStringPropertyTags($store, $properties); - $count = 0; - - // iterate through all contacts and import them :) - foreach ($contacts as $contact) { - if (isset($contact["display_name"]) && ($importall || in_array($contact["internal_fields"]["contact_uid"], $uids))) { - // parse the arraykeys - // TODO: this is very slow... - foreach ($contact as $key => $value) { - if ($key !== "internal_fields") { - $propValuesMAPI[$properties[$key]] = $value; - } - } - - $propValuesMAPI[$properties["message_class"]] = "IPM.Contact"; - $propValuesMAPI[$properties["icon_index"]] = "512"; - $message = mapi_folder_createmessage($folder); - - - if (isset($contact["internal_fields"]["x_photo_path"])) { - $propValuesMAPI[$properties["picture"]] = 1; // contact has an image - - // import the photo - $contactPicture = file_get_contents($contact["internal_fields"]["x_photo_path"]); - $attach = mapi_message_createattach($message); - - // Set properties of the attachment - $propValuesIMG = array( - PR_ATTACH_SIZE => strlen($contactPicture), - PR_ATTACH_LONG_FILENAME => 'ContactPicture.jpg', - PR_ATTACHMENT_HIDDEN => false, - PR_DISPLAY_NAME => 'ContactPicture.jpg', - PR_ATTACH_METHOD => ATTACH_BY_VALUE, - PR_ATTACH_MIME_TAG => 'image/jpeg', - PR_ATTACHMENT_CONTACTPHOTO => true, - PR_ATTACH_DATA_BIN => $contactPicture, - PR_ATTACHMENT_FLAGS => 1, - PR_ATTACH_EXTENSION_A => '.jpg', - PR_ATTACH_NUM => 1 - ); - - mapi_setprops($attach, $propValuesIMG); - mapi_savechanges($attach); - if ($this->DEBUG) { - error_log("Contactpicture imported!"); - } - - if (mapi_last_hresult() > 0) { - error_log("Error saving attach to contact: " . get_mapi_error_name()); - } - } - - mapi_setprops($message, $propValuesMAPI); - mapi_savechanges($message); - if ($this->DEBUG) { - error_log("New contact added: \"" . $propValuesMAPI[$properties["display_name"]] . "\".\n"); - } - $count++; - } - } - - $response['status'] = true; - $response['count'] = $count; - $response['message'] = ""; - - } else { - $response['status'] = false; - $response['count'] = 0; - $response['message'] = $error ? $error_msg : "VCF file empty!"; - } - - $this->addActionData($actionType, $response); - $GLOBALS["bus"]->addData($this->getResponseData()); - } - - /** - * Get a property from the array. - * @param $props - * @param $propname - * @return string - */ - private function getProp($props, $propname) - { - $p = $this->getProperties(); - if (isset($props["props"][$propname])) { - return $props["props"][$propname]; - } - return ""; - } - - /** - * Export selected contacts to vCard. - * @param $actionType - * @param $actionData - * @return bool - */ - private function exportContacts($actionType, $actionData) - { - // Get store id - $storeid = false; - if (isset($actionData["storeid"])) { - $storeid = $actionData["storeid"]; - } - - // Get records - $records = array(); - if (isset($actionData["records"])) { - $records = $actionData["records"]; - } - - // Get folders - $folder = false; - if (isset($actionData["folder"])) { - $folder = $actionData["folder"]; - } - - $response = array(); - $error = false; - $error_msg = ""; - - // write csv - $token = $this->randomstring(16); - $file = PLUGIN_CONTACTIMPORTER_TMP_UPLOAD . "vcf_" . $token . ".vcf"; - file_put_contents($file, ""); - - $store = $GLOBALS["mapisession"]->openMessageStore(hex2bin($storeid)); - if ($store) { - // load folder first - if ($folder !== false) { - $mapifolder = mapi_msgstore_openentry($store, hex2bin($folder)); - - $table = mapi_folder_getcontentstable($mapifolder); - $list = mapi_table_queryallrows($table, array(PR_ENTRYID)); - - foreach ($list as $item) { - $records[] = bin2hex($item[PR_ENTRYID]); - } - } - for ($index = 0, $count = count($records); $index < $count; $index++) { - // define vcard - $vcard = new VCard(); - - $message = mapi_msgstore_openentry($store, hex2bin($records[$index])); - - // get message properties. - $properties = $GLOBALS['properties']->getContactProperties(); - $plaintext = true; - $messageProps = $GLOBALS['operations']->getMessageProps($store, $message, $properties, $plaintext); - - // define variables - $firstname = $this->getProp($messageProps, "given_name"); - $lastname = $this->getProp($messageProps, "surname"); - $additional = $this->getProp($messageProps, "middle_name"); - $prefix = $this->getProp($messageProps, "display_name_prefix"); - $suffix = ''; - - // add personal data - $vcard->addName($lastname, $firstname, $additional, $prefix, $suffix); - - $company = $this->getProp($messageProps, "company_name"); - if (!empty($company)) { - $vcard->addCompany($company); - } - - $jobtitle = $this->getProp($messageProps, "title"); - if (!empty($jobtitle)) { - $vcard->addJobtitle($jobtitle); - } - - // MAIL - $mail = $this->getProp($messageProps, "email_address_1"); - if (!empty($mail)) { - $vcard->addEmail($mail); - } - $mail = $this->getProp($messageProps, "email_address_2"); - if (!empty($mail)) { - $vcard->addEmail($mail); - } - $mail = $this->getProp($messageProps, "email_address_3"); - if (!empty($mail)) { - $vcard->addEmail($mail); - } - - // PHONE - $wphone = $this->getProp($messageProps, "business_telephone_number"); - if (!empty($wphone)) { - $vcard->addPhoneNumber($wphone, 'WORK'); - } - $wphone = $this->getProp($messageProps, "home_telephone_number"); - if (!empty($wphone)) { - $vcard->addPhoneNumber($wphone, 'HOME'); - } - $wphone = $this->getProp($messageProps, "cellular_telephone_number"); - if (!empty($wphone)) { - $vcard->addPhoneNumber($wphone, 'CELL'); - } - $wphone = $this->getProp($messageProps, "business_fax_number"); - if (!empty($wphone)) { - $vcard->addPhoneNumber($wphone, 'FAX'); - } - $wphone = $this->getProp($messageProps, "pager_telephone_number"); - if (!empty($wphone)) { - $vcard->addPhoneNumber($wphone, 'PAGER'); - } - $wphone = $this->getProp($messageProps, "car_telephone_number"); - if (!empty($wphone)) { - $vcard->addPhoneNumber($wphone, 'CAR'); - } - - // ADDRESS - $addr = $this->getProp($messageProps, "business_address"); - if (!empty($addr)) { - $vcard->addAddress(null, null, $this->getProp($messageProps, "business_address_street"), $this->getProp($messageProps, "business_address_city"), $this->getProp($messageProps, "business_address_state"), $this->getProp($messageProps, "business_address_postal_code"), $this->getProp($messageProps, "business_address_country"), "WORK"); - } - $addr = $this->getProp($messageProps, "home_address"); - if (!empty($addr)) { - $vcard->addAddress(null, null, $this->getProp($messageProps, "home_address_street"), $this->getProp($messageProps, "home_address_city"), $this->getProp($messageProps, "home_address_state"), $this->getProp($messageProps, "home_address_postal_code"), $this->getProp($messageProps, "home_address_country"), "HOME"); - } - $addr = $this->getProp($messageProps, "other_address"); - if (!empty($addr)) { - $vcard->addAddress(null, null, $this->getProp($messageProps, "other_address_street"), $this->getProp($messageProps, "other_address_city"), $this->getProp($messageProps, "other_address_state"), $this->getProp($messageProps, "other_address_postal_code"), $this->getProp($messageProps, "other_address_country"), "OTHER"); - } - - // MISC - $url = $this->getProp($messageProps, "webpage"); - if (!empty($url)) { - $vcard->addURL($url); - } - - $bday = $this->getProp($messageProps, "birthday"); - if (!empty($bday)) { - $vcard->addBirthday(date("Y-m-d", $bday)); - } - - $notes = $this->getProp($messageProps, "body"); - if (!empty($notes)) { - $vcard->addNote($notes); - } - - $haspicture = $this->getProp($messageProps, "has_picture"); - if (!empty($haspicture) && $haspicture === true) { - $attachnum = -1; - if (isset($messageProps["attachments"]) && isset($messageProps["attachments"]["item"])) { - foreach ($messageProps["attachments"]["item"] as $attachment) { - if ($attachment["props"]["attachment_contactphoto"] == true) { - $attachnum = $attachment["props"]["attach_num"]; - break; - } - } - } - - if ($attachnum >= 0) { - $attachment = $this->getAttachmentByAttachNum($message, $attachnum); // get first attachment only - $phototoken = $this->randomstring(16); - $tmpphoto = PLUGIN_CONTACTIMPORTER_TMP_UPLOAD . "photo_" . $phototoken . ".jpg"; - $this->storeSavedAttachment($tmpphoto, $attachment); - $vcard->addPhoto($tmpphoto, true); - unlink($tmpphoto); - } - } - // write combined vcf - file_put_contents($file, file_get_contents($file) . $vcard->getOutput()); - } - } else { - return false; - } - - if (count($records) > 0) { - $response['status'] = true; - $response['download_token'] = $token; - $response['filename'] = count($records) . "contacts.vcf"; - } else { - $response['status'] = false; - $response['message'] = "No contacts found. Export skipped!"; - } - - $this->addActionData($actionType, $response); - $GLOBALS["bus"]->addData($this->getResponseData()); - } - - /** - * Returns attachment based on specified attachNum, additionally it will also get embedded message - * if we want to get the inline image attachment. - * @param $message - * @param array $attachNum - * @return MAPIAttach embedded message attachment or attachment that is requested - */ - private function getAttachmentByAttachNum($message, $attachNum) - { - // open the attachment - $attachment = mapi_message_openattach($message, $attachNum); - - return $attachment; - } - - /** - * Function will open passed attachment and generate response for that attachment to send it to client. - * This should only be used to download attachment that is already saved in MAPIMessage. - * @param MAPIAttach $attachment attachment which will be dumped to client side - * @return Response response to sent to client including attachment data - */ - private function storeSavedAttachment($temppath, $attachment) - { - // Check if the attachment is opened - if ($attachment) { - // Open a stream to get the attachment data - $stream = mapi_openproperty($attachment, PR_ATTACH_DATA_BIN, IID_IStream, 0, 0); - $stat = mapi_stream_stat($stream); - - // Read the attachment content from the stream - $body = ''; - for ($i = 0; $i < $stat['cb']; $i += BLOCK_SIZE) { - $body .= mapi_stream_read($stream, BLOCK_SIZE); - } - - file_put_contents($temppath, $body); - } - } - - /** - * Replace String Property Tags - * @param $store - * @param $properties - * @return array - */ - private function replaceStringPropertyTags($store, $properties) - { - $newProperties = array(); - - $ids = array("name" => array(), "id" => array(), "guid" => array(), "type" => array()); // this array stores all the information needed to retrieve a named property - $num = 0; - - // caching - $guids = array(); - - foreach ($properties as $name => $val) { - if (is_string($val)) { - $split = explode(":", $val); - - if (count($split) != 3) { // invalid string, ignore - trigger_error(sprintf("Invalid property: %s \"%s\"", $name, $val), E_USER_NOTICE); - continue; - } - - if (substr($split[2], 0, 2) == "0x") { - $id = hexdec(substr($split[2], 2)); - } else { - $id = $split[2]; - } - - // have we used this guid before? - if (!defined($split[1])) { - if (!array_key_exists($split[1], $guids)) { - $guids[$split[1]] = makeguid($split[1]); - } - $guid = $guids[$split[1]]; - } else { - $guid = constant($split[1]); - } - - // temp store info about named prop, so we have to call mapi_getidsfromnames just one time - $ids["name"][$num] = $name; - $ids["id"][$num] = $id; - $ids["guid"][$num] = $guid; - $ids["type"][$num] = $split[0]; - $num++; - } else { - // not a named property - $newProperties[$name] = $val; - } - } - - if (count($ids["id"]) == 0) { - return $newProperties; - } - - // get the ids - $named = mapi_getidsfromnames($store, $ids["id"], $ids["guid"]); - foreach ($named as $num => $prop) { - $newProperties[$ids["name"][$num]] = mapi_prop_tag(constant($ids["type"][$num]), mapi_prop_id($prop)); - } - - return $newProperties; - } - - /** - * A simple Property map initialization - * - * @return [array] the propertyarray - */ - private function getProperties() - { - $properties = array(); - - $properties["subject"] = PR_SUBJECT; - $properties["hide_attachments"] = "PT_BOOLEAN:PSETID_Common:0x851"; - $properties["icon_index"] = PR_ICON_INDEX; - $properties["message_class"] = PR_MESSAGE_CLASS; - $properties["display_name"] = PR_DISPLAY_NAME; - $properties["given_name"] = PR_GIVEN_NAME; - $properties["middle_name"] = PR_MIDDLE_NAME; - $properties["surname"] = PR_SURNAME; - $properties["home_telephone_number"] = PR_HOME_TELEPHONE_NUMBER; - $properties["cellular_telephone_number"] = PR_CELLULAR_TELEPHONE_NUMBER; - $properties["office_telephone_number"] = PR_OFFICE_TELEPHONE_NUMBER; - $properties["business_fax_number"] = PR_BUSINESS_FAX_NUMBER; - $properties["company_name"] = PR_COMPANY_NAME; - $properties["title"] = PR_TITLE; - $properties["department_name"] = PR_DEPARTMENT_NAME; - $properties["office_location"] = PR_OFFICE_LOCATION; - $properties["profession"] = PR_PROFESSION; - $properties["manager_name"] = PR_MANAGER_NAME; - $properties["assistant"] = PR_ASSISTANT; - $properties["nickname"] = PR_NICKNAME; - $properties["display_name_prefix"] = PR_DISPLAY_NAME_PREFIX; - $properties["spouse_name"] = PR_SPOUSE_NAME; - $properties["generation"] = PR_GENERATION; - $properties["birthday"] = PR_BIRTHDAY; - $properties["wedding_anniversary"] = PR_WEDDING_ANNIVERSARY; - $properties["sensitivity"] = PR_SENSITIVITY; - $properties["fileas"] = "PT_STRING8:PSETID_Address:0x8005"; - $properties["fileas_selection"] = "PT_LONG:PSETID_Address:0x8006"; - $properties["email_address_1"] = "PT_STRING8:PSETID_Address:0x8083"; - $properties["email_address_display_name_1"] = "PT_STRING8:PSETID_Address:0x8080"; - $properties["email_address_display_name_email_1"] = "PT_STRING8:PSETID_Address:0x8084"; - $properties["email_address_type_1"] = "PT_STRING8:PSETID_Address:0x8082"; - $properties["email_address_2"] = "PT_STRING8:PSETID_Address:0x8093"; - $properties["email_address_display_name_2"] = "PT_STRING8:PSETID_Address:0x8090"; - $properties["email_address_display_name_email_2"] = "PT_STRING8:PSETID_Address:0x8094"; - $properties["email_address_type_2"] = "PT_STRING8:PSETID_Address:0x8092"; - $properties["email_address_3"] = "PT_STRING8:PSETID_Address:0x80a3"; - $properties["email_address_display_name_3"] = "PT_STRING8:PSETID_Address:0x80a0"; - $properties["email_address_display_name_email_3"] = "PT_STRING8:PSETID_Address:0x80a4"; - $properties["email_address_type_3"] = "PT_STRING8:PSETID_Address:0x80a2"; - $properties["home_address"] = "PT_STRING8:PSETID_Address:0x801a"; - $properties["business_address"] = "PT_STRING8:PSETID_Address:0x801b"; - $properties["other_address"] = "PT_STRING8:PSETID_Address:0x801c"; - $properties["mailing_address"] = "PT_LONG:PSETID_Address:0x8022"; - $properties["im"] = "PT_STRING8:PSETID_Address:0x8062"; - $properties["webpage"] = "PT_STRING8:PSETID_Address:0x802b"; - $properties["business_home_page"] = PR_BUSINESS_HOME_PAGE; - $properties["email_address_entryid_1"] = "PT_BINARY:PSETID_Address:0x8085"; - $properties["email_address_entryid_2"] = "PT_BINARY:PSETID_Address:0x8095"; - $properties["email_address_entryid_3"] = "PT_BINARY:PSETID_Address:0x80a5"; - $properties["address_book_mv"] = "PT_MV_LONG:PSETID_Address:0x8028"; - $properties["address_book_long"] = "PT_LONG:PSETID_Address:0x8029"; - $properties["oneoff_members"] = "PT_MV_BINARY:PSETID_Address:0x8054"; - $properties["members"] = "PT_MV_BINARY:PSETID_Address:0x8055"; - $properties["private"] = "PT_BOOLEAN:PSETID_Common:0x8506"; - $properties["contacts"] = "PT_MV_STRING8:PSETID_Common:0x853a"; - $properties["contacts_string"] = "PT_STRING8:PSETID_Common:0x8586"; - $properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords"; - $properties["last_modification_time"] = PR_LAST_MODIFICATION_TIME; - - // Detailed contacts properties - // Properties for phone numbers - $properties["assistant_telephone_number"] = PR_ASSISTANT_TELEPHONE_NUMBER; - $properties["business2_telephone_number"] = PR_BUSINESS2_TELEPHONE_NUMBER; - $properties["callback_telephone_number"] = PR_CALLBACK_TELEPHONE_NUMBER; - $properties["car_telephone_number"] = PR_CAR_TELEPHONE_NUMBER; - $properties["company_telephone_number"] = PR_COMPANY_MAIN_PHONE_NUMBER; - $properties["home2_telephone_number"] = PR_HOME2_TELEPHONE_NUMBER; - $properties["home_fax_number"] = PR_HOME_FAX_NUMBER; - $properties["isdn_number"] = PR_ISDN_NUMBER; - $properties["other_telephone_number"] = PR_OTHER_TELEPHONE_NUMBER; - $properties["pager_telephone_number"] = PR_PAGER_TELEPHONE_NUMBER; - $properties["primary_fax_number"] = PR_PRIMARY_FAX_NUMBER; - $properties["primary_telephone_number"] = PR_PRIMARY_TELEPHONE_NUMBER; - $properties["radio_telephone_number"] = PR_RADIO_TELEPHONE_NUMBER; - $properties["telex_telephone_number"] = PR_TELEX_NUMBER; - $properties["ttytdd_telephone_number"] = PR_TTYTDD_PHONE_NUMBER; - $properties["business_telephone_number"] = PR_BUSINESS_TELEPHONE_NUMBER; - - // Additional fax properties - $properties["fax_1_address_type"] = "PT_STRING8:PSETID_Address:0x80B2"; - $properties["fax_1_email_address"] = "PT_STRING8:PSETID_Address:0x80B3"; - $properties["fax_1_original_display_name"] = "PT_STRING8:PSETID_Address:0x80B4"; - $properties["fax_1_original_entryid"] = "PT_BINARY:PSETID_Address:0x80B5"; - $properties["fax_2_address_type"] = "PT_STRING8:PSETID_Address:0x80C2"; - $properties["fax_2_email_address"] = "PT_STRING8:PSETID_Address:0x80C3"; - $properties["fax_2_original_display_name"] = "PT_STRING8:PSETID_Address:0x80C4"; - $properties["fax_2_original_entryid"] = "PT_BINARY:PSETID_Address:0x80C5"; - $properties["fax_3_address_type"] = "PT_STRING8:PSETID_Address:0x80D2"; - $properties["fax_3_email_address"] = "PT_STRING8:PSETID_Address:0x80D3"; - $properties["fax_3_original_display_name"] = "PT_STRING8:PSETID_Address:0x80D4"; - $properties["fax_3_original_entryid"] = "PT_BINARY:PSETID_Address:0x80D5"; - - // Properties for addresses - // Home address - $properties["home_address_street"] = PR_HOME_ADDRESS_STREET; - $properties["home_address_city"] = PR_HOME_ADDRESS_CITY; - $properties["home_address_state"] = PR_HOME_ADDRESS_STATE_OR_PROVINCE; - $properties["home_address_postal_code"] = PR_HOME_ADDRESS_POSTAL_CODE; - $properties["home_address_country"] = PR_HOME_ADDRESS_COUNTRY; - // Other address - $properties["other_address_street"] = PR_OTHER_ADDRESS_STREET; - $properties["other_address_city"] = PR_OTHER_ADDRESS_CITY; - $properties["other_address_state"] = PR_OTHER_ADDRESS_STATE_OR_PROVINCE; - $properties["other_address_postal_code"] = PR_OTHER_ADDRESS_POSTAL_CODE; - $properties["other_address_country"] = PR_OTHER_ADDRESS_COUNTRY; - // Business address - $properties["business_address_street"] = "PT_STRING8:PSETID_Address:0x8045"; - $properties["business_address_city"] = "PT_STRING8:PSETID_Address:0x8046"; - $properties["business_address_state"] = "PT_STRING8:PSETID_Address:0x8047"; - $properties["business_address_postal_code"] = "PT_STRING8:PSETID_Address:0x8048"; - $properties["business_address_country"] = "PT_STRING8:PSETID_Address:0x8049"; - // Mailing address - $properties["country"] = PR_COUNTRY; - $properties["city"] = PR_LOCALITY; - $properties["postal_address"] = PR_POSTAL_ADDRESS; - $properties["postal_code"] = PR_POSTAL_CODE; - $properties["state"] = PR_STATE_OR_PROVINCE; - $properties["street"] = PR_STREET_ADDRESS; - // Special Date such as birthday n anniversary appoitment's entryid is store - $properties["birthday_eventid"] = "PT_BINARY:PSETID_Address:0x804D"; - $properties["anniversary_eventid"] = "PT_BINARY:PSETID_Address:0x804E"; - - $properties["notes"] = PR_BODY; - - // hasimage - $properties["picture"] = "PT_BOOLEAN:{00062004-0000-0000-C000-000000000046}:0x8015"; - - return $properties; - } - - /** - * Function that parses the uploaded vcf file and posts it via json - * @param $actionType - * @param $actionData - */ - private function loadContacts($actionType, $actionData) - { - $error = false; - $error_msg = ""; - - if (is_readable($actionData["vcf_filepath"])) { - $parser = null; - - try { - $parser = VCardParser::parseFromFile($actionData["vcf_filepath"]); - } catch (Exception $e) { - $error = true; - $error_msg = $e->getMessage(); - } - if ($error) { - $response['status'] = false; - $response['message'] = $error_msg; - } else { - if (iterator_count($parser) == 0) { - $response['status'] = false; - $response['message'] = "No contacts in vcf file"; - } else { - $response['status'] = true; - $response['parsed_file'] = $actionData["vcf_filepath"]; - $response['parsed'] = array( - 'contacts' => $this->parseContactsToArray($parser) - ); - } - } - } else { - $response['status'] = false; - $response['message'] = "File could not be read by server"; - } - - $this->addActionData($actionType, $response); - $GLOBALS["bus"]->addData($this->getResponseData()); - - if ($this->DEBUG) { - error_log("parsing done, bus data written!"); - } - } - - /** - * Create a array with contacts - * - * @param contacts vcard or csv contacts - * @param csv optional, true if contacts are csv contacts - * @return array parsed contacts - * @private - */ - private function parseContactsToArray($contacts, $csv = false) - { - $carr = array(); - - if (!$csv) { - foreach ($contacts as $Index => $vCard) { - $properties = array(); - if (isset($vCard->fullname)) { - $properties["display_name"] = $vCard->fullname; - $properties["fileas"] = $vCard->fullname; - } elseif (!isset($vCard->organization)) { - error_log("Skipping entry! No fullname/organization given."); - continue; - } - - $properties["hide_attachments"] = true; - - //uid - used for front/backend communication - $properties["internal_fields"] = array(); - $properties["internal_fields"]["contact_uid"] = base64_encode($Index . $properties["fileas"]); - - $properties["given_name"] = $vCard->firstname; - $properties["middle_name"] = $vCard->additional; - $properties["surname"] = $vCard->lastname; - $properties["display_name_prefix"] = $vCard->prefix; - - if (isset($vCard->phone) && count($vCard->phone) > 0) { - foreach ($vCard->phone as $type => $number) { - $number = $number[0]; // we only can store one number - if ($this->startswith(strtolower($type), "home") || strtolower($type) === "default") { - $properties["home_telephone_number"] = $number; - } else { - if ($this->startswith(strtolower($type), "cell")) { - $properties["cellular_telephone_number"] = $number; - } else { - if ($this->startswith(strtolower($type), "work")) { - $properties["business_telephone_number"] = $number; - } else { - if ($this->startswith(strtolower($type), "fax")) { - $properties["business_fax_number"] = $number; - } else { - if ($this->startswith(strtolower($type), "pager")) { - $properties["pager_telephone_number"] = $number; - } else { - if ($this->startswith(strtolower($type), "isdn")) { - $properties["isdn_number"] = $number; - } else { - if ($this->startswith(strtolower($type), "car")) { - $properties["car_telephone_number"] = $number; - } else { - if ($this->startswith(strtolower($type), "modem")) { - $properties["ttytdd_telephone_number"] = $number; - } - } - } - } - } - } - } - } - } - } - if (isset($vCard->email) && count($vCard->email) > 0) { - $emailcount = 0; - $properties["address_book_long"] = 0; - foreach ($vCard->email as $type => $email) { - foreach ($email as $mail) { - $fileas = $mail; - if (isset($properties["fileas"]) && !empty($properties["fileas"])) { - $fileas = $properties["fileas"]; // set to real name - } - - // we only have storage for 3 mail addresses! - /** - * type of email address address_book_mv address_book_long - * email1 0 1 (0x00000001) - * email2 1 2 (0x00000002) - * email3 2 4 (0x00000004) - * fax2(business fax) 3 8 (0x00000008) - * fax3(home fax) 4 16 (0x00000010) - * fax1(primary fax) 5 32 (0x00000020) - * - * address_book_mv is a multivalued property so all the values are passed in array - * address_book_long stores sum of the flags - * these both properties should be in sync always - */ - switch ($emailcount) { - case 0: - $properties["email_address_1"] = $mail; - $properties["email_address_display_name_1"] = $fileas . " (" . $mail . ")"; - $properties["email_address_display_name_email_1"] = $mail; - $properties["address_book_mv"][] = 0; // this is needed for adding the contact to the email address book, 0 = email 1 - $properties["address_book_long"] += 1; // this specifies the number of elements in address_book_mv - break; - case 1: - $properties["email_address_2"] = $mail; - $properties["email_address_display_name_2"] = $fileas . " (" . $mail . ")"; - $properties["email_address_display_name_email_2"] = $mail; - $properties["address_book_mv"][] = 1; // this is needed for adding the contact to the email address book, 1 = email 2 - $properties["address_book_long"] += 2; // this specifies the number of elements in address_book_mv - break; - case 2: - $properties["email_address_3"] = $mail; - $properties["email_address_display_name_3"] = $fileas . " (" . $mail . ")"; - $properties["email_address_display_name_email_3"] = $mail; - $properties["address_book_mv"][] = 2; // this is needed for adding the contact to the email address book, 2 = email 3 - $properties["address_book_long"] += 4; // this specifies the number of elements in address_book_mv - break; - default: - break; - } - $emailcount++; - } - } - } - if (isset($vCard->organization)) { - $properties["company_name"] = $vCard->organization; - if (empty($properties["display_name"])) { - $properties["display_name"] = $vCard->organization; // if we have no displayname - use the company name as displayname - $properties["fileas"] = $vCard->organization; - } - } - if (isset($vCard->title)) { - $properties["title"] = $vCard->title; - } - if (isset($vCard->url) && count($vCard->url) > 0) { - foreach ($vCard->url as $type => $url) { - $url = $url[0]; // only 1 webaddress per type - $properties["webpage"] = $url; - break; // we can only store on url - } - } - if (isset($vCard->address) && count($vCard->address) > 0) { - - foreach ($vCard->address as $type => $address) { - $address = $address[0]; // we only can store one address per type - if ($this->startswith(strtolower($type), "work")) { - $properties["business_address_street"] = $address->street; - if (!empty($address->extended)) { - $properties["business_address_street"] .= "\n" . $address->extended; - } - $properties["business_address_city"] = $address->city; - $properties["business_address_state"] = $address->region; - $properties["business_address_postal_code"] = $address->zip; - $properties["business_address_country"] = $address->country; - $properties["business_address"] = $this->buildAddressString($properties["business_address_street"], $address->zip, $address->city, $address->region, $address->country); - } else { - if ($this->startswith(strtolower($type), "home")) { - $properties["home_address_street"] = $address->street; - if (!empty($address->extended)) { - $properties["home_address_street"] .= "\n" . $address->extended; - } - $properties["home_address_city"] = $address->city; - $properties["home_address_state"] = $address->region; - $properties["home_address_postal_code"] = $address->zip; - $properties["home_address_country"] = $address->country; - $properties["home_address"] = $this->buildAddressString($properties["home_address_street"], $address->zip, $address->city, $address->region, $address->country); - } else { - $properties["other_address_street"] = $address->street; - if (!empty($address->extended)) { - $properties["other_address_street"] .= "\n" . $address->extended; - } - $properties["other_address_city"] = $address->city; - $properties["other_address_state"] = $address->region; - $properties["other_address_postal_code"] = $address->zip; - $properties["other_address_country"] = $address->country; - $properties["other_address"] = $this->buildAddressString($properties["other_address_street"], $address->zip, $address->city, $address->region, $address->country); - } - } - } - } - if (isset($vCard->birthday)) { - $properties["birthday"] = $vCard->birthday->getTimestamp(); - } - if (isset($vCard->note)) { - $properties["notes"] = $vCard->note; - } - if (isset($vCard->rawPhoto) || isset($vCard->photo)) { - if (!is_writable(TMP_PATH . "/")) { - error_log("Can not write to export tmp directory!"); - } else { - $tmppath = TMP_PATH . "/" . $this->randomstring(15); - if (isset($vCard->rawPhoto)) { - if (file_put_contents($tmppath, $vCard->rawPhoto)) { - $properties["internal_fields"]["x_photo_path"] = $tmppath; - } - } elseif (isset($vCard->photo)) { - if ($this->startswith(strtolower($vCard->photo), "http://") || $this->startswith(strtolower($vCard->photo), "https://")) { // check if it starts with http - $ctx = stream_context_create(array('http' => - array( - 'timeout' => 3, //3 Seconds timout - ) - )); - - if (file_put_contents($tmppath, file_get_contents($vCard->photo, false, $ctx))) { - $properties["internal_fields"]["x_photo_path"] = $tmppath; - } - } else { - error_log("Invalid photo url: " . $vCard->photo); - } - } - } - } - array_push($carr, $properties); - } - } else { - error_log("csv parsing not implemented"); - } - - return $carr; - } - - /** - * Generate the whole addressstring - * - * @param street - * @param zip - * @param city - * @param state - * @param country - * @return string the concatinated address string - * @private - */ - private function buildAddressString($street, $zip, $city, $state, $country) - { - $out = ""; - - if (isset($country) && $street != "") { - $out = $country; - } - - $zcs = ""; - if (isset($zip) && $zip != "") { - $zcs = $zip; - } - if (isset($city) && $city != "") { - $zcs .= (($zcs) ? " " : "") . $city; - } - if (isset($state) && $state != "") { - $zcs .= (($zcs) ? " " : "") . $state; - } - if ($zcs) { - $out = $zcs . "\n" . $out; - } - - if (isset($street) && $street != "") { - $out = $street . (($out) ? "\n\n" . $out : ""); - } - - return $out; - } - - /** - * Store the file to a temporary directory - * @param $actionType - * @param $actionData - * @private - */ - private function getAttachmentPath($actionType, $actionData) - { - // Get store id - $storeid = false; - if (isset($actionData["store"])) { - $storeid = $actionData["store"]; - } - - // Get message entryid - $entryid = false; - if (isset($actionData["entryid"])) { - $entryid = $actionData["entryid"]; - } - - // Check which type isset - $openType = "attachment"; - - // Get number of attachment which should be opened. - $attachNum = false; - if (isset($actionData["attachNum"])) { - $attachNum = $actionData["attachNum"]; - } - - // Check if storeid and entryid isset - if ($storeid && $entryid) { - // Open the store - $store = $GLOBALS["mapisession"]->openMessageStore(hex2bin($storeid)); - - if ($store) { - // Open the message - $message = mapi_msgstore_openentry($store, hex2bin($entryid)); - - if ($message) { - $attachment = false; - - // Check if attachNum isset - if ($attachNum) { - // Loop through the attachNums, message in message in message ... - for ($i = 0; $i < (count($attachNum) - 1); $i++) { - // Open the attachment - $tempattach = mapi_message_openattach($message, (int)$attachNum[$i]); - if ($tempattach) { - // Open the object in the attachment - $message = mapi_attach_openobj($tempattach); - } - } - - // Open the attachment - $attachment = mapi_message_openattach($message, (int)$attachNum[(count($attachNum) - 1)]); - } - - // Check if the attachment is opened - if ($attachment) { - - // Get the props of the attachment - $props = mapi_attach_getprops($attachment, array(PR_ATTACH_LONG_FILENAME, PR_ATTACH_MIME_TAG, PR_DISPLAY_NAME, PR_ATTACH_METHOD)); - // Content Type - $contentType = "application/octet-stream"; - // Filename - $filename = "ERROR"; - - // Set filename - if (isset($props[PR_ATTACH_LONG_FILENAME])) { - $filename = $props[PR_ATTACH_LONG_FILENAME]; - } else { - if (isset($props[PR_ATTACH_FILENAME])) { - $filename = $props[PR_ATTACH_FILENAME]; - } else { - if (isset($props[PR_DISPLAY_NAME])) { - $filename = $props[PR_DISPLAY_NAME]; - } - } - } - - // Set content type - if (isset($props[PR_ATTACH_MIME_TAG])) { - $contentType = $props[PR_ATTACH_MIME_TAG]; - } else { - // Parse the extension of the filename to get the content type - if (strrpos($filename, ".") !== false) { - $extension = strtolower(substr($filename, strrpos($filename, "."))); - $contentType = "application/octet-stream"; - if (is_readable("mimetypes.dat")) { - $fh = fopen("mimetypes.dat", "r"); - $ext_found = false; - while (!feof($fh) && !$ext_found) { - $line = fgets($fh); - preg_match("/(\.[a-z0-9]+)[ \t]+([^ \t\n\r]*)/i", $line, $result); - if ($extension == $result[1]) { - $ext_found = true; - $contentType = $result[2]; - } - } - fclose($fh); - } - } - } - - - $tmpname = tempnam(TMP_PATH, stripslashes($filename)); - - // Open a stream to get the attachment data - $stream = mapi_openpropertytostream($attachment, PR_ATTACH_DATA_BIN); - $stat = mapi_stream_stat($stream); - // File length = $stat["cb"] - - $fhandle = fopen($tmpname, 'w'); - $buffer = null; - for ($i = 0; $i < $stat["cb"]; $i += BLOCK_SIZE) { - // Write stream - $buffer = mapi_stream_read($stream, BLOCK_SIZE); - fwrite($fhandle, $buffer, strlen($buffer)); - } - fclose($fhandle); - - $response = array(); - $response['tmpname'] = $tmpname; - $response['filename'] = $filename; - $response['status'] = true; - $this->addActionData($actionType, $response); - $GLOBALS["bus"]->addData($this->getResponseData()); - } - } - } else { - $response['status'] = false; - $response['message'] = "Store could not be opened!"; - $this->addActionData($actionType, $response); - $GLOBALS["bus"]->addData($this->getResponseData()); - } - } else { - $response['status'] = false; - $response['message'] = "Wrong call, store and entryid have to be set!"; - $this->addActionData($actionType, $response); - $GLOBALS["bus"]->addData($this->getResponseData()); - } - } - - /** - * Check if string starts with other string. - * @param $haystack - * @param $needle - * @return bool - */ - private function startswith($haystack, $needle) - { - $haystack = str_replace("type=", "", $haystack); // remove type from string - return substr($haystack, 0, strlen($needle)) === $needle; - } + private $DEBUG = false; // enable error_log debugging + + /** + * @constructor + * @param $id + * @param $data + */ + public function __construct($id, $data) + { + parent::__construct($id, $data); + } + + /** + * Executes all the actions in the $data variable. + * Exception part is used for authentication errors also + * @return boolean true on success or false on failure. + */ + public function execute() + { + $result = false; + + if (!$this->DEBUG) { + /* disable error printing - otherwise json communication might break... */ + ini_set('display_errors', '0'); + } + + foreach ($this->data as $actionType => $actionData) { + if (isset($actionType)) { + try { + if ($this->DEBUG) { + error_log("exec: " . $actionType); + } + switch ($actionType) { + case "load": + $result = $this->loadContacts($actionType, $actionData); + break; + case "import": + $result = $this->importContacts($actionType, $actionData); + break; + case "export": + $result = $this->exportContacts($actionType, $actionData); + break; + case "importattachment": + $result = $this->getAttachmentPath($actionType, $actionData); + break; + default: + $this->handleUnknownActionType($actionType); + } + + } catch (MAPIException $e) { + if ($this->DEBUG) { + error_log("mapi exception: " . $e->getMessage()); + } + } catch (Exception $e) { + if ($this->DEBUG) { + error_log("exception: " . $e->getMessage()); + } + } + } + } + + return $result; + } + + /** + * Generates a random string with variable length. + * @param $length the lenght of the generated string + * @return string a random string + */ + private function randomstring($length = 6) + { + // $chars - all allowed charakters + $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; + + srand((double)microtime() * 1000000); + $i = 0; + $pass = ""; + while ($i < $length) { + $num = rand() % strlen($chars); + $tmp = substr($chars, $num, 1); + $pass = $pass . $tmp; + $i++; + } + return $pass; + } + + /** + * Add an attachment to the give contact + * @param $actionType + * @param $actionData + */ + private function importContacts($actionType, $actionData) + { + + // Get uploaded vcf path + $vcffile = false; + if (isset($actionData["vcf_filepath"])) { + $vcffile = $actionData["vcf_filepath"]; + } + + // Get store id + $storeid = false; + if (isset($actionData["storeid"])) { + $storeid = $actionData["storeid"]; + } + + // Get folder entryid + $folderid = false; + if (isset($actionData["folderid"])) { + $folderid = $actionData["folderid"]; + } + + // Get uids + $uids = array(); + if (isset($actionData["uids"])) { + $uids = $actionData["uids"]; + } + + $response = array(); + $error = false; + $error_msg = ""; + + // parse the vcf file a last time... + $parser = null; + try { + $parser = VCardParser::parseFromFile($vcffile); + } catch (Exception $e) { + $error = true; + $error_msg = $e->getMessage(); + } + + $contacts = array(); + + if (!$error && iterator_count($parser) > 0) { + $contacts = $this->parseContactsToArray($parser); + $store = $GLOBALS["mapisession"]->openMessageStore(hex2bin($storeid)); + $folder = mapi_msgstore_openentry($store, hex2bin($folderid)); + + $importall = false; + if (count($uids) == count($contacts)) { + $importall = true; + } + + $propValuesMAPI = array(); + $properties = $this->getProperties(); + $properties = $this->replaceStringPropertyTags($store, $properties); + $count = 0; + + // iterate through all contacts and import them :) + foreach ($contacts as $contact) { + if (isset($contact["display_name"]) && ($importall || in_array($contact["internal_fields"]["contact_uid"], $uids))) { + // parse the arraykeys + // TODO: this is very slow... + foreach ($contact as $key => $value) { + if ($key !== "internal_fields") { + $propValuesMAPI[$properties[$key]] = $value; + } + } + + $propValuesMAPI[$properties["message_class"]] = "IPM.Contact"; + $propValuesMAPI[$properties["icon_index"]] = "512"; + $message = mapi_folder_createmessage($folder); + + + if (isset($contact["internal_fields"]["x_photo_path"])) { + $propValuesMAPI[$properties["picture"]] = 1; // contact has an image + + // import the photo + $contactPicture = file_get_contents($contact["internal_fields"]["x_photo_path"]); + $attach = mapi_message_createattach($message); + + // Set properties of the attachment + $propValuesIMG = array( + PR_ATTACH_SIZE => strlen($contactPicture), + PR_ATTACH_LONG_FILENAME => 'ContactPicture.jpg', + PR_ATTACHMENT_HIDDEN => false, + PR_DISPLAY_NAME => 'ContactPicture.jpg', + PR_ATTACH_METHOD => ATTACH_BY_VALUE, + PR_ATTACH_MIME_TAG => 'image/jpeg', + PR_ATTACHMENT_CONTACTPHOTO => true, + PR_ATTACH_DATA_BIN => $contactPicture, + PR_ATTACHMENT_FLAGS => 1, + PR_ATTACH_EXTENSION_A => '.jpg', + PR_ATTACH_NUM => 1 + ); + + mapi_setprops($attach, $propValuesIMG); + mapi_savechanges($attach); + if ($this->DEBUG) { + error_log("Contactpicture imported!"); + } + + if (mapi_last_hresult() > 0) { + error_log("Error saving attach to contact: " . get_mapi_error_name()); + } + } + + mapi_setprops($message, $propValuesMAPI); + mapi_savechanges($message); + if ($this->DEBUG) { + error_log("New contact added: \"" . $propValuesMAPI[$properties["display_name"]] . "\".\n"); + } + $count++; + } + } + + $response['status'] = true; + $response['count'] = $count; + $response['message'] = ""; + + } else { + $response['status'] = false; + $response['count'] = 0; + $response['message'] = $error ? $error_msg : dgettext("plugin_contactimporter", "VCF file is empty!"); + } + + $this->addActionData($actionType, $response); + $GLOBALS["bus"]->addData($this->getResponseData()); + } + + /** + * Get a property from the array. + * @param $props + * @param $propname + * @return string + */ + private function getProp($props, $propname) + { + $p = $this->getProperties(); + if (isset($props["props"][$propname])) { + return $props["props"][$propname]; + } + return ""; + } + + /** + * Export selected contacts to vCard. + * @param $actionType + * @param $actionData + * @return bool + */ + private function exportContacts($actionType, $actionData) + { + // Get store id + $storeid = false; + if (isset($actionData["storeid"])) { + $storeid = $actionData["storeid"]; + } + + // Get records + $records = array(); + if (isset($actionData["records"])) { + $records = $actionData["records"]; + } + + // Get folders + $folder = false; + if (isset($actionData["folder"])) { + $folder = $actionData["folder"]; + } + + $response = array(); + $error = false; + $error_msg = ""; + + // write csv + $token = $this->randomstring(16); + $file = PLUGIN_CONTACTIMPORTER_TMP_UPLOAD . "vcf_" . $token . ".vcf"; + file_put_contents($file, ""); + + $store = $GLOBALS["mapisession"]->openMessageStore(hex2bin($storeid)); + if ($store) { + // load folder first + if ($folder !== false) { + $mapifolder = mapi_msgstore_openentry($store, hex2bin($folder)); + + $table = mapi_folder_getcontentstable($mapifolder); + $list = mapi_table_queryallrows($table, array(PR_ENTRYID)); + + foreach ($list as $item) { + $records[] = bin2hex($item[PR_ENTRYID]); + } + } + for ($index = 0, $count = count($records); $index < $count; $index++) { + // define vcard + $vcard = new VCard(); + + $message = mapi_msgstore_openentry($store, hex2bin($records[$index])); + + // get message properties. + $properties = $GLOBALS['properties']->getContactProperties(); + $plaintext = true; + $messageProps = $GLOBALS['operations']->getMessageProps($store, $message, $properties, $plaintext); + + // define variables + $firstname = $this->getProp($messageProps, "given_name"); + $lastname = $this->getProp($messageProps, "surname"); + $additional = $this->getProp($messageProps, "middle_name"); + $prefix = $this->getProp($messageProps, "display_name_prefix"); + $suffix = ''; + + // add personal data + $vcard->addName($lastname, $firstname, $additional, $prefix, $suffix); + + $company = $this->getProp($messageProps, "company_name"); + if (!empty($company)) { + $vcard->addCompany($company); + } + + $jobtitle = $this->getProp($messageProps, "title"); + if (!empty($jobtitle)) { + $vcard->addJobtitle($jobtitle); + } + + // MAIL + $mail = $this->getProp($messageProps, "email_address_1"); + if (!empty($mail)) { + $vcard->addEmail($mail); + } + $mail = $this->getProp($messageProps, "email_address_2"); + if (!empty($mail)) { + $vcard->addEmail($mail); + } + $mail = $this->getProp($messageProps, "email_address_3"); + if (!empty($mail)) { + $vcard->addEmail($mail); + } + + // PHONE + $wphone = $this->getProp($messageProps, "business_telephone_number"); + if (!empty($wphone)) { + $vcard->addPhoneNumber($wphone, 'WORK'); + } + $wphone = $this->getProp($messageProps, "home_telephone_number"); + if (!empty($wphone)) { + $vcard->addPhoneNumber($wphone, 'HOME'); + } + $wphone = $this->getProp($messageProps, "cellular_telephone_number"); + if (!empty($wphone)) { + $vcard->addPhoneNumber($wphone, 'CELL'); + } + $wphone = $this->getProp($messageProps, "business_fax_number"); + if (!empty($wphone)) { + $vcard->addPhoneNumber($wphone, 'FAX'); + } + $wphone = $this->getProp($messageProps, "pager_telephone_number"); + if (!empty($wphone)) { + $vcard->addPhoneNumber($wphone, 'PAGER'); + } + $wphone = $this->getProp($messageProps, "car_telephone_number"); + if (!empty($wphone)) { + $vcard->addPhoneNumber($wphone, 'CAR'); + } + + // ADDRESS + $addr = $this->getProp($messageProps, "business_address"); + if (!empty($addr)) { + $vcard->addAddress(null, null, $this->getProp($messageProps, "business_address_street"), $this->getProp($messageProps, "business_address_city"), $this->getProp($messageProps, "business_address_state"), $this->getProp($messageProps, "business_address_postal_code"), $this->getProp($messageProps, "business_address_country"), "WORK"); + } + $addr = $this->getProp($messageProps, "home_address"); + if (!empty($addr)) { + $vcard->addAddress(null, null, $this->getProp($messageProps, "home_address_street"), $this->getProp($messageProps, "home_address_city"), $this->getProp($messageProps, "home_address_state"), $this->getProp($messageProps, "home_address_postal_code"), $this->getProp($messageProps, "home_address_country"), "HOME"); + } + $addr = $this->getProp($messageProps, "other_address"); + if (!empty($addr)) { + $vcard->addAddress(null, null, $this->getProp($messageProps, "other_address_street"), $this->getProp($messageProps, "other_address_city"), $this->getProp($messageProps, "other_address_state"), $this->getProp($messageProps, "other_address_postal_code"), $this->getProp($messageProps, "other_address_country"), "OTHER"); + } + + // MISC + $url = $this->getProp($messageProps, "webpage"); + if (!empty($url)) { + $vcard->addURL($url); + } + + $bday = $this->getProp($messageProps, "birthday"); + if (!empty($bday)) { + $vcard->addBirthday(date("Y-m-d", $bday)); + } + + $notes = $this->getProp($messageProps, "body"); + if (!empty($notes)) { + $vcard->addNote($notes); + } + + $haspicture = $this->getProp($messageProps, "has_picture"); + if (!empty($haspicture) && $haspicture === true) { + $attachnum = -1; + if (isset($messageProps["attachments"]) && isset($messageProps["attachments"]["item"])) { + foreach ($messageProps["attachments"]["item"] as $attachment) { + if ($attachment["props"]["attachment_contactphoto"] == true) { + $attachnum = $attachment["props"]["attach_num"]; + break; + } + } + } + + if ($attachnum >= 0) { + $attachment = $this->getAttachmentByAttachNum($message, $attachnum); // get first attachment only + $phototoken = $this->randomstring(16); + $tmpphoto = PLUGIN_CONTACTIMPORTER_TMP_UPLOAD . "photo_" . $phototoken . ".jpg"; + $this->storeSavedAttachment($tmpphoto, $attachment); + $vcard->addPhoto($tmpphoto, true); + unlink($tmpphoto); + } + } + // write combined vcf + file_put_contents($file, file_get_contents($file) . $vcard->getOutput()); + } + } else { + return false; + } + + if (count($records) > 0) { + $response['status'] = true; + $response['download_token'] = $token; + // TRANSLATORS: Filename suffix for exported files + $response['filename'] = count($records) . dgettext("plugin_contactimporter", "_contacts.vcf"); + } else { + $response['status'] = false; + $response['message'] = dgettext("plugin_contactimporter", "No contacts found. Export skipped!"); + } + + $this->addActionData($actionType, $response); + $GLOBALS["bus"]->addData($this->getResponseData()); + } + + /** + * Returns attachment based on specified attachNum, additionally it will also get embedded message + * if we want to get the inline image attachment. + * @param $message + * @param array $attachNum + * @return MAPIAttach embedded message attachment or attachment that is requested + */ + private function getAttachmentByAttachNum($message, $attachNum) + { + // open the attachment + $attachment = mapi_message_openattach($message, $attachNum); + + return $attachment; + } + + /** + * Function will open passed attachment and generate response for that attachment to send it to client. + * This should only be used to download attachment that is already saved in MAPIMessage. + * @param MAPIAttach $attachment attachment which will be dumped to client side + * @return Response response to sent to client including attachment data + */ + private function storeSavedAttachment($temppath, $attachment) + { + // Check if the attachment is opened + if ($attachment) { + // Open a stream to get the attachment data + $stream = mapi_openproperty($attachment, PR_ATTACH_DATA_BIN, IID_IStream, 0, 0); + $stat = mapi_stream_stat($stream); + + // Read the attachment content from the stream + $body = ''; + for ($i = 0; $i < $stat['cb']; $i += BLOCK_SIZE) { + $body .= mapi_stream_read($stream, BLOCK_SIZE); + } + + file_put_contents($temppath, $body); + } + } + + /** + * Replace String Property Tags + * @param $store + * @param $properties + * @return array + */ + private function replaceStringPropertyTags($store, $properties) + { + $newProperties = array(); + + $ids = array("name" => array(), "id" => array(), "guid" => array(), "type" => array()); // this array stores all the information needed to retrieve a named property + $num = 0; + + // caching + $guids = array(); + + foreach ($properties as $name => $val) { + if (is_string($val)) { + $split = explode(":", $val); + + if (count($split) != 3) { // invalid string, ignore + trigger_error(sprintf("Invalid property: %s \"%s\"", $name, $val), E_USER_NOTICE); + continue; + } + + if (substr($split[2], 0, 2) == "0x") { + $id = hexdec(substr($split[2], 2)); + } else { + $id = $split[2]; + } + + // have we used this guid before? + if (!defined($split[1])) { + if (!array_key_exists($split[1], $guids)) { + $guids[$split[1]] = makeguid($split[1]); + } + $guid = $guids[$split[1]]; + } else { + $guid = constant($split[1]); + } + + // temp store info about named prop, so we have to call mapi_getidsfromnames just one time + $ids["name"][$num] = $name; + $ids["id"][$num] = $id; + $ids["guid"][$num] = $guid; + $ids["type"][$num] = $split[0]; + $num++; + } else { + // not a named property + $newProperties[$name] = $val; + } + } + + if (count($ids["id"]) == 0) { + return $newProperties; + } + + // get the ids + $named = mapi_getidsfromnames($store, $ids["id"], $ids["guid"]); + foreach ($named as $num => $prop) { + $newProperties[$ids["name"][$num]] = mapi_prop_tag(constant($ids["type"][$num]), mapi_prop_id($prop)); + } + + return $newProperties; + } + + /** + * A simple Property map initialization + * + * @return [array] the propertyarray + */ + private function getProperties() + { + $properties = array(); + + $properties["subject"] = PR_SUBJECT; + $properties["hide_attachments"] = "PT_BOOLEAN:PSETID_Common:0x851"; + $properties["icon_index"] = PR_ICON_INDEX; + $properties["message_class"] = PR_MESSAGE_CLASS; + $properties["display_name"] = PR_DISPLAY_NAME; + $properties["given_name"] = PR_GIVEN_NAME; + $properties["middle_name"] = PR_MIDDLE_NAME; + $properties["surname"] = PR_SURNAME; + $properties["home_telephone_number"] = PR_HOME_TELEPHONE_NUMBER; + $properties["cellular_telephone_number"] = PR_CELLULAR_TELEPHONE_NUMBER; + $properties["office_telephone_number"] = PR_OFFICE_TELEPHONE_NUMBER; + $properties["business_fax_number"] = PR_BUSINESS_FAX_NUMBER; + $properties["company_name"] = PR_COMPANY_NAME; + $properties["title"] = PR_TITLE; + $properties["department_name"] = PR_DEPARTMENT_NAME; + $properties["office_location"] = PR_OFFICE_LOCATION; + $properties["profession"] = PR_PROFESSION; + $properties["manager_name"] = PR_MANAGER_NAME; + $properties["assistant"] = PR_ASSISTANT; + $properties["nickname"] = PR_NICKNAME; + $properties["display_name_prefix"] = PR_DISPLAY_NAME_PREFIX; + $properties["spouse_name"] = PR_SPOUSE_NAME; + $properties["generation"] = PR_GENERATION; + $properties["birthday"] = PR_BIRTHDAY; + $properties["wedding_anniversary"] = PR_WEDDING_ANNIVERSARY; + $properties["sensitivity"] = PR_SENSITIVITY; + $properties["fileas"] = "PT_STRING8:PSETID_Address:0x8005"; + $properties["fileas_selection"] = "PT_LONG:PSETID_Address:0x8006"; + $properties["email_address_1"] = "PT_STRING8:PSETID_Address:0x8083"; + $properties["email_address_display_name_1"] = "PT_STRING8:PSETID_Address:0x8080"; + $properties["email_address_display_name_email_1"] = "PT_STRING8:PSETID_Address:0x8084"; + $properties["email_address_type_1"] = "PT_STRING8:PSETID_Address:0x8082"; + $properties["email_address_2"] = "PT_STRING8:PSETID_Address:0x8093"; + $properties["email_address_display_name_2"] = "PT_STRING8:PSETID_Address:0x8090"; + $properties["email_address_display_name_email_2"] = "PT_STRING8:PSETID_Address:0x8094"; + $properties["email_address_type_2"] = "PT_STRING8:PSETID_Address:0x8092"; + $properties["email_address_3"] = "PT_STRING8:PSETID_Address:0x80a3"; + $properties["email_address_display_name_3"] = "PT_STRING8:PSETID_Address:0x80a0"; + $properties["email_address_display_name_email_3"] = "PT_STRING8:PSETID_Address:0x80a4"; + $properties["email_address_type_3"] = "PT_STRING8:PSETID_Address:0x80a2"; + $properties["home_address"] = "PT_STRING8:PSETID_Address:0x801a"; + $properties["business_address"] = "PT_STRING8:PSETID_Address:0x801b"; + $properties["other_address"] = "PT_STRING8:PSETID_Address:0x801c"; + $properties["mailing_address"] = "PT_LONG:PSETID_Address:0x8022"; + $properties["im"] = "PT_STRING8:PSETID_Address:0x8062"; + $properties["webpage"] = "PT_STRING8:PSETID_Address:0x802b"; + $properties["business_home_page"] = PR_BUSINESS_HOME_PAGE; + $properties["email_address_entryid_1"] = "PT_BINARY:PSETID_Address:0x8085"; + $properties["email_address_entryid_2"] = "PT_BINARY:PSETID_Address:0x8095"; + $properties["email_address_entryid_3"] = "PT_BINARY:PSETID_Address:0x80a5"; + $properties["address_book_mv"] = "PT_MV_LONG:PSETID_Address:0x8028"; + $properties["address_book_long"] = "PT_LONG:PSETID_Address:0x8029"; + $properties["oneoff_members"] = "PT_MV_BINARY:PSETID_Address:0x8054"; + $properties["members"] = "PT_MV_BINARY:PSETID_Address:0x8055"; + $properties["private"] = "PT_BOOLEAN:PSETID_Common:0x8506"; + $properties["contacts"] = "PT_MV_STRING8:PSETID_Common:0x853a"; + $properties["contacts_string"] = "PT_STRING8:PSETID_Common:0x8586"; + $properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords"; + $properties["last_modification_time"] = PR_LAST_MODIFICATION_TIME; + + // Detailed contacts properties + // Properties for phone numbers + $properties["assistant_telephone_number"] = PR_ASSISTANT_TELEPHONE_NUMBER; + $properties["business2_telephone_number"] = PR_BUSINESS2_TELEPHONE_NUMBER; + $properties["callback_telephone_number"] = PR_CALLBACK_TELEPHONE_NUMBER; + $properties["car_telephone_number"] = PR_CAR_TELEPHONE_NUMBER; + $properties["company_telephone_number"] = PR_COMPANY_MAIN_PHONE_NUMBER; + $properties["home2_telephone_number"] = PR_HOME2_TELEPHONE_NUMBER; + $properties["home_fax_number"] = PR_HOME_FAX_NUMBER; + $properties["isdn_number"] = PR_ISDN_NUMBER; + $properties["other_telephone_number"] = PR_OTHER_TELEPHONE_NUMBER; + $properties["pager_telephone_number"] = PR_PAGER_TELEPHONE_NUMBER; + $properties["primary_fax_number"] = PR_PRIMARY_FAX_NUMBER; + $properties["primary_telephone_number"] = PR_PRIMARY_TELEPHONE_NUMBER; + $properties["radio_telephone_number"] = PR_RADIO_TELEPHONE_NUMBER; + $properties["telex_telephone_number"] = PR_TELEX_NUMBER; + $properties["ttytdd_telephone_number"] = PR_TTYTDD_PHONE_NUMBER; + $properties["business_telephone_number"] = PR_BUSINESS_TELEPHONE_NUMBER; + + // Additional fax properties + $properties["fax_1_address_type"] = "PT_STRING8:PSETID_Address:0x80B2"; + $properties["fax_1_email_address"] = "PT_STRING8:PSETID_Address:0x80B3"; + $properties["fax_1_original_display_name"] = "PT_STRING8:PSETID_Address:0x80B4"; + $properties["fax_1_original_entryid"] = "PT_BINARY:PSETID_Address:0x80B5"; + $properties["fax_2_address_type"] = "PT_STRING8:PSETID_Address:0x80C2"; + $properties["fax_2_email_address"] = "PT_STRING8:PSETID_Address:0x80C3"; + $properties["fax_2_original_display_name"] = "PT_STRING8:PSETID_Address:0x80C4"; + $properties["fax_2_original_entryid"] = "PT_BINARY:PSETID_Address:0x80C5"; + $properties["fax_3_address_type"] = "PT_STRING8:PSETID_Address:0x80D2"; + $properties["fax_3_email_address"] = "PT_STRING8:PSETID_Address:0x80D3"; + $properties["fax_3_original_display_name"] = "PT_STRING8:PSETID_Address:0x80D4"; + $properties["fax_3_original_entryid"] = "PT_BINARY:PSETID_Address:0x80D5"; + + // Properties for addresses + // Home address + $properties["home_address_street"] = PR_HOME_ADDRESS_STREET; + $properties["home_address_city"] = PR_HOME_ADDRESS_CITY; + $properties["home_address_state"] = PR_HOME_ADDRESS_STATE_OR_PROVINCE; + $properties["home_address_postal_code"] = PR_HOME_ADDRESS_POSTAL_CODE; + $properties["home_address_country"] = PR_HOME_ADDRESS_COUNTRY; + // Other address + $properties["other_address_street"] = PR_OTHER_ADDRESS_STREET; + $properties["other_address_city"] = PR_OTHER_ADDRESS_CITY; + $properties["other_address_state"] = PR_OTHER_ADDRESS_STATE_OR_PROVINCE; + $properties["other_address_postal_code"] = PR_OTHER_ADDRESS_POSTAL_CODE; + $properties["other_address_country"] = PR_OTHER_ADDRESS_COUNTRY; + // Business address + $properties["business_address_street"] = "PT_STRING8:PSETID_Address:0x8045"; + $properties["business_address_city"] = "PT_STRING8:PSETID_Address:0x8046"; + $properties["business_address_state"] = "PT_STRING8:PSETID_Address:0x8047"; + $properties["business_address_postal_code"] = "PT_STRING8:PSETID_Address:0x8048"; + $properties["business_address_country"] = "PT_STRING8:PSETID_Address:0x8049"; + // Mailing address + $properties["country"] = PR_COUNTRY; + $properties["city"] = PR_LOCALITY; + $properties["postal_address"] = PR_POSTAL_ADDRESS; + $properties["postal_code"] = PR_POSTAL_CODE; + $properties["state"] = PR_STATE_OR_PROVINCE; + $properties["street"] = PR_STREET_ADDRESS; + // Special Date such as birthday n anniversary appoitment's entryid is store + $properties["birthday_eventid"] = "PT_BINARY:PSETID_Address:0x804D"; + $properties["anniversary_eventid"] = "PT_BINARY:PSETID_Address:0x804E"; + + $properties["notes"] = PR_BODY; + + // hasimage + $properties["picture"] = "PT_BOOLEAN:{00062004-0000-0000-C000-000000000046}:0x8015"; + + return $properties; + } + + /** + * Function that parses the uploaded vcf file and posts it via json + * @param $actionType + * @param $actionData + */ + private function loadContacts($actionType, $actionData) + { + $error = false; + $error_msg = ""; + + if (is_readable($actionData["vcf_filepath"])) { + $parser = null; + + try { + $parser = VCardParser::parseFromFile($actionData["vcf_filepath"]); + } catch (Exception $e) { + $error = true; + $error_msg = $e->getMessage(); + } + if ($error) { + $response['status'] = false; + $response['message'] = $error_msg; + } else { + if (iterator_count($parser) == 0) { + $response['status'] = false; + $response['message'] = dgettext("plugin_contactimporter", "No contacts in vcf file"); + } else { + $response['status'] = true; + $response['parsed_file'] = $actionData["vcf_filepath"]; + $response['parsed'] = array( + 'contacts' => $this->parseContactsToArray($parser) + ); + } + } + } else { + $response['status'] = false; + $response['message'] = dgettext("plugin_contactimporter", "File could not be read by server"); + } + + $this->addActionData($actionType, $response); + $GLOBALS["bus"]->addData($this->getResponseData()); + + if ($this->DEBUG) { + error_log("parsing done, bus data written!"); + } + } + + /** + * Create a array with contacts + * + * @param contacts vcard or csv contacts + * @param csv optional, true if contacts are csv contacts + * @return array parsed contacts + * @private + */ + private function parseContactsToArray($contacts, $csv = false) + { + $carr = array(); + + if (!$csv) { + foreach ($contacts as $Index => $vCard) { + $properties = array(); + if (isset($vCard->fullname)) { + $properties["display_name"] = $vCard->fullname; + $properties["fileas"] = $vCard->fullname; + } elseif (!isset($vCard->organization)) { + error_log("Skipping entry! No fullname/organization given."); + continue; + } + + $properties["hide_attachments"] = true; + + //uid - used for front/backend communication + $properties["internal_fields"] = array(); + $properties["internal_fields"]["contact_uid"] = base64_encode($Index . $properties["fileas"]); + + $properties["given_name"] = $vCard->firstname; + $properties["middle_name"] = $vCard->additional; + $properties["surname"] = $vCard->lastname; + $properties["display_name_prefix"] = $vCard->prefix; + + if (isset($vCard->phone) && count($vCard->phone) > 0) { + foreach ($vCard->phone as $type => $number) { + $number = $number[0]; // we only can store one number + if ($this->startswith(strtolower($type), "home") || strtolower($type) === "default") { + $properties["home_telephone_number"] = $number; + } else { + if ($this->startswith(strtolower($type), "cell")) { + $properties["cellular_telephone_number"] = $number; + } else { + if ($this->startswith(strtolower($type), "work")) { + $properties["business_telephone_number"] = $number; + } else { + if ($this->startswith(strtolower($type), "fax")) { + $properties["business_fax_number"] = $number; + } else { + if ($this->startswith(strtolower($type), "pager")) { + $properties["pager_telephone_number"] = $number; + } else { + if ($this->startswith(strtolower($type), "isdn")) { + $properties["isdn_number"] = $number; + } else { + if ($this->startswith(strtolower($type), "car")) { + $properties["car_telephone_number"] = $number; + } else { + if ($this->startswith(strtolower($type), "modem")) { + $properties["ttytdd_telephone_number"] = $number; + } + } + } + } + } + } + } + } + } + } + if (isset($vCard->email) && count($vCard->email) > 0) { + $emailcount = 0; + $properties["address_book_long"] = 0; + foreach ($vCard->email as $type => $email) { + foreach ($email as $mail) { + $fileas = $mail; + if (isset($properties["fileas"]) && !empty($properties["fileas"])) { + $fileas = $properties["fileas"]; // set to real name + } + + // we only have storage for 3 mail addresses! + /** + * type of email address address_book_mv address_book_long + * email1 0 1 (0x00000001) + * email2 1 2 (0x00000002) + * email3 2 4 (0x00000004) + * fax2(business fax) 3 8 (0x00000008) + * fax3(home fax) 4 16 (0x00000010) + * fax1(primary fax) 5 32 (0x00000020) + * + * address_book_mv is a multivalued property so all the values are passed in array + * address_book_long stores sum of the flags + * these both properties should be in sync always + */ + switch ($emailcount) { + case 0: + $properties["email_address_1"] = $mail; + $properties["email_address_display_name_1"] = $fileas . " (" . $mail . ")"; + $properties["email_address_display_name_email_1"] = $mail; + $properties["address_book_mv"][] = 0; // this is needed for adding the contact to the email address book, 0 = email 1 + $properties["address_book_long"] += 1; // this specifies the number of elements in address_book_mv + break; + case 1: + $properties["email_address_2"] = $mail; + $properties["email_address_display_name_2"] = $fileas . " (" . $mail . ")"; + $properties["email_address_display_name_email_2"] = $mail; + $properties["address_book_mv"][] = 1; // this is needed for adding the contact to the email address book, 1 = email 2 + $properties["address_book_long"] += 2; // this specifies the number of elements in address_book_mv + break; + case 2: + $properties["email_address_3"] = $mail; + $properties["email_address_display_name_3"] = $fileas . " (" . $mail . ")"; + $properties["email_address_display_name_email_3"] = $mail; + $properties["address_book_mv"][] = 2; // this is needed for adding the contact to the email address book, 2 = email 3 + $properties["address_book_long"] += 4; // this specifies the number of elements in address_book_mv + break; + default: + break; + } + $emailcount++; + } + } + } + if (isset($vCard->organization)) { + $properties["company_name"] = $vCard->organization; + if (empty($properties["display_name"])) { + $properties["display_name"] = $vCard->organization; // if we have no displayname - use the company name as displayname + $properties["fileas"] = $vCard->organization; + } + } + if (isset($vCard->title)) { + $properties["title"] = $vCard->title; + } + if (isset($vCard->url) && count($vCard->url) > 0) { + foreach ($vCard->url as $type => $url) { + $url = $url[0]; // only 1 webaddress per type + $properties["webpage"] = $url; + break; // we can only store on url + } + } + if (isset($vCard->address) && count($vCard->address) > 0) { + + foreach ($vCard->address as $type => $address) { + $address = $address[0]; // we only can store one address per type + if ($this->startswith(strtolower($type), "work")) { + $properties["business_address_street"] = $address->street; + if (!empty($address->extended)) { + $properties["business_address_street"] .= "\n" . $address->extended; + } + $properties["business_address_city"] = $address->city; + $properties["business_address_state"] = $address->region; + $properties["business_address_postal_code"] = $address->zip; + $properties["business_address_country"] = $address->country; + $properties["business_address"] = $this->buildAddressString($properties["business_address_street"], $address->zip, $address->city, $address->region, $address->country); + } else { + if ($this->startswith(strtolower($type), "home")) { + $properties["home_address_street"] = $address->street; + if (!empty($address->extended)) { + $properties["home_address_street"] .= "\n" . $address->extended; + } + $properties["home_address_city"] = $address->city; + $properties["home_address_state"] = $address->region; + $properties["home_address_postal_code"] = $address->zip; + $properties["home_address_country"] = $address->country; + $properties["home_address"] = $this->buildAddressString($properties["home_address_street"], $address->zip, $address->city, $address->region, $address->country); + } else { + $properties["other_address_street"] = $address->street; + if (!empty($address->extended)) { + $properties["other_address_street"] .= "\n" . $address->extended; + } + $properties["other_address_city"] = $address->city; + $properties["other_address_state"] = $address->region; + $properties["other_address_postal_code"] = $address->zip; + $properties["other_address_country"] = $address->country; + $properties["other_address"] = $this->buildAddressString($properties["other_address_street"], $address->zip, $address->city, $address->region, $address->country); + } + } + } + } + if (isset($vCard->birthday)) { + $properties["birthday"] = $vCard->birthday->getTimestamp(); + } + if (isset($vCard->note)) { + $properties["notes"] = $vCard->note; + } + if (isset($vCard->rawPhoto) || isset($vCard->photo)) { + if (!is_writable(TMP_PATH . "/")) { + error_log("Can not write to export tmp directory!"); + } else { + $tmppath = TMP_PATH . "/" . $this->randomstring(15); + if (isset($vCard->rawPhoto)) { + if (file_put_contents($tmppath, $vCard->rawPhoto)) { + $properties["internal_fields"]["x_photo_path"] = $tmppath; + } + } elseif (isset($vCard->photo)) { + if ($this->startswith(strtolower($vCard->photo), "http://") || $this->startswith(strtolower($vCard->photo), "https://")) { // check if it starts with http + $ctx = stream_context_create(array('http' => + array( + 'timeout' => 3, //3 Seconds timout + ) + )); + + if (file_put_contents($tmppath, file_get_contents($vCard->photo, false, $ctx))) { + $properties["internal_fields"]["x_photo_path"] = $tmppath; + } + } else { + error_log("Invalid photo url: " . $vCard->photo); + } + } + } + } + array_push($carr, $properties); + } + } else { + error_log("csv parsing not implemented"); + } + + return $carr; + } + + /** + * Generate the whole addressstring + * + * @param street + * @param zip + * @param city + * @param state + * @param country + * @return string the concatinated address string + * @private + */ + private function buildAddressString($street, $zip, $city, $state, $country) + { + $out = ""; + + if (isset($country) && $street != "") { + $out = $country; + } + + $zcs = ""; + if (isset($zip) && $zip != "") { + $zcs = $zip; + } + if (isset($city) && $city != "") { + $zcs .= (($zcs) ? " " : "") . $city; + } + if (isset($state) && $state != "") { + $zcs .= (($zcs) ? " " : "") . $state; + } + if ($zcs) { + $out = $zcs . "\n" . $out; + } + + if (isset($street) && $street != "") { + $out = $street . (($out) ? "\n\n" . $out : ""); + } + + return $out; + } + + /** + * Store the file to a temporary directory + * @param $actionType + * @param $actionData + * @private + */ + private function getAttachmentPath($actionType, $actionData) + { + // Get store id + $storeid = false; + if (isset($actionData["store"])) { + $storeid = $actionData["store"]; + } + + // Get message entryid + $entryid = false; + if (isset($actionData["entryid"])) { + $entryid = $actionData["entryid"]; + } + + // Check which type isset + $openType = "attachment"; + + // Get number of attachment which should be opened. + $attachNum = false; + if (isset($actionData["attachNum"])) { + $attachNum = $actionData["attachNum"]; + } + + // Check if storeid and entryid isset + if ($storeid && $entryid) { + // Open the store + $store = $GLOBALS["mapisession"]->openMessageStore(hex2bin($storeid)); + + if ($store) { + // Open the message + $message = mapi_msgstore_openentry($store, hex2bin($entryid)); + + if ($message) { + $attachment = false; + + // Check if attachNum isset + if ($attachNum) { + // Loop through the attachNums, message in message in message ... + for ($i = 0; $i < (count($attachNum) - 1); $i++) { + // Open the attachment + $tempattach = mapi_message_openattach($message, (int)$attachNum[$i]); + if ($tempattach) { + // Open the object in the attachment + $message = mapi_attach_openobj($tempattach); + } + } + + // Open the attachment + $attachment = mapi_message_openattach($message, (int)$attachNum[(count($attachNum) - 1)]); + } + + // Check if the attachment is opened + if ($attachment) { + + // Get the props of the attachment + $props = mapi_attach_getprops($attachment, array(PR_ATTACH_LONG_FILENAME, PR_ATTACH_MIME_TAG, PR_DISPLAY_NAME, PR_ATTACH_METHOD)); + // Content Type + $contentType = "application/octet-stream"; + // Filename + $filename = "ERROR"; + + // Set filename + if (isset($props[PR_ATTACH_LONG_FILENAME])) { + $filename = $props[PR_ATTACH_LONG_FILENAME]; + } else { + if (isset($props[PR_ATTACH_FILENAME])) { + $filename = $props[PR_ATTACH_FILENAME]; + } else { + if (isset($props[PR_DISPLAY_NAME])) { + $filename = $props[PR_DISPLAY_NAME]; + } + } + } + + // Set content type + if (isset($props[PR_ATTACH_MIME_TAG])) { + $contentType = $props[PR_ATTACH_MIME_TAG]; + } else { + // Parse the extension of the filename to get the content type + if (strrpos($filename, ".") !== false) { + $extension = strtolower(substr($filename, strrpos($filename, "."))); + $contentType = "application/octet-stream"; + if (is_readable("mimetypes.dat")) { + $fh = fopen("mimetypes.dat", "r"); + $ext_found = false; + while (!feof($fh) && !$ext_found) { + $line = fgets($fh); + preg_match("/(\.[a-z0-9]+)[ \t]+([^ \t\n\r]*)/i", $line, $result); + if ($extension == $result[1]) { + $ext_found = true; + $contentType = $result[2]; + } + } + fclose($fh); + } + } + } + + + $tmpname = tempnam(TMP_PATH, stripslashes($filename)); + + // Open a stream to get the attachment data + $stream = mapi_openpropertytostream($attachment, PR_ATTACH_DATA_BIN); + $stat = mapi_stream_stat($stream); + // File length = $stat["cb"] + + $fhandle = fopen($tmpname, 'w'); + $buffer = null; + for ($i = 0; $i < $stat["cb"]; $i += BLOCK_SIZE) { + // Write stream + $buffer = mapi_stream_read($stream, BLOCK_SIZE); + fwrite($fhandle, $buffer, strlen($buffer)); + } + fclose($fhandle); + + $response = array(); + $response['tmpname'] = $tmpname; + $response['filename'] = $filename; + $response['status'] = true; + $this->addActionData($actionType, $response); + $GLOBALS["bus"]->addData($this->getResponseData()); + } + } + } else { + $response['status'] = false; + $response['message'] = dgettext("plugin_contactimporter", "Store could not be opened!"); + $this->addActionData($actionType, $response); + $GLOBALS["bus"]->addData($this->getResponseData()); + } + } else { + $response['status'] = false; + $response['message'] = dgettext("plugin_contactimporter", "Wrong call, store and entryid have to be set!"); + $this->addActionData($actionType, $response); + $GLOBALS["bus"]->addData($this->getResponseData()); + } + } + + /** + * Check if string starts with other string. + * @param $haystack + * @param $needle + * @return bool + */ + private function startswith($haystack, $needle) + { + $haystack = str_replace("type=", "", $haystack); // remove type from string + return substr($haystack, 0, strlen($needle)) === $needle; + } } - -; - -?> diff --git a/php/plugin.contactimporter.php b/php/plugin.contactimporter.php index f30b830..5613573 100644 --- a/php/plugin.contactimporter.php +++ b/php/plugin.contactimporter.php @@ -1,6 +1,6 @@ * Copyright (C) 2012-2016 Christoph Haas @@ -30,63 +30,63 @@ require_once __DIR__ . "/download.php"; */ class Plugincontactimporter extends Plugin { - /** - * Constructor - */ - function __construct() {} + /** + * Constructor + */ + function __construct() + { + } - /** - * Function initializes the Plugin and registers all hooks - * - * @return void - */ - function init() - { - $this->registerHook('server.core.settings.init.before'); - $this->registerHook('server.index.load.custom'); - } + /** + * Function initializes the Plugin and registers all hooks + * + * @return void + */ + function init() + { + $this->registerHook('server.core.settings.init.before'); + $this->registerHook('server.index.load.custom'); + } - /** - * Function is executed when a hook is triggered by the PluginManager - * - * @param string $eventID the id of the triggered hook - * @param mixed $data object(s) related to the hook - * @return void - */ - function execute($eventID, &$data) - { - switch ($eventID) { - case 'server.core.settings.init.before' : - $this->injectPluginSettings($data); - break; - case 'server.index.load.custom': - if ($data['name'] == 'download_vcf') { - DownloadHandler::doDownload(); - } - break; - } - } + /** + * Function is executed when a hook is triggered by the PluginManager + * + * @param string $eventID the id of the triggered hook + * @param mixed $data object(s) related to the hook + * @return void + */ + function execute($eventID, &$data) + { + switch ($eventID) { + case 'server.core.settings.init.before' : + $this->injectPluginSettings($data); + break; + case 'server.index.load.custom': + if ($data['name'] == 'download_vcf') { + DownloadHandler::doDownload(); + } + break; + } + } - /** - * Called when the core Settings class is initialized and ready to accept sysadmin default - * settings. - * @param Array $data Reference to the data of the triggered hook - */ - function injectPluginSettings(&$data) - { - $data['settingsObj']->addSysAdminDefaults(Array( - 'zarafa' => Array( - 'v1' => Array( - 'plugins' => Array( - 'contactimporter' => Array( - 'enable' => PLUGIN_CONTACTIMPORTER_USER_DEFAULT_ENABLE, - 'default_addressbook' => PLUGIN_CONTACTIMPORTER_DEFAULT - ) - ) - ) - ) - )); - } -} - -?> + /** + * Called when the core Settings class is initialized and ready to accept sysadmin default + * settings. + * @param [] $data Reference to the data of the triggered hook + */ + function injectPluginSettings(&$data) + { + $data['settingsObj']->addSysAdminDefaults(Array( + 'zarafa' => Array( + 'v1' => Array( + 'plugins' => Array( + 'contactimporter' => Array( + 'enable' => PLUGIN_CONTACTIMPORTER_USER_DEFAULT_ENABLE, + 'default_addressbook' => PLUGIN_CONTACTIMPORTER_DEFAULT + ) + ) + ) + ) + )); + } +} \ No newline at end of file diff --git a/php/upload.php b/php/upload.php index 73b97c8..314b577 100644 --- a/php/upload.php +++ b/php/upload.php @@ -1,6 +1,6 @@ * Copyright (C) 2012-2016 Christoph Haas @@ -32,7 +32,7 @@ ini_set('display_errors', '0'); */ function respondJSON($arr) { - echo json_encode($arr); + echo json_encode($arr); } /** @@ -42,33 +42,33 @@ function respondJSON($arr) */ function randomstring($length = 6) { - // $chars - all allowed charakters - $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; + // $chars - all allowed charakters + $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; - srand((double)microtime() * 1000000); - $i = 0; - $pass = ""; - while ($i < $length) { - $num = rand() % strlen($chars); - $tmp = substr($chars, $num, 1); - $pass = $pass . $tmp; - $i++; - } - return $pass; + srand((double)microtime() * 1000000); + $i = 0; + $pass = ""; + while ($i < $length) { + $num = rand() % strlen($chars); + $tmp = substr($chars, $num, 1); + $pass = $pass . $tmp; + $i++; + } + return $pass; } $destpath = PLUGIN_CONTACTIMPORTER_TMP_UPLOAD; $destpath .= $_FILES['vcfdata']['name'] . randomstring(); if (is_readable($_FILES['vcfdata']['tmp_name'])) { - $result = move_uploaded_file($_FILES['vcfdata']['tmp_name'], $destpath); + $result = move_uploaded_file($_FILES['vcfdata']['tmp_name'], $destpath); - if ($result) { - respondJSON(array('success' => true, 'vcf_file' => $destpath)); - } else { - respondJSON(array('success' => false, 'error' => "File could not be moved to TMP path! Check plugin config and folder permissions!")); - } + if ($result) { + respondJSON(array('success' => true, 'vcf_file' => $destpath)); + } else { + respondJSON(array('success' => false, 'error' => dgettext("plugin_contactimporter", "File could not be moved to TMP path! Check plugin config and folder permissions!"))); + } } else { - respondJSON(array('success' => false, 'error' => "File could not be read by server, upload error!")); + respondJSON(array('success' => false, 'error' => dgettext("plugin_contactimporter", "File could not be read by server, upload error!"))); } ?> \ No newline at end of file