source: trunk/zpush/backend/imap.php @ 7589

Revision 7589, 76.0 KB checked in by douglas, 11 years ago (diff)

Ticket #3209 - Integrar módulo de sincronização Z-push ao Expresso

Line 
1<?php
2/***********************************************
3* File      :   imap.php
4* Project   :   Z-Push
5* Descr     :   This backend is based on
6*               'BackendDiff' and implements an
7*               IMAP interface
8*
9* Created   :   10.10.2007
10*
11* Copyright 2007 - 2012 Zarafa Deutschland GmbH
12*
13* This program is free software: you can redistribute it and/or modify
14* it under the terms of the GNU Affero General Public License, version 3,
15* as published by the Free Software Foundation with the following additional
16* term according to sec. 7:
17*
18* According to sec. 7 of the GNU Affero General Public License, version 3,
19* the terms of the AGPL are supplemented with the following terms:
20*
21* "Zarafa" is a registered trademark of Zarafa B.V.
22* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
23* The licensing of the Program under the AGPL does not imply a trademark license.
24* Therefore any rights, title and interest in our trademarks remain entirely with us.
25*
26* However, if you propagate an unmodified version of the Program you are
27* allowed to use the term "Z-Push" to indicate that you distribute the Program.
28* Furthermore you may use our trademarks where it is necessary to indicate
29* the intended purpose of a product or service provided you use it in accordance
30* with honest practices in industrial or commercial matters.
31* If you want to propagate modified versions of the Program under the name "Z-Push",
32* you may only do so if you have a written permission by Zarafa Deutschland GmbH
33* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
34*
35* This program is distributed in the hope that it will be useful,
36* but WITHOUT ANY WARRANTY; without even the implied warranty of
37* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
38* GNU Affero General Public License for more details.
39*
40* You should have received a copy of the GNU Affero General Public License
41* along with this program.  If not, see <http://www.gnu.org/licenses/>.
42*
43* Consult LICENSE file for details
44************************************************/
45
46include_once('lib/default/diffbackend/diffbackend.php');
47include_once('include/mimeDecode.php');
48require_once('include/z_RFC822.php');
49
50
51class BackendIMAP extends BackendDiff {
52    protected $wasteID;
53    protected $sentID;
54    protected $server;
55    protected $mbox;
56    protected $mboxFolder;
57    protected $username;
58    protected $domain;
59    protected $serverdelimiter;
60    protected $sinkfolders;
61    protected $sinkstates;
62    protected $excludedFolders; /* fmbiete's contribution r1527, ZP-319 */
63
64    /**----------------------------------------------------------------------------------------------------------
65     * default backend methods
66     */
67
68    /**
69     * Authenticates the user
70     *
71     * @param string        $username
72     * @param string        $domain
73     * @param string        $password
74     *
75     * @access public
76     * @return boolean
77     * @throws FatalException   if php-imap module can not be found
78     */
79    public function Logon($username, $domain, $password) {
80        $this->wasteID = false;
81        $this->sentID = false;
82        $this->server = "{" . IMAP_SERVER . ":" . IMAP_PORT . "/imap" . IMAP_OPTIONS . "}";
83
84        if (!function_exists("imap_open"))
85            throw new FatalException("BackendIMAP(): php-imap module is not installed", 0, null, LOGLEVEL_FATAL);
86
87        /* BEGIN fmbiete's contribution r1527, ZP-319 */
88        $this->excludedFolders = array();
89        if (defined('IMAP_EXCLUDED_FOLDERS')) {
90            $this->excludedFolders = explode("|", IMAP_EXCLUDED_FOLDERS);
91            ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->Logon(): Excluding Folders (%s)", IMAP_EXCLUDED_FOLDERS));
92        }
93        /* END fmbiete's contribution r1527, ZP-319 */
94
95        // open the IMAP-mailbox
96        $this->mbox = @imap_open($this->server , $username, $password, OP_HALFOPEN);
97        $this->mboxFolder = "";
98
99        if ($this->mbox) {
100            ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->Logon(): User '%s' is authenticated on IMAP",$username));
101            $this->username = $username;
102            $this->domain = $domain;
103            // set serverdelimiter
104            $this->serverdelimiter = $this->getServerDelimiter();
105            return true;
106        }
107        else {
108            ZLog::Write(LOGLEVEL_ERROR, "BackendIMAP->Logon(): can't connect: " . imap_last_error());
109            return false;
110        }
111    }
112
113    /**
114     * Logs off
115     * Called before shutting down the request to close the IMAP connection
116     * writes errors to the log
117     *
118     * @access public
119     * @return boolean
120     */
121    public function Logoff() {
122        if ($this->mbox) {
123            // list all errors
124            $errors = imap_errors();
125            if (is_array($errors)) {
126                foreach ($errors as $e) {
127                    if (stripos($e, "fail") !== false) {
128                        $level = LOGLEVEL_WARN;
129                    }
130                    else {
131                        $level = LOGLEVEL_DEBUG;
132                    }
133                    ZLog::Write($level, "BackendIMAP->Logoff(): IMAP said: " . $e);
134                }
135            }
136            @imap_close($this->mbox);
137            ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->Logoff(): IMAP connection closed");
138        }
139        $this->SaveStorages();
140    }
141
142    /**
143     * Sends an e-mail
144     * This messages needs to be saved into the 'sent items' folder
145     *
146     * @param SyncSendMail  $sm     SyncSendMail object
147     *
148     * @access public
149     * @return boolean
150     * @throws StatusException
151     */
152    // TODO implement , $saveInSent = true
153    public function SendMail($sm) {
154        $forward = $reply = (isset($sm->source->itemid) && $sm->source->itemid) ? $sm->source->itemid : false;
155        $parent = false;
156
157        ZLog::Write(LOGLEVEL_DEBUG, sprintf("IMAPBackend->SendMail(): RFC822: %d bytes  forward-id: '%s' reply-id: '%s' parent-id: '%s' SaveInSent: '%s' ReplaceMIME: '%s'",
158                                            strlen($sm->mime), Utils::PrintAsString($sm->forwardflag), Utils::PrintAsString($sm->replyflag),
159                                            Utils::PrintAsString((isset($sm->source->folderid) ? $sm->source->folderid : false)),
160                                            Utils::PrintAsString(($sm->saveinsent)), Utils::PrintAsString(isset($sm->replacemime)) ));
161
162        if (isset($sm->source->folderid) && $sm->source->folderid)
163            // convert parent folder id back to work on an imap-id
164            $parent = $this->getImapIdFromFolderId($sm->source->folderid);
165
166
167        // by splitting the message in several lines we can easily grep later
168        foreach(preg_split("/((\r)?\n)/", $sm->mime) as $rfc822line)
169            ZLog::Write(LOGLEVEL_WBXML, "RFC822: ". $rfc822line);
170
171        $mobj = new Mail_mimeDecode($sm->mime);
172        $message = $mobj->decode(array('decode_headers' => false, 'decode_bodies' => true, 'include_bodies' => true, 'charset' => 'utf-8'));
173
174        $Mail_RFC822 = new Mail_RFC822();
175        $toaddr = $ccaddr = $bccaddr = "";
176        if(isset($message->headers["to"]))
177            $toaddr = $this->parseAddr($Mail_RFC822->parseAddressList($message->headers["to"]));
178        if(isset($message->headers["cc"]))
179            $ccaddr = $this->parseAddr($Mail_RFC822->parseAddressList($message->headers["cc"]));
180        if(isset($message->headers["bcc"]))
181            $bccaddr = $this->parseAddr($Mail_RFC822->parseAddressList($message->headers["bcc"]));
182
183        // save some headers when forwarding mails (content type & transfer-encoding)
184        $headers = "";
185        $forward_h_ct = "";
186        $forward_h_cte = "";
187        $envelopefrom = "";
188
189        $use_orgbody = false;
190
191        // clean up the transmitted headers
192        // remove default headers because we are using imap_mail
193        $changedfrom = false;
194        $returnPathSet = false;
195        $body_base64 = false;
196        $org_charset = "";
197        $org_boundary = false;
198        $multipartmixed = false;
199        foreach($message->headers as $k => $v) {
200            if ($k == "subject" || $k == "to" || $k == "cc" || $k == "bcc")
201                continue;
202
203            if ($k == "content-type") {
204                // if the message is a multipart message, then we should use the sent body
205                if (preg_match("/multipart/i", $v)) {
206                    $use_orgbody = true;
207                    $org_boundary = $message->ctype_parameters["boundary"];
208                }
209
210                // save the original content-type header for the body part when forwarding
211                if ($sm->forwardflag && !$use_orgbody) {
212                    $forward_h_ct = $v;
213                    continue;
214                }
215
216                // set charset always to utf-8
217                $org_charset = $v;
218                $v = preg_replace("/charset=([A-Za-z0-9-\"']+)/", "charset=\"utf-8\"", $v);
219            }
220
221            if ($k == "content-transfer-encoding") {
222                // if the content was base64 encoded, encode the body again when sending
223                if (trim($v) == "base64") $body_base64 = true;
224
225                // save the original encoding header for the body part when forwarding
226                if ($sm->forwardflag) {
227                    $forward_h_cte = $v;
228                    continue;
229                }
230            }
231
232            // check if "from"-header is set, do nothing if it's set
233            // else set it to IMAP_DEFAULTFROM
234            if ($k == "from") {
235                if (trim($v)) {
236                    $changedfrom = true;
237                } elseif (! trim($v) && IMAP_DEFAULTFROM) {
238                    $changedfrom = true;
239                    if      (IMAP_DEFAULTFROM == 'username') $v = $this->username;
240                    else if (IMAP_DEFAULTFROM == 'domain')   $v = $this->domain;
241                    else $v = $this->username . IMAP_DEFAULTFROM;
242                    $envelopefrom = "-f$v";
243                }
244            }
245
246            // check if "Return-Path"-header is set
247            if ($k == "return-path") {
248                $returnPathSet = true;
249                if (! trim($v) && IMAP_DEFAULTFROM) {
250                    if      (IMAP_DEFAULTFROM == 'username') $v = $this->username;
251                    else if (IMAP_DEFAULTFROM == 'domain')   $v = $this->domain;
252                    else $v = $this->username . IMAP_DEFAULTFROM;
253                }
254            }
255
256            // all other headers stay
257            if ($headers) $headers .= "\n";
258            $headers .= ucfirst($k) . ": ". $v;
259        }
260
261        // set "From" header if not set on the device
262        if(IMAP_DEFAULTFROM && !$changedfrom){
263            if      (IMAP_DEFAULTFROM == 'username') $v = $this->username;
264            else if (IMAP_DEFAULTFROM == 'domain')   $v = $this->domain;
265            else $v = $this->username . IMAP_DEFAULTFROM;
266            if ($headers) $headers .= "\n";
267            $headers .= 'From: '.$v;
268            $envelopefrom = "-f$v";
269        }
270
271        // set "Return-Path" header if not set on the device
272        if(IMAP_DEFAULTFROM && !$returnPathSet){
273            if      (IMAP_DEFAULTFROM == 'username') $v = $this->username;
274            else if (IMAP_DEFAULTFROM == 'domain')   $v = $this->domain;
275            else $v = $this->username . IMAP_DEFAULTFROM;
276            if ($headers) $headers .= "\n";
277            $headers .= 'Return-Path: '.$v;
278        }
279
280        // if this is a multipart message with a boundary, we must use the original body
281        if ($use_orgbody) {
282            list(,$body) = $mobj->_splitBodyHeader($sm->mime);
283            $repl_body = $this->getBody($message);
284        }
285        else
286            $body = $this->getBody($message);
287
288        // reply
289        if ($sm->replyflag && $parent) {
290            $this->imap_reopenFolder($parent);
291            // receive entire mail (header + body) to decode body correctly
292            $origmail = @imap_fetchheader($this->mbox, $reply, FT_UID) . @imap_body($this->mbox, $reply, FT_PEEK | FT_UID);
293            if (!$origmail)
294                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);
295
296            $mobj2 = new Mail_mimeDecode($origmail);
297            // receive only body
298            $body .= $this->getBody($mobj2->decode(array('decode_headers' => false, 'decode_bodies' => true, 'include_bodies' => true, 'charset' => 'utf-8')));
299            // unset mimedecoder & origmail - free memory
300            unset($mobj2);
301            unset($origmail);
302        }
303
304        // encode the body to base64 if it was sent originally in base64 by the pda
305        // contrib - chunk base64 encoded body
306        if ($body_base64 && !$sm->forwardflag) $body = chunk_split(base64_encode($body));
307
308
309        // forward
310        if ($sm->forwardflag && $parent) {
311            $this->imap_reopenFolder($parent);
312            // receive entire mail (header + body)
313            $origmail = @imap_fetchheader($this->mbox, $forward, FT_UID) . @imap_body($this->mbox, $forward, FT_PEEK | FT_UID);
314
315            if (!$origmail)
316                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);
317
318            if (!defined('IMAP_INLINE_FORWARD') || IMAP_INLINE_FORWARD === false) {
319                // contrib - chunk base64 encoded body
320                if ($body_base64) $body = chunk_split(base64_encode($body));
321                //use original boundary if it's set
322                $boundary = ($org_boundary) ? $org_boundary : false;
323                // build a new mime message, forward entire old mail as file
324                list($aheader, $body) = $this->mail_attach("forwarded_message.eml",strlen($origmail),$origmail, $body, $forward_h_ct, $forward_h_cte,$boundary);
325                // add boundary headers
326                $headers .= "\n" . $aheader;
327
328            }
329            else {
330                $mobj2 = new Mail_mimeDecode($origmail);
331                $mess2 = $mobj2->decode(array('decode_headers' => true, 'decode_bodies' => true, 'include_bodies' => true, 'charset' => 'utf-8'));
332
333                if (!$use_orgbody)
334                    $nbody = $body;
335                else
336                    $nbody = $repl_body;
337
338                $nbody .= "\r\n\r\n";
339                $nbody .= "-----Original Message-----\r\n";
340                if(isset($mess2->headers['from']))
341                    $nbody .= "From: " . $mess2->headers['from'] . "\r\n";
342                if(isset($mess2->headers['to']) && strlen($mess2->headers['to']) > 0)
343                    $nbody .= "To: " . $mess2->headers['to'] . "\r\n";
344                if(isset($mess2->headers['cc']) && strlen($mess2->headers['cc']) > 0)
345                    $nbody .= "Cc: " . $mess2->headers['cc'] . "\r\n";
346                if(isset($mess2->headers['date']))
347                    $nbody .= "Sent: " . $mess2->headers['date'] . "\r\n";
348                if(isset($mess2->headers['subject']))
349                    $nbody .= "Subject: " . $mess2->headers['subject'] . "\r\n";
350                $nbody .= "\r\n";
351                $nbody .= $this->getBody($mess2);
352
353                if ($body_base64) {
354                    // contrib - chunk base64 encoded body
355                    $nbody = chunk_split(base64_encode($nbody));
356                    if ($use_orgbody)
357                    // contrib - chunk base64 encoded body
358                        $repl_body = chunk_split(base64_encode($repl_body));
359                }
360
361                if ($use_orgbody) {
362                    ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->SendMail(): -------------------");
363                    ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->SendMail(): old:\n'$repl_body'\nnew:\n'$nbody'\nund der body:\n'$body'");
364                    //$body is quoted-printable encoded while $repl_body and $nbody are plain text,
365                    //so we need to decode $body in order replace to take place
366                    $body = str_replace($repl_body, $nbody, quoted_printable_decode($body));
367                }
368                else
369                    $body = $nbody;
370
371
372                if(isset($mess2->parts)) {
373                    $attached = false;
374
375                    if ($org_boundary) {
376                        $att_boundary = $org_boundary;
377                        // cut end boundary from body
378                        $body = substr($body, 0, strrpos($body, "--$att_boundary--"));
379                    }
380                    else {
381                        $att_boundary = strtoupper(md5(uniqid(time())));
382                        // add boundary headers
383                        $headers .= "\n" . "Content-Type: multipart/mixed; boundary=$att_boundary";
384                        $multipartmixed = true;
385                    }
386
387                    foreach($mess2->parts as $part) {
388                        if(isset($part->disposition) && ($part->disposition == "attachment" || $part->disposition == "inline")) {
389
390                            if(isset($part->d_parameters['filename']))
391                                $attname = $part->d_parameters['filename'];
392                            else if(isset($part->ctype_parameters['name']))
393                                $attname = $part->ctype_parameters['name'];
394                            else if(isset($part->headers['content-description']))
395                                $attname = $part->headers['content-description'];
396                            else $attname = "unknown attachment";
397
398                            // ignore html content
399                            if ($part->ctype_primary == "text" && $part->ctype_secondary == "html") {
400                                continue;
401                            }
402                            //
403                            if ($use_orgbody || $attached) {
404                                $body .= $this->enc_attach_file($att_boundary, $attname, strlen($part->body),$part->body, $part->ctype_primary ."/". $part->ctype_secondary);
405                            }
406                            // first attachment
407                            else {
408                                $encmail = $body;
409                                $attached = true;
410                                $body = $this->enc_multipart($att_boundary, $body, $forward_h_ct, $forward_h_cte);
411                                $body .= $this->enc_attach_file($att_boundary, $attname, strlen($part->body),$part->body, $part->ctype_primary ."/". $part->ctype_secondary);
412                            }
413                        }
414                    }
415                    if ($multipartmixed && strpos(strtolower($mess2->headers['content-type']), "alternative") !== false) {
416                        //this happens if a multipart/alternative message is forwarded
417                        //then it's a multipart/mixed message which consists of:
418                        //1. text/plain part which was written on the mobile
419                        //2. multipart/alternative part which is the original message
420                        $body = "This is a message with multiple parts in MIME format.\n--".
421                                $att_boundary.
422                                "\nContent-Type: $forward_h_ct\nContent-Transfer-Encoding: $forward_h_cte\n\n".
423                                (($body_base64) ? chunk_split(base64_encode($message->body)) : rtrim($message->body)).
424                                "\n--".$att_boundary.
425                                "\nContent-Type: {$mess2->headers['content-type']}\n\n".
426                                @imap_body($this->mbox, $forward, FT_PEEK | FT_UID)."\n\n";
427                    }
428                    $body .= "--$att_boundary--\n\n";
429                }
430
431                unset($mobj2);
432            }
433
434            // unset origmail - free memory
435            unset($origmail);
436
437        }
438
439        // remove carriage-returns from body
440        $body = str_replace("\r\n", "\n", $body);
441
442        if (!$multipartmixed) {
443            if (!empty($forward_h_ct)) $headers .= "\nContent-Type: $forward_h_ct";
444            if (!empty($forward_h_cte)) $headers .= "\nContent-Transfer-Encoding: $forward_h_cte";
445        //  if body was quoted-printable, convert it again
446            if (isset($message->headers["content-transfer-encoding"]) && strtolower($message->headers["content-transfer-encoding"]) == "quoted-printable") {
447                $body = quoted_printable_encode($body);
448            }
449        }
450
451        // more debugging
452        ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->SendMail(): parsed message: ". print_r($message,1));
453        ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->SendMail(): headers: $headers");
454        /* BEGIN fmbiete's contribution r1528, ZP-320 */
455        if (isset($message->headers["subject"])) {
456            ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->SendMail(): subject: {$message->headers["subject"]}");
457        }
458        else {
459            ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->SendMail(): subject: no subject set. Set to empty.");
460            $message->headers["subject"] = ""; // added by mku ZP-330
461        }
462        /* END fmbiete's contribution r1528, ZP-320 */
463        ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->SendMail(): body: $body");
464
465        if (!defined('IMAP_USE_IMAPMAIL') || IMAP_USE_IMAPMAIL == true) {
466            // changed by mku ZP-330
467            $send =  @imap_mail ( $toaddr, $message->headers["subject"], $body, $headers, $ccaddr, $bccaddr);
468        }
469        else {
470            if (!empty($ccaddr))  $headers .= "\nCc: $ccaddr";
471            if (!empty($bccaddr)) $headers .= "\nBcc: $bccaddr";
472            // changed by mku ZP-330
473            $send =  @mail ( $toaddr, $message->headers["subject"], $body, $headers, $envelopefrom );
474        }
475
476        // email sent?
477        if (!$send)
478            throw new StatusException(sprintf("BackendIMAP->SendMail(): The email could not be sent. Last IMAP-error: %s", imap_last_error()), SYNC_COMMONSTATUS_MAILSUBMISSIONFAILED);
479
480        // add message to the sent folder
481        // build complete headers
482        $headers .= "\nTo: $toaddr";
483        $headers .= "\nSubject: " . $message->headers["subject"]; // changed by mku ZP-330
484
485        if (!defined('IMAP_USE_IMAPMAIL') || IMAP_USE_IMAPMAIL == true) {
486            if (!empty($ccaddr))  $headers .= "\nCc: $ccaddr";
487            if (!empty($bccaddr)) $headers .= "\nBcc: $bccaddr";
488        }
489        ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->SendMail(): complete headers: $headers");
490
491        $asf = false;
492        if ($this->sentID) {
493            $asf = $this->addSentMessage($this->sentID, $headers, $body);
494        }
495        else if (IMAP_SENTFOLDER) {
496            $asf = $this->addSentMessage(IMAP_SENTFOLDER, $headers, $body);
497            ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->SendMail(): Outgoing mail saved in configured 'Sent' folder '%s': %s", IMAP_SENTFOLDER, Utils::PrintAsString($asf)));
498        }
499        // No Sent folder set, try defaults
500        else {
501            ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->SendMail(): No Sent mailbox set");
502            if($this->addSentMessage("INBOX.Sent", $headers, $body)) {
503                ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->SendMail(): Outgoing mail saved in 'INBOX.Sent'");
504                $asf = true;
505            }
506            else if ($this->addSentMessage("Sent", $headers, $body)) {
507                ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->SendMail(): Outgoing mail saved in 'Sent'");
508                $asf = true;
509            }
510            else if ($this->addSentMessage("Sent Items", $headers, $body)) {
511                ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->SendMail():IMAP-SendMail: Outgoing mail saved in 'Sent Items'");
512                $asf = true;
513            }
514        }
515
516        if (!$asf) {
517            ZLog::Write(LOGLEVEL_ERROR, "BackendIMAP->SendMail(): The email could not be saved to Sent Items folder. Check your configuration.");
518        }
519
520        return $send;
521    }
522
523    /**
524     * Returns the waste basket
525     *
526     * @access public
527     * @return string
528     */
529    public function GetWasteBasket() {
530        // TODO this could be retrieved from the DeviceFolderCache
531        if ($this->wasteID == false) {
532            //try to get the waste basket without doing complete hierarchy sync
533            $wastebaskt = @imap_getmailboxes($this->mbox, $this->server, "Trash");
534            if (isset($wastebaskt[0])) {
535                $this->wasteID = $this->convertImapId(substr($wastebaskt[0]->name, strlen($this->server)));
536                return $this->wasteID;
537            }
538            //try get waste id from hierarchy if it wasn't possible with above for some reason
539            $this->GetHierarchy();
540        }
541        return $this->wasteID;
542    }
543
544    /**
545     * Returns the content of the named attachment as stream. The passed attachment identifier is
546     * the exact string that is returned in the 'AttName' property of an SyncAttachment.
547     * Any information necessary to find the attachment must be encoded in that 'attname' property.
548     * Data is written directly (with print $data;)
549     *
550     * @param string        $attname
551     *
552     * @access public
553     * @return SyncItemOperationsAttachment
554     * @throws StatusException
555     */
556    public function GetAttachmentData($attname) {
557        ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->GetAttachmentData('%s')", $attname));
558
559        list($folderid, $id, $part) = explode(":", $attname);
560
561        if (!$folderid || !$id || !$part)
562            throw new StatusException(sprintf("BackendIMAP->GetAttachmentData('%s'): Error, attachment name key can not be parsed", $attname), SYNC_ITEMOPERATIONSSTATUS_INVALIDATT);
563
564        // convert back to work on an imap-id
565        $folderImapid = $this->getImapIdFromFolderId($folderid);
566
567        $this->imap_reopenFolder($folderImapid);
568        $mail = @imap_fetchheader($this->mbox, $id, FT_UID) . @imap_body($this->mbox, $id, FT_PEEK | FT_UID);
569
570        $mobj = new Mail_mimeDecode($mail);
571        $message = $mobj->decode(array('decode_headers' => true, 'decode_bodies' => true, 'include_bodies' => true, 'charset' => 'utf-8'));
572
573        /* BEGIN fmbiete's contribution r1528, ZP-320 */
574        //trying parts
575        $mparts = $message->parts;
576        for ($i = 0; $i < count($mparts); $i++) {
577            $auxpart = $mparts[$i];
578            //recursively add parts
579            if($auxpart->ctype_primary == "multipart" && ($auxpart->ctype_secondary == "mixed" || $auxpart->ctype_secondary == "alternative"  || $auxpart->ctype_secondary == "related")) {
580                foreach($auxpart->parts as $spart)
581                    $mparts[] = $spart;
582            }
583        }
584        /* END fmbiete's contribution r1528, ZP-320 */
585
586        if (!isset($mparts[$part]->body))
587            throw new StatusException(sprintf("BackendIMAP->GetAttachmentData('%s'): Error, requested part key can not be found: '%d'", $attname, $part), SYNC_ITEMOPERATIONSSTATUS_INVALIDATT);
588
589        // unset mimedecoder & mail
590        unset($mobj);
591        unset($mail);
592
593        include_once('include/stringstreamwrapper.php');
594        $attachment = new SyncItemOperationsAttachment();
595        /* BEGIN fmbiete's contribution r1528, ZP-320 */
596        $attachment->data = StringStreamWrapper::Open($mparts[$part]->body);
597        if (isset($mparts[$part]->ctype_primary) && isset($mparts[$part]->ctype_secondary))
598            $attachment->contenttype = $mparts[$part]->ctype_primary .'/'.$mparts[$part]->ctype_secondary;
599
600        unset($mparts);
601        unset($message);
602
603        ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->GetAttachmentData contenttype %s", $attachment->contenttype));
604        /* END fmbiete's contribution r1528, ZP-320 */
605
606        return $attachment;
607    }
608
609    /**
610     * Indicates if the backend has a ChangesSink.
611     * A sink is an active notification mechanism which does not need polling.
612     * The IMAP backend simulates a sink by polling status information of the folder
613     *
614     * @access public
615     * @return boolean
616     */
617    public function HasChangesSink() {
618        $this->sinkfolders = array();
619        $this->sinkstates = array();
620        return true;
621    }
622
623    /**
624     * The folder should be considered by the sink.
625     * Folders which were not initialized should not result in a notification
626     * of IBacken->ChangesSink().
627     *
628     * @param string        $folderid
629     *
630     * @access public
631     * @return boolean      false if found can not be found
632     */
633    public function ChangesSinkInitialize($folderid) {
634        ZLog::Write(LOGLEVEL_DEBUG, sprintf("IMAPBackend->ChangesSinkInitialize(): folderid '%s'", $folderid));
635
636        $imapid = $this->getImapIdFromFolderId($folderid);
637
638        if ($imapid) {
639            $this->sinkfolders[] = $imapid;
640            return true;
641        }
642
643        return false;
644    }
645
646    /**
647     * The actual ChangesSink.
648     * For max. the $timeout value this method should block and if no changes
649     * are available return an empty array.
650     * If changes are available a list of folderids is expected.
651     *
652     * @param int           $timeout        max. amount of seconds to block
653     *
654     * @access public
655     * @return array
656     */
657    public function ChangesSink($timeout = 30) {
658        $notifications = array();
659        $stopat = time() + $timeout - 1;
660
661        while($stopat > time() && empty($notifications)) {
662            foreach ($this->sinkfolders as $imapid) {
663                $this->imap_reopenFolder($imapid);
664
665                // courier-imap only cleares the status cache after checking
666                @imap_check($this->mbox);
667
668                $status = @imap_status($this->mbox, $this->server . $imapid, SA_ALL);
669                if (!$status) {
670                    ZLog::Write(LOGLEVEL_WARN, sprintf("ChangesSink: could not stat folder '%s': %s ", $this->getFolderIdFromImapId($imapid), imap_last_error()));
671                }
672                else {
673                    $newstate = "M:". $status->messages ."-R:". $status->recent ."-U:". $status->unseen;
674
675                    if (! isset($this->sinkstates[$imapid]) )
676                        $this->sinkstates[$imapid] = $newstate;
677
678                    if ($this->sinkstates[$imapid] != $newstate) {
679                        $notifications[] = $this->getFolderIdFromImapId($imapid);
680                        $this->sinkstates[$imapid] = $newstate;
681                    }
682                }
683            }
684
685            if (empty($notifications))
686                sleep(5);
687        }
688
689        return $notifications;
690    }
691
692
693    /**----------------------------------------------------------------------------------------------------------
694     * implemented DiffBackend methods
695     */
696
697
698    /**
699     * Returns a list (array) of folders.
700     *
701     * @access public
702     * @return array/boolean        false if the list could not be retrieved
703     */
704    public function GetFolderList() {
705        $folders = array();
706
707        $list = @imap_getmailboxes($this->mbox, $this->server, "*");
708        if (is_array($list)) {
709            // reverse list to obtain folders in right order
710            $list = array_reverse($list);
711
712            foreach ($list as $val) {
713                /* BEGIN fmbiete's contribution r1527, ZP-319 */
714                // don't return the excluded folders
715                $notExcluded = true;
716                for ($i = 0, $cnt = count($this->excludedFolders); $notExcluded && $i < $cnt; $i++) { // expr1, expr2 modified by mku ZP-329
717                    // fix exclude folders with special chars by mku ZP-329
718                    if (strpos(strtolower($val->name), strtolower(Utils::Utf7_iconv_encode(Utils::Utf8_to_utf7($this->excludedFolders[$i])))) !== false) {
719                        $notExcluded = false;
720                        ZLog::Write(LOGLEVEL_DEBUG, sprintf("Pattern: <%s> found, excluding folder: '%s'", $this->excludedFolders[$i], $val->name)); // sprintf added by mku ZP-329
721                    }
722                }
723
724                if ($notExcluded) {
725                    $box = array();
726                    // cut off serverstring
727                    $imapid = substr($val->name, strlen($this->server));
728                    $box["id"] = $this->convertImapId($imapid);
729
730                    $fhir = explode($val->delimiter, $imapid);
731                    if (count($fhir) > 1) {
732                        $this->getModAndParentNames($fhir, $box["mod"], $imapparent);
733                        $box["parent"] = $this->convertImapId($imapparent);
734                    }
735                    else {
736                        $box["mod"] = $imapid;
737                        $box["parent"] = "0";
738                    }
739                    $folders[]=$box;
740                    /* END fmbiete's contribution r1527, ZP-319 */
741                }
742            }
743        }
744        else {
745            ZLog::Write(LOGLEVEL_WARN, "BackendIMAP->GetFolderList(): imap_list failed: " . imap_last_error());
746            return false;
747        }
748
749        return $folders;
750    }
751
752    /**
753     * Returns an actual SyncFolder object
754     *
755     * @param string        $id           id of the folder
756     *
757     * @access public
758     * @return object       SyncFolder with information
759     */
760    public function GetFolder($id) {
761        $folder = new SyncFolder();
762        $folder->serverid = $id;
763
764        // convert back to work on an imap-id
765        $imapid = $this->getImapIdFromFolderId($id);
766
767        // explode hierarchy
768        $fhir = explode($this->serverdelimiter, $imapid);
769
770        // compare on lowercase strings
771        $lid = strtolower($imapid);
772// TODO WasteID or SentID could be saved for later ussage
773        if($lid == "inbox") {
774            $folder->parentid = "0"; // Root
775            $folder->displayname = "Inbox";
776            $folder->type = SYNC_FOLDER_TYPE_INBOX;
777        }
778        // Zarafa IMAP-Gateway outputs
779        else if($lid == "drafts") {
780            $folder->parentid = "0";
781            $folder->displayname = "Drafts";
782            $folder->type = SYNC_FOLDER_TYPE_DRAFTS;
783        }
784        else if($lid == "trash") {
785            $folder->parentid = "0";
786            $folder->displayname = "Trash";
787            $folder->type = SYNC_FOLDER_TYPE_WASTEBASKET;
788            $this->wasteID = $id;
789        }
790        else if($lid == "sent" || $lid == "sent items" || $lid == IMAP_SENTFOLDER) {
791            $folder->parentid = "0";
792            $folder->displayname = "Sent";
793            $folder->type = SYNC_FOLDER_TYPE_SENTMAIL;
794            $this->sentID = $id;
795        }
796        // courier-imap outputs and cyrus-imapd outputs
797        else if($lid == "inbox.drafts" || $lid == "inbox/drafts") {
798            $folder->parentid = $this->convertImapId($fhir[0]);
799            $folder->displayname = "Drafts";
800            $folder->type = SYNC_FOLDER_TYPE_DRAFTS;
801        }
802        else if($lid == "inbox.trash" || $lid == "inbox/trash") {
803            $folder->parentid = $this->convertImapId($fhir[0]);
804            $folder->displayname = "Trash";
805            $folder->type = SYNC_FOLDER_TYPE_WASTEBASKET;
806            $this->wasteID = $id;
807        }
808        else if($lid == "inbox.sent" || $lid == "inbox/sent") {
809            $folder->parentid = $this->convertImapId($fhir[0]);
810            $folder->displayname = "Sent";
811            $folder->type = SYNC_FOLDER_TYPE_SENTMAIL;
812            $this->sentID = $id;
813        }
814
815        // define the rest as other-folders
816        else {
817            if (count($fhir) > 1) {
818                $this->getModAndParentNames($fhir, $folder->displayname, $imapparent);
819                $folder->parentid = $this->convertImapId($imapparent);
820                $folder->displayname = Utils::Utf7_to_utf8(Utils::Utf7_iconv_decode($folder->displayname));
821            }
822            else {
823                $folder->displayname = Utils::Utf7_to_utf8(Utils::Utf7_iconv_decode($imapid));
824                $folder->parentid = "0";
825            }
826            $folder->type = SYNC_FOLDER_TYPE_USER_MAIL;
827        }
828
829        //advanced debugging
830        ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->GetFolder('%s'): '%s'", $id, $folder));
831
832        return $folder;
833    }
834
835    /**
836     * Returns folder stats. An associative array with properties is expected.
837     *
838     * @param string        $id             id of the folder
839     *
840     * @access public
841     * @return array
842     */
843    public function StatFolder($id) {
844        $folder = $this->GetFolder($id);
845
846        $stat = array();
847        $stat["id"] = $id;
848        $stat["parent"] = $folder->parentid;
849        $stat["mod"] = $folder->displayname;
850
851        return $stat;
852    }
853
854    /**
855     * Creates or modifies a folder
856     * The folder type is ignored in IMAP, as all folders are Email folders
857     *
858     * @param string        $folderid       id of the parent folder
859     * @param string        $oldid          if empty -> new folder created, else folder is to be renamed
860     * @param string        $displayname    new folder name (to be created, or to be renamed to)
861     * @param int           $type           folder type
862     *
863     * @access public
864     * @return boolean                      status
865     * @throws StatusException              could throw specific SYNC_FSSTATUS_* exceptions
866     *
867     */
868    public function ChangeFolder($folderid, $oldid, $displayname, $type){
869        ZLog::Write(LOGLEVEL_INFO, sprintf("BackendIMAP->ChangeFolder('%s','%s','%s','%s')", $folderid, $oldid, $displayname, $type));
870
871        // go to parent mailbox
872        $this->imap_reopenFolder($folderid);
873
874        // build name for new mailboxBackendMaildir
875        $displayname = Utils::Utf7_iconv_encode(Utils::Utf8_to_utf7($displayname));
876        $newname = $this->server . $folderid . $this->serverdelimiter . $displayname;
877
878        $csts = false;
879        // if $id is set => rename mailbox, otherwise create
880        if ($oldid) {
881            // rename doesn't work properly with IMAP
882            // the activesync client doesn't support a 'changing ID'
883            // TODO this would be solved by implementing hex ids (Mantis #459)
884            //$csts = imap_renamemailbox($this->mbox, $this->server . imap_utf7_encode(str_replace(".", $this->serverdelimiter, $oldid)), $newname);
885        }
886        else {
887            $csts = @imap_createmailbox($this->mbox, $newname);
888        }
889        if ($csts) {
890            return $this->StatFolder($folderid . $this->serverdelimiter . $displayname);
891        }
892        else
893            return false;
894    }
895
896    /**
897     * Deletes a folder
898     *
899     * @param string        $id
900     * @param string        $parent         is normally false
901     *
902     * @access public
903     * @return boolean                      status - false if e.g. does not exist
904     * @throws StatusException              could throw specific SYNC_FSSTATUS_* exceptions
905     *
906     */
907    public function DeleteFolder($id, $parentid){
908        // TODO implement
909        return false;
910    }
911
912    /**
913     * Returns a list (array) of messages
914     *
915     * @param string        $folderid       id of the parent folder
916     * @param long          $cutoffdate     timestamp in the past from which on messages should be returned
917     *
918     * @access public
919     * @return array/false  array with messages or false if folder is not available
920     */
921    public function GetMessageList($folderid, $cutoffdate) {
922        ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->GetMessageList('%s','%s')", $folderid, $cutoffdate));
923
924        $folderid = $this->getImapIdFromFolderId($folderid);
925
926        if ($folderid == false)
927            throw new StatusException("Folderid not found in cache", SYNC_STATUS_FOLDERHIERARCHYCHANGED);
928
929        $messages = array();
930        $this->imap_reopenFolder($folderid, true);
931
932        $sequence = "1:*";
933        if ($cutoffdate > 0) {
934            $search = @imap_search($this->mbox, "SINCE ". date("d-M-Y", $cutoffdate));
935            if ($search !== false)
936                $sequence = implode(",", $search);
937        }
938        ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->GetMessageList(): searching with sequence '%s'", $sequence));
939        $overviews = @imap_fetch_overview($this->mbox, $sequence);
940
941        if (!$overviews || !is_array($overviews)) {
942            ZLog::Write(LOGLEVEL_WARN, sprintf("BackendIMAP->GetMessageList('%s','%s'): Failed to retrieve overview: %s",$folderid, $cutoffdate, imap_last_error()));
943            return $messages;
944        }
945
946        foreach($overviews as $overview) {
947            $date = "";
948            $vars = get_object_vars($overview);
949            if (array_key_exists( "date", $vars)) {
950                // message is out of range for cutoffdate, ignore it
951                if ($this->cleanupDate($overview->date) < $cutoffdate) continue;
952                $date = $overview->date;
953            }
954
955            // cut of deleted messages
956            if (array_key_exists("deleted", $vars) && $overview->deleted)
957                continue;
958
959            if (array_key_exists("uid", $vars)) {
960                $message = array();
961                $message["mod"] = $date;
962                $message["id"] = $overview->uid;
963                // 'seen' aka 'read' is the only flag we want to know about
964                $message["flags"] = 0;
965
966                if(array_key_exists( "seen", $vars) && $overview->seen)
967                    $message["flags"] = 1;
968
969                array_push($messages, $message);
970            }
971        }
972        return $messages;
973    }
974
975    /**
976     * Returns the actual SyncXXX object type.
977     *
978     * @param string            $folderid           id of the parent folder
979     * @param string            $id                 id of the message
980     * @param ContentParameters $contentparameters  parameters of the requested message (truncation, mimesupport etc)
981     *
982     * @access public
983     * @return object/false     false if the message could not be retrieved
984     */
985    public function GetMessage($folderid, $id, $contentparameters) {
986        $truncsize = Utils::GetTruncSize($contentparameters->GetTruncation());
987        $mimesupport = $contentparameters->GetMimeSupport();
988        $bodypreference = $contentparameters->GetBodyPreference(); /* fmbiete's contribution r1528, ZP-320 */
989        ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->GetMessage('%s','%s')", $folderid,  $id));
990
991        $folderImapid = $this->getImapIdFromFolderId($folderid);
992
993        // Get flags, etc
994        $stat = $this->StatMessage($folderid, $id);
995
996        if ($stat) {
997            $this->imap_reopenFolder($folderImapid);
998            $mail = @imap_fetchheader($this->mbox, $id, FT_UID) . @imap_body($this->mbox, $id, FT_PEEK | FT_UID);
999
1000            $mobj = new Mail_mimeDecode($mail);
1001            $message = $mobj->decode(array('decode_headers' => true, 'decode_bodies' => true, 'include_bodies' => true, 'charset' => 'utf-8'));
1002
1003            /* BEGIN fmbiete's contribution r1528, ZP-320 */
1004            $output = new SyncMail();
1005
1006            //Select body type preference
1007            $bpReturnType = SYNC_BODYPREFERENCE_PLAIN;
1008            if ($bodypreference !== false) {
1009                $bpReturnType = Utils::GetBodyPreferenceBestMatch($bodypreference); // changed by mku ZP-330
1010            }
1011            ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->GetMessage - getBodyPreferenceBestMatch: %d", $bpReturnType));
1012
1013            //Get body data
1014            $this->getBodyRecursive($message, "plain", $plainBody);
1015            $this->getBodyRecursive($message, "html", $htmlBody);
1016            if ($plainBody == "") {
1017                $plainBody = Utils::ConvertHtmlToText($htmlBody);
1018            }
1019            $htmlBody = str_replace("\n","\r\n", str_replace("\r","",$htmlBody));
1020            $plainBody = str_replace("\n","\r\n", str_replace("\r","",$plainBody));
1021
1022            if (Request::GetProtocolVersion() >= 12.0) {
1023                $output->asbody = new SyncBaseBody();
1024
1025                switch($bpReturnType) {
1026                    case SYNC_BODYPREFERENCE_PLAIN:
1027                        $output->asbody->data = $plainBody;
1028                        break;
1029                    case SYNC_BODYPREFERENCE_HTML:
1030                        if ($htmlBody == "") {
1031                            $output->asbody->data = $plainBody;
1032                            $bpReturnType = SYNC_BODYPREFERENCE_PLAIN;
1033                        }
1034                        else {
1035                            $output->asbody->data = $htmlBody;
1036                        }
1037                        break;
1038                    case SYNC_BODYPREFERENCE_MIME:
1039                        //We don't need to create a new MIME mail, we already have one!!
1040                        $output->asbody->data = $mail;
1041                        break;
1042                    case SYNC_BODYPREFERENCE_RTF:
1043                        ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->GetMessage RTF Format NOT CHECKED");
1044                        $output->asbody->data = base64_encode($plainBody);
1045                        break;
1046                }
1047                // truncate body, if requested
1048                if(strlen($output->asbody->data) > $truncsize) {
1049                    $output->asbody->data = Utils::Utf8_truncate($output->asbody->data, $truncsize);
1050                    $output->asbody->truncated = 1;
1051                }
1052
1053                $output->asbody->type = $bpReturnType;
1054                $output->nativebodytype = $bpReturnType;
1055                $output->asbody->estimatedDataSize = strlen($output->asbody->data);
1056
1057                $bpo = $contentparameters->BodyPreference($output->asbody->type);
1058                if (Request::GetProtocolVersion() >= 14.0 && $bpo->GetPreview()) {
1059                    $output->asbody->preview = Utils::Utf8_truncate(Utils::ConvertHtmlToText($plainBody), $bpo->GetPreview());
1060                }
1061                else {
1062                    $output->asbody->truncated = 0;
1063                }
1064            }
1065            /* END fmbiete's contribution r1528, ZP-320 */
1066            else { // ASV_2.5
1067                $output->bodytruncated = 0;
1068                /* BEGIN fmbiete's contribution r1528, ZP-320 */
1069                if ($bpReturnType == SYNC_BODYPREFERENCE_MIME) {
1070                    if (strlen($mail) > $truncsize) {
1071                        $output->mimedata = Utils::Utf8_truncate($mail, $truncsize);
1072                        $output->mimetruncated = 1;
1073                    }
1074                    else {
1075                        $output->mimetruncated = 0;
1076                        $output->mimedata = $mail;
1077                    }
1078                    $output->mimesize = strlen($output->mimedata);
1079                }
1080                else {
1081                    // truncate body, if requested
1082                    if (strlen($plainBody) > $truncsize) {
1083                        $output->body = Utils::Utf8_truncate($plainBody, $truncsize);
1084                        $output->bodytruncated = 1;
1085                    }
1086                    else {
1087                        $output->body = $plainBody;
1088                        $output->bodytruncated = 0;
1089                    }
1090                    $output->bodysize = strlen($output->body);
1091                }
1092                /* END fmbiete's contribution r1528, ZP-320 */
1093            }
1094
1095            $output->datereceived = isset($message->headers["date"]) ? $this->cleanupDate($message->headers["date"]) : null;
1096            $output->messageclass = "IPM.Note";
1097            $output->subject = isset($message->headers["subject"]) ? $message->headers["subject"] : "";
1098            $output->read = $stat["flags"];
1099            $output->from = isset($message->headers["from"]) ? $message->headers["from"] : null;
1100
1101            /* BEGIN fmbiete's contribution r1528, ZP-320 */
1102            if (isset($message->headers["thread-topic"])) {
1103                $output->threadtopic = $message->headers["thread-topic"];
1104            }
1105
1106            // Language Code Page ID: http://msdn.microsoft.com/en-us/library/windows/desktop/dd317756%28v=vs.85%29.aspx
1107            $output->internetcpid = INTERNET_CPID_UTF8;
1108            if (Request::GetProtocolVersion() >= 12.0) {
1109                $output->contentclass = "urn:content-classes:message";
1110            }
1111            /* END fmbiete's contribution r1528, ZP-320 */
1112
1113            $Mail_RFC822 = new Mail_RFC822();
1114            $toaddr = $ccaddr = $replytoaddr = array();
1115            if(isset($message->headers["to"]))
1116                $toaddr = $Mail_RFC822->parseAddressList($message->headers["to"]);
1117            if(isset($message->headers["cc"]))
1118                $ccaddr = $Mail_RFC822->parseAddressList($message->headers["cc"]);
1119            if(isset($message->headers["reply_to"]))
1120                $replytoaddr = $Mail_RFC822->parseAddressList($message->headers["reply_to"]);
1121
1122            $output->to = array();
1123            $output->cc = array();
1124            $output->reply_to = array();
1125            foreach(array("to" => $toaddr, "cc" => $ccaddr, "reply_to" => $replytoaddr) as $type => $addrlist) {
1126                foreach($addrlist as $addr) {
1127                    $address = $addr->mailbox . "@" . $addr->host;
1128                    $name = $addr->personal;
1129
1130                    if (!isset($output->displayto) && $name != "")
1131                        $output->displayto = $name;
1132
1133                    if($name == "" || $name == $address)
1134                        $fulladdr = w2u($address);
1135                    else {
1136                        if (substr($name, 0, 1) != '"' && substr($name, -1) != '"') {
1137                            $fulladdr = "\"" . w2u($name) ."\" <" . w2u($address) . ">";
1138                        }
1139                        else {
1140                            $fulladdr = w2u($name) ." <" . w2u($address) . ">";
1141                        }
1142                    }
1143
1144                    array_push($output->$type, $fulladdr);
1145                }
1146            }
1147
1148            // convert mime-importance to AS-importance
1149            if (isset($message->headers["x-priority"])) {
1150                $mimeImportance =  preg_replace("/\D+/", "", $message->headers["x-priority"]);
1151                //MAIL 1 - most important, 3 - normal, 5 - lowest
1152                //AS 0 - low, 1 - normal, 2 - important
1153                if ($mimeImportance > 3)
1154                    $output->importance = 0;
1155                if ($mimeImportance == 3)
1156                    $output->importance = 1;
1157                if ($mimeImportance < 3)
1158                    $output->importance = 2;
1159            } else { /* fmbiete's contribution r1528, ZP-320 */
1160                $output->importance = 1;
1161            }
1162
1163            // Attachments are not needed for MIME messages
1164            if($bpReturnType != SYNC_BODYPREFERENCE_MIME && isset($message->parts)) {
1165                $mparts = $message->parts;
1166                for ($i=0; $i<count($mparts); $i++) {
1167                    $part = $mparts[$i];
1168                    //recursively add parts
1169                    if($part->ctype_primary == "multipart" && ($part->ctype_secondary == "mixed" || $part->ctype_secondary == "alternative"  || $part->ctype_secondary == "related")) {
1170                        foreach($part->parts as $spart)
1171                            $mparts[] = $spart;
1172                        continue;
1173                    }
1174                    //add part as attachment if it's disposition indicates so or if it is not a text part
1175                    if ((isset($part->disposition) && ($part->disposition == "attachment" || $part->disposition == "inline")) ||
1176                        (isset($part->ctype_primary) && $part->ctype_primary != "text")) {
1177
1178                        if(isset($part->d_parameters['filename']))
1179                            $attname = $part->d_parameters['filename'];
1180                        else if(isset($part->ctype_parameters['name']))
1181                            $attname = $part->ctype_parameters['name'];
1182                        else if(isset($part->headers['content-description']))
1183                            $attname = $part->headers['content-description'];
1184                        else $attname = "unknown attachment";
1185
1186                        /* BEGIN fmbiete's contribution r1528, ZP-320 */
1187                        if (Request::GetProtocolVersion() >= 12.0) {
1188                            if (!isset($output->asattachments) || !is_array($output->asattachments))
1189                                $output->asattachments = array();
1190
1191                            $attachment = new SyncBaseAttachment();
1192
1193                            $attachment->estimatedDataSize = isset($part->d_parameters['size']) ? $part->d_parameters['size'] : isset($part->body) ? strlen($part->body) : 0;
1194
1195                            $attachment->displayname = $attname;
1196                            $attachment->filereference = $folderid . ":" . $id . ":" . $i;
1197                            $attachment->method = 1; //Normal attachment
1198                            $attachment->contentid = isset($part->headers['content-id']) ? str_replace("<", "", str_replace(">", "", $part->headers['content-id'])) : "";
1199                            if (isset($part->disposition) && $part->disposition == "inline") {
1200                                $attachment->isinline = 1;
1201                            }
1202                            else {
1203                                $attachment->isinline = 0;
1204                            }
1205
1206                            array_push($output->asattachments, $attachment);
1207                        }
1208                        else { //ASV_2.5
1209                            if (!isset($output->attachments) || !is_array($output->attachments))
1210                                $output->attachments = array();
1211
1212                            $attachment = new SyncAttachment();
1213
1214                            $attachment->attsize = isset($part->d_parameters['size']) ? $part->d_parameters['size'] : isset($part->body) ? strlen($part->body) : 0;
1215
1216                            $attachment->displayname = $attname;
1217                            $attachment->attname = $folderid . ":" . $id . ":" . $i;
1218                            $attachment->attmethod = 1;
1219                            $attachment->attoid = isset($part->headers['content-id']) ? str_replace("<", "", str_replace(">", "", $part->headers['content-id'])) : "";
1220
1221                            array_push($output->attachments, $attachment);
1222                        }
1223                        /* END fmbiete's contribution r1528, ZP-320 */
1224                    }
1225                }
1226            }
1227            // unset mimedecoder & mail
1228            unset($mobj);
1229            unset($mail);
1230            return $output;
1231        }
1232
1233        return false;
1234    }
1235
1236    /**
1237     * Returns message stats, analogous to the folder stats from StatFolder().
1238     *
1239     * @param string        $folderid       id of the folder
1240     * @param string        $id             id of the message
1241     *
1242     * @access public
1243     * @return array/boolean
1244     */
1245    public function StatMessage($folderid, $id) {
1246        ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->StatMessage('%s','%s')", $folderid,  $id));
1247        $folderImapid = $this->getImapIdFromFolderId($folderid);
1248
1249        $this->imap_reopenFolder($folderImapid);
1250        $overview = @imap_fetch_overview( $this->mbox , $id , FT_UID);
1251
1252        if (!$overview) {
1253            ZLog::Write(LOGLEVEL_WARN, sprintf("BackendIMAP->StatMessage('%s','%s'): Failed to retrieve overview: %s", $folderid,  $id, imap_last_error()));
1254            return false;
1255        }
1256
1257        // check if variables for this overview object are available
1258        $vars = get_object_vars($overview[0]);
1259
1260        // without uid it's not a valid message
1261        if (! array_key_exists( "uid", $vars)) return false;
1262
1263        $entry = array();
1264        $entry["mod"] = (array_key_exists( "date", $vars)) ? $overview[0]->date : "";
1265        $entry["id"] = $overview[0]->uid;
1266        // 'seen' aka 'read' is the only flag we want to know about
1267        $entry["flags"] = 0;
1268
1269        if(array_key_exists( "seen", $vars) && $overview[0]->seen)
1270            $entry["flags"] = 1;
1271
1272        return $entry;
1273    }
1274
1275    /**
1276     * Called when a message has been changed on the mobile.
1277     * Added support for FollowUp flag
1278     *
1279     * @param string        $folderid       id of the folder
1280     * @param string        $id             id of the message
1281     * @param SyncXXX       $message        the SyncObject containing a message
1282     *
1283     * @access public
1284     * @return array                        same return value as StatMessage()
1285     * @throws StatusException              could throw specific SYNC_STATUS_* exceptions
1286     */
1287    public function ChangeMessage($folderid, $id, $message) {
1288        ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->ChangeMessage('%s','%s','%s')", $folderid, $id, get_class($message)));
1289
1290        /* BEGIN fmbiete's contribution r1529, ZP-321 */
1291        if (isset($message->flag)) {
1292            ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->ChangeMessage('Setting flag')"));
1293
1294            $folderImapid = $this->getImapIdFromFolderId($folderid);
1295
1296            $this->imap_reopenFolder($folderImapid);
1297
1298            if (isset($message->flag->flagstatus) && $message->flag->flagstatus == 2) {
1299                ZLog::Write(LOGLEVEL_DEBUG, "Set On FollowUp -> IMAP Flagged");
1300                $status = @imap_setflag_full($this->mbox, $id, "\\Flagged",ST_UID);
1301            }
1302            else {
1303                ZLog::Write(LOGLEVEL_DEBUG, "Clearing Flagged");
1304                $status = @imap_clearflag_full ( $this->mbox, $id, "\\Flagged", ST_UID);
1305            }
1306
1307            if ($status) {
1308                ZLog::Write(LOGLEVEL_DEBUG, "Flagged changed");
1309            }
1310            else {
1311                ZLog::Write(LOGLEVEL_DEBUG, "Flagged failed");
1312            }
1313        }
1314
1315        return $this->StatMessage($folderid, $id);
1316        /* END fmbiete's contribution r1529, ZP-321 */
1317    }
1318
1319    /**
1320     * Changes the 'read' flag of a message on disk
1321     *
1322     * @param string        $folderid       id of the folder
1323     * @param string        $id             id of the message
1324     * @param int           $flags          read flag of the message
1325     *
1326     * @access public
1327     * @return boolean                      status of the operation
1328     * @throws StatusException              could throw specific SYNC_STATUS_* exceptions
1329     */
1330    public function SetReadFlag($folderid, $id, $flags) {
1331        ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->SetReadFlag('%s','%s','%s')", $folderid, $id, $flags));
1332        $folderImapid = $this->getImapIdFromFolderId($folderid);
1333
1334        $this->imap_reopenFolder($folderImapid);
1335
1336        if ($flags == 0) {
1337            // set as "Unseen" (unread)
1338            $status = @imap_clearflag_full ( $this->mbox, $id, "\\Seen", ST_UID);
1339        } else {
1340            // set as "Seen" (read)
1341            $status = @imap_setflag_full($this->mbox, $id, "\\Seen",ST_UID);
1342        }
1343
1344        return $status;
1345    }
1346
1347    /**
1348     * Called when the user has requested to delete (really delete) a message
1349     *
1350     * @param string        $folderid       id of the folder
1351     * @param string        $id             id of the message
1352     *
1353     * @access public
1354     * @return boolean                      status of the operation
1355     * @throws StatusException              could throw specific SYNC_STATUS_* exceptions
1356     */
1357    public function DeleteMessage($folderid, $id) {
1358        ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->DeleteMessage('%s','%s')", $folderid, $id));
1359        $folderImapid = $this->getImapIdFromFolderId($folderid);
1360
1361        $this->imap_reopenFolder($folderImapid);
1362        $s1 = @imap_delete ($this->mbox, $id, FT_UID);
1363        $s11 = @imap_setflag_full($this->mbox, $id, "\\Deleted", FT_UID);
1364        $s2 = @imap_expunge($this->mbox);
1365
1366        ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->DeleteMessage('%s','%s'): result: s-delete: '%s' s-expunge: '%s' setflag: '%s'", $folderid, $id, $s1, $s2, $s11));
1367
1368        return ($s1 && $s2 && $s11);
1369    }
1370
1371    /**
1372     * Called when the user moves an item on the PDA from one folder to another
1373     *
1374     * @param string        $folderid       id of the source folder
1375     * @param string        $id             id of the message
1376     * @param string        $newfolderid    id of the destination folder
1377     *
1378     * @access public
1379     * @return boolean                      status of the operation
1380     * @throws StatusException              could throw specific SYNC_MOVEITEMSSTATUS_* exceptions
1381     */
1382    public function MoveMessage($folderid, $id, $newfolderid) {
1383        ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->MoveMessage('%s','%s','%s')", $folderid, $id, $newfolderid));
1384        $folderImapid = $this->getImapIdFromFolderId($folderid);
1385        $newfolderImapid = $this->getImapIdFromFolderId($newfolderid);
1386
1387
1388        $this->imap_reopenFolder($folderImapid);
1389
1390        // TODO this should throw a StatusExceptions on errors like SYNC_MOVEITEMSSTATUS_SAMESOURCEANDDEST,SYNC_MOVEITEMSSTATUS_INVALIDSOURCEID,SYNC_MOVEITEMSSTATUS_CANNOTMOVE
1391
1392        // read message flags
1393        $overview = @imap_fetch_overview ( $this->mbox , $id, FT_UID);
1394
1395        if (!$overview)
1396            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);
1397        else {
1398            // get next UID for destination folder
1399            // when moving a message we have to announce through ActiveSync the new messageID in the
1400            // destination folder. This is a "guessing" mechanism as IMAP does not inform that value.
1401            // when lots of simultaneous operations happen in the destination folder this could fail.
1402            // in the worst case the moved message is displayed twice on the mobile.
1403            $destStatus = imap_status($this->mbox, $this->server . $newfolderImapid, SA_ALL);
1404            if (!$destStatus)
1405                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);
1406
1407            $newid = $destStatus->uidnext;
1408
1409            // move message
1410            $s1 = imap_mail_move($this->mbox, $id, $newfolderImapid, CP_UID);
1411            if (! $s1)
1412                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);
1413
1414
1415            // delete message in from-folder
1416            $s2 = imap_expunge($this->mbox);
1417
1418            // open new folder
1419            $stat = $this->imap_reopenFolder($newfolderImapid);
1420            if (! $s1)
1421                throw new StatusException(sprintf("BackendIMAP->MoveMessage('%s','%s','%s'): Error, openeing the destination folder: %s", $folderid, $id, $newfolderid, imap_last_error()), SYNC_MOVEITEMSSTATUS_CANNOTMOVE);
1422
1423
1424            // remove all flags
1425            $s3 = @imap_clearflag_full ($this->mbox, $newid, "\\Seen \\Answered \\Flagged \\Deleted \\Draft", FT_UID);
1426            $newflags = "";
1427            if ($overview[0]->seen) $newflags .= "\\Seen";
1428            if ($overview[0]->flagged) $newflags .= " \\Flagged";
1429            if ($overview[0]->answered) $newflags .= " \\Answered";
1430            $s4 = @imap_setflag_full ($this->mbox, $newid, $newflags, FT_UID);
1431
1432            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)));
1433
1434            // return the new id "as string""
1435            return $newid . "";
1436        }
1437    }
1438
1439
1440    /**----------------------------------------------------------------------------------------------------------
1441     * protected IMAP methods
1442     */
1443
1444    /**
1445     * Unmasks a hex folderid and returns the imap folder id
1446     *
1447     * @param string        $folderid       hex folderid generated by convertImapId()
1448     *
1449     * @access protected
1450     * @return string       imap folder id
1451     */
1452    protected function getImapIdFromFolderId($folderid) {
1453        $this->InitializePermanentStorage();
1454
1455        if (isset($this->permanentStorage->fmFidFimap)) {
1456            if (isset($this->permanentStorage->fmFidFimap[$folderid])) {
1457                $imapId = $this->permanentStorage->fmFidFimap[$folderid];
1458                ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->getImapIdFromFolderId('%s') = %s", $folderid, $imapId));
1459                return $imapId;
1460            }
1461            else {
1462                ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->getImapIdFromFolderId('%s') = %s", $folderid, 'not found'));
1463                return false;
1464            }
1465        }
1466        ZLog::Write(LOGLEVEL_WARN, sprintf("BackendIMAP->getImapIdFromFolderId('%s') = %s", $folderid, 'not initialized!'));
1467        return false;
1468    }
1469
1470    /**
1471     * Retrieves a hex folderid previousily masked imap
1472     *
1473     * @param string        $imapid         Imap folder id
1474     *
1475     * @access protected
1476     * @return string       hex folder id
1477     */
1478    protected function getFolderIdFromImapId($imapid) {
1479        $this->InitializePermanentStorage();
1480
1481        if (isset($this->permanentStorage->fmFimapFid)) {
1482            if (isset($this->permanentStorage->fmFimapFid[$imapid])) {
1483                $folderid = $this->permanentStorage->fmFimapFid[$imapid];
1484                ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->getFolderIdFromImapId('%s') = %s", $imapid, $folderid));
1485                return $folderid;
1486            }
1487            else {
1488                ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->getFolderIdFromImapId('%s') = %s", $imapid, 'not found'));
1489                return false;
1490            }
1491        }
1492        ZLog::Write(LOGLEVEL_WARN, sprintf("BackendIMAP->getFolderIdFromImapId('%s') = %s", $imapid, 'not initialized!'));
1493        return false;
1494    }
1495
1496    /**
1497     * Masks a imap folder id into a generated hex folderid
1498     * The method getFolderIdFromImapId() is consulted so that an
1499     * imapid always returns the same hex folder id
1500     *
1501     * @param string        $imapid         Imap folder id
1502     *
1503     * @access protected
1504     * @return string       hex folder id
1505     */
1506    protected function convertImapId($imapid) {
1507        $this->InitializePermanentStorage();
1508
1509        // check if this imap id was converted before
1510        $folderid = $this->getFolderIdFromImapId($imapid);
1511
1512        // nothing found, so generate a new id and put it in the cache
1513        if (!$folderid) {
1514            // generate folderid and add it to the mapping
1515            $folderid = sprintf('%04x%04x', mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ));
1516
1517            // folderId to folderImap mapping
1518            if (!isset($this->permanentStorage->fmFidFimap))
1519                $this->permanentStorage->fmFidFimap = array();
1520
1521            $a = $this->permanentStorage->fmFidFimap;
1522            $a[$folderid] = $imapid;
1523            $this->permanentStorage->fmFidFimap = $a;
1524
1525            // folderImap to folderid mapping
1526            if (!isset($this->permanentStorage->fmFimapFid))
1527                $this->permanentStorage->fmFimapFid = array();
1528
1529            $b = $this->permanentStorage->fmFimapFid;
1530            $b[$imapid] = $folderid;
1531            $this->permanentStorage->fmFimapFid = $b;
1532        }
1533
1534        ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->convertImapId('%s') = %s", $imapid, $folderid));
1535
1536        return $folderid;
1537    }
1538
1539
1540    /**
1541     * Parses the message and return only the plaintext body
1542     *
1543     * @param string        $message        html message
1544     *
1545     * @access protected
1546     * @return string       plaintext message
1547     */
1548    protected function getBody($message) {
1549        $body = "";
1550        $htmlbody = "";
1551
1552        $this->getBodyRecursive($message, "plain", $body);
1553
1554        if($body === "") {
1555            $this->getBodyRecursive($message, "html", $body);
1556        }
1557
1558        return $body;
1559    }
1560
1561    /**
1562     * Get all parts in the message with specified type and concatenate them together, unless the
1563     * Content-Disposition is 'attachment', in which case the text is apparently an attachment
1564     *
1565     * @param string        $message        mimedecode message(part)
1566     * @param string        $message        message subtype
1567     * @param string        &$body          body reference
1568     *
1569     * @access protected
1570     * @return
1571     */
1572    protected function getBodyRecursive($message, $subtype, &$body) {
1573        if(!isset($message->ctype_primary)) return;
1574        if(strcasecmp($message->ctype_primary,"text")==0 && strcasecmp($message->ctype_secondary,$subtype)==0 && isset($message->body))
1575            $body .= $message->body;
1576
1577        if(strcasecmp($message->ctype_primary,"multipart")==0 && isset($message->parts) && is_array($message->parts)) {
1578            foreach($message->parts as $part) {
1579                if(!isset($part->disposition) || strcasecmp($part->disposition,"attachment"))  {
1580                    $this->getBodyRecursive($part, $subtype, $body);
1581                }
1582            }
1583        }
1584    }
1585
1586    /**
1587     * Returns the serverdelimiter for folder parsing
1588     *
1589     * @access protected
1590     * @return string       delimiter
1591     */
1592    protected function getServerDelimiter() {
1593        $list = @imap_getmailboxes($this->mbox, $this->server, "*");
1594        if (is_array($list)) {
1595            $val = $list[0];
1596
1597            return $val->delimiter;
1598        }
1599        return "."; // default "."
1600    }
1601
1602    /**
1603     * Helper to re-initialize the folder to speed things up
1604     * Remember what folder is currently open and only change if necessary
1605     *
1606     * @param string        $folderid       id of the folder
1607     * @param boolean       $force          re-open the folder even if currently opened
1608     *
1609     * @access protected
1610     * @return
1611     */
1612    protected function imap_reopenFolder($folderid, $force = false) {
1613        // to see changes, the folder has to be reopened!
1614           if ($this->mboxFolder != $folderid || $force) {
1615               $s = @imap_reopen($this->mbox, $this->server . $folderid);
1616               // TODO throw status exception
1617               if (!$s) {
1618                ZLog::Write(LOGLEVEL_WARN, "BackendIMAP->imap_reopenFolder('%s'): failed to change folder: ",$folderid, implode(", ", imap_errors()));
1619                return false;
1620               }
1621            $this->mboxFolder = $folderid;
1622        }
1623    }
1624
1625
1626    /**
1627     * Build a multipart RFC822, embedding body and one file (for attachments)
1628     *
1629     * @param string        $filenm         name of the file to be attached
1630     * @param long          $filesize       size of the file to be attached
1631     * @param string        $file_cont      content of the file
1632     * @param string        $body           current body
1633     * @param string        $body_ct        content-type
1634     * @param string        $body_cte       content-transfer-encoding
1635     * @param string        $boundary       optional existing boundary
1636     *
1637     * @access protected
1638     * @return array        with [0] => $mail_header and [1] => $mail_body
1639     */
1640    protected function mail_attach($filenm,$filesize,$file_cont,$body, $body_ct, $body_cte, $boundary = false) {
1641        if (!$boundary) $boundary = strtoupper(md5(uniqid(time())));
1642
1643        //remove the ending boundary because we will add it at the end
1644        $body = str_replace("--$boundary--", "", $body);
1645
1646        $mail_header = "Content-Type: multipart/mixed; boundary=$boundary\n";
1647
1648        // build main body with the sumitted type & encoding from the pda
1649        $mail_body  = $this->enc_multipart($boundary, $body, $body_ct, $body_cte);
1650        $mail_body .= $this->enc_attach_file($boundary, $filenm, $filesize, $file_cont);
1651
1652        $mail_body .= "--$boundary--\n\n";
1653        return array($mail_header, $mail_body);
1654    }
1655
1656    /**
1657     * Helper for mail_attach()
1658     *
1659     * @param string        $boundary       boundary
1660     * @param string        $body           current body
1661     * @param string        $body_ct        content-type
1662     * @param string        $body_cte       content-transfer-encoding
1663     *
1664     * @access protected
1665     * @return string       message body
1666     */
1667    protected function enc_multipart($boundary, $body, $body_ct, $body_cte) {
1668        $mail_body = "This is a multi-part message in MIME format\n\n";
1669        $mail_body .= "--$boundary\n";
1670        $mail_body .= "Content-Type: $body_ct\n";
1671        $mail_body .= "Content-Transfer-Encoding: $body_cte\n\n";
1672        $mail_body .= "$body\n\n";
1673
1674        return $mail_body;
1675    }
1676
1677    /**
1678     * Helper for mail_attach()
1679     *
1680     * @param string        $boundary       boundary
1681     * @param string        $filenm         name of the file to be attached
1682     * @param long          $filesize       size of the file to be attached
1683     * @param string        $file_cont      content of the file
1684     * @param string        $content_type   optional content-type
1685     *
1686     * @access protected
1687     * @return string       message body
1688     */
1689    protected function enc_attach_file($boundary, $filenm, $filesize, $file_cont, $content_type = "") {
1690        if (!$content_type) $content_type = "text/plain";
1691        $mail_body = "--$boundary\n";
1692        $mail_body .= "Content-Type: $content_type; name=\"$filenm\"\n";
1693        $mail_body .= "Content-Transfer-Encoding: base64\n";
1694        $mail_body .= "Content-Disposition: attachment; filename=\"$filenm\"\n";
1695        $mail_body .= "Content-Description: $filenm\n\n";
1696        //contrib - chunk base64 encoded attachments
1697        $mail_body .= chunk_split(base64_encode($file_cont)) . "\n\n";
1698
1699        return $mail_body;
1700    }
1701
1702    /**
1703     * Adds a message with seen flag to a specified folder (used for saving sent items)
1704     *
1705     * @param string        $folderid       id of the folder
1706     * @param string        $header         header of the message
1707     * @param long          $body           body of the message
1708     *
1709     * @access protected
1710     * @return boolean      status
1711     */
1712    protected function addSentMessage($folderid, $header, $body) {
1713        $header_body = str_replace("\n", "\r\n", str_replace("\r", "", $header . "\n\n" . $body));
1714
1715        return @imap_append($this->mbox, $this->server . $folderid, $header_body, "\\Seen");
1716    }
1717
1718    /**
1719     * Parses an mimedecode address array back to a simple "," separated string
1720     *
1721     * @param array         $ad             addresses array
1722     *
1723     * @access protected
1724     * @return string       mail address(es) string
1725     */
1726    protected function parseAddr($ad) {
1727        $addr_string = "";
1728        if (isset($ad) && is_array($ad)) {
1729            foreach($ad as $addr) {
1730                if ($addr_string) $addr_string .= ",";
1731                    $addr_string .= $addr->mailbox . "@" . $addr->host;
1732            }
1733        }
1734        return $addr_string;
1735    }
1736
1737    /**
1738     * Recursive way to get mod and parent - repeat until only one part is left
1739     * or the folder is identified as an IMAP folder
1740     *
1741     * @param string        $fhir           folder hierarchy string
1742     * @param string        &$displayname   reference of the displayname
1743     * @param long          &$parent        reference of the parent folder
1744     *
1745     * @access protected
1746     * @return
1747     */
1748    protected function getModAndParentNames($fhir, &$displayname, &$parent) {
1749        // if mod is already set add the previous part to it as it might be a folder which has
1750        // delimiter in its name
1751        $displayname = (isset($displayname) && strlen($displayname) > 0) ? $displayname = array_pop($fhir).$this->serverdelimiter.$displayname : array_pop($fhir);
1752        $parent = implode($this->serverdelimiter, $fhir);
1753
1754        if (count($fhir) == 1 || $this->checkIfIMAPFolder($parent)) {
1755            return;
1756        }
1757        //recursion magic
1758        $this->getModAndParentNames($fhir, $displayname, $parent);
1759    }
1760
1761    /**
1762     * Checks if a specified name is a folder in the IMAP store
1763     *
1764     * @param string        $foldername     a foldername
1765     *
1766     * @access protected
1767     * @return boolean
1768     */
1769    protected function checkIfIMAPFolder($folderName) {
1770        $parent = imap_list($this->mbox, $this->server, $folderName);
1771        if ($parent === false) return false;
1772        return true;
1773    }
1774
1775    /**
1776     * Removes parenthesis (comments) from the date string because
1777     * strtotime returns false if received date has them
1778     *
1779     * @param string        $receiveddate   a date as a string
1780     *
1781     * @access protected
1782     * @return string
1783     */
1784    protected function cleanupDate($receiveddate) {
1785        $receiveddate = strtotime(preg_replace("/\(.*\)/", "", $receiveddate));
1786        if ($receiveddate == false || $receiveddate == -1) {
1787            debugLog("Received date is false. Message might be broken.");
1788            return null;
1789        }
1790
1791        return $receiveddate;
1792    }
1793
1794    /* BEGIN fmbiete's contribution r1528, ZP-320 */
1795    /**
1796     * Indicates which AS version is supported by the backend.
1797     *
1798     * @access public
1799     * @return string       AS version constant
1800     */
1801    public function GetSupportedASVersion() {
1802        return ZPush::ASV_14;
1803    }
1804    /* END fmbiete's contribution r1528, ZP-320 */
1805};
1806
1807?>
Note: See TracBrowser for help on using the repository browser.