* Copyright (C) 2012-2016 Christoph Haas * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ include_once('vendor/autoload.php'); use JeroenDesloovere\VCard\VCard; 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 : 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; } }