source: contrib/z-push/backend/imap.php @ 4219

Revision 4219, 36.8 KB checked in by emersonfaria, 13 years ago (diff)

Ticket #1829 - Criado trace detalhado filtrado por usuario e backend

  • Property svn:executable set to *
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 * ï¿œ Zarafa Deutschland GmbH, www.zarafaserver.de
12 * This file is distributed under GPL v2.
13 * Consult LICENSE file for details
14 ************************************************/
15
16include_once('diffbackend.php');
17
18// The is an improved version of mimeDecode from PEAR that correctly
19// handles charsets and charset conversion
20include_once('mimeDecode.php');
21require_once('z_RFC822.php');
22
23class BackendIMAP extends BackendDiff {
24        /* Called to logon a user. These are the three authentication strings that you must
25         * specify in ActiveSync on the PDA. Normally you would do some kind of password
26         * check here. Alternatively, you could ignore the password here and have Apache
27         * do authentication via mod_auth_*
28         */
29        function Logon($username, $domain, $password) {
30                $this->_wasteID = false;
31                $this->_sentID = false;
32                $this->_server = "{" . IMAP_SERVER . ":" . IMAP_PORT . "/imap" . IMAP_OPTIONS . "}";
33
34                if (!function_exists("imap_open"))
35                debugLog("ERROR BackendIMAP : PHP-IMAP module not installed!!!!!");
36
37                // open the IMAP-mailbox
38                $this->_mbox = @imap_open($this->_server , $username, $password, OP_HALFOPEN);
39                $this->_mboxFolder = "";
40
41                if ($this->_mbox) {
42                        debugLog("IMAP connection opened sucessfully ");
43                        $this->_username = $username;
44                        $this->_domain = $domain;
45                        // set serverdelimiter
46                        $this->_serverdelimiter = $this->getServerDelimiter();
47                        return true;
48                }
49                else {
50                        debugLog("IMAP can't connect: " . imap_last_error());
51                        return false;
52                }
53        }
54
55        /* Called before shutting down the request to close the IMAP connection
56         */
57        function Logoff() {
58                if ($this->_mbox) {
59                        // list all errors
60                        $errors = imap_errors();
61                        if (is_array($errors)) {
62                                foreach ($errors as $e)    debugLog("IMAP-errors: $e");
63                        }
64                        @imap_close($this->_mbox);
65                        debugLog("IMAP connection closed");
66                }
67        }
68
69        /* Called directly after the logon. This specifies the client's protocol version
70         * and device id. The device ID can be used for various things, including saving
71         * per-device state information.
72         * The $user parameter here is normally equal to the $username parameter from the
73         * Logon() call. In theory though, you could log on a 'foo', and then sync the emails
74         * of user 'bar'. The $user here is the username specified in the request URL, while the
75         * $username in the Logon() call is the username which was sent as a part of the HTTP
76         * authentication.
77         */
78        function Setup($user, $devid, $protocolversion) {
79                $this->_user = $user;
80                $this->_devid = $devid;
81                $this->_protocolversion = $protocolversion;
82
83                return true;
84        }
85
86        /* Sends a message which is passed as rfc822. You basically can do two things
87         * 1) Send the message to an SMTP server as-is
88         * 2) Parse the message yourself, and send it some other way
89         * It is up to you whether you want to put the message in the sent items folder. If you
90         * want it in 'sent items', then the next sync on the 'sent items' folder should return
91         * the new message as any other new message in a folder.
92         */
93        function SendMail($rfc822, $forward = false, $reply = false, $parent = false) {
94                debugLog("IMAP-SendMail: " . $rfc822 . "for: $forward   reply: $reply   parent: $parent" );
95
96                $mobj = new Mail_mimeDecode($rfc822);
97                $message = $mobj->decode(array('decode_headers' => false, 'decode_bodies' => true, 'include_bodies' => true, 'input' => $rfc822, 'crlf' => "\n", 'charset' => 'utf-8'));
98
99                $toaddr = $ccaddr = $bccaddr = "";
100                if(isset($message->headers["to"]))
101                $toaddr = $this->parseAddr(Mail_RFC822::parseAddressList($message->headers["to"]));
102                if(isset($message->headers["cc"]))
103                $ccaddr = $this->parseAddr(Mail_RFC822::parseAddressList($message->headers["cc"]));
104                if(isset($message->headers["bcc"]))
105                $bccaddr = $this->parseAddr(Mail_RFC822::parseAddressList($message->headers["bcc"]));
106
107                // save some headers when forwarding mails (content type & transfer-encoding)
108                $headers = "";
109                $forward_h_ct = "";
110                $forward_h_cte = "";
111
112                $use_orgbody = false;
113
114                // clean up the transmitted headers
115                // remove default headers because we are using imap_mail
116                $changedfrom = false;
117                $returnPathSet = false;
118                $body_base64 = false;
119                $org_charset = "";
120                foreach($message->headers as $k => $v) {
121                        if ($k == "subject" || $k == "to" || $k == "cc" || $k == "bcc")
122                        continue;
123
124                        if ($k == "content-type") {
125                                // save the original content-type header for the body part when forwarding
126                                if ($forward) {
127                                        $forward_h_ct = $v;
128                                        continue;
129                                }
130
131                                // set charset always to utf-8
132                                $org_charset = $v;
133                                $v = preg_replace("/charset=([A-Za-z0-9-\"']+)/", "charset=\"utf-8\"", $v);
134                        }
135
136                        if ($k == "content-transfer-encoding") {
137                                // if the content was base64 encoded, encode the body again when sending
138                                if (trim($v) == "base64") $body_base64 = true;
139
140                                // save the original encoding header for the body part when forwarding
141                                if ($forward) {
142                                        $forward_h_cte = $v;
143                                        continue;
144                                }
145                        }
146
147                        // if the message is a multipart message, then we should use the sent body
148                        if (!$forward && $k == "content-type" && preg_match("/multipart/i", $v)) {
149                                $use_orgbody = true;
150                        }
151
152                        // check if "from"-header is set
153                        if ($k == "from" && ! trim($v) && IMAP_DEFAULTFROM) {
154                                $changedfrom = true;
155                                if      (IMAP_DEFAULTFROM == 'username') $v = $this->_username;
156                                else if (IMAP_DEFAULTFROM == 'domain')   $v = $this->_domain;
157                                else $v = $this->_username . IMAP_DEFAULTFROM;
158                        }
159
160                        // check if "Return-Path"-header is set
161                        if ($k == "return-path") {
162                                $returnPathSet = true;
163                                if (! trim($v) && IMAP_DEFAULTFROM) {
164                                        if      (IMAP_DEFAULTFROM == 'username') $v = $this->_username;
165                                        else if (IMAP_DEFAULTFROM == 'domain')   $v = $this->_domain;
166                                        else $v = $this->_username . IMAP_DEFAULTFROM;
167                                }
168                        }
169
170                        // all other headers stay
171                        if ($headers) $headers .= "\n";
172                        $headers .= ucfirst($k) . ": ". $v;
173                }
174
175                // set "From" header if not set on the device
176                if(IMAP_DEFAULTFROM && !$changedfrom){
177                        if      (IMAP_DEFAULTFROM == 'username') $v = $this->_username;
178                        else if (IMAP_DEFAULTFROM == 'domain')   $v = $this->_domain;
179                        else $v = $this->_username . IMAP_DEFAULTFROM;
180                        if ($headers) $headers .= "\n";
181                        $headers .= 'From: '.$v;
182                }
183
184                // set "Return-Path" header if not set on the device
185                if(IMAP_DEFAULTFROM && !$returnPathSet){
186                        if      (IMAP_DEFAULTFROM == 'username') $v = $this->_username;
187                        else if (IMAP_DEFAULTFROM == 'domain')   $v = $this->_domain;
188                        else $v = $this->_username . IMAP_DEFAULTFROM;
189                        if ($headers) $headers .= "\n";
190                        $headers .= 'Return-Path: '.$v;
191                }
192
193                // if this is a multipart message with a boundary, we must use the original body
194                if ($use_orgbody) {
195                        list(,$body) = $mobj->_splitBodyHeader($rfc822);
196                }
197                else
198                $body = $this->getBody($message);
199
200                // reply
201                if (isset($reply) && isset($parent) &&  $reply && $parent) {
202                        $this->imap_reopenFolder($parent);
203                        // receive entire mail (header + body) to decode body correctly
204                        $origmail = @imap_fetchheader($this->_mbox, $reply, FT_PREFETCHTEXT | FT_UID) . @imap_body($this->_mbox, $reply, FT_PEEK | FT_UID);
205                        $mobj2 = new Mail_mimeDecode($origmail);
206                        // receive only body
207                        $body .= $this->getBody($mobj2->decode(array('decode_headers' => false, 'decode_bodies' => true, 'include_bodies' => true, 'input' => $origmail, 'crlf' => "\n", 'charset' => 'utf-8')));
208                        // unset mimedecoder & origmail - free memory
209                        unset($mobj2);
210                        unset($origmail);
211                }
212
213                // encode the body to base64 if it was sent originally in base64 by the pda
214                // the encoded body is included in the forward
215                if ($body_base64) $body = base64_encode($body);
216
217
218                // forward
219                if (isset($forward) && isset($parent) && $forward && $parent) {
220                        $this->imap_reopenFolder($parent);
221                        // receive entire mail (header + body)
222                        $origmail = @imap_fetchheader($this->_mbox, $forward, FT_PREFETCHTEXT | FT_UID) . @imap_body($this->_mbox, $forward, FT_PEEK | FT_UID);
223
224                        // build a new mime message, forward entire old mail as file
225                        list($aheader, $body) = $this->mail_attach("forwarded_message.eml",strlen($origmail),$origmail, $body, $forward_h_ct, $forward_h_cte);
226
227                        // unset origmail - free memory
228                        unset($origmail);
229
230                        // add boundary headers
231                        $headers .= "\n" . $aheader;
232                }
233
234                //advanced debugging
235                //debugLog("IMAP-SendMail: parsed message: ". print_r($message,1));
236                //debugLog("IMAP-SendMail: headers: $headers");
237                //debugLog("IMAP-SendMail: subject: {$message->headers["subject"]}");
238                //debugLog("IMAP-SendMail: body: $body");
239
240                $send =  @imap_mail ( $toaddr, $message->headers["subject"], $body, $headers, $ccaddr, $bccaddr);
241
242                // email sent?
243                if (!$send) {
244                        debugLog("The email could not be sent. Last-IMAP-error: ". imap_last_error());
245                }
246
247                // add message to the sent folder
248                // build complete headers
249                $cheaders  = "To: " . $toaddr. "\n";
250                $cheaders .= "Subject: " . $message->headers["subject"] . "\n";
251                $cheaders .= "Cc: " . $ccaddr . "\n";
252                $cheaders .= $headers;
253
254                $asf = false;
255                if ($this->_sentID) {
256                        $asf = $this->addSentMessage($this->_sentID, $cheaders, $body);
257                }
258                else if (IMAP_SENTFOLDER) {
259                        $asf = $this->addSentMessage(IMAP_SENTFOLDER, $cheaders, $body);
260                        debugLog("IMAP-SendMail: Outgoing mail saved in configured 'Sent' folder '".IMAP_SENTFOLDER."': ". (($asf)?"success":"failed"));
261                }
262                // No Sent folder set, try defaults
263                else {
264                        debugLog("IMAP-SendMail: No Sent mailbox set");
265                        if($this->addSentMessage("INBOX.Sent", $cheaders, $body)) {
266                                debugLog("IMAP-SendMail: Outgoing mail saved in 'INBOX.Sent'");
267                                $asf = true;
268                        }
269                        else if ($this->addSentMessage("Sent", $cheaders, $body)) {
270                                debugLog("IMAP-SendMail: Outgoing mail saved in 'Sent'");
271                                $asf = true;
272                        }
273                        else if ($this->addSentMessage("Sent Items", $cheaders, $body)) {
274                                debugLog("IMAP-SendMail: Outgoing mail saved in 'Sent Items'");
275                                $asf = true;
276                        }
277                }
278
279                // unset mimedecoder - free memory
280                unset($mobj);
281                return ($send && $asf);
282        }
283
284        /* Should return a wastebasket folder if there is one. This is used when deleting
285         * items; if this function returns a valid folder ID, then all deletes are handled
286         * as moves and are sent to your backend as a move. If it returns FALSE, then deletes
287         * are always handled as real deletes and will be sent to your importer as a DELETE
288         */
289        function GetWasteBasket() {
290                return $this->_wasteID;
291        }
292
293        /* Should return a list (array) of messages, each entry being an associative array
294         * with the same entries as StatMessage(). This function should return stable information; ie
295         * if nothing has changed, the items in the array must be exactly the same. The order of
296         * the items within the array is not important though.
297         *
298         * The cutoffdate is a date in the past, representing the date since which items should be shown.
299         * This cutoffdate is determined by the user's setting of getting 'Last 3 days' of e-mail, etc. If
300         * you ignore the cutoffdate, the user will not be able to select their own cutoffdate, but all
301         * will work OK apart from that.
302         */
303
304        function GetMessageList($folderid, $cutoffdate) {
305                debugLog("IMAP-GetMessageList: (fid: '$folderid'  cutdate: '$cutoffdate' )");
306
307                $messages = array();
308                $this->imap_reopenFolder($folderid, true);
309
310                $sequence = "1:*";
311                if ($cutoffdate > 0) {
312                        $search = @imap_search($this->_mbox, "SINCE ". date("d-M-Y", $cutoffdate));
313                        if ($search !== false)
314                        $sequence = implode(",", $search);
315                        if (TRACE_UID !== false and (TRACE_TYPE == 'IMAP' or TRACE_TYPE == 'ALL')) traceLog('IMAP::GetMessageList-> A função @imap_search leu as seguintes SEQUÊNCIAS do servidor IMAP com base no filtro de data: '.$sequence);
316                }
317
318                $overviews = @imap_fetch_overview($this->_mbox, $sequence);
319
320                if (!$overviews) {
321                        debugLog("IMAP-GetMessageList: Failed to retrieve overview");
322                } else {
323                        foreach($overviews as $overview) {
324                                $date = "";
325                                $vars = get_object_vars($overview);
326                                if (TRACE_UID !== false and (TRACE_TYPE == 'IMAP' or TRACE_TYPE == 'ALL')) traceLog('IMAP::GetMessageList-> A função @imap_fetch_overview leu mais detalhes da mensagem: '. print_r($overview,1));
327                                if (array_key_exists( "date", $vars)) {
328                                        // message is out of range for cutoffdate, ignore it
329                                        if(strtotime(preg_replace("/\(.*\)/", "", $overview->date)) < $cutoffdate) { // emerson-faria.nobre@serpro.gov.br - 07/feb/2011
330                                                if (TRACE_UID !== false and (TRACE_TYPE == 'IMAP' or TRACE_TYPE == 'ALL')) traceLog('IMAP::GetMessageList-> O overview->date: '.$overview->date.' (overview->date_timestamp: '.strtotime(preg_replace("/\(.*\)/", "", $overview->date)).') é menor que o $cutoffdate: '.date("d/m/Y G:i:s",$cutoffdate).' ($cutoffdate_timestamp: '.$cutoffdate.') ou a função "strtotime" gerou um ERRO porque não conseguiu retornar um valor para o overview->date_timestamp. A mensagem será descartada da sincronização.');
331                                                continue;
332                                        }
333                                        //if(strtotime($overview->date) < $cutoffdate) continue;
334                                        $date = $overview->date;
335                                } else if (TRACE_UID !== false and (TRACE_TYPE == 'IMAP' or TRACE_TYPE == 'ALL')) traceLog('IMAP::GetMessageList-> ERRO: O campo date não existe no overview da mensagem.');
336
337                                // cut of deleted messages
338                                if (array_key_exists( "deleted", $vars) && $overview->deleted) {
339                                        if (TRACE_UID !== false and (TRACE_TYPE == 'IMAP' or TRACE_TYPE == 'ALL')) traceLog('IMAP::GetMessageList-> A mensagem está com o flag deleted ativo e será descartada da sincronização');
340                                        continue;
341                                }
342
343                                if (array_key_exists( "uid", $vars)) {
344                                        $message = array();
345                                        $message["mod"] = $date;
346                                        $message["id"] = $overview->uid;
347                                        // 'seen' aka 'read' is the only flag we want to know about
348                                        $message["flags"] = 0;
349
350                                        if(array_key_exists( "seen", $vars) && $overview->seen)
351                                        $message["flags"] = 1;
352                                        array_push($messages, $message);
353                                } else if (TRACE_UID !== false and (TRACE_TYPE == 'IMAP' or TRACE_TYPE == 'ALL')) traceLog('IMAP::GetMessageList-> ERRO: O campo uid não existe no overview da mensagem.');
354                        }
355                }
356                return $messages;
357        }
358
359        /* This function is analogous to GetMessageList.
360         *
361         */
362        function GetFolderList() {
363                $folders = array();
364
365                $list = @imap_getmailboxes($this->_mbox, $this->_server, "*");
366                if (is_array($list)) {
367                        // reverse list to obtain folders in right order
368                        $list = array_reverse($list);
369                        foreach ($list as $val) {
370                                if (TRACE_UID !== false and (TRACE_TYPE == 'IMAP' or TRACE_TYPE == 'ALL')) traceLog('IMAP::GetFolderList-> Pasta lida usando a função @imap_getmailboxes: '.print_r($val,1));
371                                $box = array();
372
373                                // cut off serverstring
374                                $box["id"] = imap_utf7_decode(substr($val->name, strlen($this->_server)));
375
376                                // always use "." as folder delimiter
377                                $box["id"] = imap_utf7_encode(str_replace($val->delimiter, ".", $box["id"]));
378
379                                // explode hierarchies
380                                $fhir = explode(".", $box["id"]);
381                                if (count($fhir) > 1) {
382                                        $box["mod"] = imap_utf7_encode(array_pop($fhir)); // mod is last part of path
383                                        $box["parent"] = imap_utf7_encode(implode(".", $fhir)); // parent is all previous parts of path
384                                }
385                                else {
386                                        $box["mod"] = imap_utf7_encode($box["id"]);
387                                        $box["parent"] = "0";
388                                }
389                                if (TRACE_UID !== false and (TRACE_TYPE == 'IMAP' or TRACE_TYPE == 'ALL')) traceLog('IMAP::GetFolderList-> Parâmetros da pasta após decodificação: '.print_r($box,1));
390                                $folders[]=$box;
391                        }
392                }
393                else {
394                        debugLog("GetFolderList: imap_list failed: " . imap_last_error());
395                }
396
397                return $folders;
398        }
399
400        /* GetFolder should return an actual SyncFolder object with all the properties set. Folders
401         * are pretty simple really, having only a type, a name, a parent and a server ID.
402         */
403
404        function GetFolder($id) {
405                $folder = new SyncFolder();
406                $folder->serverid = $id;
407
408                // explode hierarchy
409                $fhir = explode(".", $id);
410
411                // compare on lowercase strings
412                $lid = strtolower($id);
413
414                if($lid == "inbox") {
415                        $folder->parentid = "0"; // Root
416                        $folder->displayname = "Inbox";
417                        $folder->type = SYNC_FOLDER_TYPE_INBOX;
418                }
419                // Zarafa IMAP-Gateway outputs
420                else if($lid == "drafts") {
421                        $folder->parentid = "0";
422                        $folder->displayname = "Drafts";
423                        $folder->type = SYNC_FOLDER_TYPE_DRAFTS;
424                }
425                else if($lid == "trash") {
426                        $folder->parentid = "0";
427                        $folder->displayname = "Trash";
428                        $folder->type = SYNC_FOLDER_TYPE_WASTEBASKET;
429                        $this->_wasteID = $id;
430                }
431                else if($lid == "sent" || $lid == "sent items" || $lid == IMAP_SENTFOLDER) {
432                        $folder->parentid = "0";
433                        $folder->displayname = "Sent";
434                        $folder->type = SYNC_FOLDER_TYPE_SENTMAIL;
435                        $this->_sentID = $id;
436                }
437                // courier-imap outputs
438                else if($lid == "inbox.drafts") {
439                        $folder->parentid = $fhir[0];
440                        $folder->displayname = "Drafts";
441                        $folder->type = SYNC_FOLDER_TYPE_DRAFTS;
442                }
443                else if($lid == "inbox.trash") {
444                        $folder->parentid = $fhir[0];
445                        $folder->displayname = "Trash";
446                        $folder->type = SYNC_FOLDER_TYPE_WASTEBASKET;
447                        $this->_wasteID = $id;
448                }
449                else if($lid == "inbox.sent") {
450                        $folder->parentid = $fhir[0];
451                        $folder->displayname = "Sent";
452                        $folder->type = SYNC_FOLDER_TYPE_SENTMAIL;
453                        $this->_sentID = $id;
454                }
455
456                // define the rest as other-folders
457                else {
458                        if (count($fhir) > 1) {
459                                $folder->displayname = windows1252_to_utf8(imap_utf7_decode(array_pop($fhir)));
460                                $folder->parentid = implode(".", $fhir);
461                        }
462                        else {
463                                $folder->displayname = windows1252_to_utf8(imap_utf7_decode($id));
464                                $folder->parentid = "0";
465                        }
466                        $folder->type = SYNC_FOLDER_TYPE_OTHER;
467                }
468
469                //advanced debugging
470                //debugLog("IMAP-GetFolder(id: '$id') -> " . print_r($folder, 1));
471                if (TRACE_UID !== false and (TRACE_TYPE == 'IMAP' or TRACE_TYPE == 'ALL')) traceLog('IMAP::GetFolder(id: '.$id.'): '.print_r($folder,1));
472
473                return $folder;
474        }
475
476        /* Return folder stats. This means you must return an associative array with the
477         * following properties:
478         * "id" => The server ID that will be used to identify the folder. It must be unique, and not too long
479         *         How long exactly is not known, but try keeping it under 20 chars or so. It must be a string.
480         * "parent" => The server ID of the parent of the folder. Same restrictions as 'id' apply.
481         * "mod" => This is the modification signature. It is any arbitrary string which is constant as long as
482         *          the folder has not changed. In practice this means that 'mod' can be equal to the folder name
483         *          as this is the only thing that ever changes in folders. (the type is normally constant)
484         */
485        function StatFolder($id) {
486                $folder = $this->GetFolder($id);
487
488                $stat = array();
489                $stat["id"] = $id;
490                $stat["parent"] = $folder->parentid;
491                $stat["mod"] = $folder->displayname;
492                if (TRACE_UID !== false and (TRACE_TYPE == 'IMAP' or TRACE_TYPE == 'ALL')) traceLog('IMAP::StatFolder(id: '.$id.'): '.print_r($stat,1));
493                return $stat;
494        }
495
496        /* Creates or modifies a folder
497         * "folderid" => id of the parent folder
498         * "oldid" => if empty -> new folder created, else folder is to be renamed
499         * "displayname" => new folder name (to be created, or to be renamed to)
500         * "type" => folder type, ignored in IMAP
501         *
502         */
503        function ChangeFolder($folderid, $oldid, $displayname, $type){
504                debugLog("ChangeFolder: (parent: '$folderid'  oldid: '$oldid'  displayname: '$displayname'  type: '$type')");
505
506                // go to parent mailbox
507                $this->imap_reopenFolder($folderid);
508
509                // build name for new mailbox
510                $newname = $this->_server . str_replace(".", $this->_serverdelimiter, $folderid) . $this->_serverdelimiter . $displayname;
511
512                $csts = false;
513                // if $id is set => rename mailbox, otherwise create
514                if ($oldid) {
515                        // rename doesn't work properly with IMAP
516                        // the activesync client doesn't support a 'changing ID'
517                        //$csts = imap_renamemailbox($this->_mbox, $this->_server . imap_utf7_encode(str_replace(".", $this->_serverdelimiter, $oldid)), $newname);
518                }
519                else {
520                        $csts = @imap_createmailbox($this->_mbox, $newname);
521                }
522                if ($csts) {
523                        return $this->StatFolder($folderid . "." . $displayname);
524                }
525                else
526                return false;
527        }
528
529        /* Should return attachment data for the specified attachment. The passed attachment identifier is
530         * the exact string that is returned in the 'AttName' property of an SyncAttachment. So, you should
531         * encode any information you need to find the attachment in that 'attname' property.
532         */
533        function GetAttachmentData($attname) {
534                debugLog("getAttachmentDate: (attname: '$attname')");
535
536                list($folderid, $id, $part) = explode(":", $attname);
537
538                $this->imap_reopenFolder($folderid);
539                $mail = @imap_fetchheader($this->_mbox, $id, FT_PREFETCHTEXT | FT_UID) . @imap_body($this->_mbox, $id, FT_PEEK | FT_UID);
540
541                $mobj = new Mail_mimeDecode($mail);
542                $message = $mobj->decode(array('decode_headers' => true, 'decode_bodies' => true, 'include_bodies' => true, 'input' => $mail, 'crlf' => "\n", 'charset' => 'utf-8'));
543
544                if (isset($message->parts[$part]->body))
545                print $message->parts[$part]->body;
546               
547                // unset mimedecoder & mail
548                unset($mobj);
549                unset($mail);
550                return true;
551        }
552
553        /* StatMessage should return message stats, analogous to the folder stats (StatFolder). Entries are:
554         * 'id'     => Server unique identifier for the message. Again, try to keep this short (under 20 chars)
555         * 'flags'     => simply '0' for unread, '1' for read
556         * 'mod'    => modification signature. As soon as this signature changes, the item is assumed to be completely
557         *             changed, and will be sent to the PDA as a whole. Normally you can use something like the modification
558         *             time for this field, which will change as soon as the contents have changed.
559         */
560
561        function StatMessage($folderid, $id) {
562                debugLog("IMAP-StatMessage: (fid: '$folderid'  id: '$id' )");
563
564                $this->imap_reopenFolder($folderid);
565                $overview = @imap_fetch_overview( $this->_mbox , $id , FT_UID);
566
567                if (!$overview) {
568                        debugLog("IMAP-StatMessage: Failed to retrieve overview: ". imap_last_error());
569                        return false;
570                }
571
572                else {
573                        // check if variables for this overview object are available
574                        $vars = get_object_vars($overview[0]);
575
576                        if (TRACE_UID !== false and (TRACE_TYPE == 'IMAP' or TRACE_TYPE == 'ALL') and (! array_key_exists( "uid", $vars))) debugLog('IMAP::StatMessage-> ERRO: A mensagem será desconsiderada porque não tem o campo "uid"');
577                        // without uid it's not a valid message
578                        if (! array_key_exists( "uid", $vars)) return false;
579
580
581                        $entry = array();
582                        $entry["mod"] = (array_key_exists( "date", $vars)) ? $overview[0]->date : "";
583                        $entry["id"] = $overview[0]->uid;
584                        // 'seen' aka 'read' is the only flag we want to know about
585                        $entry["flags"] = 0;
586
587                        if(array_key_exists( "seen", $vars) && $overview[0]->seen)
588                        $entry["flags"] = 1;
589
590                        if (TRACE_UID !== false and (TRACE_TYPE == 'IMAP' or TRACE_TYPE == 'ALL')) traceLog('IMAP::StatMessage: '.print_r($entry,1));
591
592                        //advanced debugging
593                        //debugLog("IMAP-StatMessage-parsed: ". print_r($entry,1));
594
595                        return $entry;
596                }
597        }
598
599        /* GetMessage should return the actual SyncXXX object type. You may or may not use the '$folderid' parent folder
600         * identifier here.
601         * Note that mixing item types is illegal and will be blocked by the engine; ie returning an Email object in a
602         * Tasks folder will not do anything. The SyncXXX objects should be filled with as much information as possible,
603         * but at least the subject, body, to, from, etc.
604         */
605        function GetMessage($folderid, $id, $truncsize, $mimesupport = 0) {
606                debugLog("IMAP-GetMessage: (fid: '$folderid'  id: '$id'  truncsize: $truncsize)");
607
608                // Get flags, etc
609                $stat = $this->StatMessage($folderid, $id);
610
611                if ($stat) {
612                        $this->imap_reopenFolder($folderid);
613                        $mail = @imap_fetchheader($this->_mbox, $id, FT_PREFETCHTEXT | FT_UID) . @imap_body($this->_mbox, $id, FT_PEEK | FT_UID);
614
615                        if (TRACE_UID !== false and (TRACE_TYPE == 'IMAP' or TRACE_TYPE == 'ALL')) traceLog("IMAP-GetMessage: Mensagem lida do servidor IMAP através da função @imap_fetchheader: ". print_r($mail,1));
616
617                        $mobj = new Mail_mimeDecode($mail);
618                        $message = $mobj->decode(array('decode_headers' => true, 'decode_bodies' => true, 'include_bodies' => true, 'input' => $mail, 'crlf' => "\n", 'charset' => 'utf-8'));
619
620                        if (TRACE_UID !== false and (TRACE_TYPE == 'IMAP' or TRACE_TYPE == 'ALL')) traceLog("IMAP-GetMessage: Mensagem mime decodificada: ". print_r($message,1));
621
622                        $output = new SyncMail();
623
624                        $body = $this->getBody($message);
625                        // truncate body, if requested
626                        if(strlen($body) > $truncsize) {
627                                $body = utf8_truncate($body, $truncsize);
628                                $output->bodytruncated = 1;
629                        } else {
630                                $body = $body;
631                                $output->bodytruncated = 0;
632                        }
633                        $body = str_replace("\n","\r\n", str_replace("\r","",$body));
634
635                        $output->bodysize = strlen($body);
636                        $output->body = $body;
637                        //$output->datereceived = isset($message->headers["date"]) ? strtotime($message->headers["date"]) : null;
638                        $output->datereceived = isset($message->headers["date"]) ? strtotime(preg_replace("/\(.*\)/", "", $message->headers["date"])) : null; // emerson-faria.nobre@serpro.gov.br
639                        $output->displayto = isset($message->headers["to"]) ? $message->headers["to"] : null;
640                        $output->importance = isset($message->headers["x-priority"]) ? preg_replace("/\D+/", "", $message->headers["x-priority"]) : null;
641                        $output->messageclass = "IPM.Note";
642                        $output->subject = isset($message->headers["subject"]) ? $message->headers["subject"] : "";
643                        $output->read = $stat["flags"];
644                        $output->to = isset($message->headers["to"]) ? $message->headers["to"] : null;
645                        $output->cc = isset($message->headers["cc"]) ? $message->headers["cc"] : null;
646                        $output->from = isset($message->headers["from"]) ? $message->headers["from"] : null;
647                        $output->reply_to = isset($message->headers["reply-to"]) ? $message->headers["reply-to"] : null;
648
649                        // Attachments are only searched in the top-level part
650                        $n = 0;
651                        if(isset($message->parts)) {
652                                foreach($message->parts as $part) {
653                                        if(isset($part->disposition) && ($part->disposition == "attachment" || $part->disposition == "inline")) {
654                                                $attachment = new SyncAttachment();
655
656                                                if (isset($part->body))
657                                                $attachment->attsize = strlen($part->body);
658
659                                                if(isset($part->d_parameters['filename']))
660                                                $attname = $part->d_parameters['filename'];
661                                                else if(isset($part->ctype_parameters['name']))
662                                                $attname = $part->ctype_parameters['name'];
663                                                else if(isset($part->headers['content-description']))
664                                                $attname = $part->headers['content-description'];
665                                                else $attname = "unknown attachment";
666
667                                                $attachment->displayname = $attname;
668                                                $attachment->attname = $folderid . ":" . $id . ":" . $n;
669                                                $attachment->attmethod = 1;
670                                                $attachment->attoid = isset($part->headers['content-id']) ? $part->headers['content-id'] : "";
671                                                array_push($output->attachments, $attachment);
672                                        }
673                                        $n++;
674                                }
675                        }
676                        // unset mimedecoder & mail
677                        unset($mobj);
678                        unset($mail);
679                        if (TRACE_UID !== false and (TRACE_TYPE == 'IMAP' or TRACE_TYPE == 'ALL')) traceLog("IMAP-GetMessage: Mensagem formatada para envio ao celular: ". print_r($output,1));
680                        return $output;
681                }
682                return false;
683        }
684
685        /* This function is called when the user has requested to delete (really delete) a message. Usually
686         * this means just unlinking the file its in or somesuch. After this call has succeeded, a call to
687         * GetMessageList() should no longer list the message. If it does, the message will be re-sent to the PDA
688         * as it will be seen as a 'new' item. This means that if you don't implement this function, you will
689         * be able to delete messages on the PDA, but as soon as you sync, you'll get the item back
690         */
691        function DeleteMessage($folderid, $id) {
692                debugLog("IMAP-DeleteMessage: (fid: '$folderid'  id: '$id' )");
693
694                $this->imap_reopenFolder($folderid);
695                $s1 = @imap_delete ($this->_mbox, $id, FT_UID);
696                $s11 = @imap_setflag_full($this->_mbox, $id, "\\Deleted", FT_UID);
697                $s2 = @imap_expunge($this->_mbox);
698
699                debugLog("IMAP-DeleteMessage: s-delete: $s1   s-expunge: $s2    setflag: $s11");
700
701                return ($s1 && $s2 && $s11);
702        }
703
704        /* This should change the 'read' flag of a message on disk. The $flags
705         * parameter can only be '1' (read) or '0' (unread). After a call to
706         * SetReadFlag(), GetMessageList() should return the message with the
707         * new 'flags' but should not modify the 'mod' parameter. If you do
708         * change 'mod', simply setting the message to 'read' on the PDA will trigger
709         * a full resync of the item from the server
710         */
711        function SetReadFlag($folderid, $id, $flags) {
712                debugLog("IMAP-SetReadFlag: (fid: '$folderid'  id: '$id'  flags: '$flags' )");
713
714                $this->imap_reopenFolder($folderid);
715
716                if ($flags == 0) {
717                        // set as "Unseen" (unread)
718                        $status = @imap_clearflag_full ( $this->_mbox, $id, "\\Seen", ST_UID);
719                } else {
720                        // set as "Seen" (read)
721                        $status = @imap_setflag_full($this->_mbox, $id, "\\Seen",ST_UID);
722                }
723
724                debugLog("IMAP-SetReadFlag -> set as " . (($flags) ? "read" : "unread") . "-->". $status);
725
726                return $status;
727        }
728
729        /* This function is called when a message has been changed on the PDA. You should parse the new
730         * message here and save the changes to disk. The return value must be whatever would be returned
731         * from StatMessage() after the message has been saved. This means that both the 'flags' and the 'mod'
732         * properties of the StatMessage() item may change via ChangeMessage().
733         * Note that this function will never be called on E-mail items as you can't change e-mail items, you
734         * can only set them as 'read'.
735         */
736        function ChangeMessage($folderid, $id, $message) {
737                return false;
738        }
739
740        /* This function is called when the user moves an item on the PDA. You should do whatever is needed
741         * to move the message on disk. After this call, StatMessage() and GetMessageList() should show the items
742         * to have a new parent. This means that it will disappear from GetMessageList() will not return the item
743         * at all on the source folder, and the destination folder will show the new message
744         *
745         */
746        function MoveMessage($folderid, $id, $newfolderid) {
747                debugLog("IMAP-MoveMessage: (sfid: '$folderid'  id: '$id'  dfid: '$newfolderid' )");
748
749                $this->imap_reopenFolder($folderid);
750
751                // read message flags
752                $overview = @imap_fetch_overview ( $this->_mbox , $id, FT_UID);
753
754                if (!$overview) {
755                        debugLog("IMAP-MoveMessage: Failed to retrieve overview");
756                        return false;
757                }
758                else {
759                        // move message
760                        $s1 = imap_mail_move($this->_mbox, $id, str_replace(".", $this->_serverdelimiter, $newfolderid), FT_UID);
761
762                        // delete message in from-folder
763                        $s2 = imap_expunge($this->_mbox);
764
765                        // open new folder
766                        $this->imap_reopenFolder($newfolderid);
767
768                        // remove all flags
769                        $s3 = @imap_clearflag_full ($this->_mbox, $id, "\\Seen \\Answered \\Flagged \\Deleted \\Draft", FT_UID);
770                        $newflags = "";
771                        if ($overview[0]->seen) $newflags .= "\\Seen";
772                        if ($overview[0]->flagged) $newflags .= " \\Flagged";
773                        if ($overview[0]->answered) $newflags .= " \\Answered";
774                        $s4 = @imap_setflag_full ($this->_mbox, $id, $newflags, FT_UID);
775
776                        debugLog("MoveMessage: (" . $folderid . "->" . $newfolderid . ") s-move: $s1   s-expunge: $s2    unset-Flags: $s3    set-Flags: $s4");
777
778                        return ($s1 && $s2 && $s3 && $s4);
779                }
780        }
781
782        // new ping mechanism for the IMAP-Backend
783        function AlterPing() {
784                return true;
785        }
786
787        // returns a changes array using imap_status
788        // if changes occurr default diff engine computes the actual changes
789        function AlterPingChanges($folderid, &$syncstate) {
790                debugLog("AlterPingChanges on $folderid stat: ". $syncstate);
791                $this->imap_reopenFolder($folderid);
792
793                // courier-imap only cleares the status cache after checking
794                @imap_check($this->_mbox);
795
796                $status = imap_status($this->_mbox, $this->_server . str_replace(".", $this->_serverdelimiter, $folderid), SA_ALL);
797                if (!$status) {
798                        debugLog("AlterPingChanges: could not stat folder $folderid : ". imap_last_error());
799                        return false;
800                }
801                else {
802                        $newstate = "M:". $status->messages ."-R:". $status->recent ."-U:". $status->unseen;
803
804                        // message number is different - change occured
805                        if ($syncstate != $newstate) {
806                                $syncstate = $newstate;
807                                debugLog("AlterPingChanges: Change FOUND!");
808                                // build a dummy change
809                                return array(array("type" => "fakeChange"));
810                        }
811                }
812
813                return array();
814        }
815
816        // ----------------------------------------
817        // imap-specific internals
818
819        /* Parse the message and return only the plaintext body
820         */
821        function getBody($message) {
822                $body = "";
823                $htmlbody = "";
824
825                $this->getBodyRecursive($message, "plain", $body);
826
827                if(!isset($body) or $body === '') {
828                        $this->getBodyRecursive($message, "html", $body);
829                        // remove css-style tags
830                        $body = preg_replace("/<style.*?<\/style>/is", "a", $body);
831                        // remove all other html
832                        //$body = strip_tags($body);
833
834                        // Remove the HTML tags using the 'html2text' - emerson-faria.nobre@serpro.gov.br
835                        // The 'html2text' (http://www.mbayer.de/html2text) must be installed in Z-Push server.
836
837                        // Advanced debug
838                        // debugLog("IMAP-getBody: subject: " . $message->headers["subject"]);
839                        $body = utf8_encode($body);
840                        libxml_use_internal_errors(true);
841                        try {
842                                $doc = new DOMDocument('1.0', 'UTF-8');
843                                @$doc->loadHTML($body);
844                                $tables = $doc->getElementsByTagName('table');
845                                foreach ($tables as $table)
846                                {
847                                        $tds = $table->getElementsByTagName('td');
848                                        foreach ($tds as $td)
849                                        {
850                                                foreach ($td->childNodes as $td_child) {
851                                                        if ($td_child->nodeName == 'br') $td->removeChild($td_child);
852                                                }
853                                        }
854                                }
855                                $links = $doc->getElementsByTagName('a');
856                                foreach ($links as $link)
857                                {
858                                        if ($link->hasAttributes()){
859                                                $novoNo = $doc->createTextNode(' (' . $link->getAttribute('href') . ')');
860                                                $link->parentNode->insertBefore($novoNo, $link->nextSibling);
861                                        }
862                                }
863                                $body =  $doc->saveHTML();
864                        } catch (Exception $e) {
865                                debugLog("IMAP-getBody: Alert - DOMDocument cannot parse HTML.");
866                        }
867                        $filename = "./state/" . $this->_user . "-" . $this->_devid . ".html";
868                        $fp = fopen($filename, 'w') or die("can't open file");
869                        fwrite($fp, $body);
870                        $body_aux = shell_exec('html2text -nobs -style compact "' . $filename . '"');
871                        if (trim($body_aux) != '') $body = $body_aux;
872                        unset($body_aux);
873                        fclose($fp);
874                        unlink($filename);
875                        $body = utf8_decode($body);
876                        $body = str_replace('_','',$body);
877                        // End change block - Remove the HTML tags using the 'html2text' Linux application
878                }
879                return $body;
880        }
881
882        // Get all parts in the message with specified type and concatenate them together, unless the
883        // Content-Disposition is 'attachment', in which case the text is apparently an attachment
884        function getBodyRecursive($message, $subtype, &$body) {
885                if(!isset($message->ctype_primary)) return;
886                if(strcasecmp($message->ctype_primary,"text")==0 && strcasecmp($message->ctype_secondary,$subtype)==0 && isset($message->body))
887                $body .= $message->body;
888
889                if(strcasecmp($message->ctype_primary,"multipart")==0 && isset($message->parts) && is_array($message->parts)) {
890                        foreach($message->parts as $part) {
891                                if(!isset($part->disposition) || strcasecmp($part->disposition,"attachment"))  {
892                                        $this->getBodyRecursive($part, $subtype, $body);
893                                }
894                        }
895                }
896        }
897
898        // save the serverdelimiter for later folder (un)parsing
899        function getServerDelimiter() {
900                $list = @imap_getmailboxes($this->_mbox, $this->_server, "*");
901                if (is_array($list)) {
902                        $val = $list[0];
903
904                        return $val->delimiter;
905                }
906                return "."; // default "."
907        }
908
909        // speed things up
910        // remember what folder is currently open and only change if necessary
911        function imap_reopenFolder($folderid, $force = false) {
912                // to see changes, the folder has to be reopened!
913                if ($this->_mboxFolder != $folderid || $force) {
914                        $s = @imap_reopen($this->_mbox, $this->_server . str_replace(".", $this->_serverdelimiter, $folderid));
915                        if (!$s) debugLog("failed to change folder: ". implode(", ", imap_errors()));
916                        $this->_mboxFolder = $folderid;
917                }
918        }
919
920
921        // build a multipart email, embedding body and one file (for attachments)
922        function mail_attach($filenm,$filesize,$file_cont,$body, $body_ct, $body_cte) {
923
924                $boundary = strtoupper(md5(uniqid(time())));
925
926                $mail_header = "Content-Type: multipart/mixed; boundary=$boundary\n";
927
928                // build main body with the sumitted type & encoding from the pda
929                $mail_body  = "This is a multi-part message in MIME format\n\n";
930                $mail_body .= "--$boundary\n";
931                $mail_body .= "Content-Type:$body_ct\n";
932                $mail_body .= "Content-Transfer-Encoding:$body_cte\n\n";
933                $mail_body .= "$body\n\n";
934
935                $mail_body .= "--$boundary\n";
936                $mail_body .= "Content-Type: text/plain; name=\"$filenm\"\n";
937                $mail_body .= "Content-Transfer-Encoding: base64\n";
938                $mail_body .= "Content-Disposition: attachment; filename=\"$filenm\"\n";
939                $mail_body .= "Content-Description: $filenm\n\n";
940                $mail_body .= base64_encode($file_cont) . "\n\n";
941
942                $mail_body .= "--$boundary--\n\n";
943
944                return array($mail_header, $mail_body);
945        }
946
947        // adds a message as seen to a specified folder (used for saving sent mails)
948        function addSentMessage($folderid, $header, $body) {
949                $header_body = str_replace("\n", "\r\n", str_replace("\r", "", $header . "\n\n" . $body));
950
951                return @imap_append($this->_mbox, $this->_server . $folderid, $header_body, "\\Seen");
952        }
953
954
955        // parses address objects back to a simple "," separated string
956        function parseAddr($ad) {
957                $addr_string = "";
958                if (isset($ad) && is_array($ad)) {
959                        foreach($ad as $addr) {
960                                if ($addr_string) $addr_string .= ",";
961                                $addr_string .= $addr->mailbox . "@" . $addr->host;
962                        }
963                }
964                return $addr_string;
965        }
966
967};
968
969?>
Note: See TracBrowser for help on using the repository browser.