source: trunk/zpush/backend/expresso/providers/imapProvider.php @ 7644

Revision 7644, 67.5 KB checked in by cristiano, 11 years ago (diff)

Ticket #3209 - Adicionado suporte de imagens nos contatos, Desabilitados Contas compartilhadas no email

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