_wasteID = false; $this->_sentID = false; $this->_server = "{" . IMAP_SERVER . ":" . IMAP_PORT . "/imap" . IMAP_OPTIONS . "}"; if (!function_exists("imap_open")) debugLog("ERROR BackendIMAP : PHP-IMAP module not installed!!!!!"); // open the IMAP-mailbox $this->_mbox = @imap_open($this->_server , $username, $password, OP_HALFOPEN); $this->_mboxFolder = ""; if ($this->_mbox) { debugLog("IMAP connection opened sucessfully "); $this->_username = $username; $this->_domain = $domain; // set serverdelimiter $this->_serverdelimiter = $this->getServerDelimiter(); return true; } else { debugLog("IMAP can't connect: " . imap_last_error()); return false; } } /* Called before shutting down the request to close the IMAP connection */ function Logoff() { if ($this->_mbox) { // list all errors $errors = imap_errors(); if (is_array($errors)) { foreach ($errors as $e) debugLog("IMAP-errors: $e"); } @imap_close($this->_mbox); debugLog("IMAP connection closed"); } } /* Called directly after the logon. This specifies the client's protocol version * and device id. The device ID can be used for various things, including saving * per-device state information. * The $user parameter here is normally equal to the $username parameter from the * Logon() call. In theory though, you could log on a 'foo', and then sync the emails * of user 'bar'. The $user here is the username specified in the request URL, while the * $username in the Logon() call is the username which was sent as a part of the HTTP * authentication. */ function Setup($user, $devid, $protocolversion) { $this->_user = $user; $this->_devid = $devid; $this->_protocolversion = $protocolversion; return true; } /* Sends a message which is passed as rfc822. You basically can do two things * 1) Send the message to an SMTP server as-is * 2) Parse the message yourself, and send it some other way * It is up to you whether you want to put the message in the sent items folder. If you * want it in 'sent items', then the next sync on the 'sent items' folder should return * the new message as any other new message in a folder. */ function SendMail($rfc822, $forward = false, $reply = false, $parent = false) { debugLog("IMAP-SendMail: for: $forward reply: $reply parent: $parent RFC822: \n". $rfc822 ); $mobj = new Mail_mimeDecode($rfc822); $message = $mobj->decode(array('decode_headers' => false, 'decode_bodies' => true, 'include_bodies' => true, 'charset' => 'utf-8')); $Mail_RFC822 = new Mail_RFC822(); $toaddr = $ccaddr = $bccaddr = ""; if(isset($message->headers["to"])) $toaddr = $this->parseAddr($Mail_RFC822->parseAddressList($message->headers["to"])); if(isset($message->headers["cc"])) $ccaddr = $this->parseAddr($Mail_RFC822->parseAddressList($message->headers["cc"])); if(isset($message->headers["bcc"])) $bccaddr = $this->parseAddr($Mail_RFC822->parseAddressList($message->headers["bcc"])); // save some headers when forwarding mails (content type & transfer-encoding) $headers = ""; $forward_h_ct = ""; $forward_h_cte = ""; $envelopefrom = ""; $use_orgbody = false; // clean up the transmitted headers // remove default headers because we are using imap_mail $changedfrom = false; $returnPathSet = false; $body_base64 = false; $org_charset = ""; $from_value = ""; $return_path_value = ""; $org_boundary = false; $multipartmixed = false; foreach($message->headers as $k => $v) { if ($k == "subject" || $k == "to" || $k == "cc" || $k == "bcc") continue; if ($k == "content-type") { // if the message is a multipart message, then we should use the sent body if (preg_match("/multipart/i", $v)) { $use_orgbody = true; $org_boundary = $message->ctype_parameters["boundary"]; } // save the original content-type header for the body part when forwarding if ($forward && !$use_orgbody) { $forward_h_ct = $v; continue; } // set charset always to utf-8 $org_charset = $v; $v = preg_replace("/charset=([A-Za-z0-9-\"']+)/", "charset=\"utf-8\"", $v); } if ($k == "content-transfer-encoding") { // if the content was base64 encoded, encode the body again when sending if (trim($v) == "base64") $body_base64 = true; // save the original encoding header for the body part when forwarding if ($forward) { $forward_h_cte = $v; continue; } } if ($k == "from") { $from_value = $v; $envelopefrom = "-f$v"; } if ($k == "return-path") $return_path_value = $v; // all other headers stay if($k != "from" && $k != "return-path") { if ($headers) $headers .= "\n"; $headers .= ucfirst($k) . ": ". $v; } } // override set "From" and "return-path" header if(defined("IMAP_DEFAULTFROM")){ if (IMAP_DEFAULTFROM == 'username') $v = $this->_username; else if (IMAP_DEFAULTFROM == 'domain') $v = $this->_domain; else $v = $this->_username . IMAP_DEFAULTFROM; if(trim($v) != "") { $from_value = $return_path_value = $v; $envelopefrom = "-f$v"; } } if ($headers) $headers .= "\n"; $headers .= "From: ".$from_value."\nReturn-Path: ".$return_path_value; // if this is a multipart message with a boundary, we must use the original body if ($use_orgbody) { list(,$body) = $mobj->_splitBodyHeader($rfc822); $repl_body = $this->getBody($message); if ($message->parts[0]->headers["content-transfer-encoding"] == "base64") $multipart_text_cte_base64 = true; else $multipart_text_cte_base64 = false; } else $body = $this->getBody($message); // reply if ($reply && $parent) { $this->imap_reopenFolder($parent); // receive entire mail (header + body) to decode body correctly $origmail = @imap_fetchheader($this->_mbox, $reply, FT_UID) . @imap_body($this->_mbox, $reply, FT_PEEK | FT_UID); $mobj2 = new Mail_mimeDecode($origmail); // receive only body $body .= $this->getBody($mobj2->decode(array('decode_headers' => false, 'decode_bodies' => true, 'include_bodies' => true, 'charset' => 'utf-8'))); // unset mimedecoder & origmail - free memory unset($mobj2); unset($origmail); } // encode the body to base64 if it was sent originally in base64 by the pda // contrib - chunk base64 encoded body if ($body_base64 && !$forward) $body = chunk_split(base64_encode($body)); // forward if ($forward && $parent) { $this->imap_reopenFolder($parent); // receive entire mail (header + body) $origmail = @imap_fetchheader($this->_mbox, $forward, FT_UID) . @imap_body($this->_mbox, $forward, FT_PEEK | FT_UID); if (defined('IMAP_INLINE_FORWARD') && IMAP_INLINE_FORWARD === false) { // contrib - chunk base64 encoded body if ($body_base64) $body = chunk_split(base64_encode($body)); //use original boundary if it's set $boundary = ($org_boundary) ? $org_boundary : false; // build a new mime message, forward entire old mail as file list($aheader, $body) = $this->mail_attach("forwarded_message.eml",strlen($origmail),$origmail, $body, $forward_h_ct, $forward_h_cte,$boundary); // add boundary headers $headers .= "\n" . $aheader; } else { $mobj2 = new Mail_mimeDecode($origmail); $mess2 = $mobj2->decode(array('decode_headers' => true, 'decode_bodies' => true, 'include_bodies' => true, 'charset' => 'utf-8')); if (!$use_orgbody) $nbody = $body; else $nbody = $repl_body; $nbody .= "\r\n\r\n"; $nbody .= "-----Original Message-----\r\n"; if(isset($mess2->headers['from'])) $nbody .= "From: " . $mess2->headers['from'] . "\r\n"; if(isset($mess2->headers['to']) && strlen($mess2->headers['to']) > 0) $nbody .= "To: " . $mess2->headers['to'] . "\r\n"; if(isset($mess2->headers['cc']) && strlen($mess2->headers['cc']) > 0) $nbody .= "Cc: " . $mess2->headers['cc'] . "\r\n"; if(isset($mess2->headers['date'])) $nbody .= "Sent: " . $mess2->headers['date'] . "\r\n"; if(isset($mess2->headers['subject'])) $nbody .= "Subject: " . $mess2->headers['subject'] . "\r\n"; $nbody .= "\r\n"; $nbody .= $this->getBody($mess2); if ($body_base64) { // contrib - chunk base64 encoded body $nbody = chunk_split(base64_encode($nbody)); if ($use_orgbody) // contrib - chunk base64 encoded body $repl_body = chunk_split(base64_encode($repl_body)); } if ($use_orgbody) { debugLog("-------------------"); debugLog("old:\n'$repl_body'\nnew:\n'$nbody'\nund der body:\n'$body'"); //$body is quoted-printable encoded while $repl_body and $nbody are plain text, //so we need to decode $body in order replace to take place if (!$multipart_text_cte_base64) { $body = str_replace($repl_body, $nbody, quoted_printable_decode($body)); } else { $body = str_replace(base64_encode($repl_body), base64_encode($nbody), $body); } } else $body = $nbody; if(isset($mess2->parts)) { $attached = false; if ($org_boundary) { $att_boundary = $org_boundary; // cut end boundary from body $body = substr($body, 0, strrpos($body, "--$att_boundary--")); } else { $att_boundary = strtoupper(md5(uniqid(time()))); // add boundary headers $headers .= "\n" . "Content-Type: multipart/mixed; boundary=$att_boundary"; $multipartmixed = true; } foreach($mess2->parts as $part) { if(isset($part->disposition) && ($part->disposition == "attachment" || $part->disposition == "inline")) { if(isset($part->d_parameters['filename'])) $attname = $part->d_parameters['filename']; else if(isset($part->ctype_parameters['name'])) $attname = $part->ctype_parameters['name']; else if(isset($part->headers['content-description'])) $attname = $part->headers['content-description']; else $attname = "unknown attachment"; // ignore html content if ($part->ctype_primary == "text" && $part->ctype_secondary == "html") { continue; } // if ($use_orgbody || $attached) { $body .= $this->enc_attach_file($att_boundary, $attname, strlen($part->body),$part->body, $part->ctype_primary ."/". $part->ctype_secondary); } // first attachment else { $encmail = $body; $attached = true; $body = $this->enc_multipart($att_boundary, $body, $forward_h_ct, $forward_h_cte); $body .= $this->enc_attach_file($att_boundary, $attname, strlen($part->body),$part->body, $part->ctype_primary ."/". $part->ctype_secondary); } } } if ($multipartmixed) { //this happens if a multipart/alternative message is forwarded //then it's a multipart/mixed message which consists of: //1. text/plain part which was written on the mobile //2. multipart/alternative part which is the original message //$body = "This is a message with multiple parts in MIME format.\n--". // $att_boundary. // "\nContent-Type: $forward_h_ct\nContent-Transfer-Encoding: $forward_h_cte\n\n". // (($body_base64) ? chunk_split(base64_encode($message->body)) : rtrim($message->body)). // "\n--".$att_boundary. // "\nContent-Type: {$mess2->headers['content-type']}\n\n". // @imap_body($this->_mbox, $forward, FT_PEEK | FT_UID)."\n\n"; $body = "\n--". $att_boundary. "\nContent-Type: $forward_h_ct\nContent-Transfer-Encoding: $forward_h_cte\n\n". $body; } $body .= "--$att_boundary--\n\n"; } unset($mobj2); } // unset origmail - free memory unset($origmail); } // remove carriage-returns from body $body = str_replace("\r\n", "\n", $body); if (!$multipartmixed) { if (!empty($forward_h_ct)) $headers .= "\nContent-Type: $forward_h_ct"; if (!empty($forward_h_cte)) $headers .= "\nContent-Transfer-Encoding: $forward_h_cte"; } //advanced debugging debugLog("IMAP-SendMail: parsed message: ". print_r($message,1)); debugLog("IMAP-SendMail: headers: $headers"); debugLog("IMAP-SendMail: subject: {$message->headers["subject"]}"); debugLog("IMAP-SendMail: body: $body"); if (!defined('IMAP_USE_IMAPMAIL') || IMAP_USE_IMAPMAIL == true) { $send = @imap_mail ( $toaddr, $message->headers["subject"], $body, $headers, $ccaddr, $bccaddr); } else { if (!empty($ccaddr)) $headers .= "\nCc: $ccaddr"; if (!empty($bccaddr)) $headers .= "\nBcc: $bccaddr"; $send = @mail ( $toaddr, $message->headers["subject"], $body, $headers, $envelopefrom ); } // email sent? if (!$send) { debugLog("The email could not be sent. Last-IMAP-error: ". imap_last_error()); } // add message to the sent folder // build complete headers $headers .= "\nTo: $toaddr"; $headers .= "\nSubject: " . $message->headers["subject"]; if (!defined('IMAP_USE_IMAPMAIL') || IMAP_USE_IMAPMAIL == true) { if (!empty($ccaddr)) $headers .= "\nCc: $ccaddr"; if (!empty($bccaddr)) $headers .= "\nBcc: $bccaddr"; } debugLog("IMAP-SendMail: complete headers: $headers"); $asf = false; if ($this->_sentID) { $asf = $this->addSentMessage($this->_sentID, $headers, $body); } else if(defined("IMAP_SENTFOLDER")) { $asf = $this->addSentMessage(IMAP_SENTFOLDER, $headers, $body); debugLog("IMAP-SendMail: Outgoing mail saved in configured 'Sent' folder '".IMAP_SENTFOLDER."': ". (($asf)?"success":"failed")); } // No Sent folder set, try defaults else { debugLog("IMAP-SendMail: No Sent mailbox set"); if($this->addSentMessage("INBOX.Sent", $headers, $body)) { debugLog("IMAP-SendMail: Outgoing mail saved in 'INBOX.Sent'"); $asf = true; } else if ($this->addSentMessage("Sent", $headers, $body)) { debugLog("IMAP-SendMail: Outgoing mail saved in 'Sent'"); $asf = true; } else if ($this->addSentMessage("Sent Items", $headers, $body)) { debugLog("IMAP-SendMail: Outgoing mail saved in 'Sent Items'"); $asf = true; } } // unset mimedecoder - free memory unset($mobj); return ($send && $asf); } /* Should return a wastebasket folder if there is one. This is used when deleting * items; if this function returns a valid folder ID, then all deletes are handled * as moves and are sent to your backend as a move. If it returns FALSE, then deletes * are always handled as real deletes and will be sent to your importer as a DELETE */ function GetWasteBasket() { return $this->_wasteID; } /* Should return a list (array) of messages, each entry being an associative array * with the same entries as StatMessage(). This function should return stable information; ie * if nothing has changed, the items in the array must be exactly the same. The order of * the items within the array is not important though. * * The cutoffdate is a date in the past, representing the date since which items should be shown. * This cutoffdate is determined by the user's setting of getting 'Last 3 days' of e-mail, etc. If * you ignore the cutoffdate, the user will not be able to select their own cutoffdate, but all * will work OK apart from that. */ function GetMessageList($folderid, $cutoffdate) { debugLog("IMAP-GetMessageList: (fid: '$folderid' cutdate: '$cutoffdate' )"); $messages = array(); $this->imap_reopenFolder($folderid, true); $sequence = "1:*"; if ($cutoffdate > 0) { $search = @imap_search($this->_mbox, "SINCE ". date("d-M-Y", $cutoffdate)); if ($search !== false) $sequence = implode(",", $search); if (TRACE_UID !== false and (TRACE_TYPE == 'IMAP' or TRACE_TYPE == 'ALL')) traceLog('IMAP::GetMessageList-> A função @imap_search leu as seguintes SEQUÊNCIAS do servidor IMAP com base no filtro de data: '.$sequence); } $overviews = @imap_fetch_overview($this->_mbox, $sequence); if (!$overviews) { debugLog("IMAP-GetMessageList: Failed to retrieve overview"); } else { foreach($overviews as $overview) { $date = ""; $vars = get_object_vars($overview); if (TRACE_UID !== false and (TRACE_TYPE == 'IMAP' or TRACE_TYPE == 'ALL')) traceLog('IMAP::GetMessageList-> A função @imap_fetch_overview leu mais detalhes da mensagem: '. print_r($overview,1)); if (array_key_exists( "date", $vars)) { // message is out of range for cutoffdate, ignore it if(strtotime(preg_replace("/\(.*\)/", "", $overview->date)) < $cutoffdate) { // emerson-faria.nobre@serpro.gov.br - 07/feb/2011 if (TRACE_UID !== false and (TRACE_TYPE == 'IMAP' or TRACE_TYPE == 'ALL')) traceLog('IMAP::GetMessageList-> O overview->date: '.$overview->date.' (overview->date_timestamp: '.strtotime(preg_replace("/\(.*\)/", "", $overview->date)).') é menor que o $cutoffdate: '.date("d/m/Y G:i:s",$cutoffdate).' ($cutoffdate_timestamp: '.$cutoffdate.') ou a função "strtotime" gerou um ERRO porque não conseguiu retornar um valor para o overview->date_timestamp. A mensagem será descartada da sincronização.'); continue; } //if(strtotime($overview->date) < $cutoffdate) continue; $date = $overview->date; } else if (TRACE_UID !== false and (TRACE_TYPE == 'IMAP' or TRACE_TYPE == 'ALL')) traceLog('IMAP::GetMessageList-> ERRO: O campo date não existe no overview da mensagem.'); // cut of deleted messages if (array_key_exists( "deleted", $vars) && $overview->deleted) { if (TRACE_UID !== false and (TRACE_TYPE == 'IMAP' or TRACE_TYPE == 'ALL')) traceLog('IMAP::GetMessageList-> A mensagem está com o flag deleted ativo e será descartada da sincronização'); continue; } if (array_key_exists( "uid", $vars)) { $message = array(); $message["mod"] = $date; $message["id"] = $overview->uid; // 'seen' aka 'read' is the only flag we want to know about $message["flags"] = 0; if(array_key_exists( "seen", $vars) && $overview->seen) $message["flags"] = 1; array_push($messages, $message); } else if (TRACE_UID !== false and (TRACE_TYPE == 'IMAP' or TRACE_TYPE == 'ALL')) traceLog('IMAP::GetMessageList-> ERRO: O campo uid não existe no overview da mensagem.'); } } return $messages; } /* This function is analogous to GetMessageList. * */ function GetFolderList() { $folders = array(); $list = @imap_getmailboxes($this->_mbox, $this->_server, "*"); if (is_array($list)) { // reverse list to obtain folders in right order $list = array_reverse($list); foreach ($list as $val) { if (TRACE_UID !== false and (TRACE_TYPE == 'IMAP' or TRACE_TYPE == 'ALL')) traceLog('IMAP::GetFolderList-> Pasta lida usando a função @imap_getmailboxes: '.print_r($val,1)); $box = array(); // cut off serverstring $box["id"] = imap_utf7_decode(substr($val->name, strlen($this->_server))); // always use "." as folder delimiter $box["id"] = imap_utf7_encode(str_replace($val->delimiter, ".", $box["id"])); // explode hierarchies $fhir = explode(".", $box["id"]); if (count($fhir) > 1) { $box["mod"] = imap_utf7_encode(array_pop($fhir)); // mod is last part of path $box["parent"] = imap_utf7_encode(implode(".", $fhir)); // parent is all previous parts of path } else { $box["mod"] = imap_utf7_encode($box["id"]); $box["parent"] = "0"; } if (TRACE_UID !== false and (TRACE_TYPE == 'IMAP' or TRACE_TYPE == 'ALL')) traceLog('IMAP::GetFolderList-> Parâmetros da pasta após decodificação: '.print_r($box,1)); $folders[]=$box; } } else { debugLog("GetFolderList: imap_list failed: " . imap_last_error()); } return $folders; } /* GetFolder should return an actual SyncFolder object with all the properties set. Folders * are pretty simple really, having only a type, a name, a parent and a server ID. */ function GetFolder($id) { $folder = new SyncFolder(); $folder->serverid = $id; // explode hierarchy $fhir = explode(".", $id); // compare on lowercase strings $lid = strtolower($id); $trash_folder_name = 'inbox.trash'; if(defined("IMAP_TRASHFOLDER")) $trash_folder_name = strtolower(str_replace($this->_serverdelimiter, ".", IMAP_TRASHFOLDER)); $sent_folder_name = 'inbox.sent'; if(defined("IMAP_SENTFOLDER")) $sent_folder_name = strtolower(str_replace($this->_serverdelimiter, ".", IMAP_SENTFOLDER)); $folder->parentid = (($fhir && count($fhir) > 1) ? $fhir[(count($fhir) - 2)] : "0"); if($lid == "inbox") { $folder->parentid = "0"; // Root $folder->displayname = "Inbox"; $folder->type = SYNC_FOLDER_TYPE_INBOX; } // Zarafa IMAP-Gateway outputs else if($lid == "drafts" || $lid == "inbox.drafts" || $lid == "inbox.rascunhos") { $folder->displayname = "Drafts"; $folder->type = SYNC_FOLDER_TYPE_DRAFTS; } else if($lid == "trash" || $lid == $trash_folder_name) { $folder->displayname = (defined("IMAP_DISPLAYNAME_TRASHFOLDER") ? IMAP_DISPLAYNAME_TRASHFOLDER : "Trash"); $folder->type = SYNC_FOLDER_TYPE_WASTEBASKET; $this->_wasteID = $id; } else if($lid == "sent" || $lid == "sent items" || $lid == $sent_folder_name) { $folder->displayname = (defined("IMAP_DISPLAYNAME_SENTFOLDER") ? IMAP_DISPLAYNAME_SENTFOLDER : "Sent"); $folder->type = SYNC_FOLDER_TYPE_SENTMAIL; $this->_sentID = $id; } // define the rest as other-folders else { if (count($fhir) > 1) { $folder->displayname = windows1252_to_utf8(imap_utf7_decode(array_pop($fhir))); $folder->parentid = implode(".", $fhir); } else { $folder->displayname = windows1252_to_utf8(imap_utf7_decode($id)); $folder->parentid = "0"; } $folder->type = SYNC_FOLDER_TYPE_OTHER; } //advanced debugging //debugLog("IMAP-GetFolder(id: '$id') -> " . print_r($folder, 1)); if (TRACE_UID !== false and (TRACE_TYPE == 'IMAP' or TRACE_TYPE == 'ALL')) traceLog('IMAP::GetFolder(id: '.$id.'): '.print_r($folder,1)); return $folder; } /* Return folder stats. This means you must return an associative array with the * following properties: * "id" => The server ID that will be used to identify the folder. It must be unique, and not too long * How long exactly is not known, but try keeping it under 20 chars or so. It must be a string. * "parent" => The server ID of the parent of the folder. Same restrictions as 'id' apply. * "mod" => This is the modification signature. It is any arbitrary string which is constant as long as * the folder has not changed. In practice this means that 'mod' can be equal to the folder name * as this is the only thing that ever changes in folders. (the type is normally constant) */ function StatFolder($id) { $folder = $this->GetFolder($id); $stat = array(); $stat["id"] = $id; $stat["parent"] = $folder->parentid; $stat["mod"] = $folder->displayname; if (TRACE_UID !== false and (TRACE_TYPE == 'IMAP' or TRACE_TYPE == 'ALL')) traceLog('IMAP::StatFolder(id: '.$id.'): '.print_r($stat,1)); return $stat; } /* Creates or modifies a folder * "folderid" => id of the parent folder * "oldid" => if empty -> new folder created, else folder is to be renamed * "displayname" => new folder name (to be created, or to be renamed to) * "type" => folder type, ignored in IMAP * */ function ChangeFolder($folderid, $oldid, $displayname, $type){ debugLog("ChangeFolder: (parent: '$folderid' oldid: '$oldid' displayname: '$displayname' type: '$type')"); // go to parent mailbox $this->imap_reopenFolder($folderid); // build name for new mailbox $newname = $this->_server . str_replace(".", $this->_serverdelimiter, $folderid) . $this->_serverdelimiter . $displayname; $csts = false; // if $id is set => rename mailbox, otherwise create if ($oldid) { // rename doesn't work properly with IMAP // the activesync client doesn't support a 'changing ID' //$csts = imap_renamemailbox($this->_mbox, $this->_server . imap_utf7_encode(str_replace(".", $this->_serverdelimiter, $oldid)), $newname); } else { $csts = @imap_createmailbox($this->_mbox, $newname); } if ($csts) { return $this->StatFolder($folderid . "." . $displayname); } else return false; } /* Should return attachment data for the specified attachment. The passed attachment identifier is * the exact string that is returned in the 'AttName' property of an SyncAttachment. So, you should * encode any information you need to find the attachment in that 'attname' property. */ function GetAttachmentData($attname) { debugLog("getAttachmentDate: (attname: '$attname')"); list($folderid, $id, $part) = explode(":", $attname); $this->imap_reopenFolder($folderid); $mail = @imap_fetchheader($this->_mbox, $id, FT_PREFETCHTEXT | FT_UID) . @imap_body($this->_mbox, $id, FT_PEEK | FT_UID); $mobj = new Mail_mimeDecode($mail); $message = $mobj->decode(array('decode_headers' => true, 'decode_bodies' => true, 'include_bodies' => true, 'input' => $mail, 'crlf' => "\n", 'charset' => 'utf-8')); if (isset($message->parts[$part]->body)) print $message->parts[$part]->body; // unset mimedecoder & mail unset($mobj); unset($mail); return true; } /* StatMessage should return message stats, analogous to the folder stats (StatFolder). Entries are: * 'id' => Server unique identifier for the message. Again, try to keep this short (under 20 chars) * 'flags' => simply '0' for unread, '1' for read * 'mod' => modification signature. As soon as this signature changes, the item is assumed to be completely * changed, and will be sent to the PDA as a whole. Normally you can use something like the modification * time for this field, which will change as soon as the contents have changed. */ function StatMessage($folderid, $id) { debugLog("IMAP-StatMessage: (fid: '$folderid' id: '$id' )"); $this->imap_reopenFolder($folderid); $overview = @imap_fetch_overview( $this->_mbox , $id , FT_UID); if (!$overview) { debugLog("IMAP-StatMessage: Failed to retrieve overview: ". imap_last_error()); return false; } else { // check if variables for this overview object are available $vars = get_object_vars($overview[0]); if (TRACE_UID !== false and (TRACE_TYPE == 'IMAP' or TRACE_TYPE == 'ALL') and (! array_key_exists( "uid", $vars))) debugLog('IMAP::StatMessage-> ERRO: A mensagem será desconsiderada porque não tem o campo "uid"'); // without uid it's not a valid message if (! array_key_exists( "uid", $vars)) return false; $entry = array(); $entry["mod"] = (array_key_exists( "date", $vars)) ? $overview[0]->date : ""; $entry["id"] = $overview[0]->uid; // 'seen' aka 'read' is the only flag we want to know about $entry["flags"] = 0; if(array_key_exists( "seen", $vars) && $overview[0]->seen) $entry["flags"] = 1; if (TRACE_UID !== false and (TRACE_TYPE == 'IMAP' or TRACE_TYPE == 'ALL')) traceLog('IMAP::StatMessage: '.print_r($entry,1)); //advanced debugging //debugLog("IMAP-StatMessage-parsed: ". print_r($entry,1)); return $entry; } } /* GetMessage should return the actual SyncXXX object type. You may or may not use the '$folderid' parent folder * identifier here. * Note that mixing item types is illegal and will be blocked by the engine; ie returning an Email object in a * Tasks folder will not do anything. The SyncXXX objects should be filled with as much information as possible, * but at least the subject, body, to, from, etc. */ function GetMessage($folderid, $id, $truncsize, $mimesupport = 0) { debugLog("IMAP-GetMessage: (fid: '$folderid' id: '$id' truncsize: $truncsize)"); // Get flags, etc $stat = $this->StatMessage($folderid, $id); if ($stat) { $this->imap_reopenFolder($folderid); $mail = @imap_fetchheader($this->_mbox, $id, FT_PREFETCHTEXT | FT_UID) . @imap_body($this->_mbox, $id, FT_PEEK | FT_UID); if (TRACE_UID !== false and (TRACE_TYPE == 'IMAP' or TRACE_TYPE == 'ALL')) traceLog("IMAP-GetMessage: Mensagem lida do servidor IMAP através da função @imap_fetchheader: ". print_r($mail,1)); $mobj = new Mail_mimeDecode($mail); $message = $mobj->decode(array('decode_headers' => true, 'decode_bodies' => true, 'include_bodies' => true, 'input' => $mail, 'crlf' => "\n", 'charset' => 'utf-8')); if (TRACE_UID !== false and (TRACE_TYPE == 'IMAP' or TRACE_TYPE == 'ALL')) traceLog("IMAP-GetMessage: Mensagem mime decodificada: ". print_r($message,1)); $output = new SyncMail(); $body = $this->getBody($message, true); // true - Truncate body due to celular memory limit // truncate body, if requested if(strlen($body) > $truncsize) { $body = utf8_truncate($body, $truncsize); $output->bodytruncated = 1; } else { $body = $body; $output->bodytruncated = 0; } $body = str_replace("\n","\r\n", str_replace("\r","",$body)); $output->bodysize = strlen($body); $output->body = $body; //$output->datereceived = isset($message->headers["date"]) ? strtotime($message->headers["date"]) : null; $output->datereceived = isset($message->headers["date"]) ? strtotime(preg_replace("/\(.*\)/", "", $message->headers["date"])) : null; // emerson-faria.nobre@serpro.gov.br $output->displayto = isset($message->headers["to"]) ? $message->headers["to"] : null; $output->importance = isset($message->headers["x-priority"]) ? preg_replace("/\D+/", "", $message->headers["x-priority"]) : null; $output->messageclass = "IPM.Note"; $output->subject = isset($message->headers["subject"]) ? $message->headers["subject"] : ""; $output->read = $stat["flags"]; $output->to = isset($message->headers["to"]) ? $message->headers["to"] : null; $output->cc = isset($message->headers["cc"]) ? $message->headers["cc"] : null; $output->from = isset($message->headers["from"]) ? $message->headers["from"] : null; $output->reply_to = isset($message->headers["reply-to"]) ? $message->headers["reply-to"] : null; // Attachments are only searched in the top-level part $n = 0; if(isset($message->parts)) { foreach($message->parts as $part) { if(isset($part->disposition) && ($part->disposition == "attachment" || $part->disposition == "inline")) { $attachment = new SyncAttachment(); if (isset($part->body)) $attachment->attsize = strlen($part->body); if(isset($part->d_parameters['filename'])) $attname = $part->d_parameters['filename']; else if(isset($part->ctype_parameters['name'])) $attname = $part->ctype_parameters['name']; else if(isset($part->headers['content-description'])) $attname = $part->headers['content-description']; else $attname = "unknown attachment"; $attachment->displayname = $attname; $attachment->attname = $folderid . ":" . $id . ":" . $n; $attachment->attmethod = 1; $attachment->attoid = isset($part->headers['content-id']) ? $part->headers['content-id'] : ""; array_push($output->attachments, $attachment); } $n++; } } // unset mimedecoder & mail unset($mobj); unset($mail); if (TRACE_UID !== false and (TRACE_TYPE == 'IMAP' or TRACE_TYPE == 'ALL')) traceLog("IMAP-GetMessage: Mensagem formatada para envio ao celular: ". print_r($output,1)); return $output; } return false; } /* This function is called when the user has requested to delete (really delete) a message. Usually * this means just unlinking the file its in or somesuch. After this call has succeeded, a call to * GetMessageList() should no longer list the message. If it does, the message will be re-sent to the PDA * as it will be seen as a 'new' item. This means that if you don't implement this function, you will * be able to delete messages on the PDA, but as soon as you sync, you'll get the item back */ function DeleteMessage($folderid, $id) { debugLog("IMAP-DeleteMessage: (fid: '$folderid' id: '$id' )"); $this->imap_reopenFolder($folderid); $s1 = @imap_delete ($this->_mbox, $id, FT_UID); $s11 = @imap_setflag_full($this->_mbox, $id, "\\Deleted", FT_UID); $s2 = @imap_expunge($this->_mbox); debugLog("IMAP-DeleteMessage: s-delete: $s1 s-expunge: $s2 setflag: $s11"); return ($s1 && $s2 && $s11); } /* This should change the 'read' flag of a message on disk. The $flags * parameter can only be '1' (read) or '0' (unread). After a call to * SetReadFlag(), GetMessageList() should return the message with the * new 'flags' but should not modify the 'mod' parameter. If you do * change 'mod', simply setting the message to 'read' on the PDA will trigger * a full resync of the item from the server */ function SetReadFlag($folderid, $id, $flags) { debugLog("IMAP-SetReadFlag: (fid: '$folderid' id: '$id' flags: '$flags' )"); $this->imap_reopenFolder($folderid); if ($flags == 0) { // set as "Unseen" (unread) $status = @imap_clearflag_full ( $this->_mbox, $id, "\\Seen", ST_UID); } else { // set as "Seen" (read) $status = @imap_setflag_full($this->_mbox, $id, "\\Seen",ST_UID); } debugLog("IMAP-SetReadFlag -> set as " . (($flags) ? "read" : "unread") . "-->". $status); return $status; } /* This function is called when a message has been changed on the PDA. You should parse the new * message here and save the changes to disk. The return value must be whatever would be returned * from StatMessage() after the message has been saved. This means that both the 'flags' and the 'mod' * properties of the StatMessage() item may change via ChangeMessage(). * Note that this function will never be called on E-mail items as you can't change e-mail items, you * can only set them as 'read'. */ function ChangeMessage($folderid, $id, $message) { return false; } /* This function is called when the user moves an item on the PDA. You should do whatever is needed * to move the message on disk. After this call, StatMessage() and GetMessageList() should show the items * to have a new parent. This means that it will disappear from GetMessageList() will not return the item * at all on the source folder, and the destination folder will show the new message * */ function MoveMessage($folderid, $id, $newfolderid) { debugLog("IMAP-MoveMessage: (sfid: '$folderid' id: '$id' dfid: '$newfolderid' )"); $this->imap_reopenFolder($folderid); // read message flags $overview = @imap_fetch_overview ( $this->_mbox , $id, FT_UID); if (!$overview) { debugLog("IMAP-MoveMessage: Failed to retrieve overview"); return false; } else { // move message $s1 = imap_mail_move($this->_mbox, $id, str_replace(".", $this->_serverdelimiter, $newfolderid), FT_UID); // delete message in from-folder $s2 = imap_expunge($this->_mbox); // open new folder $this->imap_reopenFolder($newfolderid); // remove all flags $s3 = @imap_clearflag_full ($this->_mbox, $id, "\\Seen \\Answered \\Flagged \\Deleted \\Draft", FT_UID); $newflags = ""; if ($overview[0]->seen) $newflags .= "\\Seen"; if ($overview[0]->flagged) $newflags .= " \\Flagged"; if ($overview[0]->answered) $newflags .= " \\Answered"; $s4 = @imap_setflag_full ($this->_mbox, $id, $newflags, FT_UID); debugLog("MoveMessage: (" . $folderid . "->" . $newfolderid . ") s-move: $s1 s-expunge: $s2 unset-Flags: $s3 set-Flags: $s4"); return ($s1 && $s2 && $s3 && $s4); } } // new ping mechanism for the IMAP-Backend function AlterPing() { return true; } // returns a changes array using imap_status // if changes occurr default diff engine computes the actual changes function AlterPingChanges($folderid, &$syncstate) { debugLog("AlterPingChanges on $folderid stat: ". $syncstate); $this->imap_reopenFolder($folderid); // courier-imap only cleares the status cache after checking @imap_check($this->_mbox); $status = imap_status($this->_mbox, $this->_server . str_replace(".", $this->_serverdelimiter, $folderid), SA_ALL); if (!$status) { debugLog("AlterPingChanges: could not stat folder $folderid : ". imap_last_error()); return false; } else { $newstate = "M:". $status->messages ."-R:". $status->recent ."-U:". $status->unseen; // message number is different - change occured if ($syncstate != $newstate) { $syncstate = $newstate; debugLog("AlterPingChanges: Change FOUND!"); // build a dummy change return array(array("type" => "fakeChange")); } } return array(); } // ---------------------------------------- // imap-specific internals /* Parse the message and return only the plaintext body */ function getBody($message, $trunc = false) { $body = ""; $htmlbody = ""; $this->getBodyRecursive($message, "plain", $body); if($trunc == true and isset($body) and strlen($body) > 102400) { $body = substr($body, 0, 102400); $body .= "\n\n\n A MENSAGEM FOI TRUNCADA NO CELULAR(MENSAGEM MUITO GRANDE)."; } if(!isset($body) or $body === '') { $this->getBodyRecursive($message, "html", $body); if($trunc == true and isset($body) and strlen($body) > 209715) { $body = substr($body, 0, 209715); $body .= "


