/**
* Builds a proper AS mail message object.
*
* @param Horde_Imap_Client_Mailbox $mbox The IMAP mailbox.
* @param Horde_Imap_Client_Data_Fetch $data The fetch results.
* @param array $options Additional Options:
* - truncation: (integer) Truncate the message body to this length.
* DEFAULT: No truncation.
* - bodyprefs: (array) Bodyprefs, if sent from device.
* DEFAULT: none (No body prefs sent or enforced).
* - mimesupport: (integer) Indicates if MIME is supported or not.
* Possible values: 0 - Not supported 1 - Only S/MIME or
* 2 - All MIME.
* DEFAULT: 0 (No MIME support)
* - protocolversion: (float) The EAS protocol version to support.
* DEFAULT: 2.5
*
* @return Horde_ActiveSync_Message_Mail The message object suitable for
* streaming to the device.
*/
protected function _buildMailMessage(Horde_Imap_Client_Mailbox $mbox, Horde_Imap_Client_Data_Fetch $data, $options = array())
{
$imap = $this->_getImapOb();
$version = empty($options['protocolversion']) ? Horde_ActiveSync::VERSION_TWOFIVE : $options['protocolversion'];
$imap_message = new Horde_ActiveSync_Imap_Message($imap, $mbox, $data);
$eas_message = Horde_ActiveSync::messageFactory('Mail');
// Build To: data (POOMMAIL_TO has a max length of 1024).
$to = $imap_message->getToAddresses();
$eas_message->to = array_pop($to['to']);
foreach ($to['to'] as $to_atom) {
if (strlen($eas_message->to) + strlen($to_atom) > 1024) {
break;
}
$eas_message->to .= ',' . $to_atom;
}
$eas_message->displayto = implode(';', $to['displayto']);
if (empty($eas_message->displayto)) {
$eas_message->displayto = $eas_message->to;
}
// Ensure we don't send broken UTF8 data to the client. It makes clients
// angry. And we don't like angry clients.
$hdr_charset = $imap_message->getStructure()->getHeaderCharset();
// Fill in other header data
$eas_message->from = $imap_message->getFromAddress();
$eas_message->subject = Horde_ActiveSync_Utils::ensureUtf8($imap_message->getSubject(), $hdr_charset);
$eas_message->threadtopic = $eas_message->subject;
$eas_message->datereceived = $imap_message->getDate();
$eas_message->read = $imap_message->getFlag(Horde_Imap_Client::FLAG_SEEN);
$eas_message->cc = $imap_message->getCc();
$eas_message->reply_to = $imap_message->getReplyTo();
// Default to IPM.Note - may change below depending on message content.
$eas_message->messageclass = 'IPM.Note';
// Codepage id. MS recommends to always set to UTF-8 when possible.
// See http://msdn.microsoft.com/en-us/library/windows/desktop/dd317756%28v=vs.85%29.aspx
$eas_message->cpid = Horde_ActiveSync_Message_Mail::INTERNET_CPID_UTF8;
// Message importance. First try X-Priority, then Importance since
// Outlook sends the later.
if ($priority = $imap_message->getHeaders()->getValue('X-priority')) {
$priorty = preg_replace('/\\D+/', '', $priority);
} else {
$priority = $imap_message->getHeaders()->getValue('Importance');
}
$eas_message->importance = $this->_getEASImportance($priority);
// Get the body data and ensure we have something to send.
$message_body_data = $this->_validateMessageBodyData($imap_message->getMessageBodyData($options));
if ($version == Horde_ActiveSync::VERSION_TWOFIVE) {
$eas_message->body = $message_body_data['plain']['body']->stream;
$eas_message->bodysize = $message_body_data['plain']['body']->length(true);
$eas_message->bodytruncated = $message_body_data['plain']['truncated'];
$eas_message->attachments = $imap_message->getAttachments($version);
} else {
// Get the message body and determine original type.
if (!empty($message_body_data['html'])) {
$eas_message->airsyncbasenativebodytype = Horde_ActiveSync::BODYPREF_TYPE_HTML;
} else {
$eas_message->airsyncbasenativebodytype = Horde_ActiveSync::BODYPREF_TYPE_PLAIN;
}
$airsync_body = Horde_ActiveSync::messageFactory('AirSyncBaseBody');
if (isset($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_MIME]) && ($options['mimesupport'] == Horde_ActiveSync::MIME_SUPPORT_ALL || $options['mimesupport'] == Horde_ActiveSync::MIME_SUPPORT_SMIME && $imap_message->isSigned())) {
$this->_logger->info(sprintf('[%s] Sending MIME Message.', $this->_procid));
// ActiveSync *REQUIRES* all data sent to be in UTF-8, so we
// must convert the body parts to UTF-8. Unfortunately if the
// email is signed (or encrypted for that matter) we can't
// alter the data in anyway or the signature will not be
// verified, so we fetch the entire message and hope for the best.
if (!$imap_message->isSigned()) {
// Sending a non-signed MIME message, start building the
// UTF-8 converted structure.
$mime = new Horde_Mime_Part();
$mime->setType('multipart/alternative');
// Populate the text/plain part if we have one.
if (!empty($message_body_data['plain'])) {
$plain_mime = new Horde_Mime_Part();
$plain_mime->setType('text/plain');
$plain_mime->setContents($message_body_data['plain']['body']->stream, array('usestream' => true));
$plain_mime->setCharset('UTF-8');
$mime->addPart($plain_mime);
}
// Populate the text/html part if we have one.
if (!empty($message_body_data['html'])) {
$html_mime = new Horde_Mime_Part();
$html_mime->setType('text/html');
$html_mime->setContents($message_body_data['html']['body']->stream, array('usestream' => true));
$html_mime->setCharset('UTF-8');
$mime->addPart($html_mime);
}
// If we have attachments, create a multipart/mixed wrapper.
if ($imap_message->hasAttachments()) {
$base = new Horde_Mime_Part();
$base->setType('multipart/mixed');
$base->addPart($mime);
$atc = $imap_message->getAttachmentsMimeParts();
foreach ($atc as $atc_part) {
$base->addPart($atc_part);
}
$eas_message->airsyncbaseattachments = $imap_message->getAttachments($version);
} else {
$base = $mime;
}
// Populate the EAS body structure with the MIME data.
$airsync_body->data = $base->toString(array('headers' => $imap_message->getHeaders(), 'stream' => true));
$airsync_body->estimateddatasize = $base->getBytes();
} else {
// Signed/Encrypted message - can't mess with it at all.
$raw = new Horde_ActiveSync_Rfc822($imap_message->getFullMsg(true), false);
$airsync_body->estimateddatasize = $raw->getBytes();
$airsync_body->data = $raw->getString();
$eas_message->messageclass = 'IPM.Note.SMIME.MultipartSigned';
// Might not know if we have attachments, but take a best
// guess.
$eas_message->airsyncbaseattachments = $imap_message->getAttachments($version);
}
// MIME Truncation
$airsync_body->type = Horde_ActiveSync::BODYPREF_TYPE_MIME;
$this->_logger->info(sprintf('[%s] Checking MIMETRUNCATION: %s, ServerData: %s', $this->_procid, $options['truncation'], $airsync_body->estimateddatasize));
if (!empty($options['truncation']) && $airsync_body->estimateddatasize > $options['truncation']) {
ftruncate($airsync_body->data, $options['truncation']);
$airsync_body->truncated = '1';
} else {
$airsync_body->truncated = '0';
}
$eas_message->airsyncbasebody = $airsync_body;
} elseif (isset($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_HTML]) || isset($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_RTF])) {
// Sending non MIME encoded HTML message text.
$this->_logger->info(sprintf('[%s] Sending HTML Message.', $this->_procid));
if (empty($message_body_data['html'])) {
$airsync_body->type = Horde_ActiveSync::BODYPREF_TYPE_PLAIN;
$message_body_data['html'] = array('body' => $message_body_data['plain']['body'], 'estimated_size' => $message_body_data['plain']['size'], 'truncated' => $message_body_data['plain']['truncated']);
} else {
$airsync_body->type = Horde_ActiveSync::BODYPREF_TYPE_HTML;
}
if (!empty($message_body_data['html']['estimated_size'])) {
$airsync_body->estimateddatasize = $message_body_data['html']['estimated_size'];
$airsync_body->truncated = $message_body_data['html']['truncated'];
$airsync_body->data = $message_body_data['html']['body']->stream;
$eas_message->airsyncbasebody = $airsync_body;
}
$eas_message->airsyncbaseattachments = $imap_message->getAttachments($version);
} elseif (isset($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_PLAIN])) {
// Non MIME encoded plaintext
$this->_logger->info(sprintf('[%s] Sending PLAINTEXT Message.', $this->_procid));
if (!empty($message_body_data['plain']['size'])) {
$airsync_body->estimateddatasize = $message_body_data['plain']['size'];
$airsync_body->truncated = $message_body_data['plain']['truncated'];
$airsync_body->data = $message_body_data['plain']['body']->stream;
$airsync_body->type = Horde_ActiveSync::BODYPREF_TYPE_PLAIN;
$eas_message->airsyncbasebody = $airsync_body;
}
$eas_message->airsyncbaseattachments = $imap_message->getAttachments($version);
}
if ($version > Horde_ActiveSync::VERSION_TWELVEONE) {
$flags = array();
$msgFlags = $this->_getMsgFlags();
foreach ($imap_message->getFlags() as $flag) {
if (($key = array_search(strtolower($flag), array_map('strtolower', $msgFlags))) !== false) {
$flags[] = $msgFlags[$key];
}
}
$eas_message->categories = $flags;
}
}
// Preview?
if ($version >= Horde_ActiveSync::VERSION_FOURTEEN && !empty($options['bodyprefs']['preview'])) {
$message_body_data['plain']['body']->rewind();
$eas_message->airsyncbasebody->preview = $message_body_data['plain']['body']->substring(0, $options['bodyprefs']['preview']);
}
// Check for special message types.
$part = $imap_message->getStructure();
if ($part->getType() == 'multipart/report') {
$ids = array_keys($imap_message->contentTypeMap());
reset($ids);
$part1_id = next($ids);
$part2_id = Horde_Mime::mimeIdArithmetic($part1_id, 'next');
$lines = explode(chr(13), $imap_message->getBodyPart($part2_id, array('decode' => true)));
switch ($part->getContentTypeParameter('report-type')) {
case 'delivery-status':
foreach ($lines as $line) {
if (strpos(trim($line), 'Action:') === 0) {
switch (trim(substr(trim($line), 7))) {
case 'failed':
$eas_message->messageclass = 'REPORT.IPM.NOTE.NDR';
break 2;
case 'delayed':
$eas_message->messageclass = 'REPORT.IPM.NOTE.DELAYED';
break 2;
case 'delivered':
$eas_message->messageclass = 'REPORT.IPM.NOTE.DR';
break 2;
}
}
}
break;
case 'disposition-notification':
foreach ($lines as $line) {
if (strpos(trim($line), 'Disposition:') === 0) {
if (strpos($line, 'displayed') !== false) {
$eas_message->messageclass = 'REPORT.IPM.NOTE.IPNRN';
} elseif (strpos($line, 'deleted') !== false) {
$eas_message->messageclass = 'REPORT.IPM.NOTE.IPNNRN';
}
break;
}
}
}
}
// Check for meeting requests and POOMMAIL_FLAG data
if ($version >= Horde_ActiveSync::VERSION_TWELVE) {
$eas_message->contentclass = 'urn:content-classes:message';
if ($mime_part = $imap_message->hasiCalendar()) {
$data = Horde_ActiveSync_Utils::ensureUtf8($mime_part->getContents(), $mime_part->getCharset());
$vCal = new Horde_Icalendar();
if ($vCal->parsevCalendar($data, 'VCALENDAR', $mime_part->getCharset())) {
try {
$method = $vCal->getAttribute('METHOD');
$eas_message->contentclass = 'urn:content-classes:calendarmessage';
} catch (Horde_Icalendar_Exception $e) {
}
switch ($method) {
case 'REQUEST':
case 'PUBLISH':
$eas_message->messageclass = 'IPM.Schedule.Meeting.Request';
$mtg = Horde_ActiveSync::messageFactory('MeetingRequest');
$mtg->fromvEvent($vCal);
$eas_message->meetingrequest = $mtg;
break;
case 'REPLY':
try {
$reply_status = $this->_getiTipStatus($vCal, $eas_message->from);
switch ($reply_status) {
case 'ACCEPTED':
$eas_message->messageclass = 'IPM.Schedule.Meeting.Resp.Pos';
break;
case 'DECLINED':
$eas_message->messageclass = 'IPM.Schedule.Meeting.Resp.Neg';
break;
case 'TENTATIVE':
$eas_message->messageclass = 'IPM.Schedule.Meeting.Resp.Tent';
}
$mtg = Horde_ActiveSync::messageFactory('MeetingRequest');
$mtg->fromvEvent($vCal);
$eas_message->meetingrequest = $mtg;
} catch (Horde_ActiveSync_Exception $e) {
$this->_logger->err($e->getMessage());
}
}
}
}
if ($imap_message->getFlag(Horde_Imap_Client::FLAG_FLAGGED)) {
$poommail_flag = Horde_ActiveSync::messageFactory('Flag');
$poommail_flag->subject = $imap_message->getSubject();
$poommail_flag->flagstatus = Horde_ActiveSync_Message_Flag::FLAG_STATUS_ACTIVE;
$poommail_flag->flagtype = Horde_Imap_Client::FLAG_FLAGGED;
$eas_message->flag = $poommail_flag;
}
}
if ($version >= Horde_ActiveSync::VERSION_FOURTEEN) {
$eas_message->messageid = $imap_message->getHeaders()->getValue('Message-ID');
$eas_message->forwarded = $imap_message->getFlag(Horde_Imap_Client::FLAG_FORWARDED);
$eas_message->answered = $imap_message->getFlag(Horde_Imap_Client::FLAG_ANSWERED);
}
return $eas_message;
}