source: branches/2.5/zpush/backend/expresso/providers/imapProvider.php @ 8033

Revision 8033, 77.7 KB checked in by douglas, 11 years ago (diff)

Ticket #3395 - Envio de e-mail através do Z-push em servidor SMTP distribuido

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