A MENSAGEM FOI TRUNCADA NO CELULAR(MENSAGEM MUITO GRANDE)."; } // remove css-style tags $body = preg_replace("//is", "a", $body); // remove all other html //$body = strip_tags($body); // Remove the HTML tags using the 'html2text' - emerson-faria.nobre@serpro.gov.br // The 'html2text' (http://www.mbayer.de/html2text) must be installed in Z-Push server. // Advanced debug // debugLog("IMAP-getBody: subject: " . $message->headers["subject"]); $body = utf8_encode($body); libxml_use_internal_errors(true); try { $doc = new DOMDocument('1.0', 'UTF-8'); @$doc->loadHTML($body); $tables = $doc->getElementsByTagName('table'); foreach ($tables as $table) { $tds = $table->getElementsByTagName('td'); foreach ($tds as $td) { foreach ($td->childNodes as $td_child) { if ($td_child->nodeName == 'br') $td->removeChild($td_child); } } } $links = $doc->getElementsByTagName('a'); foreach ($links as $link) { if ($link->hasAttributes()){ $novoNo = $doc->createTextNode(' (' . $link->getAttribute('href') . ')'); $link->parentNode->insertBefore($novoNo, $link->nextSibling); } } $body = $doc->saveHTML(); } catch (Exception $e) { debugLog("IMAP-getBody: Alert - DOMDocument cannot parse HTML."); } $filename = "./state/" . $this->_user . "-" . $this->_devid . ".html"; $fp = fopen($filename, 'w') or die("can't open file"); fwrite($fp, $body); $body_aux = shell_exec('html2text -nobs -style compact "' . $filename . '"'); if (trim($body_aux) != '') $body = $body_aux; unset($body_aux); fclose($fp); unlink($filename); $body = utf8_decode($body); $body = str_replace('_','',$body); // End change block - Remove the HTML tags using the 'html2text' Linux application } return $body; } // Get all parts in the message with specified type and concatenate them together, unless the // Content-Disposition is 'attachment', in which case the text is apparently an attachment function getBodyRecursive($message, $subtype, &$body) { if(!isset($message->ctype_primary)) return; if(strcasecmp($message->ctype_primary,"text")==0 && strcasecmp($message->ctype_secondary,$subtype)==0 && isset($message->body)) $body .= $message->body; if(strcasecmp($message->ctype_primary,"multipart")==0 && isset($message->parts) && is_array($message->parts)) { foreach($message->parts as $part) { if(!isset($part->disposition) || strcasecmp($part->disposition,"attachment")) { $this->getBodyRecursive($part, $subtype, $body); } } } } // save the serverdelimiter for later folder (un)parsing function getServerDelimiter() { $list = @imap_getmailboxes($this->_mbox, $this->_server, "*"); if (is_array($list)) { $val = $list[0]; return $val->delimiter; } return "."; // default "." } // speed things up // remember what folder is currently open and only change if necessary function imap_reopenFolder($folderid, $force = false) { // to see changes, the folder has to be reopened! if ($this->_mboxFolder != $folderid || $force) { $s = @imap_reopen($this->_mbox, $this->_server . str_replace(".", $this->_serverdelimiter, $folderid)); if (!$s) debugLog("failed to change folder: ". implode(", ", imap_errors())); $this->_mboxFolder = $folderid; } } // build a multipart email, embedding body and one file (for attachments) function mail_attach($filenm,$filesize,$file_cont,$body, $body_ct, $body_cte, $boundary = false) { if (!$boundary) $boundary = strtoupper(md5(uniqid(time()))); //remove the ending boundary because we will add it at the end $body = str_replace("--$boundary--", "", $body); $mail_header = "Content-Type: multipart/mixed; boundary=$boundary\n"; // build main body with the sumitted type & encoding from the pda $mail_body = $this->enc_multipart($boundary, $body, $body_ct, $body_cte); $mail_body .= $this->enc_attach_file($boundary, $filenm, $filesize, $file_cont); $mail_body .= "--$boundary--\n\n"; return array($mail_header, $mail_body); } function enc_multipart($boundary, $body, $body_ct, $body_cte) { // $mail_body = "This is a multi-part message in MIME format\n\n"; // $mail_body .= "--$boundary\n"; // $mail_body .= "Content-Type: $body_ct\n"; // $mail_body .= "Content-Transfer-Encoding: $body_cte\n\n"; $mail_body = "$body\n\n"; return $mail_body; } function enc_attach_file($boundary, $filenm, $filesize, $file_cont, $content_type = "") { if (!$content_type) $content_type = "text/plain"; $mail_body = "--$boundary\n"; $mail_body .= "Content-Type: $content_type; name=\"$filenm\"\n"; $mail_body .= "Content-Transfer-Encoding: base64\n"; $mail_body .= "Content-Disposition: attachment; filename=\"$filenm\"\n"; $mail_body .= "Content-Description: $filenm\n\n"; //contrib - chunk base64 encoded attachments $mail_body .= chunk_split(base64_encode($file_cont)) . "\n\n"; return $mail_body; } // adds a message as seen to a specified folder (used for saving sent mails) function addSentMessage($folderid, $header, $body) { $header_body = str_replace("\n", "\r\n", str_replace("\r", "", $header . "\n\n" . $body)); return @imap_append($this->_mbox, $this->_server . $folderid, $header_body, "\\Seen"); } // parses address objects back to a simple "," separated string function parseAddr($ad) { $addr_string = ""; if (isset($ad) && is_array($ad)) { foreach($ad as $addr) { if ($addr_string) $addr_string .= ","; $addr_string .= $addr->mailbox . "@" . $addr->host; } } return $addr_string; } }; ?>