Back to index

d-push  2.0
imap.php
Go to the documentation of this file.
00001 <?php
00002 /***********************************************
00003 * File      :   imap.php
00004 * Project   :   Z-Push
00005 * Descr     :   This backend is based on
00006 *               'BackendDiff' and implements an
00007 *               IMAP interface
00008 *
00009 * Created   :   10.10.2007
00010 *
00011 * Copyright 2007 - 2011 Zarafa Deutschland GmbH
00012 *
00013 * This program is free software: you can redistribute it and/or modify
00014 * it under the terms of the GNU Affero General Public License, version 3,
00015 * as published by the Free Software Foundation with the following additional
00016 * term according to sec. 7:
00017 *
00018 * According to sec. 7 of the GNU Affero General Public License, version 3,
00019 * the terms of the AGPL are supplemented with the following terms:
00020 *
00021 * "Zarafa" is a registered trademark of Zarafa B.V.
00022 * "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
00023 * The licensing of the Program under the AGPL does not imply a trademark license.
00024 * Therefore any rights, title and interest in our trademarks remain entirely with us.
00025 *
00026 * However, if you propagate an unmodified version of the Program you are
00027 * allowed to use the term "Z-Push" to indicate that you distribute the Program.
00028 * Furthermore you may use our trademarks where it is necessary to indicate
00029 * the intended purpose of a product or service provided you use it in accordance
00030 * with honest practices in industrial or commercial matters.
00031 * If you want to propagate modified versions of the Program under the name "Z-Push",
00032 * you may only do so if you have a written permission by Zarafa Deutschland GmbH
00033 * (to acquire a permission please contact Zarafa at trademark@zarafa.com).
00034 *
00035 * This program is distributed in the hope that it will be useful,
00036 * but WITHOUT ANY WARRANTY; without even the implied warranty of
00037 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00038 * GNU Affero General Public License for more details.
00039 *
00040 * You should have received a copy of the GNU Affero General Public License
00041 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
00042 *
00043 * Consult LICENSE file for details
00044 ************************************************/
00045 
00046 include_once('lib/default/diffbackend/diffbackend.php');
00047 include_once('include/mimeDecode.php');
00048 require_once('include/z_RFC822.php');
00049 
00050 
00051 class BackendIMAP extends BackendDiff {
00052     private $wasteID;
00053     private $sentID;
00054     private $server;
00055     private $mbox;
00056     private $mboxFolder;
00057     private $username;
00058     private $domain;
00059     private $serverdelimiter;
00060     private $sinkfolders;
00061     private $sinkstates;
00062 
00078     public function Logon($username, $domain, $password) {
00079         $this->wasteID = false;
00080         $this->sentID = false;
00081         $this->server = "{" . IMAP_SERVER . ":" . IMAP_PORT . "/imap" . IMAP_OPTIONS . "}";
00082 
00083         if (!function_exists("imap_open"))
00084             throw new FatalException("BackendIMAP(): php-imap module is not installed", 0, null, LOGLEVEL_FATAL);
00085 
00086         // open the IMAP-mailbox
00087         $this->mbox = @imap_open($this->server , $username, $password, OP_HALFOPEN);
00088         $this->mboxFolder = "";
00089 
00090         if ($this->mbox) {
00091             ZLog::Write(LOGLEVEL_INFO, sprintf("BackendIMAP->Logon(): User '%s' is authenticated on IMAP",$username));
00092             $this->username = $username;
00093             $this->domain = $domain;
00094             // set serverdelimiter
00095             $this->serverdelimiter = $this->getServerDelimiter();
00096             return true;
00097         }
00098         else {
00099             ZLog::Write(LOGLEVEL_ERROR, "BackendIMAP->Logon(): can't connect: " . imap_last_error());
00100             return false;
00101         }
00102     }
00103 
00112     public function Logoff() {
00113         if ($this->mbox) {
00114             // list all errors
00115             $errors = imap_errors();
00116             if (is_array($errors)) {
00117                 foreach ($errors as $e)
00118                     if (stripos($e, "fail") !== false)
00119                         $level = LOGLEVEL_WARN;
00120                     else
00121                         $level = LOGLEVEL_DEBUG;
00122 
00123                     ZLog::Write($level, "BackendIMAP->Logoff(): IMAP said: " . $e);
00124             }
00125             @imap_close($this->mbox);
00126             ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->Logoff(): IMAP connection closed");
00127         }
00128         $this->SaveStorages();
00129     }
00130 
00141     // TODO implement , $saveInSent = true
00142     public function SendMail($sm) {
00143         $forward = $reply = (isset($sm->source->itemid) && $sm->source->itemid) ? $sm->source->itemid : false;
00144         $parent = false;
00145 
00146         ZLog::Write(LOGLEVEL_DEBUG, sprintf("IMAPBackend->SendMail(): RFC822: %d bytes  forward-id: '%s' reply-id: '%s' parent-id: '%s' SaveInSent: '%s' ReplaceMIME: '%s'",
00147                                             strlen($sm->mime), Utils::PrintAsString($sm->forwardflag), Utils::PrintAsString($sm->replyflag),
00148                                             Utils::PrintAsString((isset($sm->source->folderid) ? $sm->source->folderid : false)),
00149                                             Utils::PrintAsString(($sm->saveinsent)), Utils::PrintAsString(isset($sm->replacemime)) ));
00150 
00151         if (isset($sm->source->folderid) && $sm->source->folderid)
00152             // convert parent folder id back to work on an imap-id
00153             $parent = $this->getImapIdFromFolderId($sm->source->folderid);
00154 
00155 
00156         // by splitting the message in several lines we can easily grep later
00157         foreach(preg_split("/((\r)?\n)/", $sm->mime) as $rfc822line)
00158             ZLog::Write(LOGLEVEL_WBXML, "RFC822: ". $rfc822line);
00159 
00160         $mobj = new Mail_mimeDecode($sm->mime);
00161         $message = $mobj->decode(array('decode_headers' => false, 'decode_bodies' => true, 'include_bodies' => true, 'charset' => 'utf-8'));
00162 
00163         $Mail_RFC822 = new Mail_RFC822();
00164         $toaddr = $ccaddr = $bccaddr = "";
00165         if(isset($message->headers["to"]))
00166             $toaddr = $this->parseAddr($Mail_RFC822->parseAddressList($message->headers["to"]));
00167         if(isset($message->headers["cc"]))
00168             $ccaddr = $this->parseAddr($Mail_RFC822->parseAddressList($message->headers["cc"]));
00169         if(isset($message->headers["bcc"]))
00170             $bccaddr = $this->parseAddr($Mail_RFC822->parseAddressList($message->headers["bcc"]));
00171 
00172         // save some headers when forwarding mails (content type & transfer-encoding)
00173         $headers = "";
00174         $forward_h_ct = "";
00175         $forward_h_cte = "";
00176         $envelopefrom = "";
00177 
00178         $use_orgbody = false;
00179 
00180         // clean up the transmitted headers
00181         // remove default headers because we are using imap_mail
00182         $changedfrom = false;
00183         $returnPathSet = false;
00184         $body_base64 = false;
00185         $org_charset = "";
00186         $org_boundary = false;
00187         $multipartmixed = false;
00188         foreach($message->headers as $k => $v) {
00189             if ($k == "subject" || $k == "to" || $k == "cc" || $k == "bcc")
00190                 continue;
00191 
00192             if ($k == "content-type") {
00193                 // if the message is a multipart message, then we should use the sent body
00194                 if (preg_match("/multipart/i", $v)) {
00195                     $use_orgbody = true;
00196                     $org_boundary = $message->ctype_parameters["boundary"];
00197                 }
00198 
00199                 // save the original content-type header for the body part when forwarding
00200                 if ($sm->forwardflag && !$use_orgbody) {
00201                     $forward_h_ct = $v;
00202                     continue;
00203                 }
00204 
00205                 // set charset always to utf-8
00206                 $org_charset = $v;
00207                 $v = preg_replace("/charset=([A-Za-z0-9-\"']+)/", "charset=\"utf-8\"", $v);
00208             }
00209 
00210             if ($k == "content-transfer-encoding") {
00211                 // if the content was base64 encoded, encode the body again when sending
00212                 if (trim($v) == "base64") $body_base64 = true;
00213 
00214                 // save the original encoding header for the body part when forwarding
00215                 if ($sm->forwardflag) {
00216                     $forward_h_cte = $v;
00217                     continue;
00218                 }
00219             }
00220 
00221             // check if "from"-header is set, do nothing if it's set
00222             // else set it to IMAP_DEFAULTFROM
00223             if ($k == "from") {
00224                 if (trim($v)) {
00225                     $changedfrom = true;
00226                 } elseif (! trim($v) && IMAP_DEFAULTFROM) {
00227                     $changedfrom = true;
00228                     if      (IMAP_DEFAULTFROM == 'username') $v = $this->username;
00229                     else if (IMAP_DEFAULTFROM == 'domain')   $v = $this->domain;
00230                     else $v = $this->username . IMAP_DEFAULTFROM;
00231                     $envelopefrom = "-f$v";
00232                 }
00233             }
00234 
00235             // check if "Return-Path"-header is set
00236             if ($k == "return-path") {
00237                 $returnPathSet = true;
00238                 if (! trim($v) && IMAP_DEFAULTFROM) {
00239                     if      (IMAP_DEFAULTFROM == 'username') $v = $this->username;
00240                     else if (IMAP_DEFAULTFROM == 'domain')   $v = $this->domain;
00241                     else $v = $this->username . IMAP_DEFAULTFROM;
00242                 }
00243             }
00244 
00245             // all other headers stay
00246             if ($headers) $headers .= "\n";
00247             $headers .= ucfirst($k) . ": ". $v;
00248         }
00249 
00250         // set "From" header if not set on the device
00251         if(IMAP_DEFAULTFROM && !$changedfrom){
00252             if      (IMAP_DEFAULTFROM == 'username') $v = $this->username;
00253             else if (IMAP_DEFAULTFROM == 'domain')   $v = $this->domain;
00254             else $v = $this->username . IMAP_DEFAULTFROM;
00255             if ($headers) $headers .= "\n";
00256             $headers .= 'From: '.$v;
00257             $envelopefrom = "-f$v";
00258         }
00259 
00260         // set "Return-Path" header if not set on the device
00261         if(IMAP_DEFAULTFROM && !$returnPathSet){
00262             if      (IMAP_DEFAULTFROM == 'username') $v = $this->username;
00263             else if (IMAP_DEFAULTFROM == 'domain')   $v = $this->domain;
00264             else $v = $this->username . IMAP_DEFAULTFROM;
00265             if ($headers) $headers .= "\n";
00266             $headers .= 'Return-Path: '.$v;
00267         }
00268 
00269         // if this is a multipart message with a boundary, we must use the original body
00270         if ($use_orgbody) {
00271             list(,$body) = $mobj->_splitBodyHeader($sm->mime);
00272             $repl_body = $this->getBody($message);
00273         }
00274         else
00275             $body = $this->getBody($message);
00276 
00277         // reply
00278         if ($sm->replyflag && $parent) {
00279             $this->imap_reopenFolder($parent);
00280             // receive entire mail (header + body) to decode body correctly
00281             $origmail = @imap_fetchheader($this->mbox, $reply, FT_UID) . @imap_body($this->mbox, $reply, FT_PEEK | FT_UID);
00282             if (!$origmail)
00283                 throw new StatusException(sprintf("BackendIMAP->SendMail(): Could not open message id '%s' in folder id '%s' to be replied: %s", $reply, $parent, imap_last_error()), SYNC_COMMONSTATUS_ITEMNOTFOUND);
00284 
00285             $mobj2 = new Mail_mimeDecode($origmail);
00286             // receive only body
00287             $body .= $this->getBody($mobj2->decode(array('decode_headers' => false, 'decode_bodies' => true, 'include_bodies' => true, 'charset' => 'utf-8')));
00288             // unset mimedecoder & origmail - free memory
00289             unset($mobj2);
00290             unset($origmail);
00291         }
00292 
00293         // encode the body to base64 if it was sent originally in base64 by the pda
00294         // contrib - chunk base64 encoded body
00295         if ($body_base64 && !$sm->forwardflag) $body = chunk_split(base64_encode($body));
00296 
00297 
00298         // forward
00299         if ($sm->forwardflag && $parent) {
00300             $this->imap_reopenFolder($parent);
00301             // receive entire mail (header + body)
00302             $origmail = @imap_fetchheader($this->mbox, $forward, FT_UID) . @imap_body($this->mbox, $forward, FT_PEEK | FT_UID);
00303 
00304             if (!$origmail)
00305                 throw new StatusException(sprintf("BackendIMAP->SendMail(): Could not open message id '%s' in folder id '%s' to be forwarded: %s", $forward, $parent, imap_last_error()), SYNC_COMMONSTATUS_ITEMNOTFOUND);
00306 
00307             if (!defined('IMAP_INLINE_FORWARD') || IMAP_INLINE_FORWARD === false) {
00308                 // contrib - chunk base64 encoded body
00309                 if ($body_base64) $body = chunk_split(base64_encode($body));
00310                 //use original boundary if it's set
00311                 $boundary = ($org_boundary) ? $org_boundary : false;
00312                 // build a new mime message, forward entire old mail as file
00313                 list($aheader, $body) = $this->mail_attach("forwarded_message.eml",strlen($origmail),$origmail, $body, $forward_h_ct, $forward_h_cte,$boundary);
00314                 // add boundary headers
00315                 $headers .= "\n" . $aheader;
00316 
00317             }
00318             else {
00319                 $mobj2 = new Mail_mimeDecode($origmail);
00320                 $mess2 = $mobj2->decode(array('decode_headers' => true, 'decode_bodies' => true, 'include_bodies' => true, 'charset' => 'utf-8'));
00321 
00322                 if (!$use_orgbody)
00323                     $nbody = $body;
00324                 else
00325                     $nbody = $repl_body;
00326 
00327                 $nbody .= "\r\n\r\n";
00328                 $nbody .= "-----Original Message-----\r\n";
00329                 if(isset($mess2->headers['from']))
00330                     $nbody .= "From: " . $mess2->headers['from'] . "\r\n";
00331                 if(isset($mess2->headers['to']) && strlen($mess2->headers['to']) > 0)
00332                     $nbody .= "To: " . $mess2->headers['to'] . "\r\n";
00333                 if(isset($mess2->headers['cc']) && strlen($mess2->headers['cc']) > 0)
00334                     $nbody .= "Cc: " . $mess2->headers['cc'] . "\r\n";
00335                 if(isset($mess2->headers['date']))
00336                     $nbody .= "Sent: " . $mess2->headers['date'] . "\r\n";
00337                 if(isset($mess2->headers['subject']))
00338                     $nbody .= "Subject: " . $mess2->headers['subject'] . "\r\n";
00339                 $nbody .= "\r\n";
00340                 $nbody .= $this->getBody($mess2);
00341 
00342                 if ($body_base64) {
00343                     // contrib - chunk base64 encoded body
00344                     $nbody = chunk_split(base64_encode($nbody));
00345                     if ($use_orgbody)
00346                     // contrib - chunk base64 encoded body
00347                         $repl_body = chunk_split(base64_encode($repl_body));
00348                 }
00349 
00350                 if ($use_orgbody) {
00351                     ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->SendMail(): -------------------");
00352                     ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->SendMail(): old:\n'$repl_body'\nnew:\n'$nbody'\nund der body:\n'$body'");
00353                     //$body is quoted-printable encoded while $repl_body and $nbody are plain text,
00354                     //so we need to decode $body in order replace to take place
00355                     $body = str_replace($repl_body, $nbody, quoted_printable_decode($body));
00356                 }
00357                 else
00358                     $body = $nbody;
00359 
00360 
00361                 if(isset($mess2->parts)) {
00362                     $attached = false;
00363 
00364                     if ($org_boundary) {
00365                         $att_boundary = $org_boundary;
00366                         // cut end boundary from body
00367                         $body = substr($body, 0, strrpos($body, "--$att_boundary--"));
00368                     }
00369                     else {
00370                         $att_boundary = strtoupper(md5(uniqid(time())));
00371                         // add boundary headers
00372                         $headers .= "\n" . "Content-Type: multipart/mixed; boundary=$att_boundary";
00373                         $multipartmixed = true;
00374                     }
00375 
00376                     foreach($mess2->parts as $part) {
00377                         if(isset($part->disposition) && ($part->disposition == "attachment" || $part->disposition == "inline")) {
00378 
00379                             if(isset($part->d_parameters['filename']))
00380                                 $attname = $part->d_parameters['filename'];
00381                             else if(isset($part->ctype_parameters['name']))
00382                                 $attname = $part->ctype_parameters['name'];
00383                             else if(isset($part->headers['content-description']))
00384                                 $attname = $part->headers['content-description'];
00385                             else $attname = "unknown attachment";
00386 
00387                             // ignore html content
00388                             if ($part->ctype_primary == "text" && $part->ctype_secondary == "html") {
00389                                 continue;
00390                             }
00391                             //
00392                             if ($use_orgbody || $attached) {
00393                                 $body .= $this->enc_attach_file($att_boundary, $attname, strlen($part->body),$part->body, $part->ctype_primary ."/". $part->ctype_secondary);
00394                             }
00395                             // first attachment
00396                             else {
00397                                 $encmail = $body;
00398                                 $attached = true;
00399                                 $body = $this->enc_multipart($att_boundary, $body, $forward_h_ct, $forward_h_cte);
00400                                 $body .= $this->enc_attach_file($att_boundary, $attname, strlen($part->body),$part->body, $part->ctype_primary ."/". $part->ctype_secondary);
00401                             }
00402                         }
00403                     }
00404                     if ($multipartmixed && strpos(strtolower($mess2->headers['content-type']), "alternative") !== false) {
00405                         //this happens if a multipart/alternative message is forwarded
00406                         //then it's a multipart/mixed message which consists of:
00407                         //1. text/plain part which was written on the mobile
00408                         //2. multipart/alternative part which is the original message
00409                         $body = "This is a message with multiple parts in MIME format.\n--".
00410                                 $att_boundary.
00411                                 "\nContent-Type: $forward_h_ct\nContent-Transfer-Encoding: $forward_h_cte\n\n".
00412                                 (($body_base64) ? chunk_split(base64_encode($message->body)) : rtrim($message->body)).
00413                                 "\n--".$att_boundary.
00414                                 "\nContent-Type: {$mess2->headers['content-type']}\n\n".
00415                                 @imap_body($this->mbox, $forward, FT_PEEK | FT_UID)."\n\n";
00416                     }
00417                     $body .= "--$att_boundary--\n\n";
00418                 }
00419 
00420                 unset($mobj2);
00421             }
00422 
00423             // unset origmail - free memory
00424             unset($origmail);
00425 
00426         }
00427 
00428         // remove carriage-returns from body
00429         $body = str_replace("\r\n", "\n", $body);
00430 
00431         if (!$multipartmixed) {
00432             if (!empty($forward_h_ct)) $headers .= "\nContent-Type: $forward_h_ct";
00433             if (!empty($forward_h_cte)) $headers .= "\nContent-Transfer-Encoding: $forward_h_cte";
00434         //  if body was quoted-printable, convert it again
00435             if (isset($message->headers["content-transfer-encoding"]) && strtolower($message->headers["content-transfer-encoding"]) == "quoted-printable") {
00436                 $body = quoted_printable_encode($body);
00437             }
00438         }
00439 
00440         // more debugging
00441         ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->SendMail(): parsed message: ". print_r($message,1));
00442         ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->SendMail(): headers: $headers");
00443         ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->SendMail(): subject: {$message->headers["subject"]}");
00444         ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->SendMail(): body: $body");
00445 
00446         if (!defined('IMAP_USE_IMAPMAIL') || IMAP_USE_IMAPMAIL == true) {
00447             $send =  @imap_mail ( $toaddr, $message->headers["subject"], $body, $headers, $ccaddr, $bccaddr);
00448         }
00449         else {
00450             if (!empty($ccaddr))  $headers .= "\nCc: $ccaddr";
00451             if (!empty($bccaddr)) $headers .= "\nBcc: $bccaddr";
00452             $send =  @mail ( $toaddr, $message->headers["subject"], $body, $headers, $envelopefrom );
00453         }
00454 
00455         // email sent?
00456         if (!$send)
00457             throw new StatusException(sprintf("BackendIMAP->SendMail(): The email could not be sent. Last IMAP-error: ". imap_last_error()), SYNC_COMMONSTATUS_MAILSUBMISSIONFAILED);
00458 
00459         // add message to the sent folder
00460         // build complete headers
00461         $headers .= "\nTo: $toaddr";
00462         $headers .= "\nSubject: " . $message->headers["subject"];
00463 
00464         if (!defined('IMAP_USE_IMAPMAIL') || IMAP_USE_IMAPMAIL == true) {
00465             if (!empty($ccaddr))  $headers .= "\nCc: $ccaddr";
00466             if (!empty($bccaddr)) $headers .= "\nBcc: $bccaddr";
00467         }
00468         ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->SendMail(): complete headers: $headers");
00469 
00470         $asf = false;
00471         if ($this->sentID) {
00472             $asf = $this->addSentMessage($this->sentID, $headers, $body);
00473         }
00474         else if (IMAP_SENTFOLDER) {
00475             $asf = $this->addSentMessage(IMAP_SENTFOLDER, $headers, $body);
00476             ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->SendMail(): Outgoing mail saved in configured 'Sent' folder '%s': %s", IMAP_SENTFOLDER, Utils::PrintAsString($asf)));
00477         }
00478         // No Sent folder set, try defaults
00479         else {
00480             ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->SendMail(): No Sent mailbox set");
00481             if($this->addSentMessage("INBOX.Sent", $headers, $body)) {
00482                 ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->SendMail(): Outgoing mail saved in 'INBOX.Sent'");
00483                 $asf = true;
00484             }
00485             else if ($this->addSentMessage("Sent", $headers, $body)) {
00486                 ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->SendMail(): Outgoing mail saved in 'Sent'");
00487                 $asf = true;
00488             }
00489             else if ($this->addSentMessage("Sent Items", $headers, $body)) {
00490                 ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->SendMail():IMAP-SendMail: Outgoing mail saved in 'Sent Items'");
00491                 $asf = true;
00492             }
00493         }
00494 
00495         // unset mimedecoder - free memory
00496         unset($mobj);
00497         return ($send && $asf);
00498     }
00499 
00506     public function GetWasteBasket() {
00507         // TODO this could be retrieved from the DeviceFolderCache
00508         if ($this->wasteID == false) {
00509             //try to get the waste basket without doing complete hierarchy sync
00510             $wastebaskt = @imap_getmailboxes($this->mbox, $this->server, "Trash");
00511             if (isset($wastebaskt[0])) {
00512                 $this->wasteID = imap_utf7_decode(substr($wastebaskt[0]->name, strlen($this->server)));
00513                 return $this->wasteID;
00514             }
00515             //try get waste id from hierarchy if it wasn't possible with above for some reason
00516             $this->GetHierarchy();
00517         }
00518         return $this->wasteID;
00519     }
00520 
00533     public function GetAttachmentData($attname) {
00534         ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->GetAttachmentData('%s')", $attname));
00535 
00536         list($folderid, $id, $part) = explode(":", $attname);
00537 
00538         if (!$folderid || !$id || !$part)
00539             throw new StatusException(sprintf("BackendIMAP->GetAttachmentData('%s'): Error, attachment name key can not be parsed", $attname), SYNC_ITEMOPERATIONSSTATUS_INVALIDATT);
00540 
00541         // convert back to work on an imap-id
00542         $folderImapid = $this->getImapIdFromFolderId($folderid);
00543 
00544         $this->imap_reopenFolder($folderImapid);
00545         $mail = @imap_fetchheader($this->mbox, $id, FT_UID) . @imap_body($this->mbox, $id, FT_PEEK | FT_UID);
00546 
00547         $mobj = new Mail_mimeDecode($mail);
00548         $message = $mobj->decode(array('decode_headers' => true, 'decode_bodies' => true, 'include_bodies' => true, 'charset' => 'utf-8'));
00549 
00550         if (!isset($message->parts[$part]->body))
00551             throw new StatusException(sprintf("BackendIMAP->GetAttachmentData('%s'): Error, requested part key can not be found: '%d'", $attname, $part), SYNC_ITEMOPERATIONSSTATUS_INVALIDATT);
00552 
00553         // unset mimedecoder & mail
00554         unset($mobj);
00555         unset($mail);
00556 
00557         include_once('include/stringstreamwrapper.php');
00558         $attachment = new SyncItemOperationsAttachment();
00559         $attachment->data = StringStreamWrapper::Open($message->parts[$part]->body);
00560         if (isset($message->parts[$part]->ctype_primary) && isset($message->parts[$part]->ctype_secondary))
00561             $attachment->contenttype = $message->parts[$part]->ctype_primary .'/'.$message->parts[$part]->ctype_secondary;
00562 
00563         return $attachment;
00564     }
00565 
00574     public function HasChangesSink() {
00575         $this->sinkfolders = array();
00576         $this->sinkstates = array();
00577         return true;
00578     }
00579 
00590     public function ChangesSinkInitialize($folderid) {
00591         ZLog::Write(LOGLEVEL_DEBUG, sprintf("IMAPBackend->ChangesSinkInitialize(): folderid '%s'", $folderid));
00592 
00593         $imapid = $this->getImapIdFromFolderId($folderid);
00594 
00595         if ($imapid) {
00596             $this->sinkfolders[] = $imapid;
00597             return true;
00598         }
00599 
00600         return false;
00601     }
00602 
00614     public function ChangesSink($timeout = 30) {
00615         $notifications = array();
00616         $stopat = time() + $timeout - 1;
00617 
00618         while($stopat > time() && empty($notifications)) {
00619             foreach ($this->sinkfolders as $imapid) {
00620                 $this->imap_reopenFolder($imapid);
00621 
00622                 // courier-imap only cleares the status cache after checking
00623                 @imap_check($this->mbox);
00624 
00625                 $status = @imap_status($this->mbox, $this->server . $imapid, SA_ALL);
00626                 if (!$status) {
00627                     ZLog::Write(LOGLEVEL_WARN, sprintf("ChangesSink: could not stat folder '%s': %s ", $this->getFolderIdFromImapId($imapid), imap_last_error()));
00628                 }
00629                 else {
00630                     $newstate = "M:". $status->messages ."-R:". $status->recent ."-U:". $status->unseen;
00631 
00632                     if (! isset($this->sinkstates[$imapid]) )
00633                         $this->sinkstates[$imapid] = $newstate;
00634 
00635                     if ($this->sinkstates[$imapid] != $newstate) {
00636                         $notifications[] = $this->getFolderIdFromImapId($imapid);
00637                         $this->sinkstates[$imapid] = $newstate;
00638                     }
00639                 }
00640             }
00641 
00642             if (empty($notifications))
00643                 sleep(5);
00644         }
00645 
00646         return $notifications;
00647     }
00648 
00649 
00661     public function GetFolderList() {
00662         $folders = array();
00663 
00664         $list = @imap_getmailboxes($this->mbox, $this->server, "*");
00665         if (is_array($list)) {
00666             // reverse list to obtain folders in right order
00667             $list = array_reverse($list);
00668 
00669             foreach ($list as $val) {
00670                 $box = array();
00671                 // cut off serverstring
00672                 $imapid = substr($val->name, strlen($this->server));
00673                 $box["id"] = $this->convertImapId($imapid);
00674 
00675                 $fhir = explode($val->delimiter, $imapid);
00676                 if (count($fhir) > 1) {
00677                     $this->getModAndParentNames($fhir, $box["mod"], $imapparent);
00678                     $box["parent"] = $this->convertImapId($imapparent);
00679                 }
00680                 else {
00681                     $box["mod"] = $imapid;
00682                     $box["parent"] = "0";
00683                 }
00684                 $folders[]=$box;
00685             }
00686         }
00687         else {
00688             ZLog::Write(LOGLEVEL_WARN, "BackendIMAP->GetFolderList(): imap_list failed: " . imap_last_error());
00689             return false;
00690         }
00691 
00692         return $folders;
00693     }
00694 
00703     public function GetFolder($id) {
00704         $folder = new SyncFolder();
00705         $folder->serverid = $id;
00706 
00707         // convert back to work on an imap-id
00708         $imapid = $this->getImapIdFromFolderId($id);
00709 
00710         // explode hierarchy
00711         $fhir = explode($this->serverdelimiter, $imapid);
00712 
00713         // compare on lowercase strings
00714         $lid = strtolower($imapid);
00715 // TODO WasteID or SentID could be saved for later ussage
00716         if($lid == "inbox") {
00717             $folder->parentid = "0"; // Root
00718             $folder->displayname = "Inbox";
00719             $folder->type = SYNC_FOLDER_TYPE_INBOX;
00720         }
00721         // Zarafa IMAP-Gateway outputs
00722         else if($lid == "drafts") {
00723             $folder->parentid = "0";
00724             $folder->displayname = "Drafts";
00725             $folder->type = SYNC_FOLDER_TYPE_DRAFTS;
00726         }
00727         else if($lid == "trash") {
00728             $folder->parentid = "0";
00729             $folder->displayname = "Trash";
00730             $folder->type = SYNC_FOLDER_TYPE_WASTEBASKET;
00731             $this->wasteID = $id;
00732         }
00733         else if($lid == "sent" || $lid == "sent items" || $lid == IMAP_SENTFOLDER) {
00734             $folder->parentid = "0";
00735             $folder->displayname = "Sent";
00736             $folder->type = SYNC_FOLDER_TYPE_SENTMAIL;
00737             $this->sentID = $id;
00738         }
00739         // courier-imap outputs and cyrus-imapd outputs
00740         else if($lid == "inbox.drafts" || $lid == "inbox/drafts") {
00741             $folder->parentid = $this->convertImapId($fhir[0]);
00742             $folder->displayname = "Drafts";
00743             $folder->type = SYNC_FOLDER_TYPE_DRAFTS;
00744         }
00745         else if($lid == "inbox.trash" || $lid == "inbox/trash") {
00746             $folder->parentid = $this->convertImapId($fhir[0]);
00747             $folder->displayname = "Trash";
00748             $folder->type = SYNC_FOLDER_TYPE_WASTEBASKET;
00749             $this->wasteID = $id;
00750         }
00751         else if($lid == "inbox.sent" || $lid == "inbox/sent") {
00752             $folder->parentid = $this->convertImapId($fhir[0]);
00753             $folder->displayname = "Sent";
00754             $folder->type = SYNC_FOLDER_TYPE_SENTMAIL;
00755             $this->sentID = $id;
00756         }
00757 
00758         // define the rest as other-folders
00759         else {
00760             if (count($fhir) > 1) {
00761                 $this->getModAndParentNames($fhir, $folder->displayname, $imapparent);
00762                 $folder->parentid = $this->convertImapId($imapparent);
00763                 $folder->displayname = Utils::Utf7_to_utf8(Utils::Utf7_iconv_decode($folder->displayname));
00764             }
00765             else {
00766                 $folder->displayname = Utils::Utf7_to_utf8(Utils::Utf7_iconv_decode($imapid));
00767                 $folder->parentid = "0";
00768             }
00769             $folder->type = SYNC_FOLDER_TYPE_OTHER;
00770         }
00771 
00772         //advanced debugging
00773         ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->GetFolder('%s'): '%s'", $id, $folder));
00774 
00775         return $folder;
00776     }
00777 
00786     public function StatFolder($id) {
00787         $folder = $this->GetFolder($id);
00788 
00789         $stat = array();
00790         $stat["id"] = $id;
00791         $stat["parent"] = $folder->parentid;
00792         $stat["mod"] = $folder->displayname;
00793 
00794         return $stat;
00795     }
00796 
00811     public function ChangeFolder($folderid, $oldid, $displayname, $type){
00812         ZLog::Write(LOGLEVEL_INFO, sprintf("BackendIMAP->ChangeFolder('%s','%s','%s','%s')", $folderid, $oldid, $displayname, $type));
00813 
00814         // go to parent mailbox
00815         $this->imap_reopenFolder($folderid);
00816 
00817         // build name for new mailboxBackendMaildir
00818         $displayname = Utils::Utf7_iconv_encode(Utils::Utf8_to_utf7($displayname));
00819         $newname = $this->server . $folderid . $this->serverdelimiter . $displayname;
00820 
00821         $csts = false;
00822         // if $id is set => rename mailbox, otherwise create
00823         if ($oldid) {
00824             // rename doesn't work properly with IMAP
00825             // the activesync client doesn't support a 'changing ID'
00826             // TODO this would be solved by implementing hex ids (Mantis #459)
00827             //$csts = imap_renamemailbox($this->mbox, $this->server . imap_utf7_encode(str_replace(".", $this->serverdelimiter, $oldid)), $newname);
00828         }
00829         else {
00830             $csts = @imap_createmailbox($this->mbox, $newname);
00831         }
00832         if ($csts) {
00833             return $this->StatFolder($folderid . $this->serverdelimiter . $displayname);
00834         }
00835         else
00836             return false;
00837     }
00838 
00850     public function DeleteFolder($id, $parentid){
00851         // TODO implement
00852         return false;
00853     }
00854 
00864     public function GetMessageList($folderid, $cutoffdate) {
00865         ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->GetMessageList('%s','%s')", $folderid, $cutoffdate));
00866 
00867         $folderid = $this->getImapIdFromFolderId($folderid);
00868 
00869         if ($folderid == false)
00870             throw new StatusException("Folderid not found in cache", SYNC_STATUS_FOLDERHIERARCHYCHANGED);
00871 
00872         $messages = array();
00873         $this->imap_reopenFolder($folderid, true);
00874 
00875         $sequence = "1:*";
00876         if ($cutoffdate > 0) {
00877             $search = @imap_search($this->mbox, "SINCE ". date("d-M-Y", $cutoffdate));
00878             if ($search !== false)
00879                 $sequence = implode(",", $search);
00880         }
00881         ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->GetMessageList(): searching with sequence '%s'", $sequence));
00882         $overviews = @imap_fetch_overview($this->mbox, $sequence);
00883 
00884         if (!$overviews || !is_array($overviews)) {
00885             ZLog::Write(LOGLEVEL_WARN, sprintf("BackendIMAP->GetMessageList('%s','%s'): Failed to retrieve overview: %s",$folderid, $cutoffdate, imap_last_error()));
00886             return $messages;
00887         }
00888 
00889         foreach($overviews as $overview) {
00890             $date = "";
00891             $vars = get_object_vars($overview);
00892             if (array_key_exists( "date", $vars)) {
00893                 // message is out of range for cutoffdate, ignore it
00894                 if ($this->cleanupDate($overview->date) < $cutoffdate) continue;
00895                 $date = $overview->date;
00896             }
00897 
00898             // cut of deleted messages
00899             if (array_key_exists( "deleted", $vars) && $overview->deleted)
00900                 continue;
00901 
00902             if (array_key_exists( "uid", $vars)) {
00903                 $message = array();
00904                 $message["mod"] = $date;
00905                 $message["id"] = $overview->uid;
00906                 // 'seen' aka 'read' is the only flag we want to know about
00907                 $message["flags"] = 0;
00908 
00909                 if(array_key_exists( "seen", $vars) && $overview->seen)
00910                     $message["flags"] = 1;
00911 
00912                 array_push($messages, $message);
00913             }
00914         }
00915         return $messages;
00916     }
00917 
00928     public function GetMessage($folderid, $id, $contentparameters) {
00929         $truncsize = Utils::GetTruncSize($contentparameters->GetTruncation());
00930         $mimesupport = $contentparameters->GetMimeSupport();
00931         ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->GetMessage('%s','%s')", $folderid,  $id));
00932 
00933         $folderImapid = $this->getImapIdFromFolderId($folderid);
00934 
00935         // Get flags, etc
00936         $stat = $this->StatMessage($folderid, $id);
00937 
00938         if ($stat) {
00939             $this->imap_reopenFolder($folderImapid);
00940             $mail = @imap_fetchheader($this->mbox, $id, FT_UID) . @imap_body($this->mbox, $id, FT_PEEK | FT_UID);
00941 
00942             $mobj = new Mail_mimeDecode($mail);
00943             $message = $mobj->decode(array('decode_headers' => true, 'decode_bodies' => true, 'include_bodies' => true, 'charset' => 'utf-8'));
00944 
00945             $output = new SyncMail();
00946 
00947             $body = $this->getBody($message);
00948             $output->bodysize = strlen($body);
00949 
00950             // truncate body, if requested
00951             if(strlen($body) > $truncsize) {
00952                 $body = Utils::Utf8_truncate($body, $truncsize);
00953                 $output->bodytruncated = 1;
00954             } else {
00955                 $body = $body;
00956                 $output->bodytruncated = 0;
00957             }
00958             $body = str_replace("\n","\r\n", str_replace("\r","",$body));
00959 
00960             $output->body = $body;
00961             $output->datereceived = isset($message->headers["date"]) ? $this->cleanupDate($message->headers["date"]) : null;
00962             $output->messageclass = "IPM.Note";
00963             $output->subject = isset($message->headers["subject"]) ? $message->headers["subject"] : "";
00964             $output->read = $stat["flags"];
00965             $output->from = isset($message->headers["from"]) ? $message->headers["from"] : null;
00966 
00967             $Mail_RFC822 = new Mail_RFC822();
00968             $toaddr = $ccaddr = $replytoaddr = array();
00969             if(isset($message->headers["to"]))
00970                 $toaddr = $Mail_RFC822->parseAddressList($message->headers["to"]);
00971             if(isset($message->headers["cc"]))
00972                 $ccaddr = $Mail_RFC822->parseAddressList($message->headers["cc"]);
00973             if(isset($message->headers["reply_to"]))
00974                 $replytoaddr = $Mail_RFC822->parseAddressList($message->headers["reply_to"]);
00975 
00976             $output->to = array();
00977             $output->cc = array();
00978             $output->reply_to = array();
00979             foreach(array("to" => $toaddr, "cc" => $ccaddr, "reply_to" => $replytoaddr) as $type => $addrlist) {
00980                 foreach($addrlist as $addr) {
00981                     $address = $addr->mailbox . "@" . $addr->host;
00982                     $name = $addr->personal;
00983 
00984                     if (!isset($output->displayto) && $name != "")
00985                         $output->displayto = $name;
00986 
00987                     if($name == "" || $name == $address)
00988                         $fulladdr = w2u($address);
00989                     else {
00990                         if (substr($name, 0, 1) != '"' && substr($name, -1) != '"') {
00991                             $fulladdr = "\"" . w2u($name) ."\" <" . w2u($address) . ">";
00992                         }
00993                         else {
00994                             $fulladdr = w2u($name) ." <" . w2u($address) . ">";
00995                         }
00996                     }
00997 
00998                     array_push($output->$type, $fulladdr);
00999                 }
01000             }
01001 
01002             // convert mime-importance to AS-importance
01003             if (isset($message->headers["x-priority"])) {
01004                 $mimeImportance =  preg_replace("/\D+/", "", $message->headers["x-priority"]);
01005                 if ($mimeImportance > 3)
01006                     $output->importance = 0;
01007                 if ($mimeImportance == 3)
01008                     $output->importance = 1;
01009                 if ($mimeImportance < 3)
01010                     $output->importance = 2;
01011             }
01012 
01013             // Attachments are only searched in the top-level part
01014             if(isset($message->parts)) {
01015                 $mparts = $message->parts;
01016                 for ($i=0; $i<count($mparts); $i++) {
01017                     $part = $mparts[$i];
01018                     //recursively add parts
01019                     if($part->ctype_primary == "multipart" && ($part->ctype_secondary == "mixed" || $part->ctype_secondary == "alternative"  || $part->ctype_secondary == "related")) {
01020                         foreach($part->parts as $spart)
01021                             $mparts[] = $spart;
01022                         continue;
01023                     }
01024                     //add part as attachment if it's disposition indicates so or if it is not a text part
01025                     if ((isset($part->disposition) && ($part->disposition == "attachment" || $part->disposition == "inline")) ||
01026                         (isset($part->ctype_primary) && $part->ctype_primary != "text")) {
01027 
01028                         if (!isset($output->attachments) || !is_array($output->attachments))
01029                             $output->attachments = array();
01030 
01031                         $attachment = new SyncAttachment();
01032 
01033                         if (isset($part->body))
01034                             $attachment->attsize = strlen($part->body);
01035 
01036                         if(isset($part->d_parameters['filename']))
01037                             $attname = $part->d_parameters['filename'];
01038                         else if(isset($part->ctype_parameters['name']))
01039                             $attname = $part->ctype_parameters['name'];
01040                         else if(isset($part->headers['content-description']))
01041                             $attname = $part->headers['content-description'];
01042                         else $attname = "unknown attachment";
01043 
01044                         $attachment->displayname = $attname;
01045                         $attachment->attname = $folderid . ":" . $id . ":" . $i;
01046                         $attachment->attmethod = 1;
01047                         $attachment->attoid = isset($part->headers['content-id']) ? $part->headers['content-id'] : "";
01048                         array_push($output->attachments, $attachment);
01049                     }
01050 
01051                 }
01052             }
01053             // unset mimedecoder & mail
01054             unset($mobj);
01055             unset($mail);
01056             return $output;
01057         }
01058 
01059         return false;
01060     }
01061 
01071     public function StatMessage($folderid, $id) {
01072         ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->StatMessage('%s','%s')", $folderid,  $id));
01073         $folderImapid = $this->getImapIdFromFolderId($folderid);
01074 
01075         $this->imap_reopenFolder($folderImapid);
01076         $overview = @imap_fetch_overview( $this->mbox , $id , FT_UID);
01077 
01078         if (!$overview) {
01079             ZLog::Write(LOGLEVEL_WARN, sprintf("BackendIMAP->StatMessage('%s','%s'): Failed to retrieve overview: %s", $folderid,  $id, imap_last_error()));
01080             return false;
01081         }
01082 
01083         // check if variables for this overview object are available
01084         $vars = get_object_vars($overview[0]);
01085 
01086         // without uid it's not a valid message
01087         if (! array_key_exists( "uid", $vars)) return false;
01088 
01089         $entry = array();
01090         $entry["mod"] = (array_key_exists( "date", $vars)) ? $overview[0]->date : "";
01091         $entry["id"] = $overview[0]->uid;
01092         // 'seen' aka 'read' is the only flag we want to know about
01093         $entry["flags"] = 0;
01094 
01095         if(array_key_exists( "seen", $vars) && $overview[0]->seen)
01096             $entry["flags"] = 1;
01097 
01098         return $entry;
01099     }
01100 
01113     public function ChangeMessage($folderid, $id, $message) {
01114         ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->ChangeMessage('%s','%s','%s')", $folderid, $id, get_class($message)));
01115         // TODO recheck implementation
01116         // TODO this could throw several StatusExceptions like e.g. SYNC_STATUS_OBJECTNOTFOUND, SYNC_STATUS_SYNCCANNOTBECOMPLETED
01117         return false;
01118     }
01119 
01131     public function SetReadFlag($folderid, $id, $flags) {
01132         ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->SetReadFlag('%s','%s','%s')", $folderid, $id, $flags));
01133         $folderImapid = $this->getImapIdFromFolderId($folderid);
01134 
01135         $this->imap_reopenFolder($folderImapid);
01136 
01137         if ($flags == 0) {
01138             // set as "Unseen" (unread)
01139             $status = @imap_clearflag_full ( $this->mbox, $id, "\\Seen", ST_UID);
01140         } else {
01141             // set as "Seen" (read)
01142             $status = @imap_setflag_full($this->mbox, $id, "\\Seen",ST_UID);
01143         }
01144 
01145         return $status;
01146     }
01147 
01158     public function DeleteMessage($folderid, $id) {
01159         ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->DeleteMessage('%s','%s')", $folderid, $id));
01160         $folderImapid = $this->getImapIdFromFolderId($folderid);
01161 
01162         $this->imap_reopenFolder($folderImapid);
01163         $s1 = @imap_delete ($this->mbox, $id, FT_UID);
01164         $s11 = @imap_setflag_full($this->mbox, $id, "\\Deleted", FT_UID);
01165         $s2 = @imap_expunge($this->mbox);
01166 
01167         ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->DeleteMessage('%s','%s'): result: s-delete: '%s' s-expunge: '%s' setflag: '%s'", $folderid, $id, $s1, $s2, $s11));
01168 
01169         return ($s1 && $s2 && $s11);
01170     }
01171 
01183     public function MoveMessage($folderid, $id, $newfolderid) {
01184         ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->MoveMessage('%s','%s','%s')", $folderid, $id, $newfolderid));
01185         $folderImapid = $this->getImapIdFromFolderId($folderid);
01186         $newfolderImapid = $this->getImapIdFromFolderId($newfolderid);
01187 
01188 
01189         $this->imap_reopenFolder($folderImapid);
01190 
01191         // TODO this should throw a StatusExceptions on errors like SYNC_MOVEITEMSSTATUS_SAMESOURCEANDDEST,SYNC_MOVEITEMSSTATUS_INVALIDSOURCEID,SYNC_MOVEITEMSSTATUS_CANNOTMOVE
01192 
01193         // read message flags
01194         $overview = @imap_fetch_overview ( $this->mbox , $id, FT_UID);
01195 
01196         if (!$overview)
01197             throw new StatusException(sprintf("BackendIMAP->MoveMessage('%s','%s','%s'): Error, unable to retrieve overview of source message: %s", $folderid, $id, $newfolderid, imap_last_error()), SYNC_MOVEITEMSSTATUS_INVALIDSOURCEID);
01198         else {
01199             // get next UID for destination folder
01200             // when moving a message we have to announce through ActiveSync the new messageID in the
01201             // destination folder. This is a "guessing" mechanism as IMAP does not inform that value.
01202             // when lots of simultaneous operations happen in the destination folder this could fail.
01203             // in the worst case the moved message is displayed twice on the mobile.
01204             $destStatus = imap_status($this->mbox, $this->server . $newfolderImapid, SA_ALL);
01205             if (!$destStatus)
01206                 throw new StatusException(sprintf("BackendIMAP->MoveMessage('%s','%s','%s'): Error, unable to open destination folder: %s", $folderid, $id, $newfolderid, imap_last_error()), SYNC_MOVEITEMSSTATUS_INVALIDDESTID);
01207 
01208             $newid = $destStatus->uidnext;
01209 
01210             // move message
01211             $s1 = imap_mail_move($this->mbox, $id, $newfolderImapid, CP_UID);
01212             if (! $s1)
01213                 throw new StatusException(sprintf("BackendIMAP->MoveMessage('%s','%s','%s'): Error, copy to destination folder failed: %s", $folderid, $id, $newfolderid, imap_last_error()), SYNC_MOVEITEMSSTATUS_CANNOTMOVE);
01214 
01215 
01216             // delete message in from-folder
01217             $s2 = imap_expunge($this->mbox);
01218 
01219             // open new folder
01220             $stat = $this->imap_reopenFolder($newfolderImapid);
01221             if (! $s1)
01222                 throw new StatusException(sprintf("BackendIMAP->MoveMessage('%s','%s','%s'): Error, openeing the destination folder: %s", $folderid, $id, $newfolderid, imap_last_error()), SYNC_MOVEITEMSSTATUS_CANNOTMOVE);
01223 
01224 
01225             // remove all flags
01226             $s3 = @imap_clearflag_full ($this->mbox, $newid, "\\Seen \\Answered \\Flagged \\Deleted \\Draft", FT_UID);
01227             $newflags = "";
01228             if ($overview[0]->seen) $newflags .= "\\Seen";
01229             if ($overview[0]->flagged) $newflags .= " \\Flagged";
01230             if ($overview[0]->answered) $newflags .= " \\Answered";
01231             $s4 = @imap_setflag_full ($this->mbox, $newid, $newflags, FT_UID);
01232 
01233             ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->MoveMessage('%s','%s','%s'): result s-move: '%s' s-expunge: '%s' unset-Flags: '%s' set-Flags: '%s'", $folderid, $id, $newfolderid, Utils::PrintAsString($s1), Utils::PrintAsString($s2), Utils::PrintAsString($s3), Utils::PrintAsString($s4)));
01234 
01235             // return the new id "as string""
01236             return $newid . "";
01237         }
01238     }
01239 
01240 
01253     private function getImapIdFromFolderId($folderid) {
01254         $this->InitializePermanentStorage();
01255 
01256         if (isset($this->permanentStorage->fmFidFimap)) {
01257             if (isset($this->permanentStorage->fmFidFimap[$folderid])) {
01258                 $imapId = $this->permanentStorage->fmFidFimap[$folderid];
01259                 ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->getImapIdFromFolderId('%s') = %s", $folderid, $imapId));
01260                 return $imapId;
01261             }
01262             else {
01263                 ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->getImapIdFromFolderId('%s') = %s", $folderid, 'not found'));
01264                 return false;
01265             }
01266         }
01267         ZLog::Write(LOGLEVEL_WARN, sprintf("BackendIMAP->getImapIdFromFolderId('%s') = %s", $folderid, 'not initialized!'));
01268         return false;
01269     }
01270 
01279     private function getFolderIdFromImapId($imapid) {
01280         $this->InitializePermanentStorage();
01281 
01282         if (isset($this->permanentStorage->fmFimapFid)) {
01283             if (isset($this->permanentStorage->fmFimapFid[$imapid])) {
01284                 $folderid = $this->permanentStorage->fmFimapFid[$imapid];
01285                 ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->getFolderIdFromImapId('%s') = %s", $imapid, $folderid));
01286                 return $folderid;
01287             }
01288             else {
01289                 ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->getFolderIdFromImapId('%s') = %s", $imapid, 'not found'));
01290                 return false;
01291             }
01292         }
01293         ZLog::Write(LOGLEVEL_WARN, sprintf("BackendIMAP->getFolderIdFromImapId('%s') = %s", $imapid, 'not initialized!'));
01294         return false;
01295     }
01296 
01307     private function convertImapId($imapid) {
01308         $this->InitializePermanentStorage();
01309 
01310         // check if this imap id was converted before
01311         $folderid = $this->getFolderIdFromImapId($imapid);
01312 
01313         // nothing found, so generate a new id and put it in the cache
01314         if (!$folderid) {
01315             // generate folderid and add it to the mapping
01316             $folderid = sprintf('%04x%04x', mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ));
01317 
01318             // folderId to folderImap mapping
01319             if (!isset($this->permanentStorage->fmFidFimap))
01320                 $this->permanentStorage->fmFidFimap = array();
01321 
01322             $a = $this->permanentStorage->fmFidFimap;
01323             $a[$folderid] = $imapid;
01324             $this->permanentStorage->fmFidFimap = $a;
01325 
01326             // folderImap to folderid mapping
01327             if (!isset($this->permanentStorage->fmFimapFid))
01328                 $this->permanentStorage->fmFimapFid = array();
01329 
01330             $b = $this->permanentStorage->fmFimapFid;
01331             $b[$imapid] = $folderid;
01332             $this->permanentStorage->fmFimapFid = $b;
01333         }
01334 
01335         ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->convertImapId('%s') = %s", $imapid, $folderid));
01336 
01337         return $folderid;
01338     }
01339 
01340 
01349     private function getBody($message) {
01350         $body = "";
01351         $htmlbody = "";
01352 
01353         $this->getBodyRecursive($message, "plain", $body);
01354 
01355         if($body === "") {
01356             $this->getBodyRecursive($message, "html", $body);
01357             // remove css-style tags
01358             $body = preg_replace("/<style.*?<\/style>/is", "", $body);
01359             // remove all other html
01360             $body = strip_tags($body);
01361         }
01362 
01363         return $body;
01364     }
01365 
01377     private function getBodyRecursive($message, $subtype, &$body) {
01378         if(!isset($message->ctype_primary)) return;
01379         if(strcasecmp($message->ctype_primary,"text")==0 && strcasecmp($message->ctype_secondary,$subtype)==0 && isset($message->body))
01380             $body .= $message->body;
01381 
01382         if(strcasecmp($message->ctype_primary,"multipart")==0 && isset($message->parts) && is_array($message->parts)) {
01383             foreach($message->parts as $part) {
01384                 if(!isset($part->disposition) || strcasecmp($part->disposition,"attachment"))  {
01385                     $this->getBodyRecursive($part, $subtype, $body);
01386                 }
01387             }
01388         }
01389     }
01390 
01397     private function getServerDelimiter() {
01398         $list = @imap_getmailboxes($this->mbox, $this->server, "*");
01399         if (is_array($list)) {
01400             $val = $list[0];
01401 
01402             return $val->delimiter;
01403         }
01404         return "."; // default "."
01405     }
01406 
01417     private function imap_reopenFolder($folderid, $force = false) {
01418         // to see changes, the folder has to be reopened!
01419            if ($this->mboxFolder != $folderid || $force) {
01420                $s = @imap_reopen($this->mbox, $this->server . $folderid);
01421                // TODO throw status exception
01422                if (!$s) {
01423                 ZLog::Write(LOGLEVEL_WARN, "BackendIMAP->imap_reopenFolder('%s'): failed to change folder: ",$folderid, implode(", ", imap_errors()));
01424                 return false;
01425                }
01426             $this->mboxFolder = $folderid;
01427         }
01428     }
01429 
01430 
01445     private function mail_attach($filenm,$filesize,$file_cont,$body, $body_ct, $body_cte, $boundary = false) {
01446         if (!$boundary) $boundary = strtoupper(md5(uniqid(time())));
01447 
01448         //remove the ending boundary because we will add it at the end
01449         $body = str_replace("--$boundary--", "", $body);
01450 
01451         $mail_header = "Content-Type: multipart/mixed; boundary=$boundary\n";
01452 
01453         // build main body with the sumitted type & encoding from the pda
01454         $mail_body  = $this->enc_multipart($boundary, $body, $body_ct, $body_cte);
01455         $mail_body .= $this->enc_attach_file($boundary, $filenm, $filesize, $file_cont);
01456 
01457         $mail_body .= "--$boundary--\n\n";
01458         return array($mail_header, $mail_body);
01459     }
01460 
01472     private function enc_multipart($boundary, $body, $body_ct, $body_cte) {
01473         $mail_body = "This is a multi-part message in MIME format\n\n";
01474         $mail_body .= "--$boundary\n";
01475         $mail_body .= "Content-Type: $body_ct\n";
01476         $mail_body .= "Content-Transfer-Encoding: $body_cte\n\n";
01477         $mail_body .= "$body\n\n";
01478 
01479         return $mail_body;
01480     }
01481 
01494     private function enc_attach_file($boundary, $filenm, $filesize, $file_cont, $content_type = "") {
01495         if (!$content_type) $content_type = "text/plain";
01496         $mail_body = "--$boundary\n";
01497         $mail_body .= "Content-Type: $content_type; name=\"$filenm\"\n";
01498         $mail_body .= "Content-Transfer-Encoding: base64\n";
01499         $mail_body .= "Content-Disposition: attachment; filename=\"$filenm\"\n";
01500         $mail_body .= "Content-Description: $filenm\n\n";
01501         //contrib - chunk base64 encoded attachments
01502         $mail_body .= chunk_split(base64_encode($file_cont)) . "\n\n";
01503 
01504         return $mail_body;
01505     }
01506 
01517     private function addSentMessage($folderid, $header, $body) {
01518         $header_body = str_replace("\n", "\r\n", str_replace("\r", "", $header . "\n\n" . $body));
01519 
01520         return @imap_append($this->mbox, $this->server . $folderid, $header_body, "\\Seen");
01521     }
01522 
01531     private function parseAddr($ad) {
01532         $addr_string = "";
01533         if (isset($ad) && is_array($ad)) {
01534             foreach($ad as $addr) {
01535                 if ($addr_string) $addr_string .= ",";
01536                     $addr_string .= $addr->mailbox . "@" . $addr->host;
01537             }
01538         }
01539         return $addr_string;
01540     }
01541 
01553     private function getModAndParentNames($fhir, &$displayname, &$parent) {
01554         // if mod is already set add the previous part to it as it might be a folder which has
01555         // delimiter in its name
01556         $displayname = (isset($displayname) && strlen($displayname) > 0) ? $displayname = array_pop($fhir).$this->serverdelimiter.$displayname : array_pop($fhir);
01557         $parent = implode($this->serverdelimiter, $fhir);
01558 
01559         if (count($fhir) == 1 || $this->checkIfIMAPFolder($parent)) {
01560             return;
01561         }
01562         //recursion magic
01563         $this->getModAndParentNames($fhir, $displayname, $parent);
01564     }
01565 
01574     private function checkIfIMAPFolder($folderName) {
01575         $parent = imap_list($this->mbox, $this->server, $folderName);
01576         if ($parent === false) return false;
01577         return true;
01578     }
01579 
01589     private function cleanupDate($receiveddate) {
01590         $receiveddate = strtotime(preg_replace("/\(.*\)/", "", $receiveddate));
01591         if ($receiveddate == false || $receiveddate == -1) {
01592             debugLog("Received date is false. Message might be broken.");
01593             return null;
01594         }
01595 
01596         return $receiveddate;
01597     }
01598 
01599 }
01600 
01601 ?>