/** * Imports a single message * * @param string $id * @param SyncObject $message * * @access public * @return boolean/string - failure / id of message * @throws StatusException */ public function ImportMessageChange($id, $message) { $parentsourcekey = $this->folderid; if ($id) { $sourcekey = hex2bin($id); } $flags = 0; $props = array(); $props[PR_PARENT_SOURCE_KEY] = $parentsourcekey; // set the PR_SOURCE_KEY if available or mark it as new message if ($id) { $props[PR_SOURCE_KEY] = $sourcekey; // check for conflicts $this->lazyLoadConflicts(); if ($this->memChanges->IsChanged($id)) { if ($this->flags & SYNC_CONFLICT_OVERWRITE_PIM) { // in these cases the status SYNC_STATUS_CONFLICTCLIENTSERVEROBJECT should be returned, so the mobile client can inform the end user throw new StatusException(sprintf("ImportChangesICS->ImportMessageChange('%s','%s'): Conflict detected. Data from PIM will be dropped! Server overwrites PIM. User is informed.", $id, get_class($message)), SYNC_STATUS_CONFLICTCLIENTSERVEROBJECT, null, LOGLEVEL_INFO); return false; } else { ZLog::Write(LOGLEVEL_INFO, sprintf("ImportChangesICS->ImportMessageChange('%s','%s'): Conflict detected. Data from Server will be dropped! PIM overwrites server.", $id, get_class($message))); } } if ($this->memChanges->IsDeleted($id)) { ZLog::Write(LOGLEVEL_INFO, sprintf("ImportChangesICS->ImportMessageChange('%s','%s'): Conflict detected. Data from PIM will be dropped! Object was deleted on server.", $id, get_class($message))); return false; } } else { $flags = SYNC_NEW_MESSAGE; } if (mapi_importcontentschanges_importmessagechange($this->importer, $props, $flags, $mapimessage)) { $this->mapiprovider->SetMessage($mapimessage, $message); mapi_message_savechanges($mapimessage); if (mapi_last_hresult()) { throw new StatusException(sprintf("ImportChangesICS->ImportMessageChange('%s','%s'): Error, mapi_message_savechanges() failed: 0x%X", $id, get_class($message), mapi_last_hresult()), SYNC_STATUS_SYNCCANNOTBECOMPLETED); } $sourcekeyprops = mapi_getprops($mapimessage, array(PR_SOURCE_KEY)); return bin2hex($sourcekeyprops[PR_SOURCE_KEY]); } else { throw new StatusException(sprintf("ImportChangesICS->ImportMessageChange('%s','%s'): Error updating object: 0x%X", $id, get_class($message), mapi_last_hresult()), SYNC_STATUS_OBJECTNOTFOUND); } }
/** * Function which submits meeting request based on arguments passed to it. *@param resource $message MAPI_message whose meeting request is to be send *@param boolean $cancel if true send request, else send cancellation *@param string $prefix subject prefix *@param integer $basedate basedate for an occurrence *@param Object $recurObject recurrence object of mr *@param boolean $copyExceptions When sending update mail for recurring item then we dont send exceptions in attachments */ function submitMeetingRequest($message, $cancel, $prefix, $basedate = false, $recurObject = false, $copyExceptions = true, $deletedRecips = false) { $newmessageprops = $messageprops = mapi_getprops($this->message); $new = $this->createOutgoingMessage(); // Copy the entire message into the new meeting request message if ($basedate) { // messageprops contains properties of whole recurring series // and newmessageprops contains properties of exception item $newmessageprops = mapi_getprops($message); // Ensure that the correct basedate is set in the new message $newmessageprops[$this->proptags['basedate']] = $basedate; // Set isRecurring to false, because this is an exception $newmessageprops[$this->proptags['recurring']] = false; // set LID_IS_EXCEPTION to true $newmessageprops[$this->proptags['is_exception']] = true; // Set to high importance if ($cancel) { $newmessageprops[PR_IMPORTANCE] = IMPORTANCE_HIGH; } // Set startdate and enddate of exception if ($cancel && $recurObject) { $newmessageprops[$this->proptags['startdate']] = $recurObject->getOccurrenceStart($basedate); $newmessageprops[$this->proptags['duedate']] = $recurObject->getOccurrenceEnd($basedate); } // Set basedate in guid (0x3) $newmessageprops[$this->proptags['goid']] = $this->setBasedateInGlobalID($messageprops[$this->proptags['goid2']], $basedate); $newmessageprops[$this->proptags['goid2']] = $messageprops[$this->proptags['goid2']]; $newmessageprops[PR_OWNER_APPT_ID] = $messageprops[PR_OWNER_APPT_ID]; // Get deleted recipiets from exception msg $restriction = array(RES_AND, array(array(RES_BITMASK, array(ULTYPE => BMR_NEZ, ULPROPTAG => PR_RECIPIENT_FLAGS, ULMASK => recipExceptionalDeleted)), array(RES_BITMASK, array(ULTYPE => BMR_EQZ, ULPROPTAG => PR_RECIPIENT_FLAGS, ULMASK => recipOrganizer)))); // In direct-booking mode, we don't need to send cancellations to resources if ($this->enableDirectBooking) { $restriction[1][] = array(RES_PROPERTY, array(RELOP => RELOP_NE, ULPROPTAG => PR_RECIPIENT_TYPE, VALUE => array(PR_RECIPIENT_TYPE => MAPI_BCC))); } $recipienttable = mapi_message_getrecipienttable($message); $recipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $restriction); if (!$deletedRecips) { $deletedRecips = array_merge(array(), $recipients); } else { $deletedRecips = array_merge($deletedRecips, $recipients); } } // Remove the PR_ICON_INDEX as it is not needed in the sent message and it also // confuses the Zarafa webaccess $newmessageprops[PR_ICON_INDEX] = null; $newmessageprops[PR_RESPONSE_REQUESTED] = true; // PR_START_DATE and PR_END_DATE will be used by outlook to show the position in the calendar $newmessageprops[PR_START_DATE] = $newmessageprops[$this->proptags['startdate']]; $newmessageprops[PR_END_DATE] = $newmessageprops[$this->proptags['duedate']]; // Set updatecounter/AppointmentSequenceNumber // get the value of latest updatecounter for the whole series and use it $newmessageprops[$this->proptags['updatecounter']] = $messageprops[$this->proptags['last_updatecounter']]; $meetingTimeInfo = $this->getMeetingTimeInfo(); if ($meetingTimeInfo) { $newmessageprops[PR_BODY] = $meetingTimeInfo; } // Send all recurrence info in mail, if this is a recurrence meeting. if (isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']]) { if (!empty($messageprops[$this->proptags['recurring_pattern']])) { $newmessageprops[$this->proptags['recurring_pattern']] = $messageprops[$this->proptags['recurring_pattern']]; } $newmessageprops[$this->proptags['recurrence_data']] = $messageprops[$this->proptags['recurrence_data']]; $newmessageprops[$this->proptags['timezone_data']] = $messageprops[$this->proptags['timezone_data']]; $newmessageprops[$this->proptags['timezone']] = $messageprops[$this->proptags['timezone']]; if ($recurObject) { $this->generateRecurDates($recurObject, $messageprops, $newmessageprops); } } if (isset($newmessageprops[$this->proptags['counter_proposal']])) { unset($newmessageprops[$this->proptags['counter_proposal']]); } // Prefix the subject if needed if ($prefix && isset($newmessageprops[PR_SUBJECT])) { $newmessageprops[PR_SUBJECT] = $prefix . $newmessageprops[PR_SUBJECT]; } mapi_setprops($new, $newmessageprops); // Copy attachments $this->replaceAttachments($message, $new, $copyExceptions); // Retrieve only those recipient who should receive this meeting request. $stripResourcesRestriction = array(RES_AND, array(array(RES_BITMASK, array(ULTYPE => BMR_EQZ, ULPROPTAG => PR_RECIPIENT_FLAGS, ULMASK => recipExceptionalDeleted)), array(RES_BITMASK, array(ULTYPE => BMR_EQZ, ULPROPTAG => PR_RECIPIENT_FLAGS, ULMASK => recipOrganizer)))); // In direct-booking mode, resources do not receive a meeting request if ($this->enableDirectBooking) { $stripResourcesRestriction[1][] = array(RES_PROPERTY, array(RELOP => RELOP_NE, ULPROPTAG => PR_RECIPIENT_TYPE, VALUE => array(PR_RECIPIENT_TYPE => MAPI_BCC))); } $recipienttable = mapi_message_getrecipienttable($message); $recipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $stripResourcesRestriction); if ($basedate && empty($recipients)) { // Retrieve full list $recipienttable = mapi_message_getrecipienttable($this->message); $recipients = mapi_table_queryallrows($recipienttable, $this->recipprops); // Save recipients in exceptions mapi_message_modifyrecipients($message, MODRECIP_ADD, $recipients); // Now retrieve only those recipient who should receive this meeting request. $recipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $stripResourcesRestriction); } //@TODO: handle nonAcceptingResources /** * Add resource recipients that did not automatically accept the meeting request. * (note: meaning that they did not decline the meeting request) */ /* for($i=0;$i<count($this->nonAcceptingResources);$i++){ $recipients[] = $this->nonAcceptingResources[$i]; }*/ if (!empty($recipients)) { // Strip out the sender/"owner" recipient mapi_message_modifyrecipients($new, MODRECIP_ADD, $recipients); // Set some properties that are different in the sent request than // in the item in our calendar // we should store busystatus value to intendedbusystatus property, because busystatus for outgoing meeting request // should always be fbTentative $newmessageprops[$this->proptags['intendedbusystatus']] = isset($newmessageprops[$this->proptags['busystatus']]) ? $newmessageprops[$this->proptags['busystatus']] : $messageprops[$this->proptags['busystatus']]; $newmessageprops[$this->proptags['busystatus']] = fbTentative; // The default status when not accepted $newmessageprops[$this->proptags['responsestatus']] = olResponseNotResponded; // The recipient has not responded yet $newmessageprops[$this->proptags['attendee_critical_change']] = time(); $newmessageprops[$this->proptags['owner_critical_change']] = time(); $newmessageprops[$this->proptags['meetingtype']] = mtgRequest; if ($cancel) { $newmessageprops[PR_MESSAGE_CLASS] = "IPM.Schedule.Meeting.Canceled"; $newmessageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // It's a cancel request $newmessageprops[$this->proptags['busystatus']] = fbFree; // set the busy status as free } else { $newmessageprops[PR_MESSAGE_CLASS] = "IPM.Schedule.Meeting.Request"; $newmessageprops[$this->proptags['meetingstatus']] = olMeetingReceived; // The recipient is receiving the request } mapi_setprops($new, $newmessageprops); mapi_message_savechanges($new); // Submit message to non-resource recipients mapi_message_submitmessage($new); } // Send cancellation to deleted attendees if ($deletedRecips && !empty($deletedRecips)) { $new = $this->createOutgoingMessage(); mapi_message_modifyrecipients($new, MODRECIP_ADD, $deletedRecips); $newmessageprops[PR_MESSAGE_CLASS] = "IPM.Schedule.Meeting.Canceled"; $newmessageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // It's a cancel request $newmessageprops[$this->proptags['busystatus']] = fbFree; // set the busy status as free $newmessageprops[PR_IMPORTANCE] = IMPORTANCE_HIGH; // HIGH Importance if (isset($newmessageprops[PR_SUBJECT])) { $newmessageprops[PR_SUBJECT] = _('Canceled: ') . $newmessageprops[PR_SUBJECT]; } mapi_setprops($new, $newmessageprops); mapi_message_savechanges($new); // Submit message to non-resource recipients mapi_message_submitmessage($new); } // Set properties on meeting object in calendar // Set requestsent to 'true' (turns on 'tracking', etc) $props = array(); $props[$this->proptags['meetingstatus']] = olMeeting; $props[$this->proptags['responsestatus']] = olResponseOrganized; $props[$this->proptags['requestsent']] = !empty($recipients) || $this->includesResources && !$this->errorSetResource; $props[$this->proptags['attendee_critical_change']] = time(); $props[$this->proptags['owner_critical_change']] = time(); $props[$this->proptags['meetingtype']] = mtgRequest; // save the new updatecounter to exception/recurring series/normal meeting $props[$this->proptags['updatecounter']] = $newmessageprops[$this->proptags['updatecounter']]; // PR_START_DATE and PR_END_DATE will be used by outlook to show the position in the calendar $props[PR_START_DATE] = $messageprops[$this->proptags['startdate']]; $props[PR_END_DATE] = $messageprops[$this->proptags['duedate']]; mapi_setprops($message, $props); // saving of these properties on calendar item should be handled by caller function // based on sending meeting request was successfull or not }
/** * Function which saves the exception data in an attachment. * @param array $exception_props the exception data (like any other MAPI appointment) * @param array $exception_recips list of recipients * @param mapi_message $copy_attach_from mapi message from which attachments should be copied * @return array properties of the exception */ function createExceptionAttachment($exception_props, $exception_recips = array(), $copy_attach_from = false) { // Create new attachment. $attachment = mapi_message_createattach($this->message); $props = array(); $props[PR_ATTACHMENT_FLAGS] = 2; $props[PR_ATTACHMENT_HIDDEN] = true; $props[PR_ATTACHMENT_LINKID] = 0; $props[PR_ATTACH_FLAGS] = 0; $props[PR_ATTACH_METHOD] = 5; $props[PR_DISPLAY_NAME] = "Exception"; $props[PR_EXCEPTION_STARTTIME] = $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]); $props[PR_EXCEPTION_ENDTIME] = $this->fromGMT($this->tz, $exception_props[$this->proptags["duedate"]]); mapi_message_setprops($attachment, $props); $imessage = mapi_attach_openobj($attachment, MAPI_CREATE | MAPI_MODIFY); if ($copy_attach_from) { $attachmentTable = mapi_message_getattachmenttable($copy_attach_from); if ($attachmentTable) { $attachments = mapi_table_queryallrows($attachmentTable, array(PR_ATTACH_NUM, PR_ATTACH_SIZE, PR_ATTACH_LONG_FILENAME, PR_ATTACHMENT_HIDDEN, PR_DISPLAY_NAME, PR_ATTACH_METHOD)); foreach ($attachments as $attach_props) { $attach_old = mapi_message_openattach($copy_attach_from, (int) $attach_props[PR_ATTACH_NUM]); $attach_newResourceMsg = mapi_message_createattach($imessage); mapi_copyto($attach_old, array(), array(), $attach_newResourceMsg, 0); mapi_savechanges($attach_newResourceMsg); } } } $props = $props + $exception_props; // FIXME: the following piece of code is written to fix the creation // of an exception. This is only a quickfix as it is not yet possible // to change an existing exception. // remove mv properties when needed foreach ($props as $propTag => $propVal) { if ((mapi_prop_type($propTag) & MV_FLAG) == MV_FLAG && is_null($propVal)) { unset($props[$propTag]); } } mapi_message_setprops($imessage, $props); $this->setExceptionRecipients($imessage, $exception_recips, true); mapi_message_savechanges($imessage); mapi_message_savechanges($attachment); }
/** * Sends an e-mail * This messages needs to be saved into the 'sent items' folder * * @param SyncSendMail $sm SyncSendMail object * * @access public * @return boolean * @throws StatusException */ public function SendMail($sm) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZarafaBackend->SendMail(): RFC822: %d bytes forward-id: '%s' reply-id: '%s' parent-id: '%s' SaveInSent: '%s' ReplaceMIME: '%s'", strlen($sm->mime), Utils::PrintAsString($sm->forwardflag), Utils::PrintAsString($sm->replyflag), Utils::PrintAsString(isset($sm->source->folderid) ? $sm->source->folderid : false), Utils::PrintAsString($sm->saveinsent), Utils::PrintAsString(isset($sm->replacemime)))); // by splitting the message in several lines we can easily grep later foreach (preg_split("/((\r)?\n)/", $sm->mime) as $rfc822line) { ZLog::Write(LOGLEVEL_WBXML, "RFC822: " . $rfc822line); } $mimeParams = array('decode_headers' => true, 'decode_bodies' => true, 'include_bodies' => true, 'charset' => 'utf-8'); $mimeObject = new Mail_mimeDecode($sm->mime); $message = $mimeObject->decode($mimeParams); $sendMailProps = MAPIMapping::GetSendMailProperties(); $sendMailProps = getPropIdsFromStrings($this->store, $sendMailProps); // Open the outbox and create the message there $storeprops = mapi_getprops($this->store, array($sendMailProps["outboxentryid"], $sendMailProps["ipmsentmailentryid"])); if (isset($storeprops[$sendMailProps["outboxentryid"]])) { $outbox = mapi_msgstore_openentry($this->store, $storeprops[$sendMailProps["outboxentryid"]]); } if (!$outbox) { throw new StatusException(sprintf("ZarafaBackend->SendMail(): No Outbox found or unable to create message: 0x%X", mapi_last_hresult()), SYNC_COMMONSTATUS_SERVERERROR); } $mapimessage = mapi_folder_createmessage($outbox); //message properties to be set $mapiprops = array(); // only save the outgoing in sent items folder if the mobile requests it $mapiprops[$sendMailProps["sentmailentryid"]] = $storeprops[$sendMailProps["ipmsentmailentryid"]]; // Check if imtomapi function is available and use it to send the mime message. // It is available since ZCP 7.0.6 // @see http://jira.zarafa.com/browse/ZCP-9508 if (function_exists('mapi_feature') && mapi_feature('INETMAPI_IMTOMAPI')) { ZLog::Write(LOGLEVEL_DEBUG, "Use the mapi_inetmapi_imtomapi function"); $ab = mapi_openaddressbook($this->session); mapi_inetmapi_imtomapi($this->session, $this->store, $ab, $mapimessage, $sm->mime, array()); // Set the appSeqNr so that tracking tab can be updated for meeting request updates // @see http://jira.zarafa.com/browse/ZP-68 $meetingRequestProps = MAPIMapping::GetMeetingRequestProperties(); $meetingRequestProps = getPropIdsFromStrings($this->store, $meetingRequestProps); $props = mapi_getprops($mapimessage, array(PR_MESSAGE_CLASS, $meetingRequestProps["goidtag"])); if (stripos($props[PR_MESSAGE_CLASS], "IPM.Schedule.Meeting.Resp.") === 0) { // search for calendar items using goid $mr = new Meetingrequest($this->store, $mapimessage); $appointments = $mr->findCalendarItems($props[$meetingRequestProps["goidtag"]]); if (is_array($appointments) && !empty($appointments)) { $app = mapi_msgstore_openentry($this->store, $appointments[0]); $appprops = mapi_getprops($app, array($meetingRequestProps["appSeqNr"])); if (isset($appprops[$meetingRequestProps["appSeqNr"]]) && $appprops[$meetingRequestProps["appSeqNr"]]) { $mapiprops[$meetingRequestProps["appSeqNr"]] = $appprops[$meetingRequestProps["appSeqNr"]]; ZLog::Write(LOGLEVEL_DEBUG, sprintf("Set sequence number to:%d", $appprops[$meetingRequestProps["appSeqNr"]])); } } } // Delete the PR_SENT_REPRESENTING_* properties because some android devices // do not send neither From nor Sender header causing empty PR_SENT_REPRESENTING_NAME and // PR_SENT_REPRESENTING_EMAIL_ADDRESS properties and "broken" PR_SENT_REPRESENTING_ENTRYID // which results in spooler not being able to send the message. // @see http://jira.zarafa.com/browse/ZP-85 mapi_deleteprops($mapimessage, array($sendMailProps["sentrepresentingname"], $sendMailProps["sentrepresentingemail"], $sendMailProps["representingentryid"], $sendMailProps["sentrepresentingaddt"], $sendMailProps["sentrepresentinsrchk"])); if (isset($sm->source->itemid) && $sm->source->itemid) { $entryid = mapi_msgstore_entryidfromsourcekey($this->store, hex2bin($sm->source->folderid), hex2bin($sm->source->itemid)); if ($entryid) { $fwmessage = mapi_msgstore_openentry($this->store, $entryid); } if (!isset($fwmessage) || !$fwmessage) { throw new StatusException(sprintf("ZarafaBackend->SendMail(): Could not open message id '%s' in folder id '%s' to be replied/forwarded: 0x%X", $sm->source->itemid, $sm->source->folderid, mapi_last_hresult()), SYNC_COMMONSTATUS_ITEMNOTFOUND); } //update icon when forwarding or replying message if ($sm->forwardflag) { mapi_setprops($fwmessage, array(PR_ICON_INDEX => 262)); } elseif ($sm->replyflag) { mapi_setprops($fwmessage, array(PR_ICON_INDEX => 261)); } mapi_savechanges($fwmessage); // only attach the original message if the mobile does not send it itself if (!isset($sm->replacemime)) { // get message's body in order to append forward or reply text $body = MAPIUtils::readPropStream($mapimessage, PR_BODY); $bodyHtml = MAPIUtils::readPropStream($mapimessage, PR_HTML); $cpid = mapi_getprops($fwmessage, array($sendMailProps["internetcpid"])); if ($sm->forwardflag) { // attach the original attachments to the outgoing message $this->copyAttachments($mapimessage, $fwmessage); } if (strlen($body) > 0) { $fwbody = MAPIUtils::readPropStream($fwmessage, PR_BODY); $fwbody = isset($cpid[$sendMailProps["internetcpid"]]) ? Utils::ConvertCodepageStringToUtf8($cpid[$sendMailProps["internetcpid"]], $fwbody) : w2u($fwbody); $mapiprops[$sendMailProps["body"]] = $body . "\r\n\r\n" . $fwbody; } if (strlen($bodyHtml) > 0) { $fwbodyHtml = MAPIUtils::readPropStream($fwmessage, PR_HTML); $fwbodyHtml = isset($cpid[$sendMailProps["internetcpid"]]) ? Utils::ConvertCodepageStringToUtf8($cpid[$sendMailProps["internetcpid"]], $fwbodyHtml) : w2u($fwbodyHtml); $mapiprops[$sendMailProps["html"]] = $bodyHtml . "<br><br>" . $fwbodyHtml; } } } mapi_setprops($mapimessage, $mapiprops); mapi_message_savechanges($mapimessage); mapi_message_submitmessage($mapimessage); $hr = mapi_last_hresult(); if ($hr) { throw new StatusException(sprintf("ZarafaBackend->SendMail(): Error saving/submitting the message to the Outbox: 0x%X", mapi_last_hresult()), SYNC_COMMONSTATUS_MAILSUBMISSIONFAILED); } ZLog::Write(LOGLEVEL_DEBUG, "ZarafaBackend->SendMail(): email submitted"); return true; } $mapiprops[$sendMailProps["subject"]] = u2wi(isset($message->headers["subject"]) ? $message->headers["subject"] : ""); $mapiprops[$sendMailProps["messageclass"]] = "IPM.Note"; $mapiprops[$sendMailProps["deliverytime"]] = time(); if (isset($message->headers["x-priority"])) { $this->getImportanceAndPriority($message->headers["x-priority"], $mapiprops, $sendMailProps); } $this->addRecipients($message->headers, $mapimessage); // Loop through message subparts. $body = ""; $body_html = ""; if ($message->ctype_primary == "multipart" && ($message->ctype_secondary == "mixed" || $message->ctype_secondary == "alternative")) { $mparts = $message->parts; for ($i = 0; $i < count($mparts); $i++) { $part = $mparts[$i]; // palm pre & iPhone send forwarded messages in another subpart which are also parsed if ($part->ctype_primary == "multipart" && ($part->ctype_secondary == "mixed" || $part->ctype_secondary == "alternative" || $part->ctype_secondary == "related")) { foreach ($part->parts as $spart) { $mparts[] = $spart; } continue; } // standard body if ($part->ctype_primary == "text" && $part->ctype_secondary == "plain" && isset($part->body) && (!isset($part->disposition) || $part->disposition != "attachment")) { $body .= u2wi($part->body); // assume only one text body } elseif ($part->ctype_primary == "text" && $part->ctype_secondary == "html") { $body_html .= u2wi($part->body); } elseif ($part->ctype_primary == "ms-tnef" || $part->ctype_secondary == "ms-tnef") { if (!isset($tnefAndIcalProps)) { $tnefAndIcalProps = MAPIMapping::GetTnefAndIcalProperties(); $tnefAndIcalProps = getPropIdsFromStrings($this->store, $tnefAndIcalProps); } require_once 'tnefparser.php'; $zptnef = new TNEFParser($this->store, $tnefAndIcalProps); $zptnef->ExtractProps($part->body, $mapiprops); if (is_array($mapiprops) && !empty($mapiprops)) { //check if it is a recurring item if (isset($mapiprops[$tnefAndIcalProps["tnefrecurr"]])) { MAPIUtils::handleRecurringItem($mapiprops, $tnefAndIcalProps); } } else { ZLog::Write(LOGLEVEL_WARN, "ZarafaBackend->Sendmail(): TNEFParser: Mapi property array was empty"); } } elseif ($part->ctype_primary == "text" && $part->ctype_secondary == "calendar") { if (!isset($tnefAndIcalProps)) { $tnefAndIcalProps = MAPIMapping::GetTnefAndIcalProperties(); $tnefAndIcalProps = getPropIdsFromStrings($this->store, $tnefAndIcalProps); } require_once 'icalparser.php'; $zpical = new ICalParser($this->store, $tnefAndIcalProps); $zpical->ExtractProps($part->body, $mapiprops); // iPhone sends a second ICS which we ignore if we can if (!isset($mapiprops[PR_MESSAGE_CLASS]) && strlen(trim($body)) == 0) { ZLog::Write(LOGLEVEL_WARN, "ZarafaBackend->Sendmail(): Secondary iPhone response is being ignored!! Mail dropped!"); return true; } if (!Utils::CheckMapiExtVersion("6.30") && is_array($mapiprops) && !empty($mapiprops)) { mapi_setprops($mapimessage, $mapiprops); } else { // store ics as attachment //see Utils::IcalTimezoneFix() in utils.php for more information $part->body = Utils::IcalTimezoneFix($part->body); MAPIUtils::StoreAttachment($mapimessage, $part); ZLog::Write(LOGLEVEL_INFO, "ZarafaBackend->Sendmail(): Sending ICS file as attachment"); } } else { MAPIUtils::StoreAttachment($mapimessage, $part); } } } else { if ($message->ctype_primary == "text" && $message->ctype_secondary == "html") { $body_html .= u2wi($message->body); } else { $body = u2wi($message->body); } } // some devices only transmit a html body if (strlen($body) == 0 && strlen($body_html) > 0) { ZLog::Write(LOGLEVEL_WARN, "ZarafaBackend->SendMail(): only html body sent, transformed into plain text"); $body = strip_tags($body_html); } if (isset($sm->source->itemid) && $sm->source->itemid) { // Append the original text body for reply/forward $entryid = mapi_msgstore_entryidfromsourcekey($this->store, hex2bin($sm->source->folderid), hex2bin($sm->source->itemid)); if ($entryid) { $fwmessage = mapi_msgstore_openentry($this->store, $entryid); } if (!isset($fwmessage) || !$fwmessage) { throw new StatusException(sprintf("ZarafaBackend->SendMail(): Could not open message id '%s' in folder id '%s' to be replied/forwarded: 0x%X", $sm->source->itemid, $sm->source->folderid, mapi_last_hresult()), SYNC_COMMONSTATUS_ITEMNOTFOUND); } //update icon when forwarding or replying message if ($sm->forwardflag) { mapi_setprops($fwmessage, array(PR_ICON_INDEX => 262)); } elseif ($sm->replyflag) { mapi_setprops($fwmessage, array(PR_ICON_INDEX => 261)); } mapi_savechanges($fwmessage); // only attach the original message if the mobile does not send it itself if (!isset($sm->replacemime)) { $fwbody = MAPIUtils::readPropStream($fwmessage, PR_BODY); $fwbodyHtml = MAPIUtils::readPropStream($fwmessage, PR_HTML); if ($sm->forwardflag) { // During a forward, we have to add the forward header ourselves. This is because // normally the forwarded message is added as an attachment. However, we don't want this // because it would be rather complicated to copy over the entire original message due // to the lack of IMessage::CopyTo .. $fwheader = $this->getForwardHeaders($fwmessage); // add fwheader to body and body_html $body .= $fwheader; if (strlen($body_html) > 0) { $body_html .= str_ireplace("\r\n", "<br>", $fwheader); } // attach the original attachments to the outgoing message $this->copyAttachments($mapimessage, $fwmessage); } if (strlen($body) > 0) { $body .= $fwbody; } if (strlen($body_html) > 0) { $body_html .= $fwbodyHtml; } } } //set PR_INTERNET_CPID to 65001 (utf-8) if store supports it and to 1252 otherwise $internetcpid = INTERNET_CPID_WINDOWS1252; if (defined('STORE_SUPPORTS_UNICODE') && STORE_SUPPORTS_UNICODE == true) { $internetcpid = INTERNET_CPID_UTF8; } $mapiprops[$sendMailProps["body"]] = $body; $mapiprops[$sendMailProps["internetcpid"]] = $internetcpid; if (strlen($body_html) > 0) { $mapiprops[$sendMailProps["html"]] = $body_html; } //TODO if setting all properties fails, try setting them infividually like in mapiprovider mapi_setprops($mapimessage, $mapiprops); mapi_savechanges($mapimessage); mapi_message_submitmessage($mapimessage); if (mapi_last_hresult()) { throw new StatusException(sprintf("ZarafaBackend->SendMail(): Error saving/submitting the message to the Outbox: 0x%X", mapi_last_hresult()), SYNC_COMMONSTATUS_MAILSUBMISSIONFAILED); } ZLog::Write(LOGLEVEL_DEBUG, "ZarafaBackend->SendMail(): email submitted"); return true; }
function ImportMessageChange($id, $message) { $parentsourcekey = $this->_folderid; if ($id) { $sourcekey = hex2bin($id); } $flags = 0; $props = array(); $props[PR_PARENT_SOURCE_KEY] = $parentsourcekey; // set the PR_SOURCE_KEY if available or mark it as new message if ($id) { $props[PR_SOURCE_KEY] = $sourcekey; } else { $flags = SYNC_NEW_MESSAGE; } if (mapi_importcontentschanges_importmessagechange($this->importer, $props, $flags, $mapimessage)) { $this->_setMessage($mapimessage, $message); mapi_message_savechanges($mapimessage); $sourcekeyprops = mapi_getprops($mapimessage, array(PR_SOURCE_KEY)); } else { debugLog("Unable to update object {$id}:" . sprintf("%x", mapi_last_hresult())); return false; } return bin2hex($sourcekeyprops[PR_SOURCE_KEY]); }
function ImportMessageChange($id, $message) { $parentsourcekey = $this->_folderid; if ($id) { $sourcekey = hex2bin($id); } $flags = 0; $props = array(); $props[PR_PARENT_SOURCE_KEY] = $parentsourcekey; // set the PR_SOURCE_KEY if available or mark it as new message if ($id) { $props[PR_SOURCE_KEY] = $sourcekey; // check for conflicts $this->_lazyLoadConflicts(); if ($this->_memChanges->isChanged($id)) { if ($this->_flags & SYNC_CONFLICT_OVERWRITE_PIM) { debugLog("Conflict detected. Data from PIM will be dropped! Server overwrites PIM."); return false; } else { debugLog("Conflict detected. Data from Server will be dropped! PIM overwrites server."); } } if ($this->_memChanges->isDeleted($id)) { debugLog("Conflict detected. Data from PIM will be dropped! Object was deleted on server."); return false; } } else { $flags = SYNC_NEW_MESSAGE; } if (mapi_importcontentschanges_importmessagechange($this->importer, $props, $flags, $mapimessage)) { $this->_setMessage($mapimessage, $message); mapi_message_savechanges($mapimessage); $sourcekeyprops = mapi_getprops($mapimessage, array(PR_SOURCE_KEY)); } else { debugLog("Unable to update object {$id}:" . sprintf("%x", mapi_last_hresult())); return false; } return bin2hex($sourcekeyprops[PR_SOURCE_KEY]); }
function ImportMessageChange($id, &$message) { $parentsourcekey = $this->_folderid; if ($id) { $sourcekey = hex2bin($id); } $flags = 0; $props = array(); $props[PR_PARENT_SOURCE_KEY] = $parentsourcekey; // set the PR_SOURCE_KEY if available or mark it as new message if ($id) { $props[PR_SOURCE_KEY] = $sourcekey; // check for conflicts if ($this->_memChanges->isChanged($id)) { if ($this->_flags == SYNC_CONFLICT_OVERWRITE_PIM) { debugLog("Conflict detected. Data from PIM will be dropped! Server overwrites PIM."); return false; } else { debugLog("Conflict detected. Data from Server will be dropped! PIM overwrites server."); } } if ($this->_memChanges->isDeleted($id)) { debugLog("Conflict detected. Data from PIM will be dropped! Object was deleted on server."); return false; } } else { $flags = SYNC_NEW_MESSAGE; } $class = strtolower(get_class($message)); debugLog("Class is " . $class . " Flags " . ($flags != SYNC_NEW_MESSAGE ? " not SYNC_NEW_MESSAGE " : " SYNC_NEW_MESSAGE")); // SMS Initial Sync deduplication of items if ($class == "syncsms") { debugLog("Class is SMS and _md5tosrvid contains values. Deduplication routine starting"); if (preg_match('/(\\"(.*)\\" ){0,1}\\[(.*):(.*)\\]$/', $message->to, $addrparts)) { $name = $addrparts[2] == "" ? $addrparts[4] : $addrparts[2]; $addrtype = $addrparts[3]; $email_address = $addrparts[4]; $message->to = "\"" . $name . "\" [MOBILE:" . $email_address . "]"; } if (preg_match('/(\\"(.*)\\" ){0,1}\\[(.*):(.*)\\]$/', $message->from, $addrparts)) { $name = $addrparts[2] == "" ? $addrparts[4] : $addrparts[2]; $addrtype = $addrparts[3]; $email_address = $addrparts[4]; $message->from = "\"" . $name . "\" [MOBILE:" . $email_address . "]"; } if (preg_match('/(\\"(.*)\\" ){0,1}\\[(.*):(.*)\\]$/', $message->cc, $addrparts)) { $name = $addrparts[2] == "" ? $addrparts[4] : $addrparts[2]; $addrtype = $addrparts[3]; $email_address = $addrparts[4]; $message->cc = "\"" . $name . "\" [MOBILE:" . $email_address . "]"; } debugLog(bin2hex(str_replace("\n", "\r\n", str_replace("\r\n", "\n", strval($message->airsyncbasebody->data))))); debugLog(bin2hex(strval($message->from))); debugLog(bin2hex(strval($message->cc))); debugLog(bin2hex(strval($message->to))); // debugLog("ImportMessageChange Message: ".print_r($message,true)); $md5msg = array('datereceived' => isset($message->datereceived) ? strval($message->datereceived) : '', 'importance' => isset($message->importance) ? strval($message->importance) : '', 'messageclass' => isset($message->messageclass) ? strval($message->messageclass) : 'IPM.Note.Mobile.SMS', 'to' => isset($message->to) ? strval($message->to) : '', 'cc' => isset($message->cc) ? strval($message->cc) : '', 'from' => isset($message->from) ? strval($message->from) : '', 'internetcpid' => isset($message->internetcpid) ? strval($message->internetcpid) : '1252', 'body' => isset($message->airsyncbasebody->data) ? str_replace("\n", "\r\n", str_replace("\r\n", "\n", strval($message->airsyncbasebody->data))) : ''); $msgmd5 = md5(serialize($md5msg)); unset($md5msg); debugLog("MD5 SMS Message is {$msgmd5}"); debugLog(print_r($this->_memChanges->_md5tosrvid, true)); if (($dupcheck = $this->_memChanges->isDuplicate($msgmd5)) !== false) { debugLog("Possible message duplicate found!"); $ret['sourcekey'] = $dupcheck['serverid']; $ret['convid'] = $dupcheck['conversationid']; $ret['convidx'] = $dupcheck['conversationindex']; return $ret; } else { debugLog("isDuplicate returned false!"); } } if ($class != "syncsms" || $class == "syncsms" && $flags != SYNC_NEW_MESSAGE) { debugLog("Update Item using mapi_importcontentschanges_importmessagechange."); if (mapi_importcontentschanges_importmessagechange($this->importer, $props, $flags, $mapimessage)) { $this->_setMessage($mapimessage, $message); mapi_message_savechanges($mapimessage); $sourcekeyprops = mapi_getprops($mapimessage, array(PR_SOURCE_KEY)); if (CONVERSATIONINDEX == true) { $convidxprops = mapi_getprops($mapimessage, array(PR_CONVERSATION_INDEX)); } } else { debugLog("Unable to update object {$id}:" . sprintf("%x", mapi_last_hresult())); return false; } } else { debugLog("New SMS detected. Create it by mapi_folder_createmessage"); $mapimessage = mapi_folder_createmessage($this->_folder); if ($mapimessage) { $this->_setMessage($mapimessage, $message); mapi_message_savechanges($mapimessage); $sourcekeyprops = mapi_getprops($mapimessage, array(PR_SOURCE_KEY)); if (CONVERSATIONINDEX == true) { $convidxprops = mapi_getprops($mapimessage, array(PR_CONVERSATION_INDEX)); } } else { debugLog("Unable to create object {$id}:" . sprintf("%x", mapi_last_hresult())); return false; } } if (isset($convidxprops) && is_array($convidxprops) && isset($convidxprops[PR_CONVERSATION_INDEX]) && strlen($convidxprops[PR_CONVERSATION_INDEX]) >= 22) { $tmp = $convidxprops[PR_CONVERSATION_INDEX]; $convid = substr($tmp, 6, 16); $convindex = substr($tmp, 0, 22); $tmp = substr($convidxprops[PR_CONVERSATION_INDEX], 22, strlen($convidxprops[PR_CONVERSATION_INDEX] - 22)); while (strlen($tmp) > 0) { $convindex .= $substr($tmp, 0, 5); $tmp = substr(5, strlen($tmp) - 5); } $ret['sourcekey'] = bin2hex($sourcekeyprops[PR_SOURCE_KEY]); $ret['convid'] = $convid; $ret['convidx'] = $convindex; return $ret; } return bin2hex($sourcekeyprops[PR_SOURCE_KEY]); }
/** * Imports a single message * * @param string $id * @param SyncObject $message * * @access public * @return boolean/string - failure / id of message * @throws StatusException */ public function ImportMessageChange($id, $message) { $flags = 0; $props = array(); $props[PR_PARENT_SOURCE_KEY] = $this->folderid; // set the PR_SOURCE_KEY if available or mark it as new message if ($id) { list(, $sk) = Utils::SplitMessageId($id); $props[PR_SOURCE_KEY] = hex2bin($sk); // on editing an existing message, check if it is in the synchronization interval if (!$this->isMessageInSyncInterval($sk)) { throw new StatusException(sprintf("ImportChangesICS->ImportMessageChange('%s','%s'): Message is outside the sync interval. Data not saved.", $id, get_class($message)), SYNC_STATUS_SYNCCANNOTBECOMPLETED); } // check for conflicts $this->lazyLoadConflicts(); if ($this->memChanges->IsChanged($id)) { if ($this->flags & SYNC_CONFLICT_OVERWRITE_PIM) { // in these cases the status SYNC_STATUS_CONFLICTCLIENTSERVEROBJECT should be returned, so the mobile client can inform the end user throw new StatusException(sprintf("ImportChangesICS->ImportMessageChange('%s','%s'): Conflict detected. Data from PIM will be dropped! Server overwrites PIM. User is informed.", $id, get_class($message)), SYNC_STATUS_CONFLICTCLIENTSERVEROBJECT, null, LOGLEVEL_INFO); return false; } else { ZLog::Write(LOGLEVEL_INFO, sprintf("ImportChangesICS->ImportMessageChange('%s','%s'): Conflict detected. Data from Server will be dropped! PIM overwrites server.", $id, get_class($message))); } } if ($this->memChanges->IsDeleted($id)) { ZLog::Write(LOGLEVEL_INFO, sprintf("ImportChangesICS->ImportMessageChange('%s','%s'): Conflict detected. Data from PIM will be dropped! Object was deleted on server.", $id, get_class($message))); return false; } // KOE ZP-990: OL updates the deleted category which causes a race condition if more than one KOE is connected to that user if (ZPush::GetDeviceManager()->IsKoe() && KOE_CAPABILITY_RECEIVEFLAGS && $message instanceof SyncMail && !isset($message->flag) && isset($message->categories)) { // check if the categories changed $mapiCategories = $this->mapiprovider->GetMessageCategories($props[PR_PARENT_SOURCE_KEY], $props[PR_SOURCE_KEY]); if (empty($message->categories) && empty($mapiCategories) || is_array($mapiCategories) && count(array_diff($mapiCategories, $message->categories)) == 0 && count(array_diff($message->categories, $mapiCategories)) == 0) { ZLog::Write(LOGLEVEL_DEBUG, "ImportChangesICS->ImportMessageChange(): KOE update of flag categories. Ignoring incoming update."); return $id; } } } else { $flags = SYNC_NEW_MESSAGE; } if (mapi_importcontentschanges_importmessagechange($this->importer, $props, $flags, $mapimessage)) { $this->mapiprovider->SetMessage($mapimessage, $message); mapi_message_savechanges($mapimessage); if (mapi_last_hresult()) { throw new StatusException(sprintf("ImportChangesICS->ImportMessageChange('%s','%s'): Error, mapi_message_savechanges() failed: 0x%X", $id, get_class($message), mapi_last_hresult()), SYNC_STATUS_SYNCCANNOTBECOMPLETED); } $sourcekeyprops = mapi_getprops($mapimessage, array(PR_SOURCE_KEY)); return $this->prefix . bin2hex($sourcekeyprops[PR_SOURCE_KEY]); } else { throw new StatusException(sprintf("ImportChangesICS->ImportMessageChange('%s','%s'): Error updating object: 0x%X", $id, get_class($message), mapi_last_hresult()), SYNC_STATUS_OBJECTNOTFOUND); } }
/** * Sends an e-mail * This messages needs to be saved into the 'sent items' folder * * @param SyncSendMail $sm SyncSendMail object * * @access public * @return boolean * @throws StatusException */ public function SendMail($sm) { // Check if imtomapi function is available and use it to send the mime message. // It is available since ZCP 7.0.6 // @see http://jira.zarafa.com/browse/ZCP-9508 if (!(function_exists('mapi_feature') && mapi_feature('INETMAPI_IMTOMAPI'))) { throw new StatusException("ZarafaBackend->SendMail(): ZCP version is too old, INETMAPI_IMTOMAPI is not available. Install at least ZCP version 7.0.6 or later.", SYNC_COMMONSTATUS_MAILSUBMISSIONFAILED, null, LOGLEVEL_FATAL); return false; } ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZarafaBackend->SendMail(): RFC822: %d bytes forward-id: '%s' reply-id: '%s' parent-id: '%s' SaveInSent: '%s' ReplaceMIME: '%s'", strlen($sm->mime), Utils::PrintAsString($sm->forwardflag), Utils::PrintAsString($sm->replyflag), Utils::PrintAsString(isset($sm->source->folderid) ? $sm->source->folderid : false), Utils::PrintAsString($sm->saveinsent), Utils::PrintAsString(isset($sm->replacemime)))); // by splitting the message in several lines we can easily grep later foreach (preg_split("/((\r)?\n)/", $sm->mime) as $rfc822line) { ZLog::Write(LOGLEVEL_WBXML, "RFC822: " . $rfc822line); } $sendMailProps = MAPIMapping::GetSendMailProperties(); $sendMailProps = getPropIdsFromStrings($this->store, $sendMailProps); // Open the outbox and create the message there $storeprops = mapi_getprops($this->store, array($sendMailProps["outboxentryid"], $sendMailProps["ipmsentmailentryid"])); if (isset($storeprops[$sendMailProps["outboxentryid"]])) { $outbox = mapi_msgstore_openentry($this->store, $storeprops[$sendMailProps["outboxentryid"]]); } if (!$outbox) { throw new StatusException(sprintf("ZarafaBackend->SendMail(): No Outbox found or unable to create message: 0x%X", mapi_last_hresult()), SYNC_COMMONSTATUS_SERVERERROR); } $mapimessage = mapi_folder_createmessage($outbox); //message properties to be set $mapiprops = array(); // only save the outgoing in sent items folder if the mobile requests it $mapiprops[$sendMailProps["sentmailentryid"]] = $storeprops[$sendMailProps["ipmsentmailentryid"]]; ZLog::Write(LOGLEVEL_DEBUG, "Use the mapi_inetmapi_imtomapi function"); $ab = mapi_openaddressbook($this->session); mapi_inetmapi_imtomapi($this->session, $this->store, $ab, $mapimessage, $sm->mime, array()); // Set the appSeqNr so that tracking tab can be updated for meeting request updates // @see http://jira.zarafa.com/browse/ZP-68 $meetingRequestProps = MAPIMapping::GetMeetingRequestProperties(); $meetingRequestProps = getPropIdsFromStrings($this->store, $meetingRequestProps); $props = mapi_getprops($mapimessage, array(PR_MESSAGE_CLASS, $meetingRequestProps["goidtag"], $sendMailProps["internetcpid"])); // Convert sent message's body to UTF-8. // @see http://jira.zarafa.com/browse/ZP-505 if (isset($props[$sendMailProps["internetcpid"]]) && $props[$sendMailProps["internetcpid"]] != INTERNET_CPID_UTF8) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("Sent email cpid is not unicode (%d). Set it to unicode and convert email body.", $props[$sendMailProps["internetcpid"]])); $mapiprops[$sendMailProps["internetcpid"]] = INTERNET_CPID_UTF8; $body = MAPIUtils::readPropStream($mapimessage, PR_BODY); $body = Utils::ConvertCodepageStringToUtf8($props[$sendMailProps["internetcpid"]], $body); $mapiprops[$sendMailProps["body"]] = $body; $bodyHtml = MAPIUtils::readPropStream($mapimessage, PR_HTML); $bodyHtml = Utils::ConvertCodepageStringToUtf8($props[$sendMailProps["internetcpid"]], $bodyHtml); $mapiprops[$sendMailProps["html"]] = $bodyHtml; mapi_setprops($mapimessage, $mapiprops); } if (stripos($props[PR_MESSAGE_CLASS], "IPM.Schedule.Meeting.Resp.") === 0) { // search for calendar items using goid $mr = new Meetingrequest($this->store, $mapimessage); $appointments = $mr->findCalendarItems($props[$meetingRequestProps["goidtag"]]); if (is_array($appointments) && !empty($appointments)) { $app = mapi_msgstore_openentry($this->store, $appointments[0]); $appprops = mapi_getprops($app, array($meetingRequestProps["appSeqNr"])); if (isset($appprops[$meetingRequestProps["appSeqNr"]]) && $appprops[$meetingRequestProps["appSeqNr"]]) { $mapiprops[$meetingRequestProps["appSeqNr"]] = $appprops[$meetingRequestProps["appSeqNr"]]; ZLog::Write(LOGLEVEL_DEBUG, sprintf("Set sequence number to:%d", $appprops[$meetingRequestProps["appSeqNr"]])); } } } // Delete the PR_SENT_REPRESENTING_* properties because some android devices // do not send neither From nor Sender header causing empty PR_SENT_REPRESENTING_NAME and // PR_SENT_REPRESENTING_EMAIL_ADDRESS properties and "broken" PR_SENT_REPRESENTING_ENTRYID // which results in spooler not being able to send the message. // @see http://jira.zarafa.com/browse/ZP-85 mapi_deleteprops($mapimessage, array($sendMailProps["sentrepresentingname"], $sendMailProps["sentrepresentingemail"], $sendMailProps["representingentryid"], $sendMailProps["sentrepresentingaddt"], $sendMailProps["sentrepresentinsrchk"])); if (isset($sm->source->itemid) && $sm->source->itemid) { // answering an email in a public/shared folder if (!$this->Setup(ZPush::GetAdditionalSyncFolderStore($sm->source->folderid))) { throw new StatusException(sprintf("ZarafaBackend->SendMail() could not Setup() the backend for folder id '%s'", $sm->source->folderid), SYNC_COMMONSTATUS_SERVERERROR); } $entryid = mapi_msgstore_entryidfromsourcekey($this->store, hex2bin($sm->source->folderid), hex2bin($sm->source->itemid)); if ($entryid) { $fwmessage = mapi_msgstore_openentry($this->store, $entryid); } if (!isset($fwmessage) || !$fwmessage) { throw new StatusException(sprintf("ZarafaBackend->SendMail(): Could not open message id '%s' in folder id '%s' to be replied/forwarded: 0x%X", $sm->source->itemid, $sm->source->folderid, mapi_last_hresult()), SYNC_COMMONSTATUS_ITEMNOTFOUND); } //update icon when forwarding or replying message if ($sm->forwardflag) { mapi_setprops($fwmessage, array(PR_ICON_INDEX => 262)); } elseif ($sm->replyflag) { mapi_setprops($fwmessage, array(PR_ICON_INDEX => 261)); } mapi_savechanges($fwmessage); // only attach the original message if the mobile does not send it itself if (!isset($sm->replacemime)) { // get message's body in order to append forward or reply text if (!isset($body)) { $body = MAPIUtils::readPropStream($mapimessage, PR_BODY); } if (!isset($bodyHtml)) { $bodyHtml = MAPIUtils::readPropStream($mapimessage, PR_HTML); } $cpid = mapi_getprops($fwmessage, array($sendMailProps["internetcpid"])); if ($sm->forwardflag) { // attach the original attachments to the outgoing message $this->copyAttachments($mapimessage, $fwmessage); } // regarding the conversion @see ZP-470 if (strlen($body) > 0) { $fwbody = MAPIUtils::readPropStream($fwmessage, PR_BODY); // if only the old message's cpid is set, convert from old charset to utf-8 if (isset($cpid[$sendMailProps["internetcpid"]]) && $cpid[$sendMailProps["internetcpid"]] != INTERNET_CPID_UTF8) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZarafaBackend->SendMail(): convert plain forwarded message charset (only fw set) from '%s' to '65001'", $cpid[$sendMailProps["internetcpid"]])); $fwbody = Utils::ConvertCodepageStringToUtf8($cpid[$sendMailProps["internetcpid"]], $fwbody); } else { ZLog::Write(LOGLEVEL_DEBUG, "ZarafaBackend->SendMail(): no charset conversion done for plain forwarded message"); $fwbody = w2u($fwbody); } $mapiprops[$sendMailProps["body"]] = $body . "\r\n\r\n" . $fwbody; } if (strlen($bodyHtml) > 0) { $fwbodyHtml = MAPIUtils::readPropStream($fwmessage, PR_HTML); // if only new message's cpid is set, convert to UTF-8 if (isset($cpid[$sendMailProps["internetcpid"]]) && $cpid[$sendMailProps["internetcpid"]] != INTERNET_CPID_UTF8) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZarafaBackend->SendMail(): convert html forwarded message charset (only fw set) from '%s' to '65001'", $cpid[$sendMailProps["internetcpid"]])); $fwbodyHtml = Utils::ConvertCodepageStringToUtf8($cpid[$sendMailProps["internetcpid"]], $fwbodyHtml); } else { ZLog::Write(LOGLEVEL_DEBUG, "ZarafaBackend->SendMail(): no charset conversion done for html forwarded message"); $fwbodyHtml = w2u($fwbodyHtml); } $mapiprops[$sendMailProps["html"]] = $bodyHtml . "<br><br>" . $fwbodyHtml; } } } mapi_setprops($mapimessage, $mapiprops); mapi_message_savechanges($mapimessage); mapi_message_submitmessage($mapimessage); $hr = mapi_last_hresult(); if ($hr) { throw new StatusException(sprintf("ZarafaBackend->SendMail(): Error saving/submitting the message to the Outbox: 0x%X", mapi_last_hresult()), SYNC_COMMONSTATUS_MAILSUBMISSIONFAILED); } ZLog::Write(LOGLEVEL_DEBUG, "ZarafaBackend->SendMail(): email submitted"); return true; }
function SendMail($rfc822, $forward = false, $reply = false, $parent = false) { if (WBXML_DEBUG == true) { debugLog("SendMail: forward: {$forward} reply: {$reply} parent: {$parent}\n" . $rfc822); } // Open the outbox and create the message there $storeprops = mapi_getprops($this->_defaultstore, array(PR_IPM_OUTBOX_ENTRYID, PR_IPM_SENTMAIL_ENTRYID)); if (!isset($storeprops[PR_IPM_OUTBOX_ENTRYID])) { debugLog("Outbox not found to create message"); return false; } $outbox = mapi_msgstore_openentry($this->_defaultstore, $storeprops[PR_IPM_OUTBOX_ENTRYID]); if (!$outbox) { debugLog("Unable to open outbox"); return false; } $mapimessage = mapi_folder_createmessage($outbox); //message properties to be set $mapiprops = array(); // only save the outgoing in sent items folder if the mobile requests it if (isset($storeprops[PR_IPM_SENTMAIL_ENTRYID])) { $mapiprops[PR_SENTMAIL_ENTRYID] = $storeprops[PR_IPM_SENTMAIL_ENTRYID]; } else { debugLog("PR_SENTMAIL_ENTRYID is not set. The sent message will not be moved to Sent Items."); } if ($forward) { $orig = $forward; } if ($reply) { $orig = $reply; } // Check if imtomapi function is available and use it to send the mime message. // It is available since ZCP 7.0.6 // @see http://jira.zarafa.com/browse/ZCP-9508 if (function_exists('mapi_feature') && mapi_feature('INETMAPI_IMTOMAPI')) { debugLog("Use the mapi_inetmapi_imtomapi function"); $ab = mapi_openaddressbook($this->_session); mapi_inetmapi_imtomapi($this->_session, $this->_defaultstore, $ab, $mapimessage, $rfc822, array()); // Delete the PR_SENT_REPRESENTING_* properties because some android devices // do not send neither From nor Sender header causing empty PR_SENT_REPRESENTING_NAME and // PR_SENT_REPRESENTING_EMAIL_ADDRESS properties and "broken" PR_SENT_REPRESENTING_ENTRYID // which results in spooler not being able to send the message. // @see http://jira.zarafa.com/browse/ZP-85 mapi_deleteprops($mapimessage, array(PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_ADDRTYPE, PR_SENT_REPRESENTING_SEARCH_KEY)); if (isset($orig) && $orig) { $entryid = mapi_msgstore_entryidfromsourcekey($this->_defaultstore, hex2bin($parent), hex2bin($orig)); $fwmessage = mapi_msgstore_openentry($this->_defaultstore, $entryid); if ($fwmessage) { //update icon when forwarding or replying message if ($forward) { mapi_setprops($fwmessage, array(PR_ICON_INDEX => 262)); } elseif ($reply) { mapi_setprops($fwmessage, array(PR_ICON_INDEX => 261)); } mapi_savechanges($fwmessage); if ($forward) { $this->_copyAttachments($mapimessage, $fwmessage); } $body = $this->_readPropStream($mapimessage, PR_BODY); $body_html = $this->_readPropStream($mapimessage, PR_HTML); if (strlen($body) > 0) { $fwbody = $this->_readPropStream($fwmessage, PR_BODY); $body .= $fwbody; } if (strlen($body_html) > 0) { $fwbody_html = $this->_readPropStream($fwmessage, PR_HTML); $body_html .= $fwbody_html; } mapi_setprops($mapimessage, array(PR_BODY => $body)); if (strlen($body_html) > 0) { mapi_setprops($mapimessage, array(PR_HTML => $body_html)); } } } if (!empty($mapiprops)) { mapi_setprops($mapimessage, $mapiprops); } mapi_message_savechanges($mapimessage); mapi_message_submitmessage($mapimessage); $hr = mapi_last_hresult(); if ($hr) { debugLog(sprintf("SendMail(): Error saving/submitting the message to the Outbox: 0x%X", mapi_last_hresult())); return false; } return true; } $mimeParams = array('decode_headers' => true, 'decode_bodies' => true, 'include_bodies' => true, 'charset' => 'utf-8'); $mimeObject = new Mail_mimeDecode($rfc822); $message = $mimeObject->decode($mimeParams); mapi_setprops($mapimessage, array(PR_SUBJECT => u2wi(isset($message->headers["subject"]) ? $message->headers["subject"] : ""), PR_SENTMAIL_ENTRYID => $storeprops[PR_IPM_SENTMAIL_ENTRYID], PR_MESSAGE_CLASS => "IPM.Note", PR_MESSAGE_DELIVERY_TIME => time())); if (isset($message->headers["x-priority"])) { switch ($message->headers["x-priority"]) { case 1: case 2: $priority = PRIO_URGENT; $importance = IMPORTANCE_HIGH; break; case 4: case 5: $priority = PRIO_NONURGENT; $importance = IMPORTANCE_LOW; break; case 3: default: $priority = PRIO_NORMAL; $importance = IMPORTANCE_NORMAL; break; } mapi_setprops($mapimessage, array(PR_IMPORTANCE => $importance, PR_PRIORITY => $priority)); } $addresses = array(); $toaddr = $ccaddr = $bccaddr = array(); $Mail_RFC822 = new Mail_RFC822(); if (isset($message->headers["to"])) { $toaddr = $Mail_RFC822->parseAddressList($message->headers["to"]); } if (isset($message->headers["cc"])) { $ccaddr = $Mail_RFC822->parseAddressList($message->headers["cc"]); } if (isset($message->headers["bcc"])) { $bccaddr = $Mail_RFC822->parseAddressList($message->headers["bcc"]); } // Add recipients $recips = array(); if (isset($toaddr)) { foreach (array(MAPI_TO => $toaddr, MAPI_CC => $ccaddr, MAPI_BCC => $bccaddr) as $type => $addrlist) { foreach ($addrlist as $addr) { $mapirecip[PR_ADDRTYPE] = "SMTP"; $mapirecip[PR_EMAIL_ADDRESS] = $addr->mailbox . "@" . $addr->host; if (isset($addr->personal) && strlen($addr->personal) > 0) { $mapirecip[PR_DISPLAY_NAME] = u2wi($addr->personal); } else { $mapirecip[PR_DISPLAY_NAME] = $mapirecip[PR_EMAIL_ADDRESS]; } $mapirecip[PR_RECIPIENT_TYPE] = $type; $mapirecip[PR_ENTRYID] = mapi_createoneoff($mapirecip[PR_DISPLAY_NAME], $mapirecip[PR_ADDRTYPE], $mapirecip[PR_EMAIL_ADDRESS]); array_push($recips, $mapirecip); } } } mapi_message_modifyrecipients($mapimessage, 0, $recips); // Loop through message subparts. $body = ""; $body_html = ""; if ($message->ctype_primary == "multipart" && ($message->ctype_secondary == "mixed" || $message->ctype_secondary == "alternative")) { $mparts = $message->parts; for ($i = 0; $i < count($mparts); $i++) { $part = $mparts[$i]; // palm pre & iPhone send forwarded messages in another subpart which are also parsed if ($part->ctype_primary == "multipart" && ($part->ctype_secondary == "mixed" || $part->ctype_secondary == "alternative" || $part->ctype_secondary == "related")) { foreach ($part->parts as $spart) { $mparts[] = $spart; } continue; } // standard body if ($part->ctype_primary == "text" && $part->ctype_secondary == "plain" && isset($part->body) && (!isset($part->disposition) || $part->disposition != "attachment")) { $body .= u2wi($part->body); // assume only one text body } elseif ($part->ctype_primary == "text" && $part->ctype_secondary == "html") { $body_html .= u2wi($part->body); } elseif ($part->ctype_primary == "ms-tnef" || $part->ctype_secondary == "ms-tnef") { $zptnef = new ZPush_tnef($this->_defaultstore); $mapiprops = array(); $zptnef->extractProps($part->body, $mapiprops); if (is_array($mapiprops) && !empty($mapiprops)) { //check if it is a recurring item $tnefrecurr = GetPropIDFromString($this->_defaultstore, "PT_BOOLEAN:{6ED8DA90-450B-101B-98DA-00AA003F1305}:0x5"); if (isset($mapiprops[$tnefrecurr])) { $this->_handleRecurringItem($mapimessage, $mapiprops); } mapi_setprops($mapimessage, $mapiprops); } else { debugLog("TNEF: Mapi props array was empty"); } } elseif ($part->ctype_primary == "text" && $part->ctype_secondary == "calendar") { $zpical = new ZPush_ical($this->_defaultstore); $mapiprops = array(); $zpical->extractProps($part->body, $mapiprops); // iPhone sends a second ICS which we ignore if we can if (!isset($mapiprops[PR_MESSAGE_CLASS]) && strlen(trim($body)) == 0) { debugLog("Secondary iPhone response is being ignored!! Mail dropped!"); return true; } if (!checkMapiExtVersion("6.30") && is_array($mapiprops) && !empty($mapiprops)) { mapi_setprops($mapimessage, $mapiprops); } else { // store ics as attachment //see icalTimezoneFix function in compat.php for more information $part->body = icalTimezoneFix($part->body); $this->_storeAttachment($mapimessage, $part); debugLog("Sending ICS file as attachment"); } } else { $this->_storeAttachment($mapimessage, $part); } } } else { if ($message->ctype_primary == "text" && $message->ctype_secondary == "html") { $body_html .= u2wi($message->body); } else { $body = u2wi($message->body); } } // some devices only transmit a html body if (strlen($body) == 0 && strlen($body_html) > 0) { debugLog("only html body sent, transformed into plain text"); $body = strip_tags($body_html); } if (isset($orig) && $orig) { // Append the original text body for reply/forward $entryid = mapi_msgstore_entryidfromsourcekey($this->_defaultstore, hex2bin($parent), hex2bin($orig)); $fwmessage = mapi_msgstore_openentry($this->_defaultstore, $entryid); if ($fwmessage) { //update icon when forwarding or replying message if ($forward) { mapi_setprops($fwmessage, array(PR_ICON_INDEX => 262)); } elseif ($reply) { mapi_setprops($fwmessage, array(PR_ICON_INDEX => 261)); } mapi_savechanges($fwmessage); $fwbody = $this->_readPropStream($fwmessage, PR_BODY); $fwbody_html = $this->_readPropStream($fwmessage, PR_HTML); if ($forward) { // During a forward, we have to add the forward header ourselves. This is because // normally the forwarded message is added as an attachment. However, we don't want this // because it would be rather complicated to copy over the entire original message due // to the lack of IMessage::CopyTo .. $fwmessageprops = mapi_getprops($fwmessage, array(PR_SENT_REPRESENTING_NAME, PR_DISPLAY_TO, PR_DISPLAY_CC, PR_SUBJECT, PR_CLIENT_SUBMIT_TIME)); $fwheader = "\r\n\r\n"; $fwheader .= "-----Original Message-----\r\n"; if (isset($fwmessageprops[PR_SENT_REPRESENTING_NAME])) { $fwheader .= "From: " . $fwmessageprops[PR_SENT_REPRESENTING_NAME] . "\r\n"; } if (isset($fwmessageprops[PR_DISPLAY_TO]) && strlen($fwmessageprops[PR_DISPLAY_TO]) > 0) { $fwheader .= "To: " . $fwmessageprops[PR_DISPLAY_TO] . "\r\n"; } if (isset($fwmessageprops[PR_DISPLAY_CC]) && strlen($fwmessageprops[PR_DISPLAY_CC]) > 0) { $fwheader .= "Cc: " . $fwmessageprops[PR_DISPLAY_CC] . "\r\n"; } if (isset($fwmessageprops[PR_CLIENT_SUBMIT_TIME])) { $fwheader .= "Sent: " . strftime("%x %X", $fwmessageprops[PR_CLIENT_SUBMIT_TIME]) . "\r\n"; } if (isset($fwmessageprops[PR_SUBJECT])) { $fwheader .= "Subject: " . $fwmessageprops[PR_SUBJECT] . "\r\n"; } $fwheader .= "\r\n"; // add fwheader to body and body_html $body .= $fwheader; if (strlen($body_html) > 0) { $body_html .= str_ireplace("\r\n", "<br>", $fwheader); } } if (strlen($body) > 0) { $body .= $fwbody; } if (strlen($body_html) > 0) { $body_html .= $fwbody_html; } } else { debugLog("Unable to open item with id {$orig} for forward/reply"); } } if ($forward) { // Add attachments from the original message in a forward $entryid = mapi_msgstore_entryidfromsourcekey($this->_defaultstore, hex2bin($parent), hex2bin($orig)); $fwmessage = mapi_msgstore_openentry($this->_defaultstore, $entryid); $attachtable = mapi_message_getattachmenttable($fwmessage); $rows = mapi_table_queryallrows($attachtable, array(PR_ATTACH_NUM)); foreach ($rows as $row) { if (isset($row[PR_ATTACH_NUM])) { $attach = mapi_message_openattach($fwmessage, $row[PR_ATTACH_NUM]); $newattach = mapi_message_createattach($mapimessage); // Copy all attachments from old to new attachment $attachprops = mapi_getprops($attach); mapi_setprops($newattach, $attachprops); if (isset($attachprops[mapi_prop_tag(PT_ERROR, mapi_prop_id(PR_ATTACH_DATA_BIN))])) { // Data is in a stream $srcstream = mapi_openpropertytostream($attach, PR_ATTACH_DATA_BIN); $dststream = mapi_openpropertytostream($newattach, PR_ATTACH_DATA_BIN, MAPI_MODIFY | MAPI_CREATE); while (1) { $data = mapi_stream_read($srcstream, 4096); if (strlen($data) == 0) { break; } mapi_stream_write($dststream, $data); } mapi_stream_commit($dststream); } mapi_savechanges($newattach); } } } //set PR_INTERNET_CPID to 65001 (utf-8) if store supports it and to 1252 otherwise $internetcpid = 1252; if (defined('STORE_SUPPORTS_UNICODE') && STORE_SUPPORTS_UNICODE == true) { $internetcpid = 65001; } mapi_setprops($mapimessage, array(PR_BODY => $body, PR_INTERNET_CPID => $internetcpid)); if (strlen($body_html) > 0) { mapi_setprops($mapimessage, array(PR_HTML => $body_html)); } mapi_savechanges($mapimessage); mapi_message_submitmessage($mapimessage); return true; }
/** * Sends an e-mail * This messages needs to be saved into the 'sent items' folder * * @param SyncSendMail $sm SyncSendMail object * * @access public * @return boolean * @throws StatusException */ public function SendMail($sm) { // Check if imtomapi function is available and use it to send the mime message. // It is available since ZCP 7.0.6 // @see http://jira.zarafa.com/browse/ZCP-9508 if (!(function_exists('mapi_feature') && mapi_feature('INETMAPI_IMTOMAPI'))) { throw new StatusException("KopanoBackend->SendMail(): ZCP/KC version is too old, INETMAPI_IMTOMAPI is not available. Install at least ZCP version 7.0.6 or later.", SYNC_COMMONSTATUS_MAILSUBMISSIONFAILED, null, LOGLEVEL_FATAL); return false; } ZLog::Write(LOGLEVEL_DEBUG, sprintf("KopanoBackend->SendMail(): RFC822: %d bytes forward-id: '%s' reply-id: '%s' parent-id: '%s' SaveInSent: '%s' ReplaceMIME: '%s'", strlen($sm->mime), Utils::PrintAsString($sm->forwardflag), Utils::PrintAsString($sm->replyflag), Utils::PrintAsString(isset($sm->source->folderid) ? $sm->source->folderid : false), Utils::PrintAsString($sm->saveinsent), Utils::PrintAsString(isset($sm->replacemime)))); // Send-As functionality - https://jira.z-hub.io/browse/ZP-908 $sendingAsSomeone = false; if (defined('KOE_CAPABILITY_SENDAS') && KOE_CAPABILITY_SENDAS) { $senderEmail = array(); // KOE: grep for the Sender header indicating we should send-as // the 'X-Push-Sender-Name' header is not used if (preg_match("/^X-Push-Sender:\\s(.*?)\$/im", $sm->mime, $senderEmail)) { $sendAsEmail = trim($senderEmail[1]); ZLog::Write(LOGLEVEL_DEBUG, sprintf("KopanoBackend->SendMail(): Send-As '%s' requested by KOE", $sendAsEmail)); $sm->mime = preg_replace("/^From: .*?\$/im", "From: " . $sendAsEmail, $sm->mime, 1); $sendingAsSomeone = true; } elseif (isset($sm->source->folderid)) { // get the owner of this folder - System is not allowed $sharedUser = ZPush::GetAdditionalSyncFolderStore($sm->source->folderid); if ($sharedUser != false && $sharedUser != 'SYSTEM') { $folders = ZPush::GetAdditionalSyncFolders(); if (isset($folders[$sm->source->folderid]) && $folders[$sm->source->folderid]->Flags & DeviceManager::FLD_FLAGS_REPLYASUSER) { $sendAs = $this->resolveRecipientGAL($sharedUser, 1); if (isset($sendAs[0])) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("KopanoBackend->SendMail(): Server side Send-As activated for shared folder. Sending as '%s'.", $sendAs[0]->emailaddress)); $sm->mime = preg_replace("/^From: .*?\$/im", "From: " . $sendAs[0]->emailaddress, $sm->mime, 1); $sendingAsSomeone = true; } } } } } // by splitting the message in several lines we can easily grep later foreach (preg_split("/((\r)?\n)/", $sm->mime) as $rfc822line) { ZLog::Write(LOGLEVEL_WBXML, "RFC822: " . $rfc822line); } $sendMailProps = MAPIMapping::GetSendMailProperties(); $sendMailProps = getPropIdsFromStrings($this->defaultstore, $sendMailProps); // Open the outbox and create the message there $storeprops = mapi_getprops($this->defaultstore, array($sendMailProps["outboxentryid"], $sendMailProps["ipmsentmailentryid"])); if (isset($storeprops[$sendMailProps["outboxentryid"]])) { $outbox = mapi_msgstore_openentry($this->defaultstore, $storeprops[$sendMailProps["outboxentryid"]]); } if (!$outbox) { throw new StatusException(sprintf("KopanoBackend->SendMail(): No Outbox found or unable to create message: 0x%X", mapi_last_hresult()), SYNC_COMMONSTATUS_SERVERERROR); } $mapimessage = mapi_folder_createmessage($outbox); //message properties to be set $mapiprops = array(); // only save the outgoing in sent items folder if the mobile requests it $mapiprops[$sendMailProps["sentmailentryid"]] = $storeprops[$sendMailProps["ipmsentmailentryid"]]; ZLog::Write(LOGLEVEL_DEBUG, "Use the mapi_inetmapi_imtomapi function"); $ab = mapi_openaddressbook($this->session); mapi_inetmapi_imtomapi($this->session, $this->defaultstore, $ab, $mapimessage, $sm->mime, array()); // Set the appSeqNr so that tracking tab can be updated for meeting request updates // @see http://jira.zarafa.com/browse/ZP-68 $meetingRequestProps = MAPIMapping::GetMeetingRequestProperties(); $meetingRequestProps = getPropIdsFromStrings($this->defaultstore, $meetingRequestProps); $props = mapi_getprops($mapimessage, array(PR_MESSAGE_CLASS, $meetingRequestProps["goidtag"], $sendMailProps["internetcpid"], $sendMailProps["body"], $sendMailProps["html"], $sendMailProps["rtf"], $sendMailProps["rtfinsync"])); // Convert sent message's body to UTF-8 if it was a HTML message. // @see http://jira.zarafa.com/browse/ZP-505 and http://jira.zarafa.com/browse/ZP-555 if (isset($props[$sendMailProps["internetcpid"]]) && $props[$sendMailProps["internetcpid"]] != INTERNET_CPID_UTF8 && MAPIUtils::GetNativeBodyType($props) == SYNC_BODYPREFERENCE_HTML) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("Sent email cpid is not unicode (%d). Set it to unicode and convert email html body.", $props[$sendMailProps["internetcpid"]])); $mapiprops[$sendMailProps["internetcpid"]] = INTERNET_CPID_UTF8; $bodyHtml = MAPIUtils::readPropStream($mapimessage, PR_HTML); $bodyHtml = Utils::ConvertCodepageStringToUtf8($props[$sendMailProps["internetcpid"]], $bodyHtml); $mapiprops[$sendMailProps["html"]] = $bodyHtml; mapi_setprops($mapimessage, $mapiprops); } if (stripos($props[PR_MESSAGE_CLASS], "IPM.Schedule.Meeting.Resp.") === 0) { // search for calendar items using goid $mr = new Meetingrequest($this->defaultstore, $mapimessage); $appointments = $mr->findCalendarItems($props[$meetingRequestProps["goidtag"]]); if (is_array($appointments) && !empty($appointments)) { $app = mapi_msgstore_openentry($this->defaultstore, $appointments[0]); $appprops = mapi_getprops($app, array($meetingRequestProps["appSeqNr"])); if (isset($appprops[$meetingRequestProps["appSeqNr"]]) && $appprops[$meetingRequestProps["appSeqNr"]]) { $mapiprops[$meetingRequestProps["appSeqNr"]] = $appprops[$meetingRequestProps["appSeqNr"]]; ZLog::Write(LOGLEVEL_DEBUG, sprintf("Set sequence number to:%d", $appprops[$meetingRequestProps["appSeqNr"]])); } } } // Delete the PR_SENT_REPRESENTING_* properties because some android devices // do not send neither From nor Sender header causing empty PR_SENT_REPRESENTING_NAME and // PR_SENT_REPRESENTING_EMAIL_ADDRESS properties and "broken" PR_SENT_REPRESENTING_ENTRYID // which results in spooler not being able to send the message. // @see http://jira.zarafa.com/browse/ZP-85 // If using KOE send-as feature, we keep this properties because they actually are the send-as if (!$sendingAsSomeone) { mapi_deleteprops($mapimessage, array($sendMailProps["sentrepresentingname"], $sendMailProps["sentrepresentingemail"], $sendMailProps["representingentryid"], $sendMailProps["sentrepresentingaddt"], $sendMailProps["sentrepresentinsrchk"])); } if (isset($sm->source->itemid) && $sm->source->itemid) { // answering an email in a public/shared folder // TODO as the store is setup, we should actually user $this->store instead of $this->defaultstore - nevertheless we need to make sure this store is able to send mail (has an outbox) if (!$this->Setup(ZPush::GetAdditionalSyncFolderStore($sm->source->folderid))) { throw new StatusException(sprintf("KopanoBackend->SendMail() could not Setup() the backend for folder id '%s'", $sm->source->folderid), SYNC_COMMONSTATUS_SERVERERROR); } $entryid = mapi_msgstore_entryidfromsourcekey($this->store, hex2bin($sm->source->folderid), hex2bin($sm->source->itemid)); if ($entryid) { $fwmessage = mapi_msgstore_openentry($this->store, $entryid); } if (isset($fwmessage) && $fwmessage) { // update icon and last_verb when forwarding or replying message // reply-all (verb 103) is not supported, as we cannot really detect this case if ($sm->forwardflag) { $updateProps = array(PR_ICON_INDEX => 262, PR_LAST_VERB_EXECUTED => 104); } elseif ($sm->replyflag) { $updateProps = array(PR_ICON_INDEX => 261, PR_LAST_VERB_EXECUTED => 102); } if (isset($updateProps)) { $updateProps[PR_LAST_VERB_EXECUTION_TIME] = time(); mapi_setprops($fwmessage, $updateProps); mapi_savechanges($fwmessage); } // only attach the original message if the mobile does not send it itself if (!isset($sm->replacemime)) { // get message's body in order to append forward or reply text if (!isset($body)) { $body = MAPIUtils::readPropStream($mapimessage, PR_BODY); } if (!isset($bodyHtml)) { $bodyHtml = MAPIUtils::readPropStream($mapimessage, PR_HTML); } $cpid = mapi_getprops($fwmessage, array($sendMailProps["internetcpid"])); if ($sm->forwardflag) { // attach the original attachments to the outgoing message $this->copyAttachments($mapimessage, $fwmessage); } // regarding the conversion @see ZP-470 if (strlen($body) > 0) { $fwbody = MAPIUtils::readPropStream($fwmessage, PR_BODY); // if only the old message's cpid is set, convert from old charset to utf-8 if (isset($cpid[$sendMailProps["internetcpid"]]) && $cpid[$sendMailProps["internetcpid"]] != INTERNET_CPID_UTF8) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("KopanoBackend->SendMail(): convert plain forwarded message charset (only fw set) from '%s' to '65001'", $cpid[$sendMailProps["internetcpid"]])); $fwbody = Utils::ConvertCodepageStringToUtf8($cpid[$sendMailProps["internetcpid"]], $fwbody); } else { ZLog::Write(LOGLEVEL_DEBUG, "KopanoBackend->SendMail(): no charset conversion done for plain forwarded message"); $fwbody = w2u($fwbody); } $mapiprops[$sendMailProps["body"]] = $body . "\r\n\r\n" . $fwbody; } if (strlen($bodyHtml) > 0) { $fwbodyHtml = MAPIUtils::readPropStream($fwmessage, PR_HTML); // if only new message's cpid is set, convert to UTF-8 if (isset($cpid[$sendMailProps["internetcpid"]]) && $cpid[$sendMailProps["internetcpid"]] != INTERNET_CPID_UTF8) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("KopanoBackend->SendMail(): convert html forwarded message charset (only fw set) from '%s' to '65001'", $cpid[$sendMailProps["internetcpid"]])); $fwbodyHtml = Utils::ConvertCodepageStringToUtf8($cpid[$sendMailProps["internetcpid"]], $fwbodyHtml); } else { ZLog::Write(LOGLEVEL_DEBUG, "KopanoBackend->SendMail(): no charset conversion done for html forwarded message"); $fwbodyHtml = w2u($fwbodyHtml); } $mapiprops[$sendMailProps["html"]] = $bodyHtml . "<br><br>" . $fwbodyHtml; } } } else { // no fwmessage could be opened and we need it because we do not replace mime if (!isset($sm->replacemime) || $sm->replacemime == false) { throw new StatusException(sprintf("KopanoBackend->SendMail(): Could not open message id '%s' in folder id '%s' to be replied/forwarded: 0x%X", $sm->source->itemid, $sm->source->folderid, mapi_last_hresult()), SYNC_COMMONSTATUS_ITEMNOTFOUND); } } } mapi_setprops($mapimessage, $mapiprops); mapi_message_savechanges($mapimessage); mapi_message_submitmessage($mapimessage); $hr = mapi_last_hresult(); if ($hr) { throw new StatusException(sprintf("KopanoBackend->SendMail(): Error saving/submitting the message to the Outbox: 0x%X", mapi_last_hresult()), SYNC_COMMONSTATUS_MAILSUBMISSIONFAILED); } ZLog::Write(LOGLEVEL_DEBUG, "KopanoBackend->SendMail(): email submitted"); return true; }