]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - modules/InboundEmail/InboundEmail.php
Release 6.4.0
[Github/sugarcrm.git] / modules / InboundEmail / InboundEmail.php
1 <?php
2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 /*********************************************************************************
4  * SugarCRM Community Edition is a customer relationship management program developed by
5  * SugarCRM, Inc. Copyright (C) 2004-2011 SugarCRM Inc.
6  * 
7  * This program is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU Affero General Public License version 3 as published by the
9  * Free Software Foundation with the addition of the following permission added
10  * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
11  * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
12  * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
13  * 
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
17  * details.
18  * 
19  * You should have received a copy of the GNU Affero General Public License along with
20  * this program; if not, see http://www.gnu.org/licenses or write to the Free
21  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22  * 02110-1301 USA.
23  * 
24  * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
25  * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
26  * 
27  * The interactive user interfaces in modified source and object code versions
28  * of this program must display Appropriate Legal Notices, as required under
29  * Section 5 of the GNU Affero General Public License version 3.
30  * 
31  * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
32  * these Appropriate Legal Notices must retain the display of the "Powered by
33  * SugarCRM" logo. If the display of the logo is not reasonably feasible for
34  * technical reasons, the Appropriate Legal Notices must display the words
35  * "Powered by SugarCRM".
36  ********************************************************************************/
37
38 /*********************************************************************************
39
40  * Description:  TODO: To be written.
41  * Portions created by SugarCRM are Copyright (C) SugarCRM, Inc.
42  * All Rights Reserved.
43  * Contributor(s): ______________________________________..
44  ********************************************************************************/
45
46
47
48 require_once('include/OutboundEmail/OutboundEmail.php');
49 require_once('include/Pear/HTML_Safe/Safe.php');
50
51 function this_callback($str) {
52         foreach($str as $match) {
53                 $ret .= chr(hexdec(str_replace("%","",$match)));
54         }
55         return $ret;
56 }
57
58 /**
59  * Stub for certain interactions;
60  */
61 class temp {
62         var $name;
63 }
64
65 class InboundEmail extends SugarBean {
66         // module specific
67         var $conn;
68         var $purifier; // HTMLPurifier object placeholder
69         var $email;
70
71         // fields
72         var $id;
73         var $deleted;
74         var $date_entered;
75         var $date_modified;
76         var $modified_user_id;
77         var $created_by;
78         var $created_by_name;
79         var $modified_by_name;
80         var $name;
81         var $status;
82         var $server_url;
83         var $email_user;
84         var $email_password;
85         var $port;
86         var $service;
87         var $mailbox;
88         var $mailboxarray;
89         var $delete_seen;
90         var $mailbox_type;
91         var $template_id;
92         var $stored_options;
93         var $group_id;
94         var $is_personal;
95         var $groupfolder_id;
96
97         // email 2.0
98         var $pop3socket;
99         var $outboundInstance; // id to outbound_email instance
100         var $autoImport;
101         var $iconFlagged = "F";
102         var $iconDraft = "D";
103         var $iconAnswered = "A";
104         var $iconDeleted = "del";
105         var $isAutoImport = false;
106         var $smarty;
107         var $attachmentCount = 0;
108         var $tempAttachment = array();
109         var $unsafeChars = array("&", "!", "'", '"', '\\', '/', '<', '>', '|', '$',);
110         var $currentCache;
111         var $defaultSort = 'date';
112         var $defaultDirection = "DESC";
113         var $hrSort = array(
114                         0 => 'flagged',
115                         1 => 'status',
116                         2 => 'from',
117                         3 => 'subj',
118                         4 => 'date',
119                 );
120         var $hrSortLocal = array(
121                         'flagged' => 'flagged',
122                         'status'  => 'answered',
123                         'from'    => 'fromaddr',
124                         'subject' => 'subject',
125                         'date'    => 'senddate',
126                 );
127
128         // default attributes
129         var $transferEncoding                             = array(0 => '7BIT',
130                                                                                                 1 => '8BIT',
131                                                                                                 2 => 'BINARY',
132                                                                                                 3 => 'BASE64',
133                                                                                                 4 => 'QUOTED-PRINTABLE',
134                                                                                                 5 => 'OTHER'
135                                                                                         );
136         // object attributes
137         var $safe; // place holder for HTML_Safe class
138         var $compoundMessageId; // concatenation of messageID and deliveredToEmail
139         var $serverConnectString;
140         var $disable_row_level_security = true;
141         var $InboundEmailCachePath;
142         var $InboundEmailCacheFile                      = 'InboundEmail.cache.php';
143         var $object_name                                        = 'InboundEmail';
144         var $module_dir                                 = 'InboundEmail';
145         var $table_name                                 = 'inbound_email';
146         var $new_schema                                 = true;
147         var $process_save_dates                         = true;
148         var $order_by;
149         var $db;
150         var $dbManager;
151         var $field_defs;
152         var $column_fields;
153         var $required_fields                            = array('name'                  => 'name',
154                                                                                                 'server_url'    => 'server_url',
155                                                                                                 'mailbox'               => 'mailbox',
156                                                                                                 'user'                  => 'user',
157                                                                                                 'port'                  => 'port',
158                                                                                         );
159         var $imageTypes                                 = array("JPG", "JPEG", "GIF", "PNG");
160         var $inlineImages                                       = array();  // temporary space to store ID of inlined images
161         var $defaultEmailNumAutoreplies24Hours = 10;
162         var $maxEmailNumAutoreplies24Hours = 10;
163         // custom ListView attributes
164         var $mailbox_type_name;
165         var $global_personal_string;
166         // service attributes
167         var $tls;
168         var $ca;
169         var $ssl;
170         var $protocol;
171         var $keyForUsersDefaultIEAccount = 'defaultIEAccount';
172         // prefix to use when importing inlinge images in emails
173         public $imagePrefix;
174
175         /**
176          * Sole constructor
177          */
178         function InboundEmail() {
179             $this->InboundEmailCachePath = sugar_cached('modules/InboundEmail');
180             $this->EmailCachePath = sugar_cached('modules/Emails');
181             parent::SugarBean();
182                 if(function_exists("imap_timeout")) {
183                         /*
184                          * 1: Open
185                          * 2: Read
186                          * 3: Write
187                          * 4: Close
188                          */
189                         imap_timeout(1, 60);
190                         imap_timeout(2, 60);
191                         imap_timeout(3, 60);
192                 }
193
194                 $this->safe = new HTML_Safe();
195                 $this->safe->whiteProtocols[] = "cid";
196                 $this->safe->clear();
197
198                 $this->smarty = new Sugar_Smarty();
199                 $this->overview = new Overview();
200                 $this->imagePrefix = "{$GLOBALS['sugar_config']['site_url']}/cache/images/";
201         }
202
203         /**
204          * retrieves I-E bean
205          * @param string id
206          * @return object Bean
207          */
208         function retrieve($id) {
209                 $ret = parent::retrieve($id);
210                 $this->email_password = blowfishDecode(blowfishGetKey('InboundEmail'), $this->email_password);
211                 $this->retrieveMailBoxFolders();
212                 return $ret;
213         }
214
215         /**
216          * wraps SugarBean->save()
217          * @param string ID of saved bean
218          */
219         function save($check_notify=false) {
220                 // generate cache table for email 2.0
221                 $multiDImArray = $this->generateMultiDimArrayFromFlatArray(explode(",", $this->mailbox), $this->retrieveDelimiter());
222                 $raw = $this->generateFlatArrayFromMultiDimArray($multiDImArray, $this->retrieveDelimiter());
223                 sort($raw);
224                 //_pp(explode(",", $this->mailbox));
225                 //_ppd($raw);
226                 $raw = $this->filterMailBoxFromRaw(explode(",", $this->mailbox), $raw);
227                 $this->mailbox = implode(",", $raw);
228                 if(!empty($this->email_password)) {
229                     $this->email_password = blowfishEncode(blowfishGetKey('InboundEmail'), $this->email_password);
230                 }
231                 $ret = parent::save($check_notify);
232                 return $ret;
233         }
234
235         function filterMailBoxFromRaw($mailboxArray, $rawArray) {
236                 $newArray = array_intersect($mailboxArray, $rawArray);
237                 sort($newArray);
238                 return $newArray;
239         } // fn
240
241         /**
242          * Overrides SugarBean's mark_deleted() to drop the related cache table
243          * @param string $id GUID of I-E instance
244          */
245         function mark_deleted($id) {
246                 parent::mark_deleted($id);
247                 $q = "update inbound_email set groupfolder_id = null WHERE id = '{$id}'";
248                 $r = $this->db->query($q);
249                 $this->deleteCache();
250         }
251
252         /**
253          * Mark cached email answered (replied)
254          * @param string $mailid (uid for imap, message_id for pop3)
255          */
256         function mark_answered($mailid, $type = 'smtp') {
257                 switch ($type) {
258                         case 'smtp' :
259                                 $q = "update email_cache set answered = 1 WHERE imap_uid = $mailid and ie_id = '{$this->id}'";
260                                 $this->db->query($q);
261                                 break;
262                         case 'pop3' :
263                                 $q = "update email_cache set answered = 1 WHERE message_id = '$mailid' and ie_id = '{$this->id}'";
264                                 $this->db->query($q);
265                                 break;
266                 }
267         }
268
269         /**
270          * Renames an IMAP mailbox
271          * @param string $newName
272          */
273         function renameFolder($oldName, $newName) {
274                 //$this->mailbox = "INBOX"
275                 $this->connectMailserver();
276         $oldConnect = $this->getConnectString('', $oldName);
277         $newConnect = $this->getConnectString('', $newName);
278                 if(!imap_renamemailbox($this->conn, $oldConnect , $newConnect)) {
279                         $GLOBALS['log']->debug("***INBOUNDEMAIL: failed to rename mailbox [ {$oldConnect} ] to [ {$newConnect} ]");
280                 } else {
281                 $this->mailbox = str_replace($oldName, $newName, $this->mailbox);
282                 $this->save();
283                 $sessionFoldersString  = $this->getSessionInboundFoldersString($this->server_url, $this->email_user, $this->port, $this->protocol);
284                 $sessionFoldersString = str_replace($oldName, $newName, $sessionFoldersString);
285                         $this->setSessionInboundFoldersString($this->server_url, $this->email_user, $this->port, $this->protocol, $sessionFoldersString);
286
287                 }
288         }
289
290         ///////////////////////////////////////////////////////////////////////////
291         ////    CUSTOM LOGIC HOOKS
292         /**
293          * Called from $this->getMessageText()
294          * Allows upgrade-safe custom processing of message text.
295          *
296          * To use:
297          * 1. Create a directory path: ./custom/modules/InboundEmail if it does not exist
298          * 2. Create a file in the ./custom/InboundEmail/ folder called "getMessageText.php"
299          * 3. Define a function named "custom_getMessageText()" that takes a string as an argument and returns a string
300          *
301          * @param string $msgPart
302          * @return string
303          */
304         function customGetMessageText($msgPart) {
305                 $custom = "custom/modules/InboundEmail/getMessageText.php";
306
307                 if(file_exists($custom)) {
308                         include_once($custom);
309
310                         if(function_exists("custom_getMessageText")) {
311                                 $GLOBALS['log']->debug("*** INBOUND EMAIL-CUSTOM_LOGIC: calling custom_getMessageText()");
312                                 $msgPart = custom_getMessageText($msgPart);
313                         }
314                 }
315
316                 return $msgPart;
317         }
318         ////    END CUSTOM LOGIC HOOKS
319         ///////////////////////////////////////////////////////////////////////////
320
321
322
323         ///////////////////////////////////////////////////////////////////////////
324         ////    EMAIL 2.0 SPECIFIC
325         /**
326          * constructs a nicely formatted version of raw source
327          * @param int $uid UID of email
328          * @return string
329          */
330         function getFormattedRawSource($uid) {
331                 global $app_strings;
332
333                 //if($this->protocol == 'pop3') {
334                 //$raw = $app_strings['LBL_EMAIL_VIEW_UNSUPPORTED'];
335                 //} else {
336                         if (empty($this->id)) {
337                                 $q = "SELECT raw_source FROM emails_text WHERE email_id = '{$uid}'";
338                                 $r = $this->db->query($q);
339                                 $a = $this->db->fetchByAssoc($r);
340                                 $ret = array();
341                                 $raw = utf8_encode($a['raw_source']);
342                                 if (empty($raw)) {
343                                         $raw = $app_strings['LBL_EMAIL_ERROR_VIEW_RAW_SOURCE'];
344                                 }
345                         } else {
346                                 if ($this->isPop3Protocol()) {
347                                         $uid = $this->getCorrectMessageNoForPop3($uid);
348                                 }
349                                 $raw  = imap_fetchheader($this->conn, $uid, FT_UID+FT_PREFETCHTEXT);
350                                 $raw .= utf8_encode(imap_body($this->conn, $uid, FT_UID));
351                         } // else
352                         $raw = to_html($raw);
353                         $raw = nl2br($raw);
354                 //}
355
356                 return $raw;
357         }
358
359         /**
360          * constructs a nicely formatted version of email headers.
361          * @param int $uid
362          * @return string
363          */
364         function getFormattedHeaders($uid) {
365                 global $app_strings;
366
367                 //if($this->protocol == 'pop3') {
368                 //      $header = $app_strings['LBL_EMAIL_VIEW_UNSUPPORTED'];
369                 //} else {
370                         if ($this->isPop3Protocol()) {
371                                 $uid = $this->getCorrectMessageNoForPop3($uid);
372                         }
373                         $headers = imap_fetchheader($this->conn, $uid, FT_UID);
374
375                         $lines = explode("\n", $headers);
376
377                         $header = "<table cellspacing='0' cellpadding='2' border='0' width='100%'>";
378
379                         foreach($lines as $line) {
380                                 $line = trim($line);
381
382                                 if(!empty($line)) {
383                                         $key = trim(substr($line, 0, strpos($line, ":")));
384                                         $value = trim(substr($line, strpos($line, ":") + 1));
385                                         $value = to_html($value);
386
387                                         $header .= "<tr>";
388                                         $header .= "<td class='displayEmailLabel' NOWRAP><b>{$key}</b>&nbsp;</td>";
389                                         $header .= "<td class='displayEmailValueWhite'>{$value}&nbsp;</td>";
390                                         $header .= "</tr>";
391                                 }
392                         }
393
394                         $header .= "</table>";
395                 //}
396                 return $header;
397         }
398
399         /**
400          * Empties Trash folders
401          */
402         function emptyTrash() {
403                 global $sugar_config;
404
405                 $this->mailbox = $this->get_stored_options("trashFolder");
406                 if (empty($this->mailbox)) {
407                         $this->mailbox = 'INBOX.Trash';
408                 }
409                 $this->connectMailserver();
410
411                 $uids = imap_search($this->conn, "ALL", SE_UID);
412
413                 foreach($uids as $uid) {
414                         if(!imap_delete($this->conn, $uid, FT_UID)) {
415                                 $lastError = imap_last_error();
416                                 $GLOBALS['log']->warn("INBOUNDEMAIL: emptyTrash() Could not delete message [ {$uid} ] from [ {$this->mailbox} ].  IMAP_ERROR [ {$lastError} ]");
417                         }
418                 }
419
420                 // remove local cache file
421                 $q = "DELETE FROM email_cache WHERE mbox = '{$this->mailbox}' AND ie_id = '{$this->id}'";
422                 $r = $this->db->query($q);
423         }
424
425         /**
426          * Fetches a timestamp
427          */
428         function getCacheTimestamp($mbox) {
429                 $key = $this->db->quote("{$this->id}_{$mbox}");
430                 $q = "SELECT ie_timestamp FROM inbound_email_cache_ts WHERE id = '{$key}'";
431                 $r = $this->db->query($q);
432                 $a = $this->db->fetchByAssoc($r);
433
434                 if(empty($a)) {
435                         return -1;
436                 }
437                 return $a['ie_timestamp'];
438         }
439
440         /**
441          * sets the cache timestamp
442          * @param string mbox
443          */
444         function setCacheTimestamp($mbox) {
445                 $key = $this->db->quote("{$this->id}_{$mbox}");
446                 $ts = mktime();
447                 $tsOld = $this->getCacheTimestamp($mbox);
448
449                 if($tsOld < 0) {
450                         $q = "INSERT INTO inbound_email_cache_ts (id, ie_timestamp) VALUES ('{$key}', {$ts})";
451                 } else {
452                         $q = "UPDATE inbound_email_cache_ts SET ie_timestamp = {$ts} WHERE id = '{$key}'";
453                 }
454
455                 $r = $this->db->query($q, true);
456                 $GLOBALS['log']->info("INBOUNDEMAIL-CACHE: setting timestamp query [ {$q} ]");
457         }
458
459
460         /**
461          * Gets a count of all rows that are flagged seen = 0
462          * @param string $mbox
463          * @return int
464          */
465         function getCacheUnreadCount($mbox) {
466                 $q = "SELECT count(*) c FROM email_cache WHERE mbox = '{$mbox}' AND seen = 0 AND ie_id = '{$this->id}'";
467                 $r = $this->db->query($q);
468                 $a = $this->db->fetchByAssoc($r);
469
470                 return $a['c'];
471         }
472
473         /**
474          * Returns total number of emails for a mailbox
475          * @param string mbox
476          * @return int
477          */
478         function getCacheCount($mbox) {
479                 $q = "SELECT count(*) c FROM email_cache WHERE mbox = '{$mbox}' AND ie_id = '{$this->id}'";
480                 $r = $this->db->query($q);
481                 $a = $this->db->fetchByAssoc($r);
482
483                 return $a['c'];
484         }
485
486     function getCacheUnread($mbox) {
487         $q = "SELECT count(*) c FROM email_cache WHERE mbox = '{$mbox}' AND ie_id = '{$this->id}' AND seen = '0'";
488         $r = $this->db->query($q);
489         $a = $this->db->fetchByAssoc($r);
490
491         return $a['c'];
492     }
493
494
495         /**
496          * Deletes all rows for a given instance
497          */
498         function deleteCache() {
499                 $q = "DELETE FROM email_cache WHERE ie_id = '{$this->id}'";
500
501                 $GLOBALS['log']->info("INBOUNDEMAIL: deleting cache using query [ {$q} ]");
502
503                 $r = $this->db->query($q);
504         }
505
506         /**
507          * Deletes all the pop3 data which has been deleted from server
508          */
509         function deletePop3Cache() {
510                 global $sugar_config;
511                 $UIDLs = $this->pop3_getUIDL();
512                 $cacheUIDLs = $this->pop3_getCacheUidls();
513                 foreach($cacheUIDLs as $msgNo => $msgId) {
514                         if (!in_array($msgId, $UIDLs)) {
515                                 $md5msgIds = md5($msgId);
516                                 $file = "{$this->EmailCachePath}/{$this->id}/messages/INBOX{$md5msgIds}.PHP";
517                                 $GLOBALS['log']->debug("INBOUNDEMAIL: deleting file [ {$file} ] ");
518                                 if(file_exists($file)) {
519                                         if(!unlink($file)) {
520                                                 $GLOBALS['log']->debug("INBOUNDEMAIL: Could not delete [ {$file} ] ");
521                                         } // if
522                                 } // if
523                                 $q = "DELETE from email_cache where imap_uid = {$msgNo} AND msgno = {$msgNo} AND ie_id = '{$this->id}' AND message_id = '{$msgId}'";
524                                 $r = $this->db->query($q);
525                         } // if
526                 } // for
527         } // fn
528
529         /**
530          * Retrieves cached headers
531          * @return array
532          */
533         function getCacheValueForUIDs($mbox, $UIDs) {
534                 if (!is_array($UIDs) || empty($UIDs)) {
535                         return array();
536                 }
537
538                 $q = "SELECT * FROM email_cache WHERE ie_id = '{$this->id}' AND mbox = '{$mbox}' AND ";
539                 $startIndex = 0;
540                 $endIndex = 5;
541
542                 $slicedArray = array_slice($UIDs, $startIndex ,$endIndex);
543                 $columnName = ($this->isPop3Protocol() ? "message_id" : "imap_uid");
544                 $ret = array(
545                         'timestamp'     => $this->getCacheTimestamp($mbox),
546                         'uids'          => array(),
547                         'retArr'        => array(),
548                 );
549                 while (!empty($slicedArray)) {
550                         $messageIdString = implode(',', $slicedArray);
551                         $GLOBALS['log']->debug("sliced array = {$messageIdString}");
552                         $extraWhere = "{$columnName} IN (";
553                         $i = 0;
554                         foreach($slicedArray as $UID) {
555                                 if($i != 0) {
556                                         $extraWhere = $extraWhere . ",";
557                                 } // if
558                                 $i++;
559                                 $extraWhere = "{$extraWhere} '{$UID}'";
560                         } // foreach
561                         $newQuery = $q . $extraWhere . ")";
562                         $r = $this->db->query($newQuery);
563
564                         while($a = $this->db->fetchByAssoc($r)) {
565                                 if (isset($a['uid'])) {
566                                         if ($this->isPop3Protocol()) {
567                                                 $ret['uids'][] = $a['message_id'];
568                                         } else {
569                                         $ret['uids'][] = $a['uid'];
570                                         }
571                                 }
572
573                                 $overview = new Overview();
574
575                                 foreach($a as $k => $v) {
576                                         $k=strtolower($k);
577                                         switch($k) {
578                                                 case "imap_uid":
579                                                         $overview->imap_uid = $v;
580                                                         if ($this->isPop3Protocol()) {
581                                                                 $overview->uid = $a['message_id'];
582                                                         } else {
583                                                                 $overview->uid = $v;
584                                                         }
585                                                 break;
586                                                 case "toaddr":
587                                                         $overview->to = from_html($v);
588                                                 break;
589
590                                                 case "fromaddr":
591                                                         $overview->from = from_html($v);
592                                                 break;
593
594                                                 case "mailsize":
595                                                         $overview->size = $v;
596                                                 break;
597
598                                                 case "senddate":
599                                                         $overview->date = $v;
600                                                 break;
601
602                                                 default:
603                                                         $overview->$k = from_html($v);
604                                                 break;
605                                         } // switch
606                                 } // foreach
607                                 $ret['retArr'][] = $overview;
608                         } // while
609                         $startIndex = $startIndex + $endIndex;
610                         $slicedArray = array_slice($UIDs, $startIndex ,$endIndex);
611                         $messageIdString = implode(',', $slicedArray);
612                         $GLOBALS['log']->debug("sliced array = {$messageIdString}");
613                 } // while
614                 return $ret;
615         }
616
617         /**
618          * Retrieves cached headers
619          * @return array
620          */
621         function getCacheValue($mbox, $limit = 20, $page = 1, $sort='', $direction='') {
622                 // try optimizing this call as we don't want repeat queries
623                 if(!empty($this->currentCache)) {
624                         return $this->currentCache;
625                 }
626
627                 $sort = (empty($sort)) ? $this->defaultSort : $sort;
628                 $direction = (empty($direction)) ? $this->defaultDirection : $direction;
629                 $order = " ORDER BY {$this->hrSortLocal[$sort]} {$direction}";
630
631                 $q = "SELECT * FROM email_cache WHERE ie_id = '{$this->id}' AND mbox = '{$mbox}' {$order}";
632
633                 if(!empty($limit)) {
634                         $start = ( $page - 1 ) * $limit;
635                         $r = $this->db->limitQuery($q, $start, $limit);
636                 } else {
637                         $r = $this->db->query($q);
638                 }
639
640                 $ret = array(
641                         'timestamp'     => $this->getCacheTimestamp($mbox),
642                         'uids'          => array(),
643                         'retArr'        => array(),
644                 );
645
646                 while($a = $this->db->fetchByAssoc($r)) {
647                         if (isset($a['uid'])) {
648                                 if ($this->isPop3Protocol()) {
649                                         $ret['uids'][] = $a['message_id'];
650                                 } else {
651                                 $ret['uids'][] = $a['uid'];
652                                 }
653                         }
654
655                         $overview = new Overview();
656
657                         foreach($a as $k => $v) {
658                                 $k=strtolower($k);
659                                 switch($k) {
660                                         case "imap_uid":
661                                                 $overview->imap_uid = $v;
662                                                 if ($this->isPop3Protocol()) {
663                                                         $overview->uid = $a['message_id'];
664                                                 } else {
665                                                         $overview->uid = $v;
666                                                 }
667                                         break;
668                                         case "toaddr":
669                                                 $overview->to = from_html($v);
670                                         break;
671
672                                         case "fromaddr":
673                                                 $overview->from = from_html($v);
674                                         break;
675
676                                         case "mailsize":
677                                                 $overview->size = $v;
678                                         break;
679
680                                         case "senddate":
681                                                 $overview->date = $v;
682                                         break;
683
684                                         default:
685                                                 $overview->$k = from_html($v);
686                                         break;
687                                 }
688                         }
689                         $ret['retArr'][] = $overview;
690                 }
691
692                 $this->currentCache = $ret;
693
694                 return $ret;
695         }
696
697         /**
698          * Sets cache values
699          */
700         function setCacheValue($mbox, $insert, $update=array(), $remove=array()) {
701                 if(empty($mbox)) {
702                         return;
703                 }
704                 global $timedate;
705
706
707                 // reset in-memory cache
708                 $this->currentCache = null;
709
710                 $table = 'email_cache';
711                 $where = "WHERE ie_id = '{$this->id}' AND mbox = '{$mbox}'";
712
713                 // handle removed rows
714                 if(!empty($remove)) {
715                         $removeIds = '';
716                         foreach($remove as $overview) {
717                                 if(!empty($removeIds)) {
718                                         $removeIds .= ",";
719                                 }
720
721                                 $removeIds .= "'{$overview->imap_uid}'";
722                         }
723
724                         $q = "DELETE FROM {$table} {$where} AND imap_uid IN ({$removeIds})";
725
726                         $GLOBALS['log']->info("INBOUNDEMAIL-CACHE: delete query [ {$q} ]");
727
728                         $r = $this->db->query($q, true, $q);
729                 }
730
731                 // handle insert rows
732                 if(!empty($insert)) {
733                         $q = "SELECT imap_uid FROM {$table} {$where}";
734                         $GLOBALS['log']->info("INBOUNDEMAIL-CACHE: filter UIDs query [ {$q} ]");
735                         $r = $this->db->query($q);
736                         $uids = array();
737
738                         while($a = $this->db->fetchByAssoc($r)) {
739                                 $uids[] = $a['imap_uid'];
740                         }
741                         $count = count($uids);
742                         $GLOBALS['log']->info("INBOUNDEMAIL-CACHE: found [ {$count} ] UIDs to filter against");
743
744                         $tmp = '';
745                         foreach($uids as $uid) {
746                                 if(!empty($tmp))
747                                         $tmp .= ", ";
748                                 $tmp .= "{$uid}";
749                         }
750                         $GLOBALS['log']->info("INBOUNDEMAIL-CACHE: filter UIDs: [ {$tmp} ]");
751
752                         $cols = "";
753
754                         foreach($this->overview->fieldDefs as $colDef) {
755                                 if(!empty($cols))
756                                         $cols .= ",";
757
758                                 $cols .= "{$colDef['name']}";
759                         }
760                         foreach($insert as $overview) {
761                                 if(in_array($overview->imap_uid, $uids)) {
762                                         $update[] = $overview;
763                                         continue;
764                                 }
765
766                                 $values = '';
767
768                                 foreach($this->overview->fieldDefs as $colDef) {
769                                         if(!empty($values)) {
770                                                 $values .= ", ";
771                                         }
772
773                                         // trim values for Oracle/MSSql
774                                         if(     isset($colDef['len']) && !empty($colDef['len']) &&
775                                                 isset($colDef['type']) && !empty($colDef['type']) &&
776                                                 $colDef['type'] == 'varchar'
777                                         ) {
778                                                 $overview->$colDef['name'] = substr($overview->$colDef['name'], 0, $colDef['len']);
779                                         }
780
781                                         switch($colDef['name']) {
782                                                 case "imap_uid":
783                                                         if(isset($overview->uid) && !empty($overview->uid)) {
784                                                                 $this->imap_uid = $overview->uid;
785                                                         }
786                                                         $values .= "'{$this->imap_uid}'";
787                                                 break;
788
789                                                 case "ie_id":
790                                                         $values .= "'{$this->id}'";
791                                                 break;
792
793                                                 case "toaddr":
794                                                         $values .= $this->db->quoted($overview->to);
795                                                 break;
796
797                                                 case "fromaddr":
798                                                         $values .= $this->db->quoted($overview->from);
799                                                 break;
800
801                                                 case "message_id" :
802                                                         $values .= $this->db->quoted($overview->message_id);
803                                                 break;
804
805                                                 case "mailsize":
806                                                         $values .= $overview->size;
807                                                 break;
808
809                                                 case "senddate":
810                                                         $conv=$timedate->fromString($overview->date);
811                                                         if (!empty($conv)) {
812                                                                 $values .= $this->db->quoted($conv->asDb());
813                                                         } else {
814                                                                 $values .= "NULL";
815                                                         }
816                                                 break;
817
818                                                 case "mbox":
819                                                         $values .= "'{$mbox}'";
820                                                 break;
821
822                                                 default:
823                                                         $overview->$colDef['name'] = from_html($overview->$colDef['name']);
824                                                         $overview->$colDef['name'] = $this->cleanContent($overview->$colDef['name']);
825                                                         $values .= $this->db->quoted($overview->$colDef['name']);
826                                                 break;
827                                         }
828                                 }
829
830                                 $q = "INSERT INTO {$table} ({$cols}) VALUES ({$values})";
831                                 $GLOBALS['log']->info("INBOUNDEMAIL-CACHE: insert query [ {$q} ]");
832                                 $r = $this->db->query($q, true, $q);
833                         }
834                 }
835
836                 // handle update rows
837                 if(!empty($update)) {
838                         $cols = "";
839                         foreach($this->overview->fieldDefs as $colDef) {
840                                 if(!empty($cols))
841                                         $cols .= ",";
842
843                                 $cols .= "{$colDef['name']}";
844                         }
845
846                         foreach($update as $overview) {
847                                 $q = "UPDATE {$table} SET ";
848
849                                 $set = '';
850                                 foreach($this->overview->fieldDefs as $colDef) {
851
852                                         switch($colDef['name']) {
853                                                 case "toaddr":
854                                                 case "fromaddr":
855                                                 case "mailsize":
856                                                 case "senddate":
857                                                 case "mbox":
858                                                 break;
859
860                                                 default:
861                                                         if(!empty($set)) {
862                                                                 $set .= ",";
863                                                         }
864                                                         $set .= "{$colDef['name']} = ".$this->db->quoted($overview->$colDef['name']);
865                                                 break;
866                                         }
867                                 }
868
869                                 $q .= $set . " WHERE ie_id = '{$this->id}' AND mbox = '{$overview->mbox}' AND imap_uid = '{$overview->imap_uid}'";
870                                 $GLOBALS['log']->info("INBOUNDEMAIL-CACHE: update query [ {$q} ]");
871                                 $r = $this->db->query($q, true, $q);
872                         }
873                 }
874
875         }
876
877         /**
878          * Opens a socket connection to the pop3 server
879          * @return bool
880          */
881         function pop3_open() {
882                 if(!is_resource($this->pop3socket)) {
883                         $GLOBALS['log']->info("*** INBOUNDEMAIL: opening socket connection");
884                         $exServ = explode('::', $this->service);
885                         $socket  = ($exServ[2] == 'ssl') ? "ssl://" : "tcp://";
886                         $socket .= $this->server_url;
887                         $this->pop3socket = fsockopen($socket, $this->port);
888                 } else {
889                         $GLOBALS['log']->info("*** INBOUNDEMAIL: REUSING socket connection");
890                         return true;
891                 }
892
893                 if(!is_resource($this->pop3socket)) {
894                         $GLOBALS['log']->debug("*** INBOUNDEMAIL: unable to open socket connection");
895                         return false;
896                 }
897
898                 // clear buffer
899                 $ret = trim(fgets($this->pop3socket, 1024));
900                 $GLOBALS['log']->info("*** INBOUNDEMAIL: got socket connection [ {$ret} ]");
901                 return true;
902         }
903
904         /**
905          * Closes connections and runs clean-up routines
906          */
907         function pop3_cleanUp() {
908                 $GLOBALS['log']->info("*** INBOUNDEMAIL: cleaning up socket connection");
909                 fputs($this->pop3socket, "QUIT\r\n");
910                 $buf = fgets($this->pop3socket, 1024);
911                 fclose($this->pop3socket);
912         }
913
914         /**
915          * sends a command down to the POP3 server
916          * @param string command
917          * @param string args
918          * @param bool return
919          * @return string
920          */
921         function pop3_sendCommand($command, $args='', $return=true) {
922                 $command .= " {$args}";
923                 $command = trim($command);
924                 $GLOBALS['log']->info("*** INBOUNDEMAIL: pop3_sendCommand() SEND [ {$command} ]");
925                 $command .= "\r\n";
926
927                 fputs($this->pop3socket, $command);
928
929                 if($return) {
930                         $ret = trim(fgets($this->pop3socket, 1024));
931                         $GLOBALS['log']->info("*** INBOUNDEMAIL: pop3_sendCommand() RECEIVE [ {$ret} ]");
932                         return $ret;
933                 }
934         }
935
936         function getPop3NewMessagesToDownload() {
937                 $pop3UIDL = $this->pop3_getUIDL();
938                 $cacheUIDLs = $this->pop3_getCacheUidls();
939                 // new email cache values we should deal with
940                 $diff = array_diff_assoc($pop3UIDL, $cacheUIDLs);
941                 // this is msgNo to UIDL array
942                 $diff = $this->pop3_shiftCache($diff, $cacheUIDLs);
943                 // get all the keys which are msgnos;
944                 return array_keys($diff);
945         }
946
947         function getPop3NewMessagesToDownloadForCron() {
948                 $pop3UIDL = $this->pop3_getUIDL();
949                 $cacheUIDLs = $this->pop3_getCacheUidls();
950                 // new email cache values we should deal with
951                 $diff = array_diff_assoc($pop3UIDL, $cacheUIDLs);
952                 // this is msgNo to UIDL array
953                 $diff = $this->pop3_shiftCache($diff, $cacheUIDLs);
954                 // insert data into email_cache
955                 if ($this->groupfolder_id != null && $this->groupfolder_id != "" && $this->isPop3Protocol()) {
956                         $searchResults = array_keys($diff);
957                         $concatResults = implode(",", $searchResults);
958                         if ($this->connectMailserver() == 'true') {
959                                 $fetchedOverviews = imap_fetch_overview($this->conn, $concatResults);
960                                 // clean up cache entry
961                                 foreach($fetchedOverviews as $k => $overview) {
962                                         $overview->message_id = trim($diff[$overview->msgno]);
963                                         $fetchedOverviews[$k] = $overview;
964                                 }
965                                 $this->updateOverviewCacheFile($fetchedOverviews);
966                         }
967                 } // if
968                 return $diff;
969         }
970
971         /**
972          * This method returns all the UIDL for this account. This should be called if the protocol is pop3
973          * @return array od messageno to UIDL array
974          */
975         function pop3_getUIDL() {
976                 $UIDLs = array();
977                 if($this->pop3_open()) {
978                         // authenticate
979                         $this->pop3_sendCommand("USER", $this->email_user);
980                         $this->pop3_sendCommand("PASS", $this->email_password);
981
982                         // get UIDLs
983                         $this->pop3_sendCommand("UIDL", '', false); // leave socket buffer alone until the while()
984                         fgets($this->pop3socket, 1024); // handle "OK+";
985                         $UIDLs = array();
986
987                         $buf = '!';
988
989                         if(is_resource($this->pop3socket)) {
990                                 while(!feof($this->pop3socket)) {
991                                         $buf = fgets($this->pop3socket, 1024); // 8kb max buffer - shouldn't be more than 80 chars via pop3...
992                                         //_pp(trim($buf));
993
994                                         if(trim($buf) == '.') {
995                                                 $GLOBALS['log']->debug("*** GOT '.'");
996                                                 break;
997                                         }
998
999                                         // format is [msgNo] [UIDL]
1000                                         $exUidl = explode(" ", $buf);
1001                                         $UIDLs[$exUidl[0]] = trim($exUidl[1]);
1002                                 } // while
1003                         } // if
1004                         $this->pop3_cleanUp();
1005                 } // if
1006                 return $UIDLs;
1007         } // fn
1008
1009         /**
1010          * Special handler for POP3 boxes.  Standard IMAP commands are useless.
1011          * This will fetch only partial emails for POP3 and hence needs to be call again and again based on status it returns
1012          */
1013         function pop3_checkPartialEmail($synch = false) {
1014                 require_once('include/utils/array_utils.php');
1015                 global $current_user;
1016                 global $sugar_config;
1017
1018                 $cacheDataExists = false;
1019                 $diff = array();
1020                 $results = array();
1021                 $cacheFilePath = clean_path("{$this->EmailCachePath}/{$this->id}/folders/MsgNOToUIDLData.php");
1022                 if(file_exists($cacheFilePath)) {
1023                         $cacheDataExists = true;
1024                         if($fh = @fopen($cacheFilePath, "rb")) {
1025                                 $data = "";
1026                                 $chunksize = 1*(1024*1024); // how many bytes per chunk
1027                                 while(!feof($fh)) {
1028                                         $buf = fgets($fh, $chunksize); // 8kb max buffer - shouldn't be more than 80 chars via pop3...
1029                                         $data = $data . $buf;
1030                                 flush();
1031                                 } // while
1032                                 fclose($fh);
1033                                 $diff = unserialize($data);
1034                                 if (!empty($diff)) {
1035                                         if (count($diff)> 50) {
1036                                 $newDiff = array_slice($diff, 50, count($diff), true);
1037                                         } else {
1038                                                 $newDiff=array();
1039                                         }
1040                         $results = array_slice(array_keys($diff), 0 ,50);
1041                                         $data = serialize($newDiff);
1042                                     if($fh = @fopen($cacheFilePath, "w")) {
1043                                         fputs($fh, $data);
1044                                         fclose($fh);
1045                                     } // if
1046                                 }
1047                         } // if
1048                 } // if
1049                 if (!$cacheDataExists) {
1050                         if ($synch) {
1051                             $this->deletePop3Cache();
1052                         }
1053                         $UIDLs = $this->pop3_getUIDL();
1054                         if(count($UIDLs) > 0) {
1055                                 // get cached UIDLs
1056                                 $cacheUIDLs = $this->pop3_getCacheUidls();
1057
1058                                 // new email cache values we should deal with
1059                                 $diff = array_diff_assoc($UIDLs, $cacheUIDLs);
1060                                 $diff = $this->pop3_shiftCache($diff, $cacheUIDLs);
1061                                 require_once('modules/Emails/EmailUI.php');
1062                                 EmailUI::preflightEmailCache("{$this->EmailCachePath}/{$this->id}");
1063
1064                                 if (count($diff)> 50) {
1065                         $newDiff = array_slice($diff, 50, count($diff), true);
1066                                 } else {
1067                                         $newDiff=array();
1068                                 }
1069
1070                                 $results = array_slice(array_keys($diff), 0 ,50);
1071                                 $data = serialize($newDiff);
1072                             if($fh = @fopen($cacheFilePath, "w")) {
1073                                 fputs($fh, $data);
1074                                 fclose($fh);
1075                             } // if
1076                         } else {
1077                                 $GLOBALS['log']->debug("*** INBOUNDEMAIL: could not open socket connection to POP3 server");
1078                                 return "could not open socket connection to POP3 server";
1079                         } // else
1080                 } // if
1081
1082                 // build up msgNo request
1083                 if(count($diff) > 0) {
1084                         // remove dirty cache entries
1085                         $startingNo = 0;
1086                         if (isset($_REQUEST['currentCount']) && $_REQUEST['currentCount'] > -1) {
1087                              $startingNo = $_REQUEST['currentCount'];
1088                         }
1089
1090                         $this->mailbox = 'INBOX';
1091                         $this->connectMailserver();
1092                         //$searchResults = array_keys($diff);
1093                         //$fetchedOverviews = array();
1094                         //$chunkArraySerachResults = array_chunk($searchResults, 50);
1095                         $concatResults = implode(",", $results);
1096                         $GLOBALS['log']->info('$$$$ '.$concatResults);
1097                         $GLOBALS['log']->info("[EMAIL] Start POP3 fetch overview on mailbox [{$this->mailbox}] for user [{$current_user->user_name}] on 50 data");
1098                         $fetchedOverviews = imap_fetch_overview($this->conn, $concatResults);
1099                         $GLOBALS['log']->info("[EMAIL] End POP3 fetch overview on mailbox [{$this->mailbox}] for user [{$current_user->user_name}] on "
1100                         . sizeof($fetchedOverviews) . " data");
1101
1102                         // clean up cache entry
1103                         foreach($fetchedOverviews as $k => $overview) {
1104                                 $overview->message_id = trim($diff[$overview->msgno]);
1105                                 $fetchedOverviews[$k] = $overview;
1106                         }
1107
1108                         $GLOBALS['log']->info("[EMAIL] Start updating overview cache for pop3 mailbox [{$this->mailbox}] for user [{$current_user->user_name}]");
1109                         $this->updateOverviewCacheFile($fetchedOverviews);
1110                         $GLOBALS['log']->info("[EMAIL] Start updating overview cache for pop3 mailbox [{$this->mailbox}] for user [{$current_user->user_name}]");
1111                         return array('status' => "In Progress", 'mbox' => $this->mailbox, 'count'=> (count($results) + $startingNo), 'totalcount' => count($diff), 'ieid' => $this->id);
1112                 } // if
1113                 unlink($cacheFilePath);
1114                 return  array('status' => "done");
1115         }
1116
1117
1118         /**
1119          * Special handler for POP3 boxes.  Standard IMAP commands are useless.
1120          */
1121         function pop3_checkEmail() {
1122                 if($this->pop3_open()) {
1123                         // authenticate
1124                         $this->pop3_sendCommand("USER", $this->email_user);
1125                         $this->pop3_sendCommand("PASS", $this->email_password);
1126
1127                         // get UIDLs
1128                         $this->pop3_sendCommand("UIDL", '', false); // leave socket buffer alone until the while()
1129                         fgets($this->pop3socket, 1024); // handle "OK+";
1130                         $UIDLs = array();
1131
1132                         $buf = '!';
1133
1134                         if(is_resource($this->pop3socket)) {
1135                                 while(!feof($this->pop3socket)) {
1136                                         $buf = fgets($this->pop3socket, 1024); // 8kb max buffer - shouldn't be more than 80 chars via pop3...
1137                                         //_pp(trim($buf));
1138
1139                                         if(trim($buf) == '.') {
1140                                                 $GLOBALS['log']->debug("*** GOT '.'");
1141                                                 break;
1142                                         }
1143
1144                                         // format is [msgNo] [UIDL]
1145                                         $exUidl = explode(" ", $buf);
1146                                         $UIDLs[$exUidl[0]] = trim($exUidl[1]);
1147                                 }
1148                         }
1149
1150                         $this->pop3_cleanUp();
1151
1152                         // get cached UIDLs
1153                         $cacheUIDLs = $this->pop3_getCacheUidls();
1154 //                      _pp($UIDLs);_pp($cacheUIDLs);
1155
1156                         // new email cache values we should deal with
1157                         $diff = array_diff_assoc($UIDLs, $cacheUIDLs);
1158
1159                         // remove dirty cache entries
1160                         $diff = $this->pop3_shiftCache($diff, $cacheUIDLs);
1161
1162                         // build up msgNo request
1163                         if(!empty($diff)) {
1164                                 $this->mailbox = 'INBOX';
1165                                 $this->connectMailserver();
1166                                 $searchResults = array_keys($diff);
1167                                 $concatResults = implode(",", $searchResults);
1168                                 $fetchedOverviews = imap_fetch_overview($this->conn, $concatResults);
1169
1170                                 // clean up cache entry
1171                                 foreach($fetchedOverviews as $k => $overview) {
1172                                         $overview->message_id = trim($diff[$overview->msgno]);
1173                                         $fetchedOverviews[$k] = $overview;
1174                                 }
1175
1176                                 $this->updateOverviewCacheFile($fetchedOverviews);
1177                         }
1178                 } else {
1179                         $GLOBALS['log']->debug("*** INBOUNDEMAIL: could not open socket connection to POP3 server");
1180                         return false;
1181                 }
1182         }
1183
1184         /**
1185          * Iterates through msgno and message_id to remove dirty cache entries
1186          * @param array diff
1187          */
1188         function pop3_shiftCache($diff, $cacheUIDLs) {
1189                 $msgNos = "";
1190                 $msgIds = "";
1191                 $newArray = array();
1192                 foreach($diff as $msgNo => $msgId) {
1193                         if (in_array($msgId, $cacheUIDLs)) {
1194                                 $q1 = "UPDATE email_cache SET imap_uid = {$msgNo}, msgno = {$msgNo} WHERE ie_id = '{$this->id}' AND message_id = '{$msgId}'";
1195                                 $this->db->query($q1);
1196                         } else {
1197                                 $newArray[$msgNo] = $msgId;
1198                         }
1199                 }
1200                 return $newArray;
1201                 /*
1202                 foreach($diff as $msgNo => $msgId) {
1203                         if(!empty($msgNos)) {
1204                                 $msgNos .= ", ";
1205                         }
1206                         if(!empty($msgIds)) {
1207                                 $msgIds .= ", ";
1208                         }
1209
1210                         $msgNos .= $msgNo;
1211                         $msgIds .= "'{$msgId}'";
1212                 }
1213
1214                 if(!empty($msgNos)) {
1215                         $q1 = "DELETE FROM email_cache WHERE ie_id = '{$this->id}' AND msgno IN ({$msgNos})";
1216                         $this->db->query($q1);
1217                 }
1218                 if(!empty($msgIds)) {
1219                         $q2 = "DELETE FROM email_cache WHERE ie_id = '{$this->id}' AND message_id IN ({$msgIds})";
1220                         $this->db->query($q2);
1221                 }
1222                 */
1223         }
1224
1225         /**
1226          * retrieves cached uidl values.
1227          * When dealing with POP3 accounts, the message_id column in email_cache will contain the UIDL.
1228          * @return array
1229          */
1230         function pop3_getCacheUidls() {
1231                 $q = "SELECT msgno, message_id FROM email_cache WHERE ie_id = '{$this->id}'";
1232                 $r = $this->db->query($q);
1233
1234                 $ret = array();
1235                 while($a = $this->db->fetchByAssoc($r)) {
1236                         $ret[$a['msgno']] = $a['message_id'];
1237                 }
1238
1239                 return $ret;
1240         }
1241
1242         /**
1243          * This function is used by cron job for group mailbox without group folder
1244          * @param string $msgno for pop
1245          * @param string $uid for imap
1246          */
1247         function getMessagesInEmailCache($msgno, $uid) {
1248                 $fetchedOverviews = array();
1249                 if ($this->isPop3Protocol()) {
1250                         $fetchedOverviews = imap_fetch_overview($this->conn, $msgno);
1251                         foreach($fetchedOverviews as $k => $overview) {
1252                                 $overview->message_id = $uid;
1253                                 $fetchedOverviews[$k] = $overview;
1254                         }
1255                 } else {
1256                         $fetchedOverviews = imap_fetch_overview($this->conn, $uid, FT_UID);
1257                 } // else
1258                 $this->updateOverviewCacheFile($fetchedOverviews);
1259
1260         } // fn
1261
1262         /**
1263          * Checks email (local caching too) for one mailbox
1264          * @param string $mailbox IMAP Mailbox path
1265          * @param bool $prefetch Flag to prefetch email body on check
1266          */
1267         function checkEmailOneMailbox($mailbox, $prefetch=true, $synchronize=false) {
1268                 global $sugar_config;
1269                 global $current_user;
1270                 global $app_strings;
1271
1272                 $GLOBALS['log']->info("INBOUNDEMAIL: checking mailbox [ {$mailbox} ]");
1273                 $this->mailbox = $mailbox;
1274                 $this->connectMailserver();
1275
1276                 $checkTime = '';
1277                 $shouldProcessRules = true;
1278
1279                 $timestamp = $this->getCacheTimestamp($mailbox);
1280
1281                 if($timestamp > 0) {
1282                         $checkTime = date('r', $timestamp);
1283                 }
1284
1285                 /* first time through, process ALL emails */
1286                 if(empty($checkTime) || $synchronize) {
1287                         // do not process rules for the first time or sunchronize
1288                         $shouldProcessRules = false;
1289                         $criteria = "ALL UNDELETED";
1290                         $prefetch = false; // do NOT prefetch emails on a brand new account - timeouts happen.
1291                         $GLOBALS['log']->info("INBOUNDEMAIL: new account detected - not prefetching email bodies.");
1292                 } else {
1293                         $criteria = "SINCE \"{$checkTime}\" UNDELETED"; // not using UNSEEN
1294                 }
1295                 $this->setCacheTimestamp($mailbox);
1296                 $GLOBALS['log']->info("[EMAIL] Performing IMAP search using criteria [{$criteria}] on mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1297                 $searchResults = imap_search($this->conn, $criteria, SE_UID);
1298                 $GLOBALS['log']->info("[EMAIL] Done IMAP search on mailbox [{$mailbox}] for user [{$current_user->user_name}]. Result count = ".count($searchResults));
1299
1300                 if(!empty($searchResults)) {
1301
1302                         $concatResults = implode(",", $searchResults);
1303                         $GLOBALS['log']->info("[EMAIL] Start IMAP fetch overview on mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1304                         $fetchedOverview = imap_fetch_overview($this->conn, $concatResults, FT_UID);
1305                         $GLOBALS['log']->info("[EMAIL] Done IMAP fetch overview on mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1306
1307                         $GLOBALS['log']->info("[EMAIL] Start updating overview cache for mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1308                         $this->updateOverviewCacheFile($fetchedOverview);
1309                         $GLOBALS['log']->info("[EMAIL] Done updating overview cache for mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1310
1311                         // prefetch emails
1312                         if($prefetch == true) {
1313                                 $GLOBALS['log']->info("[EMAIL] Start fetching emails for mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1314                                 $this->fetchCheckedEmails($fetchedOverview);
1315                                 $GLOBALS['log']->info("[EMAIL] Done fetching emails for mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1316                         }
1317                 } else {
1318                         $GLOBALS['log']->info("INBOUNDEMAIL: no results for mailbox [ {$mailbox} ]");
1319                 }
1320
1321                 /**
1322                  * To handle the use case where an external client is also connected, deleting emails, we need to clear our
1323                  * local cache of all emails with the "DELETED" flag
1324                  */
1325                 $criteria  = 'DELETED';
1326                 $criteria .= (!empty($checkTime)) ? " SINCE \"{$checkTime}\"" : "";
1327                 $GLOBALS['log']->info("INBOUNDEMAIL: checking for deleted emails using [ {$criteria} ]");
1328
1329                 $trashFolder = $this->get_stored_options("trashFolder");
1330                 if (empty($trashFolder)) {
1331                         $trashFolder = "INBOX.Trash";
1332                 }
1333
1334                 if($this->mailbox != $trashFolder) {
1335                         $searchResults = imap_search($this->conn, $criteria, SE_UID);
1336                         if(!empty($searchResults)) {
1337                                 $uids = implode($app_strings['LBL_EMAIL_DELIMITER'], $searchResults);
1338                                 $GLOBALS['log']->info("INBOUNDEMAIL: removing UIDs found deleted [ {$uids} ]");
1339                                 $this->getOverviewsFromCacheFile($uids, $mailbox, true);
1340                         }
1341                 }
1342         }
1343
1344            /**
1345      * Checks email (local caching too) for one mailbox
1346      * @param string $mailbox IMAP Mailbox path
1347      * @param bool $prefetch Flag to prefetch email body on check
1348      */
1349     function checkEmailOneMailboxPartial($mailbox, $prefetch=true, $synchronize=false, $start = 0, $max = -1) {
1350         global $sugar_config;
1351         global $current_user;
1352         global $app_strings;
1353
1354         $GLOBALS['log']->info("INBOUNDEMAIL: checking mailbox [ {$mailbox} ]");
1355         $this->mailbox = $mailbox;
1356         $this->connectMailserver();
1357
1358         $checkTime = '';
1359         $shouldProcessRules = true;
1360
1361         $timestamp = $this->getCacheTimestamp($mailbox);
1362
1363         if($timestamp > 0) {
1364             $checkTime = date('r', $timestamp);
1365         }
1366
1367         /* first time through, process ALL emails */
1368         if(empty($checkTime) || $synchronize) {
1369             // do not process rules for the first time or sunchronize
1370             $shouldProcessRules = false;
1371             $criteria = "ALL UNDELETED";
1372             $prefetch = false; // do NOT prefetch emails on a brand new account - timeouts happen.
1373             $GLOBALS['log']->info("INBOUNDEMAIL: new account detected - not prefetching email bodies.");
1374         } else {
1375             $criteria = "SINCE \"{$checkTime}\" UNDELETED"; // not using UNSEEN
1376         }
1377         $this->setCacheTimestamp($mailbox);
1378         $GLOBALS['log']->info("[EMAIL] Performing IMAP search using criteria [{$criteria}] on mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1379         $searchResults = $this->getCachedIMAPSearch($criteria);
1380
1381         if(!empty($searchResults)) {
1382
1383             $total = sizeof($searchResults);
1384             $searchResults = array_slice($searchResults, $start, $max);
1385
1386             $GLOBALS['log']->info("INBOUNDEMAIL: there are  $total messages in [{$mailbox}], we are on $start");
1387             $GLOBALS['log']->info("INBOUNDEMAIL: getting the next " . sizeof($searchResults) . " messages");
1388             $concatResults = implode(",", $searchResults);
1389             $GLOBALS['log']->info("INBOUNDEMAIL: Start IMAP fetch overview on mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1390             $fetchedOverview = imap_fetch_overview($this->conn, $concatResults, FT_UID);
1391             $GLOBALS['log']->info("INBOUNDEMAIL: Done IMAP fetch overview on mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1392
1393             $GLOBALS['log']->info("INBOUNDEMAIL: Start updating overview cache for mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1394             $this->updateOverviewCacheFile($fetchedOverview);
1395             $GLOBALS['log']->info("INBOUNDEMAIL: Done updating overview cache for mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1396
1397             // prefetch emails
1398             if($prefetch == true) {
1399                 $GLOBALS['log']->info("INBOUNDEMAIL: Start fetching emails for mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1400                 $this->fetchCheckedEmails($fetchedOverview);
1401                 $GLOBALS['log']->info("INBOUNDEMAIL: Done fetching emails for mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1402             }
1403             $status = ($total > $start + sizeof($searchResults)) ? 'continue' : 'done';
1404             $ret = array('status' => $status, 'count' => $start + sizeof($searchResults), 'mbox' => $mailbox, 'totalcount' => $total);
1405             $GLOBALS['log']->info("INBOUNDEMAIL: $status : Downloaded " . $start + sizeof($searchResults) . "messages of $total");
1406
1407         } else {
1408             $GLOBALS['log']->info("INBOUNDEMAIL: no results for mailbox [ {$mailbox} ]");
1409             $ret = array('status' =>'done');
1410         }
1411
1412         if ($ret['status'] == 'done') {
1413                 //Remove the cached search if we are done with this mailbox
1414                 $cacheFilePath = clean_path("{$this->EmailCachePath}/{$this->id}/folders/SearchData.php");
1415             unlink($cacheFilePath);
1416                 /**
1417                  * To handle the use case where an external client is also connected, deleting emails, we need to clear our
1418                  * local cache of all emails with the "DELETED" flag
1419                  */
1420                 $criteria  = 'DELETED';
1421                 $criteria .= (!empty($checkTime)) ? " SINCE \"{$checkTime}\"" : "";
1422                 $GLOBALS['log']->info("INBOUNDEMAIL: checking for deleted emails using [ {$criteria} ]");
1423
1424                         $trashFolder = $this->get_stored_options("trashFolder");
1425                         if (empty($trashFolder)) {
1426                                 $trashFolder = "INBOX.Trash";
1427                         }
1428
1429                 if($this->mailbox != $trashFolder) {
1430                     $searchResults = imap_search($this->conn, $criteria, SE_UID);
1431                     if(!empty($searchResults)) {
1432                         $uids = implode($app_strings['LBL_EMAIL_DELIMITER'], $searchResults);
1433                         $GLOBALS['log']->info("INBOUNDEMAIL: removing UIDs found deleted [ {$uids} ]");
1434                         $this->getOverviewsFromCacheFile($uids, $mailbox, true);
1435                     }
1436                 }
1437         }
1438         return $ret;
1439     }
1440
1441     function getCachedIMAPSearch($criteria) {
1442         global $current_user;
1443         global $sugar_config;
1444
1445         $cacheDataExists = false;
1446         $diff = array();
1447         $results = array();
1448         $cacheFolderPath = clean_path("{$this->EmailCachePath}/{$this->id}/folders");
1449         if (!file_exists($cacheFolderPath)) {
1450                 mkdir_recursive($cacheFolderPath);
1451         }
1452         $cacheFilePath = $cacheFolderPath . '/SearchData.php';
1453         $GLOBALS['log']->info("INBOUNDEMAIL: Cache path is $cacheFilePath");
1454         if(file_exists($cacheFilePath)) {
1455             $cacheDataExists = true;
1456             if($fh = @fopen($cacheFilePath, "rb")) {
1457                 $data = "";
1458                 $chunksize = 1*(1024*1024); // how many bytes per chunk
1459                 while(!feof($fh)) {
1460                     $buf = fgets($fh, $chunksize); // 8kb max buffer - shouldn't be more than 80 chars via pop3...
1461                     $data = $data . $buf;
1462                     flush();
1463                 } // while
1464                 fclose($fh);
1465                 $results = unserialize($data);
1466             } // if
1467         } // if
1468         if (!$cacheDataExists) {
1469             $searchResults = imap_search($this->conn, $criteria, SE_UID);
1470             if(count($searchResults) > 0) {
1471                 $results = $searchResults;
1472                 $data = serialize($searchResults);
1473                 if($fh = @fopen($cacheFilePath, "w")) {
1474                     fputs($fh, $data);
1475                     fclose($fh);
1476                 } // if
1477             }
1478         } // if
1479         return $results;
1480     }
1481
1482     function checkEmailIMAPPartial($prefetch=true, $synch = false) {
1483         $GLOBALS['log']->info("*****************INBOUNDEMAIL: at IMAP check partial");
1484         global $sugar_config;
1485         $result = $this->connectMailserver();
1486         if ($result == 'false')
1487         {
1488             return array(
1489                 'status' => 'error',
1490                 'message' => 'Email server is down'
1491             );
1492         }
1493         $mailboxes = $this->getMailboxes(true);
1494         if (!in_array('INBOX', $mailboxes)) {
1495             $mailboxes[] = 'INBOX';
1496         }
1497         sort($mailboxes);
1498         if (isset($_REQUEST['mbox']) && !empty($_REQUEST['mbox']) && isset($_REQUEST['currentCount'])) {
1499                 $GLOBALS['log']->info("INBOUNDEMAIL: Picking up from where we left off");
1500             $mbox = $_REQUEST['mbox'];
1501             $count = $_REQUEST['currentCount'];
1502         } else {
1503                 if ($synch) {
1504                         $GLOBALS['log']->info("INBOUNDEMAIL: Cleaning out the cache");
1505                         $this->cleanOutCache();
1506                 }
1507             $mbox = $mailboxes[0];
1508             $count = 0;
1509         }
1510         $GLOBALS['log']->info("INBOUNDEMAIL:found " . sizeof($mailboxes) . " Mailboxes");
1511         $index = array_search($mbox, $mailboxes) + 1;
1512         $ret = $this->checkEmailOneMailboxPartial($mbox, $prefetch, $synch, $count, 100);
1513         while($ret['status'] == 'done' && $index < sizeof($mailboxes)) {
1514             if ($ret['count'] > 100) {
1515                 $ret['mbox'] = $mailboxes[$index];
1516                 $ret['status'] = 'continue';
1517                 return $ret;
1518             }
1519             $GLOBALS['log']->info("INBOUNDEMAIL: checking account [ $index => $mbox : $count]");
1520             $mbox = $mailboxes[$index];
1521             $ret = $this->checkEmailOneMailboxPartial($mbox, $prefetch, $synch, 0, 100);
1522             $index++;
1523         }
1524
1525         return $ret;
1526     }
1527
1528         function checkEmail2_meta() {
1529                 global $sugar_config;
1530
1531                 $this->connectMailserver();
1532                 $mailboxes = $this->getMailboxes(true);
1533                 $mailboxes[] = 'INBOX';
1534                 sort($mailboxes);
1535
1536                 $GLOBALS['log']->info("INBOUNDEMAIL: checking account [ {$this->name} ]");
1537
1538                 $mailboxes_meta = array();
1539                 foreach($mailboxes as $mailbox) {
1540                         $mailboxes_meta[$mailbox] = $this->getMailboxProcessCount($mailbox);
1541                 }
1542
1543                 $ret = array();
1544                 $ret['mailboxes'] = $mailboxes_meta;
1545
1546                 foreach($mailboxes_meta as $count) {
1547                         $ret['processCount'] += $count;
1548                 }
1549                 return $ret;
1550         }
1551
1552         function getMailboxProcessCount($mailbox) {
1553                 global $sugar_config;
1554
1555                 $GLOBALS['log']->info("INBOUNDEMAIL: checking mailbox [ {$mailbox} ]");
1556                 $this->mailbox = $mailbox;
1557                 $this->connectMailserver();
1558
1559                 $timestamp = $this->getCacheTimestamp($mailbox);
1560
1561                 $checkTime = '';
1562                 if($timestamp > 0) {
1563                         $checkTime = date('r', $timestamp);
1564                 }
1565
1566                 /* first time through, process ALL emails */
1567                 if(empty($checkTime)) {
1568                         $criteria = "ALL UNDELETED";
1569                         $prefetch = false; // do NOT prefetch emails on a brand new account - timeouts happen.
1570                         $GLOBALS['log']->info("INBOUNDEMAIL: new account detected - not prefetching email bodies.");
1571                 } else {
1572                         $criteria = "SINCE \"{$checkTime}\" UNDELETED"; // not using UNSEEN
1573                 }
1574
1575                 $GLOBALS['log']->info("INBOUNDEMAIL: using [ {$criteria} ]");
1576                 $searchResults = imap_search($this->conn, $criteria, SE_UID);
1577
1578                 if(!empty($searchResults)) {
1579                         $concatResults = implode(",", $searchResults);
1580                 } else {
1581                         $GLOBALS['log']->info("INBOUNDEMAIL: no results for mailbox [ {$mailbox} ]");
1582                 }
1583
1584                 if(empty($searchResults)) {
1585                         return 0;
1586                 }
1587
1588                 return count($searchResults);
1589         }
1590
1591         /**
1592          * update INBOX
1593          */
1594         function checkEmail($prefetch=true, $synch = false) {
1595                 global $sugar_config;
1596
1597                 if($this->protocol == 'pop3') {
1598                         $this->pop3_checkEmail();
1599                 } else {
1600                         $this->connectMailserver();
1601                         $mailboxes = $this->getMailboxes(true);
1602                         $mailboxes[] = 'INBOX';
1603                         sort($mailboxes);
1604
1605                         $GLOBALS['log']->info("INBOUNDEMAIL: checking account [ {$this->name} ]");
1606
1607                         foreach($mailboxes as $mailbox) {
1608                                 $this->checkEmailOneMailbox($mailbox, $prefetch, $synch);
1609                         }
1610                 }
1611         }
1612
1613         /**
1614          * full synchronization
1615          */
1616         function syncEmail() {
1617                 global $sugar_config;
1618                 global $current_user;
1619
1620                 $showFolders = unserialize(base64_decode($current_user->getPreference('showFolders', 'Emails')));
1621
1622                 if(empty($showFolders)) {
1623                         $showFolders = array();
1624                 }
1625
1626                 $email = new Email();
1627                 $email->email2init();
1628
1629                 // personal accounts
1630                 if($current_user->hasPersonalEmail()) {
1631                         $personals = $this->retrieveByGroupId($current_user->id);
1632
1633                         foreach($personals as $personalAccount) {
1634                                 if(in_array($personalAccount->id, $showFolders)) {
1635                                         $personalAccount->email = $email;
1636                                         if ($personalAccount->isPop3Protocol()) {
1637                                                 $personalAccount->deletePop3Cache();
1638                                                 continue;
1639                                         }
1640                                         $personalAccount->cleanOutCache();
1641                                         $personalAccount->connectMailserver();
1642                                         $mailboxes = $personalAccount->getMailboxes(true);
1643                                         $mailboxes[] = 'INBOX';
1644                                         sort($mailboxes);
1645
1646                                         $GLOBALS['log']->info("[EMAIL] Start checking account [{$personalAccount->name}] for user [{$current_user->user_name}]");
1647
1648                                         foreach($mailboxes as $mailbox) {
1649                                                 $GLOBALS['log']->info("[EMAIL] Start checking mailbox [{$mailbox}] of account [{$personalAccount->name}] for user [{$current_user->user_name}]");
1650                                                 $personalAccount->checkEmailOneMailbox($mailbox, false, true);
1651                                                 $GLOBALS['log']->info("[EMAIL] Done checking mailbox [{$mailbox}] of account [{$personalAccount->name}] for user [{$current_user->user_name}]");
1652                                         }
1653                                         $GLOBALS['log']->info("[EMAIL] Done checking account [{$personalAccount->name}] for user [{$current_user->user_name}]");
1654                                 }
1655                         }
1656                 }
1657
1658                 // group accounts
1659                 $beans = $this->retrieveAllByGroupId($current_user->id, false);
1660                 foreach($beans as $k => $groupAccount) {
1661                         if(in_array($groupAccount->id, $showFolders)) {
1662                                 $groupAccount->email = $email;
1663                                 $groupAccount->cleanOutCache();
1664                                 $groupAccount->connectMailserver();
1665                                 $mailboxes = $groupAccount->getMailboxes(true);
1666                                 $mailboxes[] = 'INBOX';
1667                                 sort($mailboxes);
1668
1669                                 $GLOBALS['log']->info("INBOUNDEMAIL: checking account [ {$groupAccount->name} ]");
1670
1671                                 foreach($mailboxes as $mailbox) {
1672                                         $groupAccount->checkEmailOneMailbox($mailbox, false, true);
1673                                 }
1674                         }
1675                 }
1676         }
1677
1678
1679         /**
1680          * Deletes cached messages when moving from folder to folder
1681          * @param string $uids
1682          * @param string $fromFolder
1683          * @param string $toFolder
1684          */
1685         function deleteCachedMessages($uids, $fromFolder) {
1686                 global $sugar_config;
1687
1688                 if(!isset($this->email) && !isset($this->email->et)) {
1689                         $this->email = new Email();
1690                         $this->email->email2init();
1691                 }
1692
1693                 $uids = $this->email->et->_cleanUIDList($uids);
1694
1695                 foreach($uids as $uid) {
1696                         $file = "{$this->EmailCachePath}/{$this->id}/messages/{$fromFolder}{$uid}.php";
1697
1698                         if(file_exists($file)) {
1699                                 if(!unlink($file)) {
1700                                         $GLOBALS['log']->debug("INBOUNDEMAIL: Could not delete [ {$file} ]");
1701                                 }
1702                         }
1703                 }
1704         }
1705
1706         /**
1707          * similar to imap_fetch_overview, but it gets overviews from a local cache
1708          * file.
1709          * @param string $uids UIDs in comma-delimited format
1710          * @param string $mailbox The mailbox in focus, will default to $this->mailbox
1711          * @param bool $remove Default false
1712          * @return array
1713          */
1714         function getOverviewsFromCacheFile($uids, $mailbox='', $remove=false) {
1715                 global $app_strings;
1716                 if(!isset($this->email) && !isset($this->email->et)) {
1717                         $this->email = new Email();
1718                         $this->email->email2init();
1719                 }
1720
1721                 $uids = $this->email->et->_cleanUIDList($uids, true);
1722
1723                 // load current cache file
1724                 $mailbox = empty($mailbox) ? $this->mailbox : $mailbox;
1725                 $cacheValue = $this->getCacheValue($mailbox);
1726                 $ret = array();
1727
1728                 // prep UID array
1729                 $exUids = explode($app_strings['LBL_EMAIL_DELIMITER'], $uids);
1730                 foreach($exUids as $k => $uid) {
1731                         $exUids[$k] = trim($uid);
1732                 }
1733
1734                 // fill $ret will requested $uids
1735                 foreach($cacheValue['retArr'] as $k => $overview) {
1736                         if(in_array($overview->imap_uid, $exUids)) {
1737                                 $ret[] = $overview;
1738                         }
1739                 }
1740
1741                 // remove requested $uids from current cache file (move_mail() type action)
1742                 if($remove) {
1743                         $this->setCacheValue($mailbox, array(), array(), $ret);
1744                 }
1745                 return $ret;
1746         }
1747
1748         /**
1749          * merges new info with the saved cached file
1750          * @param array $array Array of email Overviews
1751          * @param string $type 'append' or 'remove'
1752          * @param string $mailbox Target mailbox if not current assigned
1753          */
1754         function updateOverviewCacheFile($array, $type='append', $mailbox='') {
1755                 $mailbox = empty($mailbox) ? $this->mailbox : $mailbox;
1756
1757                 $cacheValue = $this->getCacheValue($mailbox);
1758                 $uids = $cacheValue['uids'];
1759
1760                 $updateRows = array();
1761                 $insertRows = array();
1762                 $removeRows = array();
1763
1764                 // update values
1765                 if($type == 'append') { // append
1766                         /* we are adding overviews to the cache file */
1767                         foreach($array as $overview) {
1768                                 if(isset($overview->uid)) {
1769                                         $overview->imap_uid = $overview->uid; // coming from imap_fetch_overview() call
1770                                 }
1771
1772                                 if(!in_array($overview->imap_uid, $uids)) {
1773                                         $insertRows[] = $overview;
1774                                 }
1775                         }
1776                 } else {
1777                         $updatedCacheOverviews = array();
1778                         // compare against generated list
1779                         /* we are removing overviews from the cache file */
1780                         foreach($cacheValue['retArr'] as $cacheOverview) {
1781                                 if(!in_array($cacheOverview->imap_uid, $uids)) {
1782                                         $insertRows[] = $cacheOverview;
1783                                 } else {
1784                                         $removeRows[] = $cacheOverview;
1785                                 }
1786                         }
1787
1788                         $cacheValue['retArr'] = $updatedCacheOverviews;
1789                 }
1790
1791                 $this->setCacheValue($mailbox, $insertRows, $updateRows, $removeRows);
1792         }
1793
1794         /**
1795          * Check email prefetches email bodies for quicker display
1796          * @param array array of fetched overviews
1797          */
1798         function fetchCheckedEmails($fetchedOverviews) {
1799                 global $sugar_config;
1800
1801                 if(is_array($fetchedOverviews) && !empty($fetchedOverviews)) {
1802                         foreach($fetchedOverviews as $overview) {
1803                                 if($overview->size < 10000) {
1804
1805                                         $uid = $overview->imap_uid;
1806
1807                                         if(!empty($uid)) {
1808                                                 $file = "{$this->mailbox}{$uid}.php";
1809                                                 $cacheFile = clean_path("{$this->EmailCachePath}/{$this->id}/messages/{$file}");
1810
1811                                                 if(!file_exists($cacheFile)) {
1812                                                         $GLOBALS['log']->info("INBOUNDEMAIL: Prefetching email [ {$file} ]");
1813                                                         $this->setEmailForDisplay($uid);
1814                                                         $out = $this->displayOneEmail($uid, $this->mailbox);
1815                                                         $this->email->et->writeCacheFile('out', $out, $this->id, 'messages', "{$this->mailbox}{$uid}.php");
1816                                                 } else {
1817                                                         $GLOBALS['log']->debug("INBOUNDEMAIL: Trying to prefetch an email we already fetched! [ {$cacheFile} ]");
1818                                                 }
1819                                         } else {
1820                                                 $GLOBALS['log']->debug("*** INBOUNDEMAIL: prefetch has a message with no UID");
1821                                         }
1822                                 } else {
1823                                         $GLOBALS['log']->debug("INBOUNDEMAIL: skipping email prefetch - size too large [ {$overview->size} ]");
1824                                 }
1825                         }
1826                 }
1827         }
1828
1829         /**
1830          * Sets flags on emails.  Assumes that connection is live, correct folder is
1831          * set.
1832          * @param string $uids Sequence of UIDs, comma separated
1833          * @param string $type Flag to mark
1834          */
1835         function markEmails($uids, $type) {
1836                 switch($type) {
1837                         case 'unread':
1838                                 $result = imap_clearflag_full($this->conn, $uids, '\\SEEN', ST_UID);
1839                         break;
1840                         case 'read':
1841                                 $result = imap_setflag_full($this->conn, $uids, '\\SEEN', ST_UID);
1842                         break;
1843                         case 'flagged':
1844                                 $result = imap_setflag_full($this->conn, $uids, '\\FLAGGED', ST_UID);
1845                         break;
1846                         case 'unflagged':
1847                                 $result = imap_clearflag_full($this->conn, $uids, '\\FLAGGED', ST_UID);
1848                         break;
1849                         case 'answered':
1850                                 $result = imap_setflag_full($this->conn, $uids, '\\Answered', ST_UID);
1851                         break;
1852                 }
1853         }
1854         ////    END EMAIL 2.0 SPECIFIC
1855         ///////////////////////////////////////////////////////////////////////////
1856
1857
1858
1859         ///////////////////////////////////////////////////////////////////////////
1860         ////    SERVER MANIPULATION METHODS
1861         /**
1862          * Deletes the specified folder
1863          * @param string $mbox "::" delimited IMAP mailbox path, ie, INBOX.saved.stuff
1864          * @return bool
1865          */
1866         function deleteFolder($mbox) {
1867                 $returnArray = array();
1868                 if ($this->getCacheCount($mbox) > 0) {
1869                         $returnArray['status'] = false;
1870                         $returnArray['errorMessage'] = "Can not delete {$mbox} as it has emails.";
1871                         return $returnArray;
1872                 }
1873                 $connectString = $this->getConnectString('', $mbox);
1874                 //Remove Folder cache
1875                 global $sugar_config;
1876                 unlink("{$this->EmailCachePath}/{$this->id}/folders/folders.php");
1877
1878                 if(imap_unsubscribe($this->conn, imap_utf7_encode($connectString))) {
1879                         if(imap_deletemailbox($this->conn, $connectString)) {
1880                         $this->mailbox = str_replace(("," . $mbox), "", $this->mailbox);
1881                         $this->save();
1882                         $sessionFoldersString  = $this->getSessionInboundFoldersString($this->server_url, $this->email_user, $this->port, $this->protocol);
1883                         $sessionFoldersString = str_replace(("," . $mbox), "", $sessionFoldersString);
1884                                 $this->setSessionInboundFoldersString($this->server_url, $this->email_user, $this->port, $this->protocol, $sessionFoldersString);
1885                                 $returnArray['status'] = true;
1886                                 return $returnArray;
1887                         } else {
1888                                 $GLOBALS['log']->error("*** ERROR: EMAIL2.0 - could not delete IMAP mailbox with path: [ {$connectString} ]");
1889                                 $returnArray['status'] = false;
1890                                 $returnArray['errorMessage'] = "NOOP: could not delete folder: {$connectString}";
1891                                 return $returnArray;
1892                                 return false;
1893                         }
1894                 } else {
1895                         $GLOBALS['log']->error("*** ERROR: EMAIL2.0 - could not unsubscribe from folder, {$connectString} before deletion.");
1896                         $returnArray['status'] = false;
1897                         $returnArray['errorMessage'] = "NOOP: could not unsubscribe from folder, {$connectString} before deletion.";
1898                         return $returnArray;
1899                 }
1900         }
1901
1902         /**
1903          * Saves new folders
1904          * @param string $name Name of new IMAP mailbox
1905          * @param string $mbox "::" delimited IMAP mailbox path, ie, INBOX.saved.stuff
1906          * @return bool True on success
1907          */
1908         function saveNewFolder($name, $mbox) {
1909                 global $sugar_config;
1910         //Remove Folder cache
1911         global $sugar_config;
1912         //unlink("{$this->EmailCachePath}/{$this->id}/folders/folders.php");
1913
1914         //$mboxImap = $this->getImapMboxFromSugarProprietary($mbox);
1915         $delimiter = $this->get_stored_options('folderDelimiter');
1916         if (!$delimiter) {
1917                 $delimiter = '.';
1918         }
1919
1920         $newFolder = $mbox . $delimiter . $name;
1921         $mbox .= $delimiter.str_replace($delimiter, "_", $name);
1922         $connectString = $this->getConnectString('', $mbox);
1923
1924                 if(imap_createmailbox($this->conn, imap_utf7_encode($connectString))) {
1925                         imap_subscribe($this->conn, imap_utf7_encode($connectString));
1926                         $status = imap_status($this->conn, str_replace("{$delimiter}{$name}","",$connectString), SA_ALL);
1927                 $this->mailbox = $this->mailbox . "," . $newFolder;
1928                 $this->save();
1929                 $sessionFoldersString  = $this->getSessionInboundFoldersString($this->server_url, $this->email_user, $this->port, $this->protocol);
1930                 $sessionFoldersString = $sessionFoldersString . "," . $newFolder;
1931                         $this->setSessionInboundFoldersString($this->server_url, $this->email_user, $this->port, $this->protocol, $sessionFoldersString);
1932
1933                         echo json_encode($status);
1934                         return true;
1935                 } else {
1936                         echo "NOOP: could not create folder";
1937                         $GLOBALS['log']->error("*** ERROR: EMAIL2.0 - could not create IMAP mailbox with path: [ {$connectString} ]");
1938                         return false;
1939                 }
1940
1941         }
1942
1943         /**
1944          * Constructs an IMAP c-client compatible folder path from Sugar proprietary
1945          * @param string $mbox "::" delimited IMAP mailbox path, ie, INBOX.saved.stuff
1946          * @return string
1947          */
1948         function getImapMboxFromSugarProprietary($mbox) {
1949                 $exMbox = explode("::", $mbox);
1950
1951                 $mboxImap = '';
1952
1953                 for($i=2; $i<count($exMbox); $i++) {
1954                         if(!empty($mboxImap)) {
1955                                 $mboxImap .= ".";
1956                         }
1957                         $mboxImap .= $exMbox[$i];
1958                 }
1959
1960                 return $mboxImap;
1961         }
1962
1963         /**
1964          * Searches IMAP (and POP3?) accounts/folders for emails with qualifying criteria
1965          */
1966         function search($ieId, $subject='', $from='', $to='', $body='', $dateFrom='', $dateTo='') {
1967                 global $current_user;
1968                 global $app_strings;
1969                 global $timedate;
1970
1971                 $beans = array();
1972                 $bean = new InboundEmail();
1973                 $bean->retrieve($ieId);
1974                 $beans[] = $bean;
1975                 //$beans = $this->retrieveAllByGroupId($current_user->id, true);
1976
1977                 $subject = urldecode($subject);
1978
1979                 $criteria  = "";
1980                 $criteria .= (!empty($subject)) ? 'SUBJECT '.from_html($subject).'' : "";
1981                 $criteria .= (!empty($from)) ? ' FROM "'.$from.'"' : "";
1982                 $criteria .= (!empty($to)) ? ' FROM "'.$to.'"' : "";
1983                 $criteria .= (!empty($body)) ? ' TEXT "'.$body.'"' : "";
1984                 $criteria .= (!empty($dateFrom)) ? ' SINCE "'.$timedate->fromString($dateFrom)->format('d-M-Y').'"' : "";
1985                 $criteria .= (!empty($dateTo)) ? ' BEFORE "'.$timedate->fromString($dateTo)->format('d-M-Y').'"' : "";
1986                 //$criteria .= (!empty($from)) ? ' FROM "'.$from.'"' : "";
1987
1988                 $showFolders = unserialize(base64_decode($current_user->getPreference('showFolders', 'Emails')));
1989
1990                 $out = array();
1991
1992                 foreach($beans as $bean) {
1993                         if(!in_array($bean->id, $showFolders)) {
1994                                 continue;
1995                         }
1996
1997                         $GLOBALS['log']->info("*** INBOUNDEMAIL: searching [ {$bean->name} ] for [ {$subject}{$from}{$to}{$body}{$dateFrom}{$dateTo} ]");
1998                         $group = (!$bean->is_personal) ? 'group.' : '';
1999                         $bean->connectMailServer();
2000                         $mailboxes = $bean->getMailboxes(true);
2001                         if (!in_array('INBOX', $mailboxes)) {
2002                                 $mailboxes[] = 'INBOX';
2003                         }
2004                         $totalHits = 0;
2005
2006                         foreach($mailboxes as $mbox) {
2007                                 $bean->mailbox = $mbox;
2008                                 $searchOverviews = array();
2009                                 if ($bean->protocol == 'pop3') {
2010                                         $pop3Criteria = "SELECT * FROM email_cache WHERE ie_id = '{$bean->id}' AND mbox = '{$mbox}'";
2011                                         $pop3Criteria .= (!empty($subject)) ? ' AND subject like "%'.$bean->db->quote($subject).'%"' : "";
2012                                         $pop3Criteria .= (!empty($from)) ? ' AND fromaddr like "%'.$from.'%"' : "";
2013                                         $pop3Criteria .= (!empty($to)) ? ' AND toaddr like "%'.$to.'%"' : "";
2014                                         $pop3Criteria .= (!empty($dateFrom)) ? ' AND senddate > "'.$dateFrom.'"' : "";
2015                                         $pop3Criteria .= (!empty($dateTo)) ? ' AND senddate < "'.$dateTo.'"' : "";
2016                                         $GLOBALS['log']->info("*** INBOUNDEMAIL: searching [ {$mbox} ] using criteria [ {$pop3Criteria} ]");
2017
2018                                         $r = $bean->db->query($pop3Criteria);
2019                                         while($a = $bean->db->fetchByAssoc($r)) {
2020                                                 $overview = new Overview();
2021
2022                                                 foreach($a as $k => $v) {
2023                                                         $k=strtolower($k);
2024                                                         switch($k) {
2025                                                                 case "imap_uid":
2026                                                                         $overview->imap_uid = $v;
2027                                                                         $overview->uid = $a['message_id'];
2028                                                                 break;
2029                                                                 case "toaddr":
2030                                                                         $overview->to = from_html($v);
2031                                                                 break;
2032
2033                                                                 case "fromaddr":
2034                                                                         $overview->from = from_html($v);
2035                                                                 break;
2036
2037                                                                 case "mailsize":
2038                                                                         $overview->size = $v;
2039                                                                 break;
2040
2041                                                                 case "senddate":
2042                                                                         $overview->date = $timedate->fromString($v)->format('r');
2043                                                                 break;
2044
2045                                                                 default:
2046                                                                         $overview->$k = from_html($v);
2047                                                                 break;
2048                                                         } // sqitch
2049                                                 } // foreach
2050                                                 $searchOverviews[] = $overview;
2051                                         } // while
2052                                 } else {
2053                                         $bean->connectMailServer();
2054                                         $searchResult = imap_search($bean->conn, $criteria, SE_UID);
2055                                         if (!empty($searchResult)) {
2056                                                 $searchOverviews = imap_fetch_overview($bean->conn, implode(',', $searchResult), FT_UID);
2057                                         } // if
2058                                 } // else
2059                                 $numHits = count($searchOverviews);
2060
2061                                 if($numHits > 0) {
2062                                         $totalHits = $totalHits + $numHits;
2063                                         $ret = $bean->sortFetchedOverview($searchOverviews, 'date', 'desc', true);
2064                                         $mbox = "{$bean->id}.SEARCH";
2065                                         $out = array_merge($out, $bean->displayFetchedSortedListXML($ret, $mbox, false));
2066                                 }
2067                         }
2068                 }
2069
2070                 $metadata = array();
2071                 $metadata['mbox'] = $app_strings['LBL_EMAIL_SEARCH_RESULTS_TITLE'];
2072                 $metadata['ieId'] = $this->id;
2073                 $metadata['name'] = $this->name;
2074                 $metadata['unreadChecked'] = ($current_user->getPreference('showUnreadOnly', 'Emails') == 1) ? 'CHECKED' : '';
2075                 $metadata['out'] = $out;
2076
2077                 return $metadata;
2078         }
2079
2080         /**
2081          * repairs the encrypted password for a given I-E account
2082          * @return bool True on success
2083          */
2084         function repairAccount() {
2085
2086                 for($i=0; $i<3; $i++) {
2087                         if($i != 0) { // decode is performed on retrieve already
2088                                 $this->email_password = blowfishDecode(blowfishGetKey('InboundEmail'), $this->email_password);
2089                         }
2090
2091                         if($this->connectMailserver() == 'true') {
2092                                 $this->save(); // save decoded password (is encoded on save())
2093                                 return true;
2094                         }
2095                 }
2096
2097                 return false;
2098         }
2099
2100         /**
2101          * soft deletes a User's personal inbox
2102          * @param string id I-E id
2103          * @param string user_name User name of User in focus, NOT current_user
2104          * @return bool True on success
2105          */
2106         function deletePersonalEmailAccount($id, $user_name) {
2107                 $q = "SELECT ie.id FROM inbound_email ie LEFT JOIN users u ON ie.group_id = u.id WHERE u.user_name = '{$user_name}'";
2108                 $r = $this->db->query($q, true);
2109
2110                 while($a = $this->db->fetchByAssoc($r)) {
2111                         if(!empty($a) && $a['id'] == $id) {
2112                                 $this->retrieve($id);
2113                                 $this->deleted = 1;
2114                                 $this->save();
2115                                 return true;
2116                         }
2117                 }
2118                 return false;
2119         }
2120
2121         function getTeamSetIdForTeams($teamIds) {
2122                 if(!is_array($teamIds)){
2123                    $teamIds = array($teamIds);
2124                 } // if
2125                 $teamSet = new TeamSet();
2126                 $team_set_id = $teamSet->addTeams($teamIds);
2127                 return $team_set_id;
2128         } // fn
2129
2130         /**
2131          * Saves Personal Inbox settings for Users
2132          * @param string userId ID of user to assign all emails for this account
2133          * @param strings userName Name of account, for Sugar purposes
2134          * @param bool forceSave Default true.  Flag to save errored settings.
2135          * @return boolean true on success, false on fail
2136          */
2137         function savePersonalEmailAccount($userId = '', $userName = '', $forceSave=true) {
2138                 $groupId = $userId;
2139                 $accountExists = false;
2140                 if(isset($_REQUEST['ie_id']) && !empty($_REQUEST['ie_id'])) {
2141                         $this->retrieve($_REQUEST['ie_id']);
2142                         $accountExists = true;
2143                 }
2144                 $ie_name = $_REQUEST['ie_name'];
2145
2146                 $this->is_personal = 1;
2147                 $this->name = $ie_name;
2148                 $this->group_id = $groupId;
2149                 $this->status = $_REQUEST['ie_status'];
2150                 $this->server_url = trim($_REQUEST['server_url']);
2151                 $this->email_user = trim($_REQUEST['email_user']);
2152                 if(!empty($_REQUEST['email_password'])) {
2153                     $this->email_password = html_entity_decode($_REQUEST['email_password'], ENT_QUOTES);
2154                 }
2155                 $this->port = trim($_REQUEST['port']);
2156                 $this->protocol = $_REQUEST['protocol'];
2157                 if ($this->protocol == "pop3") {
2158                         $_REQUEST['mailbox'] = "INBOX";
2159                 }
2160                 $this->mailbox = $_REQUEST['mailbox'];
2161                 $this->mailbox_type = 'pick'; // forcing this
2162
2163
2164                 if(isset($_REQUEST['ssl']) && $_REQUEST['ssl'] == 1) { $useSsl = true; }
2165                 else $useSsl = false;
2166                 $this->service = '::::::::::';
2167
2168                 if($forceSave) {
2169                         $id = $this->save(); // saving here to prevent user from having to re-enter all the info in case of error
2170                         $this->retrieve($id);
2171                 }
2172
2173                 $this->protocol = $_REQUEST['protocol']; // need to set this again since we safe the "service" string to empty explode values
2174                 $opts = $this->getSessionConnectionString($this->server_url, $this->email_user, $this->port, $this->protocol);
2175                 $detectedOpts = $this->findOptimumSettings($useSsl);
2176
2177                 //If $detectedOpts is empty, there was an error connecting, so clear $opts. If $opts was empty, use $detectedOpts
2178                 if (empty($opts) || empty($detectedOpts) || (empty($detectedOpts['good']) && empty($detectedOpts['serial'])))
2179                 {
2180                   $opts = $detectedOpts;
2181                 }
2182                 $delimiter = $this->getSessionInboundDelimiterString($this->server_url, $this->email_user, $this->port, $this->protocol);
2183
2184                 if(isset($opts['serial']) && !empty($opts['serial'])) {
2185                         $this->service = $opts['serial'];
2186                         if(isset($_REQUEST['mark_read']) && $_REQUEST['mark_read'] == 1) {
2187                                 $this->delete_seen = 0;
2188                         } else {
2189                                 $this->delete_seen = 1;
2190                         }
2191
2192                         // handle stored_options serialization
2193                         if(isset($_REQUEST['only_since']) && $_REQUEST['only_since'] == 1) {
2194                                 $onlySince = true;
2195                         } else {
2196                                 $onlySince = false;
2197                         }
2198
2199                         $focusUser = new User();
2200                         $focusUser->retrieve($groupId);
2201                         $mailerId = (isset($_REQUEST['outbound_email'])) ? $_REQUEST['outbound_email'] : "";
2202
2203                         $oe = new OutboundEmail();
2204                         $oe->getSystemMailerSettings($focusUser, $mailerId);
2205
2206                         $stored_options = array();
2207                         $stored_options['from_name'] = trim($_REQUEST['from_name']);
2208                         $stored_options['from_addr'] = trim($_REQUEST['from_addr']);
2209                         $stored_options['reply_to_addr'] = trim($_REQUEST['reply_to_addr']);
2210
2211                         if (!$this->isPop3Protocol()) {
2212                                 $stored_options['trashFolder'] = (isset($_REQUEST['trashFolder']) ? trim($_REQUEST['trashFolder']) : "");
2213                                 $stored_options['sentFolder'] = (isset($_REQUEST['sentFolder']) ? trim($_REQUEST['sentFolder']) : "");
2214                         } // if
2215                         $stored_options['only_since'] = $onlySince;
2216                         $stored_options['filter_domain'] = '';
2217                         $storedOptions['folderDelimiter'] = $delimiter;
2218                         $stored_options['outbound_email'] = (isset($_REQUEST['outbound_email'])) ? $_REQUEST['outbound_email'] : $oe->id;
2219                         $this->stored_options = base64_encode(serialize($stored_options));
2220
2221                         $ieId = $this->save();
2222
2223                         //If this is the first personal account the user has setup mark it as default for them.
2224                         $currentIECount = $this->getUserPersonalAccountCount($focusUser);
2225                         if($currentIECount == 1)
2226                             $this->setUsersDefaultOutboundServerId($focusUser, $ieId);
2227
2228                         return true;
2229                 } else {
2230                         // could not find opts, no save
2231                         $GLOBALS['log']->debug('-----> InboundEmail could not find optimums for User: '.$ie_name);
2232                         return false;
2233                 }
2234         }
2235         /**
2236          * Determines if this instance of I-E is for a Group Inbox or Personal Inbox
2237          */
2238         function handleIsPersonal() {
2239                 $qp = 'SELECT users.id, users.user_name FROM users WHERE users.is_group = 0 AND users.deleted = 0 AND users.status = \'active\' AND users.id = \''.$this->group_id.'\'';
2240                 $rp = $this->db->query($qp, true);
2241                 $personalBox = array();
2242                 while($ap = $this->db->fetchByAssoc($rp)) {
2243                         $personalBox[] = array($ap['id'], $ap['user_name']);
2244                 }
2245                 if(count($personalBox) > 0) {
2246                         return true;
2247                 } else {
2248                         return false;
2249                 }
2250         }
2251
2252         function getUserNameFromGroupId() {
2253                 $r = $this->db->query('SELECT users.user_name FROM users WHERE deleted=0 AND id=\''.$this->group_id.'\'', true);
2254                 while($a = $this->db->fetchByAssoc($r)) {
2255                         return $a['user_name'];
2256                 }
2257                 return '';
2258         }
2259
2260         function getFoldersListForMailBox() {
2261                 $return = array();
2262                 $foldersList = $this->getSessionInboundFoldersString($this->server_url, $this->email_user, $this->port, $this->protocol);
2263                 if (empty($foldersList)) {
2264                         global $mod_strings;
2265                         $msg = $this->connectMailserver(true);
2266                         if (strpos($msg, "successfully")) {
2267                                 $foldersList = $this->getSessionInboundFoldersString($this->server_url, $this->email_user, $this->port, $this->protocol);
2268                                 $return['status'] = true;
2269                                 $return['foldersList'] = $foldersList;
2270                                 $return['statusMessage'] = "";
2271                         } else {
2272                                 $return['status'] = false;
2273                                 $return['statusMessage'] = $msg;
2274                         } // else
2275                 } else {
2276                         $return['status'] = true;
2277                         $return['foldersList'] = $foldersList;
2278                         $return['statusMessage'] = "";
2279                 }
2280                 return $return;
2281         } // fn
2282         /**
2283          * Programatically determines best-case settings for imap_open()
2284          */
2285         function findOptimumSettings($useSsl=false, $user='', $pass='', $server='', $port='', $prot='', $mailbox='') {
2286                 global $mod_strings;
2287                 $serviceArr = array();
2288                 $returnService = array();
2289                 $badService = array();
2290                 $goodService = array();
2291                 $errorArr = array();
2292                 $raw = array();
2293                 $retArray = array(      'good' => $goodService,
2294                                                         'bad' => $badService,
2295                                                         'err' => $errorArr);
2296
2297                 if(!function_exists('imap_open')) {
2298                         $retArray['err'][0] = $mod_strings['ERR_NO_IMAP'];
2299                         return $retArray;
2300                 }
2301
2302                 imap_errors(); // clearing error stack
2303                 error_reporting(0); // turn off notices from IMAP
2304
2305                 if(isset($_REQUEST['ssl']) && $_REQUEST['ssl'] == 1) {
2306                         $useSsl = true;
2307                 }
2308
2309                 $exServ = explode('::', $this->service);
2310                 $service = '/'.$exServ[1];
2311
2312                 $nonSsl = array('both-secure'                   => '/notls/novalidate-cert/secure',
2313                                                 'both'                                  => '/notls/novalidate-cert',
2314                                                 'nocert-secure'                 => '/novalidate-cert/secure',
2315                                                 'nocert'                                => '/novalidate-cert',
2316                                                 'notls-secure'                  => '/notls/secure',
2317                                                 'secure'                                => '/secure', // for POP3 servers that force CRAM-MD5
2318                                                 'notls'                                 => '/notls',
2319                                                 'none'                                  => '', // try default nothing
2320                                         );
2321                 $ssl = array(
2322                                                 'ssl-both-on-secure'    => '/ssl/tls/validate-cert/secure',
2323                                                 'ssl-both-on'                   => '/ssl/tls/validate-cert',
2324                                                 'ssl-cert-secure'               => '/ssl/validate-cert/secure',
2325                                                 'ssl-cert'                              => '/ssl/validate-cert',
2326                                                 'ssl-tls-secure'                => '/ssl/tls/secure',
2327                                                 'ssl-tls'                               => '/ssl/tls',
2328                                                 'ssl-both-off-secure'   => '/ssl/notls/novalidate-cert/secure',
2329                                                 'ssl-both-off'                  => '/ssl/notls/novalidate-cert',
2330                                                 'ssl-nocert-secure'             => '/ssl/novalidate-cert/secure',
2331                                                 'ssl-nocert'                    => '/ssl/novalidate-cert',
2332                                                 'ssl-notls-secure'              => '/ssl/notls/secure',
2333                                                 'ssl-notls'                             => '/ssl/notls',
2334                                                 'ssl-secure'                    => '/ssl/secure',
2335                                                 'ssl-none'                              => '/ssl',
2336                                         );
2337
2338                 if(isset($user) && !empty($user) && isset($pass) && !empty($pass)) {
2339                         $this->email_password = $pass;
2340                         $this->email_user = $user;
2341                         $this->server_url = $server;
2342                         $this->port = $port;
2343                         $this->protocol = $prot;
2344                         $this->mailbox = $mailbox;
2345                 }
2346
2347                 // in case we flip from IMAP to POP3
2348                 if($this->protocol == 'pop3')
2349                   $this->mailbox = 'INBOX';
2350
2351                 //If user has selected multiple mailboxes, we only need to test the first mailbox for the connection string.
2352                 $a_mailbox = explode(",", $this->mailbox);
2353                 $tmpMailbox = isset($a_mailbox[0]) ? $a_mailbox[0] : "";
2354
2355                 if($useSsl == true)
2356                 {
2357                         foreach($ssl as $k => $service)
2358                         {
2359                                 $returnService[$k] = 'foo'.$service;
2360                                 $serviceArr[$k] = '{'.$this->server_url.':'.$this->port.'/service='.$this->protocol.$service.'}'.$tmpMailbox;
2361                         }
2362                 }
2363                 else
2364                 {
2365                         foreach($nonSsl as $k => $service)
2366                         {
2367                                 $returnService[$k] = 'foo'.$service;
2368                                 $serviceArr[$k] = '{'.$this->server_url.':'.$this->port.'/service='.$this->protocol.$service.'}'.$tmpMailbox;
2369                         }
2370                 }
2371
2372                 $GLOBALS['log']->debug('---------------STARTING FINDOPTIMUMS LOOP----------------');
2373                 $l = 1;
2374
2375                 //php imap library will capture c-client library warnings as errors causing good connections to be ignored.
2376                 //Check against known warnings to ensure good connections are used.
2377                 $acceptableWarnings = array("SECURITY PROBLEM: insecure server advertised AUTH=PLAIN", //c-client auth_pla.c
2378                                                 "Mailbox is empty");
2379                 $login = $this->email_user;
2380                 $passw = $this->email_password;
2381                 $foundGoodConnection = false;
2382                 foreach($serviceArr as $k => $serviceTest) {
2383                         $errors = '';
2384                         $alerts = '';
2385                         $GLOBALS['log']->debug($l.': I-E testing string: '.$serviceTest);
2386
2387                         // open the connection and try the test string
2388                         $this->conn = imap_open($serviceTest, $login, $passw);
2389
2390                         if(($errors = imap_last_error()) || ($alerts = imap_alerts())) {
2391                                 if($errors == 'Too many login failures' || $errors == '[CLOSED] IMAP connection broken (server response)') { // login failure means don't bother trying the rest
2392                                         $GLOBALS['log']->debug($l.': I-E failed using ['.$serviceTest.']');
2393                                         $retArray['err'][$k] = $mod_strings['ERR_BAD_LOGIN_PASSWORD'];
2394                                         $retArray['bad'][$k] = $serviceTest;
2395                                         $GLOBALS['log']->debug($l.': I-E ERROR: $ie->findOptimums() failed due to bad user credentials for user login: '.$this->email_user);
2396                                         return $retArray;
2397                                 } elseif( in_array($errors, $acceptableWarnings, TRUE)) { // false positive
2398                                         $GLOBALS['log']->debug($l.': I-E found good connection but with warnings ['.$serviceTest.'] Errors:' . $errors);
2399                                         $retArray['good'][$k] = $returnService[$k];
2400                                         $foundGoodConnection = true;
2401                                 }
2402                                 else {
2403                                         $GLOBALS['log']->debug($l.': I-E failed using ['.$serviceTest.'] - error: '.$errors);
2404                                         $retArray['err'][$k] = $errors;
2405                                         $retArray['bad'][$k] = $serviceTest;
2406                                 }
2407                         } else {
2408                                 $GLOBALS['log']->debug($l.': I-E found good connect using ['.$serviceTest.']');
2409                                 $retArray['good'][$k] = $returnService[$k];
2410                                 $foundGoodConnection = true;
2411                         }
2412
2413                         if(is_resource($this->conn)) {
2414                                 if (!$this->isPop3Protocol()) {
2415                                         $serviceTest = str_replace("INBOX", "", $serviceTest);
2416                                         $boxes = imap_getmailboxes($this->conn, $serviceTest, "*");
2417                                         $delimiter = '.';
2418                                         // clean MBOX path names
2419                                         foreach($boxes as $k => $mbox) {
2420                                                 $raw[] = $mbox->name;
2421                                                 if ($mbox->delimiter) {
2422                                                         $delimiter = $mbox->delimiter;
2423                                                 } // if
2424                                         } // foreach
2425                                         $this->setSessionInboundDelimiterString($this->server_url, $this->email_user, $this->port, $this->protocol, $delimiter);
2426                                 } // if
2427
2428                                 if(!imap_close($this->conn)) $GLOBALS['log']->debug('imap_close() failed!');
2429                         }
2430
2431                         $GLOBALS['log']->debug($l.': I-E clearing error and alert stacks.');
2432                         imap_errors(); // clear stacks
2433                         imap_alerts();
2434                         // If you find a good connection, then don't do any further testing to find URL
2435                         if ($foundGoodConnection) {
2436                                 break;
2437                         } // if
2438                         $l++;
2439                 }
2440                 $GLOBALS['log']->debug('---------------end FINDOPTIMUMS LOOP----------------');
2441
2442                 if(!empty($retArray['good'])) {
2443                         $newTls                         = '';
2444                         $newCert                        = '';
2445                         $newSsl                         = '';
2446                         $newNotls                       = '';
2447                         $newNovalidate_cert     = '';
2448                         $good = array_pop($retArray['good']); // get most complete string
2449                         $exGood = explode('/', $good);
2450                         foreach($exGood as $v) {
2451                                 switch($v) {
2452                                         case 'ssl':
2453                                                 $newSsl = 'ssl';
2454                                         break;
2455                                         case 'tls':
2456                                                 $newTls = 'tls';
2457                                         break;
2458                                         case 'notls':
2459                                                 $newNotls = 'notls';
2460                                         break;
2461                                         case 'cert':
2462                                                 $newCert = 'validate-cert';
2463                                         break;
2464                                         case 'novalidate-cert':
2465                                                 $newNovalidate_cert = 'novalidate-cert';
2466                                         break;
2467                                         case 'secure':
2468                                                 $secure = 'secure';
2469                                         break;
2470                                 }
2471                         }
2472
2473                         $goodStr['serial'] = $newTls.'::'.$newCert.'::'.$newSsl.'::'.$this->protocol.'::'.$newNovalidate_cert.'::'.$newNotls.'::'.$secure;
2474                         $goodStr['service'] = $good;
2475                         $testConnectString = str_replace('foo','', $good);
2476                         $testConnectString = '{'.$this->server_url.':'.$this->port.'/service='.$this->protocol.$testConnectString.'}';
2477                         $this->setSessionConnectionString($this->server_url, $this->email_user, $this->port, $this->protocol, $goodStr);
2478                         $i = 0;
2479                         foreach($raw as $mbox)
2480                         {
2481                                 $raw[$i] = str_replace($testConnectString, "", $GLOBALS['locale']->translateCharset($mbox, "UTF7-IMAP", "UTF8" ));
2482                                 $i++;
2483                         } // foreach
2484                         sort($raw);
2485                         $this->setSessionInboundFoldersString($this->server_url, $this->email_user, $this->port, $this->protocol, implode(",", $raw));
2486                         return $goodStr;
2487                 } else {
2488                         return false;
2489                 }
2490         }
2491
2492         function getSessionConnectionString($server_url, $email_user, $port, $protocol) {
2493                 $sessionConnectionString = $server_url . $email_user . $port . $protocol;
2494                 return (isset($_SESSION[$sessionConnectionString]) ? $_SESSION[$sessionConnectionString] : "");
2495         }
2496
2497         function setSessionConnectionString($server_url, $email_user, $port, $protocol, $goodStr) {
2498                 $sessionConnectionString = $server_url . $email_user . $port . $protocol;
2499                 $_SESSION[$sessionConnectionString] = $goodStr;
2500         }
2501
2502         function getSessionInboundDelimiterString($server_url, $email_user, $port, $protocol) {
2503                 $sessionInboundDelimiterString = $server_url . $email_user . $port . $protocol . "delimiter";
2504                 return (isset($_SESSION[$sessionInboundDelimiterString]) ? $_SESSION[$sessionInboundDelimiterString] : "");
2505         }
2506
2507         function setSessionInboundDelimiterString($server_url, $email_user, $port, $protocol, $delimiter) {
2508                 $sessionInboundDelimiterString = $server_url . $email_user . $port . $protocol . "delimiter";
2509                 $_SESSION[$sessionInboundDelimiterString] = $delimiter;
2510         }
2511
2512         function getSessionInboundFoldersString($server_url, $email_user, $port, $protocol) {
2513                 $sessionInboundFoldersListString = $server_url . $email_user . $port . $protocol . "foldersList";
2514                 return (isset($_SESSION[$sessionInboundFoldersListString]) ? $_SESSION[$sessionInboundFoldersListString] : "");
2515         }
2516
2517         function setSessionInboundFoldersString($server_url, $email_user, $port, $protocol, $foldersList) {
2518                 $sessionInboundFoldersListString = $server_url . $email_user . $port . $protocol . "foldersList";
2519                 $_SESSION[$sessionInboundFoldersListString] = $foldersList;
2520         }
2521
2522         /**
2523          * Checks for duplicate Group User names when creating a new one at save()
2524          * @return      GUID            returns GUID of Group User if user_name match is
2525          * found
2526          * @return      boolean         false if NO DUPE IS FOUND
2527          */
2528         function groupUserDupeCheck() {
2529                 $q = "SELECT u.id FROM users u WHERE u.deleted=0 AND u.is_group=1 AND u.user_name = '".$this->name."'";
2530                 $r = $this->db->query($q, true);
2531                 $uid = '';
2532                 while($a = $this->db->fetchByAssoc($r)) {
2533                         $uid = $a['id'];
2534                 }
2535
2536                 if(strlen($uid) > 0) {
2537                         return $uid;
2538                 } else {
2539                         return false;
2540                 }
2541         }
2542
2543         /**
2544          * Returns <option> markup with the contents of Group users
2545          * @param array $groups default empty array
2546          * @return string HTML options
2547          */
2548         function getGroupsWithSelectOptions($groups = array()) {
2549                 $r = $this->db->query('SELECT id, user_name FROM users WHERE users.is_group = 1 AND deleted = 0', true);
2550                 if(is_resource($r)) {
2551                         while($a = $this->db->fetchByAssoc($r)) {
2552                                 $groups[$a['id']] = $a['user_name'];
2553                         }
2554                 }
2555
2556                 $selectOptions = get_select_options_with_id_separate_key($groups, $groups, $this->group_id);
2557                 return $selectOptions;
2558         }
2559
2560         /**
2561          * handles auto-responses to inbound emails
2562          *
2563          * @param object email Email passed as reference
2564          */
2565         function handleAutoresponse(&$email, &$contactAddr) {
2566                 if($this->template_id) {
2567                         $GLOBALS['log']->debug('found auto-reply template id - prefilling and mailing response');
2568
2569                         if($this->getAutoreplyStatus($contactAddr)
2570                         && $this->checkOutOfOffice($email->name)
2571                         && $this->checkFilterDomain($email)) { // if we haven't sent this guy 10 replies in 24hours
2572
2573                                 if(!empty($this->stored_options)) {
2574                                         $storedOptions = unserialize(base64_decode($this->stored_options));
2575                                 }
2576                                 // get FROM NAME
2577                                 if(!empty($storedOptions['from_name'])) {
2578                                         $from_name = $storedOptions['from_name'];
2579                                         $GLOBALS['log']->debug('got from_name from storedOptions: '.$from_name);
2580                                 } else { // use system default
2581                                         $rName = $this->db->query('SELECT value FROM config WHERE name = \'fromname\'', true);
2582                                         if(is_resource($rName)) {
2583                                                 $aName = $this->db->fetchByAssoc($rName);
2584                                         }
2585                                         if(!empty($aName['value'])) {
2586                                                 $from_name = $aName['value'];
2587                                         } else {
2588                                                 $from_name = '';
2589                                         }
2590                                 }
2591                                 // get FROM ADDRESS
2592                                 if(!empty($storedOptions['from_addr'])) {
2593                                         $from_addr = $storedOptions['from_addr'];
2594                                 } else {
2595                                         $rAddr = $this->db->query('SELECT value FROM config WHERE name = \'fromaddress\'', true);
2596                                         if(is_resource($rAddr)) {
2597                                                 $aAddr = $this->db->fetchByAssoc($rAddr);
2598                                         }
2599                                         if(!empty($aAddr['value'])) {
2600                                                 $from_addr = $aAddr['value'];
2601                                         } else {
2602                                                 $from_addr = '';
2603                                         }
2604                                 }
2605
2606                                 $replyToName = (!empty($storedOptions['reply_to_name']))? from_html($storedOptions['reply_to_name']) :$from_name ;
2607                                 $replyToAddr = (!empty($storedOptions['reply_to_addr'])) ? $storedOptions['reply_to_addr'] : $from_addr;
2608
2609
2610                                 if(!empty($email->reply_to_email)) {
2611                                         $to[0]['email'] = $email->reply_to_email;
2612                                 } else {
2613                                         $to[0]['email'] = $email->from_addr;
2614                                 }
2615                                 // handle to name: address, prefer reply-to
2616                                 if(!empty($email->reply_to_name)) {
2617                                         $to[0]['display'] = $email->reply_to_name;
2618                                 } elseif(!empty($email->from_name)) {
2619                                         $to[0]['display'] = $email->from_name;
2620                                 }
2621
2622                                 $et = new EmailTemplate();
2623                                 $et->retrieve($this->template_id);
2624                                 if(empty($et->subject))         { $et->subject = ''; }
2625                                 if(empty($et->body))            { $et->body = ''; }
2626                                 if(empty($et->body_html))       { $et->body_html = ''; }
2627
2628                                 $reply = new Email();
2629                                 $reply->type                            = 'out';
2630                                 $reply->to_addrs                        = $to[0]['email'];
2631                                 $reply->to_addrs_arr            = $to;
2632                                 $reply->cc_addrs_arr            = array();
2633                                 $reply->bcc_addrs_arr           = array();
2634                                 $reply->from_name                       = $from_name;
2635                                 $reply->from_addr                       = $from_addr;
2636                                 $reply->name                            = $et->subject;
2637                                 $reply->description                     = $et->body;
2638                                 $reply->description_html        = $et->body_html;
2639                                 $reply->reply_to_name           = $replyToName;
2640                                 $reply->reply_to_addr           = $replyToAddr;
2641
2642                                 $GLOBALS['log']->debug('saving and sending auto-reply email');
2643                                 //$reply->save(); // don't save the actual email.
2644                                 $reply->send();
2645                                 $this->setAutoreplyStatus($contactAddr);
2646                         } else {
2647                                 $GLOBALS['log']->debug('InboundEmail: auto-reply threshold reached for email ('.$contactAddr.') - not sending auto-reply');
2648                         }
2649                 }
2650         }
2651
2652         function handleCaseAssignment($email) {
2653                 $c = new aCase();
2654                 if($caseId = $this->getCaseIdFromCaseNumber($email->name, $c)) {
2655                         $c->retrieve($caseId);
2656                         $c->load_relationship('emails');
2657                         $c->emails->add($email->id);
2658
2659                         $email->retrieve($email->id);
2660                         $email->parent_type = "Cases";
2661                         $email->parent_id = $caseId;
2662                         // assign the email to the case owner
2663                         $email->assigned_user_id = $c->assigned_user_id;
2664                         $email->save();
2665                         $GLOBALS['log']->debug('InboundEmail found exactly 1 match for a case: '.$c->name);
2666                         return true;
2667                 } // if
2668                 return false;
2669         } // fn
2670
2671         /**
2672          * handles functionality specific to the Mailbox type (Cases, bounced
2673          * campaigns, etc.)
2674          *
2675          * @param object email Email object passed as a reference
2676          * @param object header Header object generated by imap_headerinfo();
2677          */
2678         function handleMailboxType(&$email, &$header) {
2679                 switch($this->mailbox_type) {
2680                         case 'support':
2681                                 $this->handleCaseAssignment($email);
2682                                 break;
2683                         case 'bug':
2684
2685                                 break;
2686
2687                         case 'info':
2688                                 // do something with this?
2689                                 break;
2690                         case 'sales':
2691                                 // do something with leads? we don't have an email_leads table
2692                                 break;
2693                         case 'task':
2694                                 // do something?
2695                                 break;
2696                         case 'bounce':
2697                                 require_once('modules/Campaigns/ProcessBouncedEmails.php');
2698                                 campaign_process_bounced_emails($email, $header);
2699                                 break;
2700                         case 'pick': // do all except bounce handling
2701                                 $GLOBALS['log']->debug('looking for a case for '.$email->name);
2702                                 $this->handleCaseAssignment($email);
2703                                 break;
2704                 }
2705         }
2706
2707         function isMailBoxTypeCreateCase() {
2708                 return ($this->mailbox_type == 'createcase' && !empty($this->groupfolder_id));
2709         } // fn
2710
2711         function handleCreateCase($email, $userId) {
2712                 global $current_user, $mod_strings, $current_language;
2713                 $mod_strings = return_module_language($current_language, "Emails");
2714                 $GLOBALS['log']->debug('In handleCreateCase');
2715                 $c = new aCase();
2716                 $this->getCaseIdFromCaseNumber($email->name, $c);
2717
2718                 if (!$this->handleCaseAssignment($email) && $this->isMailBoxTypeCreateCase()) {
2719                         // create a case
2720                         $GLOBALS['log']->debug('retrieveing email');
2721                         $email->retrieve($email->id);
2722                         $c = new aCase();
2723                         $c->description = $email->description;
2724                         $c->assigned_user_id = $userId;
2725                         $c->name = $email->name;
2726                         $c->status = 'New';
2727                         $c->priority = 'P1';
2728
2729                         if(!empty($email->reply_to_email)) {
2730                                 $contactAddr = $email->reply_to_email;
2731                         } else {
2732                                 $contactAddr = $email->from_addr;
2733                         }
2734
2735                         $GLOBALS['log']->debug('finding related accounts with address ' . $contactAddr);
2736                         if($accountIds = $this->getRelatedId($contactAddr, 'accounts')) {
2737                                 if (sizeof($accountIds) == 1) {
2738                                         $c->account_id = $accountIds[0];
2739
2740                                         $acct = new Account();
2741                                         $acct->retrieve($c->account_id);
2742                                         $c->account_name = $acct->name;
2743                                 } // if
2744                         } // if
2745                         $c->save(true);
2746                         $caseId = $c->id;
2747                         $c = new aCase();
2748                         $c->retrieve($caseId);
2749                         if($c->load_relationship('emails')) {
2750                                 $c->emails->add($email->id);
2751                         } // if
2752                         if($contactIds = $this->getRelatedId($contactAddr, 'contacts')) {
2753                                 if(!empty($contactIds) && $c->load_relationship('contacts')) {
2754                                         $c->contacts->add($contactIds);
2755                                 } // if
2756                         } // if
2757                         $c->email_id = $email->id;
2758                         $email->parent_type = "Cases";
2759                         $email->parent_id = $caseId;
2760                         // assign the email to the case owner
2761                         $email->assigned_user_id = $c->assigned_user_id;
2762                         $email->name = str_replace('%1', $c->case_number, $c->getEmailSubjectMacro()) . " ". $email->name;
2763                         $email->save();
2764                         $GLOBALS['log']->debug('InboundEmail created one case with number: '.$c->case_number);
2765                         $createCaseTemplateId = $this->get_stored_options('create_case_email_template', "");
2766                         if(!empty($this->stored_options)) {
2767                                 $storedOptions = unserialize(base64_decode($this->stored_options));
2768                         }
2769                         if(!empty($createCaseTemplateId)) {
2770                                 $fromName = "";
2771                                 $fromAddress = "";
2772                                 if (!empty($this->stored_options)) {
2773                                         $fromAddress = $storedOptions['from_addr'];
2774                                         $fromName = from_html($storedOptions['from_name']);
2775                                         $replyToName = (!empty($storedOptions['reply_to_name']))? from_html($storedOptions['reply_to_name']) :$fromName ;
2776                                         $replyToAddr = (!empty($storedOptions['reply_to_addr'])) ? $storedOptions['reply_to_addr'] : $fromAddress;
2777                                 } // if
2778                                 $defaults = $current_user->getPreferredEmail();
2779                                 $fromAddress = (!empty($fromAddress)) ? $fromAddress : $defaults['email'];
2780                                 $fromName = (!empty($fromName)) ? $fromName : $defaults['name'];
2781                                 $to[0]['email'] = $contactAddr;
2782
2783                                 // handle to name: address, prefer reply-to
2784                                 if(!empty($email->reply_to_name)) {
2785                                         $to[0]['display'] = $email->reply_to_name;
2786                                 } elseif(!empty($email->from_name)) {
2787                                         $to[0]['display'] = $email->from_name;
2788                                 }
2789
2790                                 $et = new EmailTemplate();
2791                                 $et->retrieve($createCaseTemplateId);
2792                                 if(empty($et->subject))         { $et->subject = ''; }
2793                                 if(empty($et->body))            { $et->body = ''; }
2794                                 if(empty($et->body_html))       { $et->body_html = ''; }
2795
2796                                 $et->subject = "Re:" . " " . str_replace('%1', $c->case_number, $c->getEmailSubjectMacro() . " ". $c->name);
2797
2798                                 $html = trim($email->description_html);
2799                                 $plain = trim($email->description);
2800
2801                                 $email->email2init();
2802                     $email->from_addr = $email->from_addr_name;
2803                     $email->to_addrs = $email->to_addrs_names;
2804                     $email->cc_addrs = $email->cc_addrs_names;
2805                     $email->bcc_addrs = $email->bcc_addrs_names;
2806                     $email->from_name = $email->from_addr;
2807
2808                 $email = $email->et->handleReplyType($email, "reply");
2809                 $ret = $email->et->displayComposeEmail($email);
2810                 $ret['description'] = empty($email->description_html) ?  str_replace("\n", "\n<BR/>", $email->description) : $email->description_html;
2811
2812                                 $reply = new Email();
2813                                 $reply->type                            = 'out';
2814                                 $reply->to_addrs                        = $to[0]['email'];
2815                                 $reply->to_addrs_arr            = $to;
2816                                 $reply->cc_addrs_arr            = array();
2817                                 $reply->bcc_addrs_arr           = array();
2818                                 $reply->from_name                       = $fromName;
2819                                 $reply->from_addr                       = $fromAddress;
2820                                 $reply->reply_to_name           = $replyToName;
2821                                 $reply->reply_to_addr           = $replyToAddr;
2822                                 $reply->name                            = $et->subject;
2823                                 $reply->description                     = $et->body . "<div><hr /></div>" .  $email->description;
2824                                 if (!$et->text_only) {
2825                                         $reply->description_html        = $et->body_html .  "<div><hr /></div>" . $email->description;
2826                                 }
2827                                 $GLOBALS['log']->debug('saving and sending auto-reply email');
2828                                 //$reply->save(); // don't save the actual email.
2829                                 $reply->send();
2830                         } // if
2831
2832                 } else {
2833                         if(!empty($email->reply_to_email)) {
2834                                 $contactAddr = $email->reply_to_email;
2835                         } else {
2836                                 $contactAddr = $email->from_addr;
2837                         }
2838                         $this->handleAutoresponse($email, $contactAddr);
2839                 }
2840
2841         } // fn
2842
2843         /**
2844          * handles linking contacts, accounts, etc. to an email
2845          *
2846          * @param object Email bean to be linked against
2847          * @return string contactAddr is the email address of the sender
2848          */
2849         function handleLinking(&$email) {
2850                 // link email to an User if emails match TO addr
2851                 if($userIds = $this->getRelatedId($email->to_addrs, 'users')) {
2852                         $GLOBALS['log']->debug('I-E linking email to User');
2853                         // link the user to the email
2854                         $email->load_relationship('users');
2855                         $email->users->add($userIds);
2856                 }
2857
2858                 // link email to a Contact, Lead, or Account if the emails match
2859                 // give precedence to REPLY-TO above FROM
2860                 if(!empty($email->reply_to_email)) {
2861                         $contactAddr = $email->reply_to_email;
2862                 } else {
2863                         $contactAddr = $email->from_addr;
2864                 }
2865
2866                 // Samir Gandhi : 12/06/07
2867                 // This changes has been done because the linking was done only with the from address and
2868                 // not with to address
2869                 $relationShipAddress = $contactAddr;
2870                 if (empty($relationShipAddress)) {
2871                         $relationShipAddress .= $email->to_addrs;
2872                 } else {
2873                         $relationShipAddress = $relationShipAddress . "," . $email->to_addrs;
2874                 }
2875                 if($leadIds = $this->getRelatedId($relationShipAddress, 'leads')) {
2876                         $GLOBALS['log']->debug('I-E linking email to Lead');
2877                         $email->load_relationship('leads');
2878                         $email->leads->add($leadIds);
2879
2880                         foreach($leadIds as $leadId) {
2881                                 $lead = new Lead();
2882                                 $lead->retrieve($leadId);
2883                                 $lead->load_relationship('emails');
2884                                 $lead->emails->add($email->id);
2885                         }
2886                 }
2887
2888                 if($contactIds = $this->getRelatedId($relationShipAddress, 'contacts')) {
2889                         $GLOBALS['log']->debug('I-E linking email to Contact');
2890                         // link the contact to the email
2891                         $email->load_relationship('contacts');
2892                         $email->contacts->add($contactIds);
2893                 }
2894
2895                 if($accountIds = $this->getRelatedId($relationShipAddress, 'accounts')) {
2896                         $GLOBALS['log']->debug('I-E linking email to Account');
2897                         // link the account to the email
2898                         $email->load_relationship('accounts');
2899                         $email->accounts->add($accountIds);
2900
2901                         /* cn: bug 9171 another cause of dying I-E - bad linking
2902                         foreach($accountIds as $accountId) {
2903                                 $GLOBALS['log']->debug('I-E reverse-linking Accounts to Emails');
2904                                 $acct = new Account();
2905                                 $acct->retrieve($accountId);
2906                                 $acct->load_relationship('emails');
2907                                 $acct->account_emails->add($email->id);
2908                         }
2909                         */
2910                 }
2911                 return $contactAddr;
2912         }
2913
2914         /**
2915          * Gets part by following breadcrumb path
2916          * @param string $bc the breadcrumb string in format (1.1.1)
2917          * @param array parts the root level parts array
2918          */
2919         protected function getPartByPath($bc, $parts)
2920         {
2921                 if(strstr($bc,'.')) {
2922                         $exBc = explode('.', $bc);
2923                 } else {
2924                         $exBc = array($bc);
2925                 }
2926
2927                 foreach($exBc as $step) {
2928                     if(empty($parts)) return false;
2929                     $res = $parts[$step-1]; // MIME starts with 1, array starts with 0
2930                     if(!empty($res->parts)) {
2931                         $parts = $res->parts;
2932                     } else {
2933                         $parts = false;
2934                     }
2935                 }
2936                 return $res;
2937         }
2938
2939         /**
2940          * takes a breadcrumb and returns the encoding at that level
2941          * @param       string bc the breadcrumb string in format (1.1.1)
2942          * @param       array parts the root level parts array
2943          * @return      int retInt Int key to transfer encoding (see handleTranserEncoding())
2944          */
2945         function getEncodingFromBreadCrumb($bc, $parts) {
2946                 if(strstr($bc,'.')) {
2947                         $exBc = explode('.', $bc);
2948                 } else {
2949                         $exBc[0] = $bc;
2950                 }
2951
2952                 $depth = count($exBc);
2953
2954                 for($i=0; $i<$depth; $i++) {
2955                         $tempObj[$i] = $parts[($exBc[$i]-1)];
2956                         $retInt = imap_utf8($tempObj[$i]->encoding);
2957                         if(!empty($tempObj[$i]->parts)) {
2958                                 $parts = $tempObj[$i]->parts;
2959                         }
2960                 }
2961                 return $retInt;
2962         }
2963
2964         /**
2965          * retrieves the charset for a given part of an email body
2966          *
2967          * @param string bc target part of the message in format (1.1.1)
2968          * @param array parts 1 level above ROOT array of Objects representing a multipart body
2969          * @return string charset name
2970          */
2971         function getCharsetFromBreadCrumb($bc, $parts)
2972         {
2973                 $tempObj = $this->getPartByPath($bc, $parts);
2974                 // now we have the tempObj at the end of the breadCrumb trail
2975
2976                 if(!empty($tempObj->ifparameters)) {
2977                         foreach($tempObj->parameters as $param) {
2978                                 if(strtolower($param->attribute) == 'charset') {
2979                                         return $param->value;
2980                                 }
2981                         }
2982                 }
2983
2984                 return 'default';
2985         }
2986
2987         /**
2988          * Get the message text from a single mime section, html or plain.
2989          *
2990          * @param string $msgNo
2991          * @param string $section
2992          * @param stdObject $structure
2993          * @return string
2994          */
2995         function getMessageTextFromSingleMimePart($msgNo,$section,$structure)
2996         {
2997             $msgPartTmp = imap_fetchbody($this->conn, $msgNo, $section);
2998             $enc = $this->getEncodingFromBreadCrumb($section, $structure->parts);
2999             $charset = $this->getCharsetFromBreadCrumb($section, $structure->parts);
3000             $msgPartTmp = $this->handleTranserEncoding($msgPartTmp, $enc);
3001             return $this->handleCharsetTranslation($msgPartTmp, $charset);
3002         }
3003
3004         /**
3005          * Givin an existing breadcrumb add a cooresponding offset
3006          *
3007          * @param string $bc
3008          * @param string $offset
3009          * @return string
3010          */
3011         function addBreadCrumbOffset($bc, $offset)
3012         {
3013             if( (empty($bc) || is_null($bc)) && !empty($offset) )
3014                return $offset;
3015
3016             $a_bc = explode(".", $bc);
3017             $a_offset = explode(".",$offset);
3018             if(count($a_bc) < count($a_offset))
3019                $a_bc = array_merge($a_bc,array_fill( count($a_bc), count($a_offset) - count($a_bc), 0));
3020
3021             $results = array();
3022             for($i=0;$i < count($a_bc); $i++)
3023             {
3024                 if(isset($a_offset[$i]))
3025                    $results[] = $a_bc[$i] + $a_offset[$i];
3026                 else
3027                    $results[] = $a_bc[$i];
3028             }
3029             return implode(".", $results);
3030         }
3031
3032         /**
3033          * returns the HTML text part of a multi-part message
3034          *
3035          * @param int msgNo the relative message number for the monitored mailbox
3036          * @param string $type the type of text processed, either 'PLAIN' or 'HTML'
3037          * @return string UTF-8 encoded version of the requested message text
3038          */
3039         function getMessageText($msgNo, $type, $structure, $fullHeader,$clean_email=true, $bcOffset = "") {
3040                 global $sugar_config;
3041
3042                 $msgPart = '';
3043                 $bc = $this->buildBreadCrumbs($structure->parts, $type);
3044                 //Add an offset if specified
3045                 if(!empty($bcOffset))
3046             $bc = $this->addBreadCrumbOffset($bc, $bcOffset);
3047
3048                 if(!empty($bc)) { // multi-part
3049                         // HUGE difference between PLAIN and HTML
3050                         if($type == 'PLAIN') {
3051                                 $msgPart = $this->getMessageTextFromSingleMimePart($msgNo,$bc,$structure);
3052                         } else {
3053                                 // get part of structure that will
3054                                 $msgPartRaw = '';
3055                                 $bcArray = $this->buildBreadCrumbsHTML($structure->parts,$bcOffset);
3056                                 // construct inline HTML/Rich msg
3057                                 foreach($bcArray as $bcArryKey => $bcArr) {
3058                                         foreach($bcArr as $type => $bcTrail) {
3059                                                 if($type == 'html')
3060                                                     $msgPartRaw .= $this->getMessageTextFromSingleMimePart($msgNo,$bcTrail,$structure);
3061                                                  else {
3062                                                         // deal with inline image
3063                                                         $part = $this->getPartByPath($bcTrail, $structure->parts);
3064                                                         if(empty($part) || empty($part->id)) continue;
3065                                                         $partid = substr($part->id, 1, -1); // strip <> around
3066                                                         if(isset($this->inlineImages[$partid])) {
3067                                                                 $imageName = $this->inlineImages[$partid];
3068                                                                 $newImagePath = "class=\"image\" src=\"{$this->imagePrefix}{$imageName}\"";
3069                                                                 $preImagePath = "src=\"cid:$partid\"";
3070                                                                 $msgPartRaw = str_replace($preImagePath, $newImagePath, $msgPartRaw);
3071                                                         }
3072                                                 }
3073                                         }
3074                                 }
3075                                 $msgPart = $msgPartRaw;
3076                         }
3077                 } else { // either PLAIN message type (flowed) or b0rk3d RFC
3078                         // make sure we're working on valid data here.
3079                         if($structure->subtype != $type) {
3080                                 return '';
3081                         }
3082
3083                         $decodedHeader = $this->decodeHeader($fullHeader);
3084
3085                         // now get actual body contents
3086                         $text = imap_body($this->conn, $msgNo);
3087
3088                         $upperCaseKeyDecodeHeader = array();
3089                         if (is_array($decodedHeader)) {
3090                                 $upperCaseKeyDecodeHeader = array_change_key_case($decodedHeader, CASE_UPPER);
3091                         } // if
3092                         if(isset($upperCaseKeyDecodeHeader[strtoupper('Content-Transfer-Encoding')])) {
3093                                 $flip = array_flip($this->transferEncoding);
3094                                 $text = $this->handleTranserEncoding($text, $flip[strtoupper($upperCaseKeyDecodeHeader[strtoupper('Content-Transfer-Encoding')])]);
3095                         }
3096
3097                         $msgPart = $text;
3098                         if(is_array($upperCaseKeyDecodeHeader['CONTENT-TYPE']) && isset($upperCaseKeyDecodeHeader['CONTENT-TYPE']['charset']) && !empty($upperCaseKeyDecodeHeader[$upperCaseKeyDecodeHeader['CONTENT-TYPE']]['charset'])) {
3099                                 $msgPart = $this->handleCharsetTranslation($text, $upperCaseKeyDecodeHeader['CONTENT-TYPE']['charset']);
3100                         } else {
3101                 $msgPart = utf8_encode($text);
3102             }
3103                 } // end else clause
3104
3105                 $msgPart = $this->customGetMessageText($msgPart);
3106                 /* cn: bug 9176 - htmlEntitites hide XSS attacks.
3107                  * decode to pass refreshed HTML to HTML_Safe */
3108                 if ($type == 'PLAIN')
3109                     return $this->cleanXssContent(to_html($msgPart));
3110                 else
3111                 {
3112             $safedMsgPart = $this->cleanContent($msgPart);
3113                     return str_replace("<img />", '', $safedMsgPart); /*SKIP_IMAGE_TAG*/
3114                 }
3115         }
3116
3117         /**
3118          * decodes raw header information and passes back an associative array with
3119          * the important elements key'd by name
3120          * @param header string the raw header
3121          * @return decodedHeader array the associative array
3122          */
3123         function decodeHeader($fullHeader) {
3124                 $decodedHeader = array();
3125                 $exHeaders = explode("\r", $fullHeader);
3126                 if (!is_array($exHeaders)) {
3127                         $exHeaders = explode("\r\n", $fullHeader);
3128                 }
3129                 $quotes = array('"', "'");
3130
3131                 foreach($exHeaders as $lineNum => $head) {
3132                         $key    = '';
3133                         $key    = trim(substr($head, 0, strpos($head, ':')));
3134                         $value  = '';
3135                         $value  = trim(substr($head, (strpos($head, ':') + 1), strlen($head)));
3136
3137                         // handle content-type section in headers
3138                         if(strtolower($key) == 'content-type' && strpos($value, ';')) { // ";" means something follows related to (such as Charset)
3139                                 $semiColPos = mb_strpos($value, ';');
3140                                 $strLenVal = mb_strlen($value);
3141                                 if(($semiColPos + 4) >= $strLenVal) {
3142                                         // the charset="[something]" is on the next line
3143                                         $value .= str_replace($quotes, "", trim($exHeaders[$lineNum+1]));
3144                                 }
3145
3146                                 $newValue = array();
3147                                 $exValue = explode(';', $value);
3148                                 $newValue['type'] = $exValue[0];
3149
3150                                 for($i=1; $i<count($exValue); $i++) {
3151                                         $exContent = explode('=', $exValue[$i]);
3152                                         $newValue[trim($exContent[0])] = trim($exContent[1]);
3153                                 }
3154                                 $value = $newValue;
3155                         }
3156
3157                         if(!empty($key) && !empty($value)) {
3158                                 $decodedHeader[$key] = $value;
3159                         }
3160                 }
3161
3162                 return $decodedHeader;
3163         }
3164
3165         /**
3166          * handles translating message text from orignal encoding into UTF-8
3167          *
3168          * @param string text test to be re-encoded
3169          * @param string charset original character set
3170          * @return string utf8 re-encoded text
3171          */
3172         function handleCharsetTranslation($text, $charset) {
3173                 global $locale;
3174
3175                 if(empty($charset)) {
3176                         $GLOBALS['log']->debug("***ERROR: InboundEmail::handleCharsetTranslation() called without a \$charset!");
3177                         $GLOBALS['log']->debug("***STACKTRACE: ".print_r(debug_backtrace(), true));
3178                         return $text;
3179                 }
3180
3181                 // typical headers have no charset - let destination pick (since it's all ASCII anyways)
3182                 if(strtolower($charset) == 'default' || strtolower($charset) == 'utf-8') {
3183                         return $text;
3184                 }
3185
3186                 return $locale->translateCharset($text, $charset);
3187         }
3188
3189
3190
3191         /**
3192          * Builds up the "breadcrumb" trail that imap_fetchbody() uses to return
3193          * parts of an email message, including attachments and inline images
3194          * @param       $parts  array of objects
3195          * @param       $subtype        what type of trail to return? HTML? Plain? binaries?
3196          * @param       $breadcrumb     text trail to build up
3197          */
3198         function buildBreadCrumbs($parts, $subtype, $breadcrumb = '0') {
3199                 //_pp('buildBreadCrumbs building for '.$subtype.' with BC at '.$breadcrumb);
3200                 // loop through available parts in the array
3201                 foreach($parts as $k => $part) {
3202                         // mark passage through level
3203                         $thisBc = ($k+1);
3204                         // if this is not the first time through, start building the map
3205                         if($breadcrumb != 0) {
3206                                 $thisBc = $breadcrumb.'.'.$thisBc;
3207                         }
3208
3209                         // found a multi-part/mixed 'part' - keep digging
3210                         if($part->type == 1 && (strtoupper($part->subtype) == 'RELATED' || strtoupper($part->subtype) == 'ALTERNATIVE' || strtoupper($part->subtype) == 'MIXED')) {
3211                                 //_pp('in loop: going deeper with subtype: '.$part->subtype.' $k is: '.$k);
3212                                 $thisBc = $this->buildBreadCrumbs($part->parts, $subtype, $thisBc);
3213                                 return $thisBc;
3214
3215                         } elseif(strtolower($part->subtype) == strtolower($subtype)) { // found the subtype we want, return the breadcrumb value
3216                                 //_pp('found '.$subtype.' bc! returning: '.$thisBc);
3217                                 return $thisBc;
3218                         } else {
3219                                 //_pp('found '.$part->subtype.' instead');
3220                         }
3221                 }
3222         }
3223
3224         /**
3225          * Similar to buildBreadCrumbs() but returns an ordered array containing all parts of the message that would be
3226          * considered "HTML" or Richtext (embedded images, formatting, etc.).
3227          * @param array parts Array of parts of a message
3228          * @param int breadcrumb Passed integer value to start breadcrumb trail
3229          * @param array stackedBreadcrumbs Persistent trail of breadcrumbs
3230          * @return array Ordered array of parts to retrieve via imap_fetchbody()
3231          */
3232         function buildBreadCrumbsHTML($parts, $breadcrumb = '0', $stackedBreadcrumbs = array()) {
3233                 $subtype = 'HTML';
3234                 $disposition = 'inline';
3235
3236                 foreach($parts as $k => $part) {
3237                         // mark passage through level
3238                         $thisBc = ($k+1);
3239
3240                         if($breadcrumb != 0) {
3241                                 $thisBc = $breadcrumb.'.'.$thisBc;
3242                         }
3243                         // found a multi-part/mixed 'part' - keep digging
3244                         if($part->type == 1 && (strtoupper($part->subtype) == 'RELATED' || strtoupper($part->subtype) == 'ALTERNATIVE' || strtoupper($part->subtype) == 'MIXED')) {
3245                                 $stackedBreadcrumbs = $this->buildBreadCrumbsHTML($part->parts, $thisBc, $stackedBreadcrumbs);
3246                         } elseif(
3247                                 (strtolower($part->subtype) == strtolower($subtype)) ||
3248                                         (
3249                                                 isset($part->disposition) && strtolower($part->disposition) == 'inline' &&
3250                                                 in_array(strtoupper($part->subtype), $this->imageTypes)
3251                                         )
3252                         ) {
3253                                 // found the subtype we want, return the breadcrumb value
3254                                 $stackedBreadcrumbs[] = array(strtolower($part->subtype) => $thisBc);
3255                         } elseif($part->type == 5) {
3256                                 $stackedBreadcrumbs[] = array(strtolower($part->subtype) => $thisBc);
3257                         }
3258                 }
3259
3260                 return $stackedBreadcrumbs;
3261         }
3262
3263         /**
3264          * Takes a PHP imap_* object's to/from/cc/bcc address field and converts it
3265          * to a standard string that SugarCRM expects
3266          * @param       $arr    an array of email address objects
3267          */
3268         function convertImapToSugarEmailAddress($arr) {
3269                 if(is_array($arr)) {
3270                         $addr = '';
3271                         foreach($arr as $key => $obj) {
3272                                 $addr .= $obj->mailbox.'@'.$obj->host.', ';
3273                         }
3274                         // strip last comma
3275                         $ret = substr_replace($addr,'',-2,-1);
3276                         return trim($ret);
3277                 }
3278         }
3279
3280         /**
3281          * tries to figure out what character set a given filename is using and
3282          * decode based on that
3283          *
3284          * @param string name Name of attachment
3285          * @return string decoded name
3286          */
3287         function handleEncodedFilename($name) {
3288                 $imapDecode = imap_mime_header_decode($name);
3289                 /******************************
3290                 $imapDecode => stdClass Object
3291                         (
3292                                 [charset] => utf-8
3293                                 [text] => w�hlen.php
3294                         )
3295
3296                                         OR
3297
3298                 $imapDecode => stdClass Object
3299                         (
3300                                 [charset] => default
3301                                 [text] => UTF-8''%E3%83%8F%E3%82%99%E3%82%A4%E3%82%AA%E3%82%AF%E3%82%99%E3%83%A9%E3%83%95%E3%82%A3%E3%83%BC.txt
3302                         )
3303                 *******************************/
3304                 if($imapDecode[0]->charset != 'default') { // mime-header encoded charset
3305                         $encoding = $imapDecode[0]->charset;
3306                         $name = $imapDecode[0]->text; // encoded in that charset
3307                 } else {
3308                         /* encoded filenames are formatted as [encoding]''[filename] */
3309                         if(strpos($name, "''") !== false) {
3310
3311                                 $encoding = substr($name, 0, strpos($name, "'"));
3312
3313                                 while(strpos($name, "'") !== false) {
3314                                         $name = trim(substr($name, (strpos($name, "'")+1), strlen($name)));
3315                                 }
3316                         }
3317                         $name = urldecode($name);
3318                 }
3319                 return (strtolower($encoding) == 'utf-8') ? $name : $GLOBALS['locale']->translateCharset($name, $encoding, 'UTF-8');
3320         }
3321
3322         /*
3323                 Primary body types for a part of a mail structure (imap_fetchstructure returned object)
3324                 0 => text
3325                 1 => multipart
3326                 2 => message
3327                 3 => application
3328                 4 => audio
3329                 5 => image
3330                 6 => video
3331                 7 => other
3332         */
3333
3334         /**
3335         Primary body types for a part of a mail structure (imap_fetchstructure returned object)
3336         @var array $imap_types
3337         */
3338         public $imap_types = array(
3339                         0 => 'text',
3340                         1 => 'multipart',
3341                         2 => 'message',
3342                         3 => 'application',
3343                         4 => 'audio',
3344                         5 => 'image',
3345                         6 => 'video',
3346         );
3347
3348         public function getMimeType($type, $subtype)
3349         {
3350                 if(isset($this->imap_types[$type])) {
3351                         return $this->imap_types[$type]."/$subtype";
3352                 } else {
3353                         return "other/$subtype";
3354                 }
3355         }
3356
3357         /**
3358          * Takes the "parts" attribute of the object that imap_fetchbody() method
3359          * returns, and recursively goes through looking for objects that have a
3360          * disposition of "attachement" or "inline"
3361          * @param int $msgNo The relative message number for the monitored mailbox
3362          * @param object $parts Array of objects to examine
3363          * @param string $emailId The GUID of the email saved prior to calling this method
3364          * @param array $breadcrumb Default 0, build up of the parts mapping
3365          * @param bool $forDisplay Default false
3366          */
3367         function saveAttachments($msgNo, $parts, $emailId, $breadcrumb='0', $forDisplay) {
3368                 global $sugar_config;
3369                 /*
3370                         Primary body types for a part of a mail structure (imap_fetchstructure returned object)
3371                         0 => text
3372                         1 => multipart
3373                         2 => message
3374                         3 => application
3375                         4 => audio
3376                         5 => image
3377                         6 => video
3378                         7 => other
3379                 */
3380
3381                 foreach($parts as $k => $part) {
3382                         $thisBc = $k+1;
3383                         if($breadcrumb != '0') {
3384                                 $thisBc = $breadcrumb.'.'.$thisBc;
3385                         }
3386                         $attach = null;
3387                         // check if we need to recurse into the object
3388                         //if($part->type == 1 && !empty($part->parts)) {
3389                         if(isset($part->parts) && !empty($part->parts) && !( isset($part->subtype) && strtolower($part->subtype) == 'rfc822')  ) {
3390                                 $this->saveAttachments($msgNo, $part->parts, $emailId, $thisBc, $forDisplay);
3391                 continue;
3392                         } elseif($part->ifdisposition) {
3393                                 // we will take either 'attachments' or 'inline'
3394                                 if(strtolower($part->disposition) == 'attachment' || ((strtolower($part->disposition) == 'inline') && $part->type != 0)) {
3395                                         $attach = $this->getNoteBeanForAttachment($emailId);
3396                                         $fname = $this->handleEncodedFilename($this->retrieveAttachmentNameFromStructure($part->dparameters));
3397
3398                                         if(!empty($fname)) {//assign name to attachment
3399                                                 $attach->name = $fname;
3400                                         } else {//if name is empty, default to filename
3401                                                 $attach->name = urlencode($this->retrieveAttachmentNameFromStructure($part->dparameters));
3402                                         }
3403                                         $attach->filename = $attach->name;
3404                                         if (empty($attach->filename)) {
3405                                                 continue;
3406                                         }
3407
3408                                         // deal with the MIME types email has
3409                                         $attach->file_mime_type = $this->getMimeType($part->type, $part->subtype);
3410                                         $attach->safeAttachmentName();
3411                                         if($forDisplay) {
3412                                                 $attach->id = $this->getTempFilename();
3413                                         } else {
3414                                                 // only save if doing a full import, else we want only the binaries
3415                                                 $attach->save();
3416                                         }
3417                                 } // end if disposition type 'attachment'
3418                         }// end ifdisposition
3419                         //Retrieve contents of subtype rfc8822
3420                         elseif ($part->type == 2 && isset($part->subtype) && strtolower($part->subtype) == 'rfc822' )
3421                         {
3422                             $tmp_eml =  imap_fetchbody($this->conn, $msgNo, $thisBc);
3423                             $attach = $this->getNoteBeanForAttachment($emailId);
3424                             $attach->file_mime_type = 'messsage/rfc822';
3425                             $attach->description = $tmp_eml;
3426                             $attach->filename = 'bounce.eml';
3427                             $attach->safeAttachmentName();
3428                             if($forDisplay) {
3429                                 $attach->id = $this->getTempFilename();
3430                             } else {
3431                                 // only save if doing a full import, else we want only the binaries
3432                                 $attach->save();
3433                             }
3434                         } elseif(!$part->ifdisposition && $part->type != 1 && $part->type != 2 && $thisBc != '1') {
3435                         // No disposition here, but some IMAP servers lie about disposition headers, try to find the truth
3436                                 // Also Outlook puts inline attachments as type 5 (image) without a disposition
3437                                 if($part->ifparameters) {
3438                     foreach($part->parameters as $param) {
3439                         if(strtolower($param->attribute) == "name" || strtolower($param->attribute) == "filename") {
3440                             $fname = $this->handleEncodedFilename($param->value);
3441                             break;
3442                         }
3443                     }
3444                     if(empty($fname)) continue;
3445
3446                                         // we assume that named parts are attachments too
3447                     $attach = $this->getNoteBeanForAttachment($emailId);
3448
3449                                         $attach->filename = $attach->name = $fname;
3450                                         $attach->file_mime_type = $this->getMimeType($part->type, $part->subtype);
3451
3452                                         $attach->safeAttachmentName();
3453                                         if($forDisplay) {
3454                                                 $attach->id = $this->getTempFilename();
3455                                         } else {
3456                                                 // only save if doing a full import, else we want only the binaries
3457                                                 $attach->save();
3458                                         }
3459                                 }
3460                         }
3461                         $this->saveAttachmentBinaries($attach, $msgNo, $thisBc, $part, $forDisplay);
3462                 } // end foreach
3463         }
3464
3465         /**
3466          * Return a new note object for attachments.
3467          *
3468          * @param string $emailId
3469          * @return Note
3470          */
3471         function getNoteBeanForAttachment($emailId)
3472         {
3473             $attach = new Note();
3474             $attach->parent_id = $emailId;
3475             $attach->parent_type = 'Emails';
3476
3477             return $attach;
3478         }
3479
3480         /**
3481          * Return the filename of the attachment by examining the dparameters returned from imap_fetch_structure which
3482          * represet the content-disposition of the MIME header.
3483          *
3484          * @param array $dparamaters
3485          * @return string
3486          */
3487         function retrieveAttachmentNameFromStructure($dparamaters)
3488         {
3489            $result = "";
3490
3491            foreach ($dparamaters as $k => $v)
3492            {
3493                if( strtolower($v->attribute) == 'filename')
3494                {
3495                    $result = $v->value;
3496                    break;
3497                }
3498            }
3499
3500            return $result;
3501
3502     }
3503         /**
3504          * saves the actual binary file of a given attachment
3505          * @param object attach Note object that is attached to the binary file
3506          * @param string msgNo Message Number on IMAP/POP3 server
3507          * @param string thisBc Breadcrumb to navigate email structure to find the content
3508          * @param object part IMAP standard object that contains the "parts" of this section of email
3509          * @param bool $forDisplay
3510          */
3511         function saveAttachmentBinaries($attach, $msgNo, $thisBc, $part, $forDisplay) {
3512                 // decide where to place the file temporarily
3513                 $uploadDir = ($forDisplay) ? "{$this->EmailCachePath}/{$this->id}/attachments/" : "upload://";
3514
3515                 // decide what name to save file as
3516                 $fileName = $attach->id;
3517
3518                 // download the attachment if we didn't do it yet
3519                 if(!file_exists($uploadDir.$fileName)) {
3520                         $msgPartRaw = imap_fetchbody($this->conn, $msgNo, $thisBc);
3521                 // deal with attachment encoding and decode the text string
3522                         $msgPart = $this->handleTranserEncoding($msgPartRaw, $part->encoding);
3523
3524                         if(file_put_contents($uploadDir.$fileName, $msgPart)) {
3525                                 $GLOBALS['log']->debug('InboundEmail saved attachment file: '.$attach->filename);
3526                         } else {
3527                 $GLOBALS['log']->debug('InboundEmail could not create attachment file: '.$attach->filename ." - temp file target: [ {$uploadDir}{$fileName} ]");
3528                 return;
3529                         }
3530                 }
3531
3532                 $this->tempAttachment[$fileName] = urldecode($attach->filename);
3533                 // if all was successful, feel for inline and cache Note ID for display:
3534                 if((strtolower($part->disposition) == 'inline' && in_array($part->subtype, $this->imageTypes))
3535                     || ($part->type == 5)) {
3536                     if(copy($uploadDir.$fileName, sugar_cached("images/{$fileName}.").strtolower($part->subtype))) {
3537                             $id = substr($part->id, 1, -1); //strip <> around
3538                             $this->inlineImages[$id] = $attach->id.".".strtolower($part->subtype);
3539                         } else {
3540                                 $GLOBALS['log']->debug('InboundEmail could not copy '.$uploadDir.$fileName.' to cache');
3541                         }
3542                 }
3543         }
3544
3545         /**
3546          * decodes a string based on its associated encoding
3547          * if nothing is passed, we default to no-encoding type
3548          * @param       $str    encoded string
3549          * @param       $enc    detected encoding
3550          */
3551         function handleTranserEncoding($str, $enc=0) {
3552                 switch($enc) {
3553                         case 2:// BINARY
3554                                 $ret = $str;
3555                                 break;
3556                         case 3:// BASE64
3557                                 $ret = base64_decode($str);
3558                                 break;
3559                         case 4:// QUOTED-PRINTABLE
3560                                 $ret = quoted_printable_decode($str);
3561                                 break;
3562                         case 0:// 7BIT or 8BIT
3563                         case 1:// already in a string-useable format - do nothing
3564                         case 5:// OTHER
3565                         default:// catch all
3566                                 $ret = $str;
3567                                 break;
3568                 }
3569
3570                 return $ret;
3571         }
3572
3573
3574         /**
3575          * Some emails do not get assigned a message_id, specifically from
3576          * Outlook/Exchange.
3577          *
3578          * We need to derive a reliable one for duplicate import checking.
3579          */
3580         function getMessageId($header) {
3581                 $message_id = md5(print_r($header, true));
3582                 return $message_id;
3583         }
3584
3585         /**
3586          * checks for duplicate emails on polling.  The uniqueness of a given email message is determined by a concatenation
3587          * of 2 values, the messageID and the delivered-to field.  This allows multiple To: and B/CC: destination addresses
3588          * to be imported by Sugar without violating the true duplicate-email issues.
3589          *
3590          * @param string message_id message ID generated by sending server
3591          * @param int message number (mailserver's key) of email
3592          * @param object header object generated by imap_headerinfo()
3593          * @param string textHeader Headers in normal text format
3594          * @return bool
3595          */
3596         function importDupeCheck($message_id, $header, $textHeader) {
3597                 $GLOBALS['log']->debug('*********** InboundEmail doing dupe check.');
3598
3599                 // generate "delivered-to" seed for email duplicate check
3600                 $deliveredTo = $this->id; // cn: bug 12236 - cc's failing dupe check
3601                 $exHeader = explode("\n", $textHeader);
3602
3603                 foreach($exHeader as $headerLine) {
3604                         if(strpos(strtolower($headerLine), 'delivered-to:') !== false) {
3605                                 $deliveredTo = substr($headerLine, strpos($headerLine, " "), strlen($headerLine));
3606                                 $GLOBALS['log']->debug('********* InboundEmail found [ '.$deliveredTo.' ] as the destination address for email [ '.$message_id.' ]');
3607                         } elseif(strpos(strtolower($headerLine), 'x-real-to:') !== false) {
3608                                 $deliveredTo = substr($headerLine, strpos($headerLine, " "), strlen($headerLine));
3609                                 $GLOBALS['log']->debug('********* InboundEmail found [ '.$deliveredTo.' ] for non-standards compliant email x-header [ '.$message_id.' ]');
3610                         }
3611                 }
3612
3613                 //if(empty($message_id) && !isset($message_id)) {
3614                 if(empty($message_id) || !isset($message_id)) {
3615                         $GLOBALS['log']->debug('*********** NO MESSAGE_ID.');
3616                         $message_id = $this->getMessageId($header);
3617                 }
3618
3619                 // generate compound messageId
3620                 $this->compoundMessageId = trim($message_id).trim($deliveredTo);
3621                 // if the length > 255 then md5 it so that the data will be of smaller length
3622                 if (strlen($this->compoundMessageId) > 255) {
3623                         $this->compoundMessageId = md5($this->compoundMessageId);
3624                 } // if
3625
3626                 if (empty($this->compoundMessageId)) {
3627                         $GLOBALS['log']->error('Inbound Email found a message without a header and message_id');
3628                         return false;
3629                 } // if
3630
3631                 $potentials = clean_xss($this->compoundMessageId, false);
3632
3633                 if(is_array($potentials) && !empty($potentials)) {
3634                         foreach($potentials as $bad) {
3635                                 $this->compoundMessageId = str_replace($bad, "", $this->compoundMessageId);
3636                         }
3637                 }
3638
3639                 $query = 'SELECT count(emails.id) AS c FROM emails WHERE emails.message_id = \''.$this->compoundMessageId.'\' and emails.deleted = 0';
3640                 $r = $this->db->query($query, true);
3641                 $a = $this->db->fetchByAssoc($r);
3642
3643                 if($a['c'] > 0) {
3644                         $GLOBALS['log']->debug('InboundEmail found a duplicate email with ID ('.$this->compoundMessageId.')');
3645                         return false; // we have a dupe and don't want to import the email'
3646                 } else {
3647                         return true;
3648                 }
3649         }
3650
3651         /**
3652          * takes the output from imap_mime_hader_decode() and handles multiple types of encoding
3653          * @param string subject Raw subject string from email
3654          * @return string ret properly formatted UTF-8 string
3655          */
3656         function handleMimeHeaderDecode($subject) {
3657                 $subjectDecoded = imap_mime_header_decode($subject);
3658
3659                 $ret = '';
3660                 foreach($subjectDecoded as $object) {
3661                         if($object->charset != 'default') {
3662                                 $ret .= $this->handleCharsetTranslation($object->text, $object->charset);
3663                         } else {
3664                                 $ret .= $object->text;
3665                         }
3666                 }
3667                 return $ret;
3668         }
3669
3670    /**
3671         * URL cleanup function
3672         * Until we have comprehensive CSRF protection, we need to sanitize URLs in emails
3673         * to avoid CSRF attacks
3674         */
3675         public function urlCleaner($attr, $value)
3676         {
3677         // hrefs are ok
3678             if(strtolower($attr) == "href") return true;
3679             $items = parse_url($value);
3680             if(empty($items)) return false;
3681             if(!empty($items['scheme']) && strtolower($items['scheme']) != 'http' && strtolower($items['scheme']) != 'https') {
3682                 // do not touch non-HTTP URLs
3683                 return true;
3684             }
3685         // don't allow relative URLs
3686                 if(empty($items['host'])) return false;
3687         // allow URLs with no query
3688                 if(empty($items['query'])) return true;
3689         // allow URLs that don't start with /? or /index.php?
3690                 if(!empty($items['path']) && $items['path'] != '/' && strtolower(substr($items['path'], -10)) != '/index.php') {
3691                         return true;
3692                 }
3693         // now we have blah-blah/index.php?query - let's see if query looks dangerous
3694                 $query_items = array();
3695                 parse_str(from_html($items['query']), $query_items);
3696         // weird query, probably harmless
3697                 if(empty($query_items)) return true;
3698         // suspiciously like SugarCRM query, reject
3699                 if(!empty($query_items['module']) && !empty($query_items['action'])) return false;
3700         // looks like non-download entry point - allow only specific entry points
3701                 if(!empty($query_items['entryPoint']) && !in_array($query_items['entryPoint'], array('download', 'image', 'getImage'))) {
3702                         return false;
3703                 }
3704
3705                 return true;
3706         }
3707
3708         /**
3709          * Cleans content for XSS and other types of attack vectors
3710          * @param string str String to clean
3711          * @return string
3712          */
3713         function cleanContent($str) {
3714                 // Safe_HTML
3715                 $this->safe->clear();
3716                 $this->safe->setUrlCallback(array($this, "urlCleaner"));
3717                 $str = $this->safe->parse($str, false);
3718                 return $this->cleanXssContent($str);
3719         }
3720
3721         /**
3722          * Cleans content for XSS
3723          * @param string str String to clean
3724          * @return string
3725          */
3726         function cleanXssContent($str) {
3727
3728                 $potentials = clean_xss($str, false);
3729                 if(is_array($potentials) && !empty($potentials)) {
3730                         foreach($potentials as $bad) {
3731                                 $str = str_replace($bad, "", $str);
3732                         }
3733                 }
3734                 return $str;
3735         }
3736         /**
3737          * Calculates the appropriate display date/time sent for an email.
3738          * @param string headerDate The date sent of email in MIME header format
3739          * @return string GMT-0 Unix timestamp
3740          */
3741         function getUnixHeaderDate($headerDate) {
3742                 global $timedate;
3743
3744                 if (empty($headerDate)) {
3745                         return "";
3746                 }
3747                 ///////////////////////////////////////////////////////////////////
3748                 ////    CALCULATE CORRECT SENT DATE/TIME FOR EMAIL
3749                 if(!empty($headerDate)) {
3750                     // Bug 25254 - Strip trailing space that come in some header dates (maybe ones with 1-digit day number)
3751                     $headerDate = trim($headerDate);
3752                         // need to hack PHP/windows' bad handling of strings when using POP3
3753                         if(strstr($headerDate,'+0000 GMT')) {
3754                                 $headerDate = str_replace('GMT','', $headerDate);
3755                         } elseif(!strtotime($headerDate)) {
3756                                 $headerDate = 'now'; // catch non-standard format times.
3757                         } else {
3758                                 // cn: bug 9196 parse the GMT offset
3759                                 if(strpos($headerDate, '-') || strpos($headerDate, '+')) {
3760                                         // cn: bug make sure last 5 chars are [+|-]nnnn
3761                                         if(strpos($headerDate, "(")) {
3762                                                 $headerDate = preg_replace('/\([\w]+\)/i', "", $headerDate);
3763                                                 $headerDate = trim($headerDate);
3764                                         }
3765
3766                                         // parse mailserver time
3767                                         $gmtEmail = trim(substr($headerDate, -5, 5));
3768                                         $posNeg = substr($gmtEmail, 0, 1);
3769                                         $gmtHours = substr($gmtEmail, 1, 2);
3770                                         $gmtMins = substr($gmtEmail, -2, 2);
3771
3772                                         // get seconds
3773                                         $secsHours = $gmtHours * 60 * 60;
3774                                         $secsTotal = $secsHours + ($gmtMins * 60);
3775                                         $secsTotal = ($posNeg == '-') ? $secsTotal : -1 * $secsTotal;
3776
3777                                         $headerDate = trim(substr_replace($headerDate, '', -5)); // mfh: bug 10961/12855 - date time values with GMT offsets not properly formatted
3778                                 }
3779                         }
3780                 } else {
3781                         $headerDate = 'now';
3782                 }
3783
3784                 $unixHeaderDate = strtotime($headerDate);
3785
3786                 if(isset($secsTotal)) {
3787                         // this gets the timestamp to true GMT-0
3788                         $unixHeaderDate += $secsTotal;
3789                 }
3790
3791                 if(strtotime('Jan 1, 2001') > $unixHeaderDate) {
3792                         $unixHeaderDate = strtotime('now');
3793                 }
3794
3795                 return $unixHeaderDate;
3796                 ////    END CALCULATE CORRECT SENT DATE/TIME FOR EMAIL
3797                 ///////////////////////////////////////////////////////////////////
3798         }
3799
3800         /**
3801          * This method returns the correct messageno for the pop3 protocol
3802          * @param String UIDL
3803          * @return returnMsgNo
3804          */
3805         function getCorrectMessageNoForPop3($messageId) {
3806                 $returnMsgNo = -1;
3807                 if ($this->protocol == 'pop3') {
3808                         if($this->pop3_open()) {
3809                                 // get the UIDL from database;
3810                                 $query = "SELECT msgno FROM email_cache WHERE ie_id = '{$this->id}' AND message_id = '{$messageId}'";
3811                                 $r = $this->db->query($query);
3812                                 $a = $this->db->fetchByAssoc($r);
3813                                 $msgNo = $a['msgno'];
3814                                 $returnMsgNo = $msgNo;
3815
3816                                 // authenticate
3817                                 $this->pop3_sendCommand("USER", $this->email_user);
3818                                 $this->pop3_sendCommand("PASS", $this->email_password);
3819
3820                                 // get UIDL for this msgNo
3821                                 $this->pop3_sendCommand("UIDL {$msgNo}", '', false); // leave socket buffer alone until the while()
3822                                 $buf = fgets($this->pop3socket, 1024); // handle "OK+ msgNo UIDL(UIDL for this messageno)";
3823
3824                                 // if it returns OK then we have found the message else get all the UIDL
3825                                 // and search for the correct msgNo;
3826                                 $foundMessageNo = false;
3827                                 if (preg_match("/OK/", $buf) > 0) {
3828                                         $mailserverResponse = explode(" ", $buf);
3829                                         // if the cachedUIDL and the UIDL from mail server matches then its the correct messageno
3830                                         if (trim($mailserverResponse[sizeof($mailserverResponse) - 1]) == $messageId) {
3831                                                 $foundMessageNo = true;
3832                                         }
3833                                 } //if
3834
3835                                 //get all the UIDL and then find the correct messageno
3836                                 if (!$foundMessageNo) {
3837                                         // get UIDLs
3838                                         $this->pop3_sendCommand("UIDL", '', false); // leave socket buffer alone until the while()
3839                                         fgets($this->pop3socket, 1024); // handle "OK+";
3840                                         $UIDLs = array();
3841                                         $buf = '!';
3842                                         if(is_resource($this->pop3socket)) {
3843                                                 while(!feof($this->pop3socket)) {
3844                                                         $buf = fgets($this->pop3socket, 1024); // 8kb max buffer - shouldn't be more than 80 chars via pop3...
3845                                                         if(trim($buf) == '.') {
3846                                                                 $GLOBALS['log']->debug("*** GOT '.'");
3847                                                                 break;
3848                                                         } // if
3849                                                         // format is [msgNo] [UIDL]
3850                                                         $exUidl = explode(" ", $buf);
3851                                                         $UIDLs[trim($exUidl[1])] = trim($exUidl[0]);
3852                                                 } // while
3853                                                 if (array_key_exists($messageId, $UIDLs)) {
3854                                                         $returnMsgNo = $UIDLs[$messageId];
3855                                                 } else {
3856                                                         // message could not be found on server
3857                                                         $returnMsgNo = -1;
3858                                                 } // else
3859                                         } // if
3860
3861                                 } // if
3862                                 $this->pop3_cleanUp();
3863                         } //if
3864                 } //if
3865                 return $returnMsgNo;
3866         }
3867
3868         /**
3869          * If the importOneEmail returns false, then findout if the duplicate email
3870          */
3871         function getDuplicateEmailId($msgNo, $uid) {
3872                 global $timedate;
3873                 global $app_strings;
3874                 global $app_list_strings;
3875                 global $sugar_config;
3876                 global $current_user;
3877
3878                 $header = imap_headerinfo($this->conn, $msgNo);
3879                 $fullHeader = imap_fetchheader($this->conn, $msgNo); // raw headers
3880
3881                 // reset inline images cache
3882                 $this->inlineImages = array();
3883
3884                 // handle messages deleted on server
3885                 if(empty($header)) {
3886                         if(!isset($this->email) || empty($this->email)) {
3887                                 $this->email = new Email();
3888                         } // if
3889                         return "";
3890                 } else {
3891                         $dupeCheckResult = $this->importDupeCheck($header->message_id, $header, $fullHeader);
3892                         if (!$dupeCheckResult && !empty($this->compoundMessageId)) {
3893                                 // we have a duplicate email
3894                                 $query = 'SELECT id FROM emails WHERE emails.message_id = \''.$this->compoundMessageId.'\' and emails.deleted = 0';
3895                                 $r = $this->db->query($query, true);
3896                                 $a = $this->db->fetchByAssoc($r);
3897
3898                                 $this->email = new Email();
3899                                 $this->email->id = $a['id'];
3900                                 return $a['id'];
3901                         } // if
3902                         return "";
3903                 } // else
3904         } // fn
3905
3906
3907         /**
3908          * shiny new importOneEmail() method
3909          * @param int msgNo
3910          * @param bool forDisplay
3911          * @param clean_email boolean, default true,
3912          */
3913         function importOneEmail($msgNo, $uid, $forDisplay=false, $clean_email=true) {
3914                 $GLOBALS['log']->debug("InboundEmail processing 1 email {$msgNo}-----------------------------------------------------------------------------------------");
3915                 global $timedate;
3916                 global $app_strings;
3917                 global $app_list_strings;
3918                 global $sugar_config;
3919                 global $current_user;
3920
3921         // Bug # 45477
3922         // So, on older versions of PHP (PHP VERSION < 5.3),
3923         // calling imap_headerinfo and imap_fetchheader can cause a buffer overflow for exteremly large headers,
3924         // This leads to the remaining messages not being read because Sugar crashes everytime it tries to read the headers.
3925         // The workaround is to mark a message as read before making trying to read the header of the msg in question
3926         // This forces this message not be read again, and we can continue processing remaining msgs.
3927
3928         // UNCOMMENT THIS IF YOU HAVE THIS PROBLEM!  See notes on Bug # 45477
3929         // $this->markEmails($uid, "read");
3930
3931                 $header = imap_headerinfo($this->conn, $msgNo);
3932                 $fullHeader = imap_fetchheader($this->conn, $msgNo); // raw headers
3933
3934                 // reset inline images cache
3935                 $this->inlineImages = array();
3936
3937                 // handle messages deleted on server
3938                 if(empty($header)) {
3939                         if(!isset($this->email) || empty($this->email)) {
3940                                 $this->email = new Email();
3941                         }
3942
3943                         $q = "";
3944                         if ($this->isPop3Protocol()) {
3945                                 $this->email->name = $app_strings['LBL_EMAIL_ERROR_MESSAGE_DELETED'];
3946                                 $q = "DELETE FROM email_cache WHERE message_id = '{$uid}' AND ie_id = '{$this->id}' AND mbox = '{$this->mailbox}'";
3947                         } else {
3948                                 $this->email->name = $app_strings['LBL_EMAIL_ERROR_IMAP_MESSAGE_DELETED'];
3949                                 $q = "DELETE FROM email_cache WHERE imap_uid = {$uid} AND ie_id = '{$this->id}' AND mbox = '{$this->mailbox}'";
3950                         } // else
3951                         // delete local cache
3952                         $r = $this->db->query($q);
3953
3954                         $this->email->date_sent = $timedate->nowDb();
3955                         return false;
3956                         //return "Message deleted from server.";
3957                 }
3958
3959                 ///////////////////////////////////////////////////////////////////////
3960                 ////    DUPLICATE CHECK
3961                 $dupeCheckResult = $this->importDupeCheck($header->message_id, $header, $fullHeader);
3962                 if($forDisplay || $dupeCheckResult) {
3963                         $GLOBALS['log']->debug('*********** NO duplicate found, continuing with processing.');
3964
3965                         $structure = imap_fetchstructure($this->conn, $msgNo); // map of email
3966
3967                         ///////////////////////////////////////////////////////////////////
3968                         ////    CREATE SEED EMAIL OBJECT
3969                         $email = new Email();
3970                         $email->isDuplicate = ($dupeCheckResult) ? false : true;
3971                         $email->mailbox_id = $this->id;
3972                         $message = array();
3973                         $email->id = create_guid();
3974                         $email->new_with_id = true; //forcing a GUID here to prevent double saves.
3975                         ////    END CREATE SEED EMAIL
3976                         ///////////////////////////////////////////////////////////////////
3977
3978                         ///////////////////////////////////////////////////////////////////
3979                         ////    PREP SYSTEM USER
3980                         if(empty($current_user)) {
3981                                 // I-E runs as admin, get admin prefs
3982
3983                                 $current_user = new User();
3984                                 $current_user->getSystemUser();
3985                         }
3986                         $tPref = $current_user->getUserDateTimePreferences();
3987                         ////    END USER PREP
3988                         ///////////////////////////////////////////////////////////////////
3989             if(!empty($header->date)) {
3990                             $unixHeaderDate = $timedate->fromString($header->date);
3991             }
3992                         ///////////////////////////////////////////////////////////////////
3993                         ////    HANDLE EMAIL ATTACHEMENTS OR HTML TEXT
3994                         ////    Inline images require that I-E handle attachments before body text
3995                         // parts defines attachments - be mindful of .html being interpreted as an attachment
3996                         if($structure->type == 1 && !empty($structure->parts)) {
3997                                 $GLOBALS['log']->debug('InboundEmail found multipart email - saving attachments if found.');
3998                                 $this->saveAttachments($msgNo, $structure->parts, $email->id, 0, $forDisplay);
3999                         } elseif($structure->type == 0) {
4000                                 $uuemail = ($this->isUuencode($email->description)) ? true : false;
4001                                 /*
4002                                  * UUEncoded attachments - legacy, but still have to deal with it
4003                                  * format:
4004                                  * begin 777 filename.txt
4005                                  * UUENCODE
4006                                  *
4007                                  * end
4008                                  */
4009                                 // set body to the filtered one
4010                                 if($uuemail) {
4011                                         $email->description = $this->handleUUEncodedEmailBody($email->description, $email->id);
4012                                         $email->retrieve($email->id);
4013                                         $email->save();
4014                                 }
4015                         } else {
4016                                 if($this->port != 110) {
4017                                         $GLOBALS['log']->debug('InboundEmail found a multi-part email (id:'.$msgNo.') with no child parts to parse.');
4018                                 }
4019                         }
4020                         ////    END HANDLE EMAIL ATTACHEMENTS OR HTML TEXT
4021                         ///////////////////////////////////////////////////////////////////
4022
4023                         ///////////////////////////////////////////////////////////////////
4024                         ////    ASSIGN APPROPRIATE ATTRIBUTES TO NEW EMAIL OBJECT
4025                         // handle UTF-8/charset encoding in the ***headers***
4026                         global $db;
4027                         $email->name                    = $this->handleMimeHeaderDecode($header->subject);
4028                         $email->date_start = (!empty($unixHeaderDate)) ? $timedate->asUserDate($unixHeaderDate) : "";
4029                         $email->time_start = (!empty($unixHeaderDate)) ? $timedate->asUserTime($unixHeaderDate) : "";
4030                         $email->type = 'inbound';
4031                         $email->date_created = (!empty($unixHeaderDate)) ? $timedate->asUser($unixHeaderDate) : "";
4032                         $email->status = 'unread'; // this is used in Contacts' Emails SubPanel
4033                         if(!empty($header->toaddress)) {
4034                                 $email->to_name  = $this->handleMimeHeaderDecode($header->toaddress);
4035                                 $email->to_addrs_names = $email->to_name;
4036                         }
4037                         if(!empty($header->to)) {
4038                                 $email->to_addrs        = $this->convertImapToSugarEmailAddress($header->to);
4039                         }
4040                         $email->from_name               = $this->handleMimeHeaderDecode($header->fromaddress);
4041                         $email->from_addr_name = $email->from_name;
4042                         $email->from_addr               = $this->convertImapToSugarEmailAddress($header->from);
4043                         if(!empty($header->cc)) {
4044                                 $email->cc_addrs        = $this->convertImapToSugarEmailAddress($header->cc);
4045                         }
4046                         if(!empty($header->ccaddress)) {
4047                                 $email->cc_addrs_names   = $this->handleMimeHeaderDecode($header->ccaddress);
4048                         } // if
4049                         $email->reply_to_name   = $this->handleMimeHeaderDecode($header->reply_toaddress);
4050                         $email->reply_to_email  = $this->convertImapToSugarEmailAddress($header->reply_to);
4051                         if (!empty($email->reply_to_email)) {
4052                                 $email->reply_to_addr   = $email->reply_to_name;
4053                         }
4054                         $email->intent                  = $this->mailbox_type;
4055
4056                         $email->message_id              = $this->compoundMessageId; // filled by importDupeCheck();
4057
4058                         $oldPrefix = $this->imagePrefix;
4059                         if(!$forDisplay) {
4060                                 // Store CIDs in imported messages, convert on display
4061                                 $this->imagePrefix = "cid:";
4062                         }
4063                         // handle multi-part email bodies
4064                         $email->description_html= $this->getMessageText($msgNo, 'HTML', $structure, $fullHeader,$clean_email); // runs through handleTranserEncoding() already
4065                         $email->description     = $this->getMessageText($msgNo, 'PLAIN', $structure, $fullHeader,$clean_email); // runs through handleTranserEncoding() already
4066                         $this->imagePrefix = $oldPrefix;
4067
4068                         // empty() check for body content
4069                         if(empty($email->description)) {
4070                                 $GLOBALS['log']->debug('InboundEmail Message (id:'.$email->message_id.') has no body');
4071                         }
4072
4073                         // assign_to group
4074                         if (!empty($_REQUEST['user_id'])) {
4075                                 $email->assigned_user_id = $_REQUEST['user_id'];
4076                         } else {
4077                                 // Samir Gandhi : Commented out this code as its not needed
4078                                 //$email->assigned_user_id = $this->group_id;
4079                         }
4080
4081                 //Assign Parent Values if set
4082                 if (!empty($_REQUEST['parent_id']) && !empty($_REQUEST['parent_type'])) {
4083                 $email->parent_id = $_REQUEST['parent_id'];
4084                 $email->parent_type = $_REQUEST['parent_type'];
4085
4086                 $mod = strtolower($email->parent_type);
4087                 $rel = array_key_exists($mod, $email->field_defs) ? $mod : $mod . "_activities_emails"; //Custom modules rel name
4088
4089                 if(! $email->load_relationship($rel) )
4090                     return FALSE;
4091                 $email->$rel->add($email->parent_id);
4092                 }
4093
4094                         // override $forDisplay w/user pref
4095                         if($forDisplay) {
4096                                 if($this->isAutoImport()) {
4097                                         $forDisplay = false; // triggers save of imported email
4098                                 }
4099                         }
4100
4101                         if(!$forDisplay) {
4102                                 $email->save();
4103
4104                                 $email->new_with_id = false; // to allow future saves by UPDATE, instead of INSERT
4105                                 ////    ASSIGN APPROPRIATE ATTRIBUTES TO NEW EMAIL OBJECT
4106                                 ///////////////////////////////////////////////////////////////////
4107
4108                                 ///////////////////////////////////////////////////////////////////
4109                                 ////    LINK APPROPRIATE BEANS TO NEWLY SAVED EMAIL
4110                                 //$contactAddr = $this->handleLinking($email);
4111                                 ////    END LINK APPROPRIATE BEANS TO NEWLY SAVED EMAIL
4112                                 ///////////////////////////////////////////////////////////////////
4113
4114                                 ///////////////////////////////////////////////////////////////////
4115                                 ////    MAILBOX TYPE HANDLING
4116                                 $this->handleMailboxType($email, $header);
4117                                 ////    END MAILBOX TYPE HANDLING
4118                                 ///////////////////////////////////////////////////////////////////
4119
4120                                 ///////////////////////////////////////////////////////////////////
4121                                 ////    SEND AUTORESPONSE
4122                                 if(!empty($email->reply_to_email)) {
4123                                         $contactAddr = $email->reply_to_email;
4124                                 } else {
4125                                         $contactAddr = $email->from_addr;
4126                                 }
4127                                 if (!$this->isMailBoxTypeCreateCase()) {
4128                                         $this->handleAutoresponse($email, $contactAddr);
4129                                 }
4130                                 ////    END SEND AUTORESPONSE
4131                                 ///////////////////////////////////////////////////////////////////
4132                                 ////    END IMPORT ONE EMAIL
4133                                 ///////////////////////////////////////////////////////////////////
4134                         }
4135                 } else {
4136                         // only log if not POP3; pop3 iterates through ALL mail
4137                         if($this->protocol != 'pop3') {
4138                                 $GLOBALS['log']->info("InboundEmail found a duplicate email: ".$header->message_id);
4139                                 //echo "This email has already been imported";
4140                         }
4141                         return false;
4142                 }
4143                 ////    END DUPLICATE CHECK
4144                 ///////////////////////////////////////////////////////////////////////
4145
4146                 ///////////////////////////////////////////////////////////////////////
4147                 ////    DEAL WITH THE MAILBOX
4148                 if(!$forDisplay) {
4149                         imap_setflag_full($this->conn, $msgNo, '\\SEEN');
4150
4151                         // if delete_seen, mark msg as deleted
4152                         if($this->delete_seen == 1  && !$forDisplay) {
4153                                 $GLOBALS['log']->info("INBOUNDEMAIL: delete_seen == 1 - deleting email");
4154                                 imap_setflag_full($this->conn, $msgNo, '\\DELETED');
4155                         }
4156                 } else {
4157                         // for display - don't touch server files?
4158                         //imap_setflag_full($this->conn, $msgNo, '\\UNSEEN');
4159                 }
4160
4161                 $GLOBALS['log']->debug('********************************* InboundEmail finished import of 1 email: '.$email->name);
4162                 ////    END DEAL WITH THE MAILBOX
4163                 ///////////////////////////////////////////////////////////////////////
4164
4165                 ///////////////////////////////////////////////////////////////////////
4166                 ////    TO SUPPORT EMAIL 2.0
4167                 $this->email = $email;
4168
4169                 if(empty($this->email->et)) {
4170                         $this->email->email2init();
4171                 }
4172
4173                 return true;
4174         }
4175
4176         /**
4177          * figures out if a plain text email body has UUEncoded attachments
4178          * @param string string The email body
4179          * @return bool True if UUEncode is detected.
4180          */
4181         function isUuencode($string) {
4182                 $rx = "begin [0-9]{3} .*";
4183
4184                 $exBody = explode("\r", $string);
4185                 foreach($exBody as $line) {
4186                         if(preg_match("/begin [0-9]{3} .*/i", $line)) {
4187                                 return true;
4188                         }
4189                 }
4190
4191                 return false;
4192         }
4193
4194         /**
4195          * handles UU Encoded emails - a legacy from pre-RFC 822 which must still be supported (?)
4196          * @param string raw The raw email body
4197          * @param string id Parent email ID
4198          * @return string The filtered email body, stripped of attachments
4199          */
4200         function handleUUEncodedEmailBody($raw, $id) {
4201                 global $locale;
4202
4203                 $emailBody = '';
4204                 $attachmentBody = '';
4205                 $inAttachment = false;
4206
4207                 $exRaw = explode("\n", $raw);
4208
4209                 foreach($exRaw as $k => $line) {
4210                         $line = trim($line);
4211
4212                         if(preg_match("/begin [0-9]{3} .*/i", $line, $m)) {
4213                                 $inAttachment = true;
4214                                 $fileName = $this->handleEncodedFilename(substr($m[0], 10, strlen($m[0])));
4215
4216                                 $attachmentBody = ''; // reset for next part of loop;
4217                                 continue;
4218                         }
4219
4220                         // handle "end"
4221                         if(strpos($line, "end") === 0) {
4222                                 if(!empty($fileName) && !empty($attachmentBody)) {
4223                                         $this->handleUUDecode($id, $fileName, trim($attachmentBody));
4224                                         $attachmentBody = ''; // reset for next part of loop;
4225                                 }
4226                         }
4227
4228                         if($inAttachment === false) {
4229                                 $emailBody .= "\n".$line;
4230                         } else {
4231                                 $attachmentBody .= "\n".$line;
4232                         }
4233                 }
4234
4235                 /* since UUEncode was developed before MIME, we have NO idea what character set encoding was used.  we will assume the user's locale character set */
4236                 $emailBody = $locale->translateCharset($emailBody, $locale->getExportCharset(), 'UTF-8');
4237                 return $emailBody;
4238         }
4239
4240         /**
4241          * wrapper for UUDecode
4242          * @param string id Id of the email
4243          * @param string UUEncode Encode US-ASCII
4244          */
4245         function handleUUDecode($id, $fileName, $UUEncode) {
4246                 global $sugar_config;
4247                 /* include PHP_Compat library; it auto-feels for PHP5's compiled convert_uuencode() function */
4248                 require_once('include/PHP_Compat/convert_uudecode.php');
4249
4250                 $attach = new Note();
4251                 $attach->parent_id = $id;
4252                 $attach->parent_type = 'Emails';
4253
4254                 $fname = $this->handleEncodedFilename($fileName);
4255
4256                 if(!empty($fname)) {//assign name to attachment
4257                         $attach->name = $fname;
4258                 } else {//if name is empty, default to filename
4259                         $attach->name = urlencode($fileName);
4260                 }
4261
4262                 $attach->filename = urlencode($attach->name);
4263
4264                 //get position of last "." in file name
4265                 $file_ext_beg = strrpos($attach->filename,".");
4266                 $file_ext = "";
4267                 //get file extension
4268                 if($file_ext_beg >0) {
4269                         $file_ext = substr($attach->filename, $file_ext_beg+1);
4270                 }
4271                 //check to see if this is a file with extension located in "badext"
4272                 foreach($sugar_config['upload_badext'] as $badExt) {
4273                         if(strtolower($file_ext) == strtolower($badExt)) {
4274                                 //if found, then append with .txt and break out of lookup
4275                                 $attach->name = $attach->name . ".txt";
4276                                 $attach->file_mime_type = 'text/';
4277                                 $attach->filename = $attach->filename . ".txt";
4278                                 break; // no need to look for more
4279                         }
4280                 }
4281                 $attach->save();
4282
4283                 $bin = convert_uudecode($UUEncode);
4284                 $filename = "upload://{$attach->id}";
4285                 if(file_put_contents($filename, $bin)) {
4286                 $GLOBALS['log']->debug('InboundEmail saved attachment file: '.$filename);
4287                 } else {
4288                 $GLOBALS['log']->debug('InboundEmail could not create attachment file: '.$filename);
4289                 }
4290         }
4291
4292         /**
4293          * returns true if the email's domain is NOT in the filter domain string
4294          *
4295          * @param object email Email object in question
4296          * @return bool true if not filtered, false if filtered
4297          */
4298         function checkFilterDomain($email) {
4299                 $filterDomain = $this->get_stored_options('filter_domain');
4300                 if(!isset($filterDomain) || empty($filterDomain)) {
4301                         return true; // nothing set for this
4302                 } else {
4303                         $replyTo = strtolower($email->reply_to_email);
4304                         $from = strtolower($email->from_addr);
4305                         $filterDomain = '@'.strtolower($filterDomain);
4306                         if(strpos($replyTo, $filterDomain) !== false) {
4307                                 $GLOBALS['log']->debug('Autoreply cancelled - [reply to] address domain matches filter domain.');
4308                                 return false;
4309                         } elseif(strpos($from, $filterDomain) !== false) {
4310                                 $GLOBALS['log']->debug('Autoreply cancelled - [from] address domain matches filter domain.');
4311                                 return false;
4312                         } else {
4313                                 return true; // no match
4314                         }
4315                 }
4316         }
4317
4318         /**
4319          * returns true if subject is NOT "out of the office" type
4320          *
4321          * @param string subject Subject line of email in question
4322          * @return bool returns false if OOTO found
4323          */
4324         function checkOutOfOffice($subject) {
4325                 $ooto = array("Out of the Office", "Out of Office");
4326
4327                 foreach($ooto as $str) {
4328                         if(preg_match('/'.$str.'/i', $subject)) {
4329                                 $GLOBALS['log']->debug('Autoreply cancelled - found "Out of Office" type of subject.');
4330                                 return false;
4331                         }
4332                 }
4333                 return true; // no matches to ooto strings
4334         }
4335
4336
4337         /**
4338          * sets a timestamp for an autoreply to a single email addy
4339          *
4340          * @param string addr Address of auto-replied target
4341          */
4342         function setAutoreplyStatus($addr) {
4343             $timedate = TimeDate::getInstance();
4344                 $this->db->query(       'INSERT INTO inbound_email_autoreply (id, deleted, date_entered, date_modified, autoreplied_to, ie_id) VALUES (
4345                                                         \''.create_guid().'\',
4346                                                         0,
4347                                                         \''.$timedate->nowDb().'\',
4348                                                         \''.$timedate->nowDb().'\',
4349                                                         \''.$addr.'\',
4350                                     \''.$this->id.'\') ', true);
4351         }
4352
4353
4354         /**
4355          * returns true if recipient has NOT received 10 auto-replies in 24 hours
4356          *
4357          * @param string from target address for auto-reply
4358          * @return bool true if target is valid/under limit
4359          */
4360         function getAutoreplyStatus($from) {
4361                 global $sugar_config;
4362         $timedate = TimeDate::getInstance();
4363
4364                 $q_clean = 'UPDATE inbound_email_autoreply SET deleted = 1 WHERE date_entered < \''.$timedate->getNow()->modify("-24 hours")->asDb().'\'';
4365                 $r_clean = $this->db->query($q_clean, true);
4366
4367                 $q = 'SELECT count(*) AS c FROM inbound_email_autoreply WHERE deleted = 0 AND autoreplied_to = \''.$from.'\' AND ie_id = \''.$this->id.'\'';
4368                 $r = $this->db->query($q, true);
4369                 $a = $this->db->fetchByAssoc($r);
4370
4371                 $email_num_autoreplies_24_hours = $this->get_stored_options('email_num_autoreplies_24_hours');
4372                 $maxReplies = (isset($email_num_autoreplies_24_hours)) ? $email_num_autoreplies_24_hours : $this->maxEmailNumAutoreplies24Hours;
4373
4374                 if($a['c'] >= $maxReplies) {
4375                         $GLOBALS['log']->debug('Autoreply cancelled - more than ' . $maxReplies . ' replies sent in 24 hours.');
4376                         return false;
4377                 } else {
4378                         return true;
4379                 }
4380         }
4381
4382         /**
4383          * returns exactly 1 id match. if more than one, than returns false
4384          * @param       $emailName              the subject of the email to match
4385          * @param       $tableName              the table of the matching bean type
4386          */
4387         function getSingularRelatedId($emailName, $tableName) {
4388                 $repStrings = array('RE:','Re:','re:');
4389                 $preppedName = str_replace($repStrings,'',trim($emailName));
4390
4391                 //TODO add team security to this query
4392                 $q = 'SELECT count(id) AS c FROM '.$tableName.' WHERE deleted = 0 AND name LIKE \'%'.$preppedName.'%\'';
4393                 $r = $this->db->query($q, true);
4394                 $a = $this->db->fetchByAssoc($r);
4395
4396                 if($a['c'] == 0) {
4397                         $q = 'SELECT id FROM '.$tableName.' WHERE deleted = 0 AND name LIKE \'%'.$preppedName.'%\'';
4398                         $r = $this->db->query($q, true);
4399                         $a = $this->db->fetchByAssoc($r);
4400                         return $a['id'];
4401                 } else {
4402                         return false;
4403                 }
4404         }
4405
4406         /**
4407          * saves InboundEmail parse macros to config.php
4408          * @param string type Bean to link
4409          * @param string macro The new macro
4410          */
4411         function saveInboundEmailSystemSettings($type, $macro) {
4412                 global $sugar_config;
4413
4414                 // inbound_email_case_subject_macro
4415                 $var = "inbound_email_".strtolower($type)."_subject_macro";
4416                 $sugar_config[$var] = $macro;
4417
4418                 ksort($sugar_config);
4419
4420                 $sugar_config_string = "<?php\n" .
4421                         '// created: ' . date('Y-m-d H:i:s') . "\n" .
4422                         '$sugar_config = ' .
4423                         var_export($sugar_config, true) .
4424                         ";\n?>\n";
4425
4426                 write_array_to_file("sugar_config", $sugar_config, "config.php");
4427         }
4428
4429         /**
4430          * returns the HTML for InboundEmail system settings
4431          * @return string HTML
4432          */
4433         function getSystemSettingsForm() {
4434                 global $sugar_config;
4435                 global $mod_strings;
4436                 global $app_strings;
4437                 global $app_list_strings;
4438
4439                 ////    Case Macro
4440                 $c = new aCase();
4441
4442                 $macro = $c->getEmailSubjectMacro();
4443
4444                 $ret =<<<eoq
4445                         <form action="index.php" method="post" name="Macro" id="form">
4446                                                 <input type="hidden" name="module" value="InboundEmail">
4447                                                 <input type="hidden" name="action" value="ListView">
4448                                                 <input type="hidden" name="save" value="true">
4449
4450                         <table width="100%" cellpadding="0" cellspacing="0" border="0">
4451                                 <tr>
4452                                         <td>
4453                                                 <input  title="{$app_strings['LBL_SAVE_BUTTON_TITLE']}"
4454                                                                 accessKey="{$app_strings['LBL_SAVE_BUTTON_KEY']}"
4455                                                                 class="button"
4456                                                                 onclick="this.form.return_module.value='InboundEmail'; this.form.return_action.value='ListView';"
4457                                                                 type="submit" name="Edit" value="  {$app_strings['LBL_SAVE_BUTTON_LABEL']}  ">
4458                                         </td>
4459                                 </tr>
4460                         </table>
4461
4462                         <table width="100%" border="0" cellspacing="0" cellpadding="0" class="detail view">
4463                                 <tr>
4464                                         <td valign="top" width='10%' NOWRAP scope="row">
4465                                                 <slot>
4466                                                         <b>{$mod_strings['LBL_CASE_MACRO']}:</b>
4467                                                 </slot>
4468                                         </td>
4469                                         <td valign="top" width='20%'>
4470                                                 <slot>
4471                                                         <input name="inbound_email_case_macro" type="text" value="{$macro}">
4472                                                 </slot>
4473                                         </td>
4474                                         <td valign="top" width='70%'>
4475                                                 <slot>
4476                                                         {$mod_strings['LBL_CASE_MACRO_DESC']}
4477                                                         <br />
4478                                                         <i>{$mod_strings['LBL_CASE_MACRO_DESC2']}</i>
4479                                                 </slot>
4480                                         </td>
4481                                 </tr>
4482                         </table>
4483                         </form>
4484 eoq;
4485                 return $ret;
4486         }
4487
4488         /**
4489          * for mailboxes of type "Support" parse for '[CASE:%1]'
4490          * @param       $emailName              the subject line of the email
4491          * @param       $aCase                  a Case object
4492          */
4493         function getCaseIdFromCaseNumber($emailName, $aCase) {
4494                 //$emailSubjectMacro
4495                 $exMacro = explode('%1', $aCase->getEmailSubjectMacro());
4496                 $open = $exMacro[0];
4497                 $close = $exMacro[1];
4498
4499                 if($sub = stristr($emailName, $open)) { // eliminate everything up to the beginning of the macro and return the rest
4500                         // $sub is [CASE:XX] xxxxxxxxxxxxxxxxxxxxxx
4501                         $sub2 = str_replace($open, '', $sub);
4502                         // $sub2 is XX] xxxxxxxxxxxxxx
4503                         $sub3 = substr($sub2, 0, strpos($sub2, $close));
4504
4505                         $r = $this->db->query("SELECT id FROM cases WHERE case_number = '{$sub3}'", true);
4506                         $a = $this->db->fetchByAssoc($r);
4507                         if(!empty($a['id'])) {
4508                                 return $a['id'];
4509                         } else {
4510                                 return false;
4511                         }
4512                 } else {
4513                         return false;
4514                 }
4515         }
4516
4517         function get_stored_options($option_name,$default_value=null,$stored_options=null) {
4518                 if (empty($stored_options)) {
4519                         $stored_options=$this->stored_options;
4520                 }
4521                 if(!empty($stored_options)) {
4522                         $storedOptions = unserialize(base64_decode($stored_options));
4523                         if (isset($storedOptions[$option_name])) {
4524                                 $default_value=$storedOptions[$option_name];
4525                         }
4526                 }
4527                 return $default_value;
4528         }
4529
4530
4531         /**
4532          * This function returns a contact or user ID if a matching email is found
4533          * @param       $email          the email address to match
4534          * @param       $table          which table to query
4535          */
4536         function getRelatedId($email, $module) {
4537                 $email = trim(strtoupper($email));
4538                 if(strpos($email, ',') !== false) {
4539                         $emailsArray = explode(',', $email);
4540                         $emailAddressString = "";
4541                         foreach($emailsArray as $emailAddress) {
4542                                 if (!empty($emailAddressString)) {
4543                                         $emailAddressString .= ",";
4544                                 }
4545                                 $emailAddressString .=  "'" . $emailAddress. "'";
4546                         } // foreach
4547                         $email = $emailAddressString;
4548                 } else {
4549                         $email = "'" . $email . "'";
4550                 } // else
4551                 $module = ucfirst($module);
4552
4553                 $q = "SELECT bean_id FROM email_addr_bean_rel eabr
4554                                 JOIN email_addresses ea ON (eabr.email_address_id = ea.id)
4555                                 WHERE bean_module = '{$module}' AND ea.email_address_caps in ( {$email} ) AND eabr.deleted=0";
4556
4557                 $r = $this->db->query($q, true);
4558
4559                 $retArr = array();
4560                 while($a = $this->db->fetchByAssoc($r)) {
4561                         $retArr[] = $a['bean_id'];
4562                 }
4563                 if(count($retArr) > 0) {
4564                         return $retArr;
4565                 } else {
4566                         return false;
4567                 }
4568         }
4569
4570         /**
4571          * finds emails tagged "//UNSEEN" on mailserver and "SINCE: [date]" if that
4572          * option is set
4573          *
4574          * @return array Array of messageNumbers (mail server's internal keys)
4575          */
4576         function getNewMessageIds() {
4577                 $storedOptions = unserialize(base64_decode($this->stored_options));
4578
4579                 //TODO figure out if the since date is UDT
4580                 if($storedOptions['only_since']) {// POP3 does not support Unseen flags
4581                         if(!isset($storedOptions['only_since_last']) && !empty($storedOptions['only_since_last'])) {
4582                                 $q = 'SELECT last_run FROM schedulers WHERE job = \'function::pollMonitoredInboxes\'';
4583                                 $r = $this->db->query($q, true);
4584                                 $a = $this->db->fetchByAssoc($r);
4585
4586                                 $date = date('r', strtotime($a['last_run']));
4587                         } else {
4588                                 $date = $storedOptions['only_since_last'];
4589                         }
4590                         $ret = imap_search($this->conn, 'SINCE "'.$date.'" UNDELETED UNSEEN');
4591                         $check = imap_check($this->conn);
4592                         $storedOptions['only_since_last'] = $check->Date;
4593                         $this->stored_options = base64_encode(serialize($storedOptions));
4594                         $this->save();
4595                 } else {
4596                         $ret = imap_search($this->conn, 'UNDELETED UNSEEN');
4597                 }
4598
4599                 $GLOBALS['log']->debug('-----> getNewMessageIds() got '.count($ret).' new Messages');
4600                 return $ret;
4601         }
4602
4603         /**
4604          * Constructs the resource connection string that IMAP needs
4605          * @param string $service Service string, will generate if not passed
4606          * @return string
4607          */
4608         function getConnectString($service='', $mbox='', $includeMbox=true) {
4609                 $service = empty($service) ? $this->getServiceString() : $service;
4610                 $mbox = empty($mbox) ? $this->mailbox : $mbox;
4611
4612                 $connectString = '{'.$this->server_url.':'.$this->port.'/service='.$this->protocol.$service.'}';
4613                 $connectString .= ($includeMbox) ? $mbox : "";
4614
4615                 return $connectString;
4616         }
4617
4618         function disconnectMailserver() {
4619                 if(is_resource($this->conn)) {
4620                         imap_close($this->conn);
4621                 }
4622         }
4623
4624         /**
4625          * Connects to mailserver.  If an existing IMAP resource is available, it
4626          * will attempt to reuse the connection, updating the mailbox path.
4627          *
4628          * @param bool test Flag to test connection
4629          * @param bool force Force reconnect
4630          * @return string "true" on success, "false" or $errorMessage on failure
4631          */
4632         function connectMailserver($test=false, $force=false) {
4633                 global $mod_strings;
4634                 if(!function_exists("imap_open")) {
4635                         $GLOBALS['log']->debug('------------------------- IMAP libraries NOT available!!!! die()ing thread.----');
4636                         return $mod_strings['LBL_WARN_NO_IMAP'];
4637                 }
4638
4639                 imap_errors(); // clearing error stack
4640                 error_reporting(0); // turn off notices from IMAP
4641
4642                 // tls::ca::ssl::protocol::novalidate-cert::notls
4643                 $useSsl = ($_REQUEST['ssl'] == 'true') ? true : false;
4644                 if($test) {
4645                         imap_timeout(1, 15); // 60 secs is the default
4646                         imap_timeout(2, 15);
4647                         imap_timeout(3, 15);
4648
4649                         $opts = $this->findOptimumSettings($useSsl);
4650                         if(isset($opts['good']) && empty($opts['good'])) {
4651                                 return array_pop($opts['err']);
4652                         } else {
4653                                 $service = $opts['service'];
4654                                 $service = str_replace('foo','', $service); // foo there to support no-item explodes
4655                         }
4656                 } else {
4657                         $service = $this->getServiceString();
4658                 }
4659
4660                 $connectString = $this->getConnectString($service, $this->mailbox);
4661
4662                 /*
4663                  * Try to recycle the current connection to reduce response times
4664                  */
4665                 if(is_resource($this->conn)) {
4666                         if($force) {
4667                                 // force disconnect
4668                                 imap_close($this->conn);
4669                         }
4670
4671                         if(imap_ping($this->conn)) {
4672                                 // we have a live connection
4673                                 imap_reopen($this->conn, $connectString, CL_EXPUNGE);
4674                         }
4675                 }
4676
4677                 // final test
4678                 if(!is_resource($this->conn) && !$test) {
4679                         $this->conn = imap_open($connectString, $this->email_user, $this->email_password, CL_EXPUNGE);
4680                 }
4681
4682                 if($test) {
4683                         if ($opts == false && !is_resource($this->conn)) {
4684                                 $this->conn = imap_open($connectString, $this->email_user, $this->email_password, CL_EXPUNGE);
4685                         }
4686                         $errors = '';
4687                         $alerts = '';
4688                         $successful = false;
4689                         if(($errors = imap_last_error()) || ($alerts = imap_alerts())) {
4690                                 if($errors == 'Mailbox is empty') { // false positive
4691                                         $successful = true;
4692                                 } else {
4693                                         $msg .= $errors;
4694                                         $msg .= '<p>'.$alerts.'<p>';
4695                                         $msg .= '<p>'.$mod_strings['ERR_TEST_MAILBOX'];
4696                                 }
4697                         } else {
4698                                 $successful = true;
4699                         }
4700
4701                         if($successful) {
4702                                 if($this->protocol == 'imap') {
4703                                         $msg .= $mod_strings['LBL_TEST_SUCCESSFUL'];
4704                                         /*
4705                                         $testConnectString = '{'.$this->server_url.':'.$this->port.'/service='.$this->protocol.$service.'}';
4706                                         if (!is_resource($this->conn)) {
4707                                                 $this->conn = imap_open($connectString, $this->email_user, $this->email_password, CL_EXPUNGE);
4708                                         }
4709                                         $list = imap_getmailboxes($this->conn, $testConnectString, "*");
4710                                         if(isset($_REQUEST['personal']) && $_REQUEST['personal'] == 'true') {
4711                                                 $msg .= $mod_strings['LBL_TEST_SUCCESSFUL'];
4712                                         } elseif (is_array($list)) {
4713                                                 sort($list);
4714                                                 _ppd($boxes);
4715
4716                                                 $msg .= '<b>'.$mod_strings['LBL_FOUND_MAILBOXES'].'</b><p>';
4717                                                 foreach ($list as $key => $val) {
4718                                                         $mb = imap_utf7_decode(str_replace($testConnectString,'',$val->name));
4719                                                         $msg .= '<a onClick=\'setMailbox(\"'.$mb.'\"); window.close();\'>';
4720                                                         $msg .= $mb;
4721                                                         $msg .= '</a><br>';
4722                                                 }
4723                                         } else {
4724                                                 $msg .= $errors;
4725                                                 $msg .= '<p>'.$mod_strings['ERR_MAILBOX_FAIL'].imap_last_error().'</p>';
4726                                                 $msg .= '<p>'.$mod_strings['ERR_TEST_MAILBOX'].'</p>';
4727                                         }
4728                                         */
4729                                 } else {
4730                                         $msg .= $mod_strings['LBL_POP3_SUCCESS'];
4731                                 }
4732                         }
4733
4734                         imap_errors(); // collapse error stack
4735                         imap_close($this->conn);
4736                         return $msg;
4737                 } elseif(!is_resource($this->conn)) {
4738                         return "false";
4739                 } else {
4740                         return "true";
4741                 }
4742         }
4743
4744
4745
4746         function checkImap() {
4747                 global $mod_strings;
4748
4749                 if(!function_exists('imap_open')) {
4750                         echo '
4751                         <table cellpadding="0" cellspacing="0" width="100%" border="0" class="list view">
4752                                 <tr height="20">
4753                                         <td scope="col" width="25%"  colspan="2"><slot>
4754                                                 '.$mod_strings['LBL_WARN_IMAP_TITLE'].'
4755                                         </slot></td>
4756                                 </tr>
4757                                 <tr>
4758                                         <td scope="row" valign=TOP bgcolor="#fdfdfd" width="20%"><slot>
4759                                                 '.$mod_strings['LBL_WARN_IMAP'].'
4760                                         <td scope="row" valign=TOP class="oddListRowS1" bgcolor="#fdfdfd" width="80%"><slot>
4761                                                 <span class=error>'.$mod_strings['LBL_WARN_NO_IMAP'].'</span>
4762                                         </slot></td>
4763                                 </tr>
4764                         </table>
4765                         <br>';
4766                 }
4767         }
4768
4769         /**
4770          * retrieves an array of I-E beans based on the group_id
4771          * @param       string  $groupId        GUID of the group user or Individual
4772          * @return      array   $beans          array of beans
4773          * @return      boolean false if none returned
4774          */
4775         function retrieveByGroupId($groupId) {
4776                 $q = 'SELECT id FROM inbound_email WHERE group_id = \''.$groupId.'\' AND deleted = 0 AND status = \'Active\'';
4777                 $r = $this->db->query($q, true);
4778
4779                 $beans = array();
4780                 while($a = $this->db->fetchByAssoc($r)) {
4781                         $ie = new InboundEmail();
4782                         $ie->retrieve($a['id']);
4783                         $beans[$a['id']] = $ie;
4784                 }
4785                 return $beans;
4786         }
4787
4788         /**
4789          * Retrieves the current count of personal accounts for the user specified.
4790          *
4791          * @param unknown_type $user
4792          */
4793         function getUserPersonalAccountCount($user = null)
4794         {
4795             if($user == null)
4796                $user = $GLOBALS['current_user'];
4797
4798             $query = "SELECT count(*) as c FROM inbound_email WHERE deleted=0 AND is_personal='1' AND group_id='{$user->id}' AND status='Active'";
4799
4800             $rs = $this->db->query($query);
4801                 $row = $this->db->fetchByAssoc($rs);
4802         return $row['c'];
4803         }
4804
4805         /**
4806          * retrieves an array of I-E beans based on the group folder id
4807          * @param       string  $groupFolderId  GUID of the group folder
4808          * @return      array   $beans          array of beans
4809          * @return      boolean false if none returned
4810          */
4811         function retrieveByGroupFolderId($groupFolderId) {
4812                 $q = 'SELECT id FROM inbound_email WHERE groupfolder_id = \''.$groupFolderId.'\' AND deleted = 0 ';
4813                 $r = $this->db->query($q, true);
4814
4815                 $beans = array();
4816                 while($a = $this->db->fetchByAssoc($r)) {
4817                         $ie = new InboundEmail();
4818                         $ie->retrieve($a['id']);
4819                         $beans[] = $ie;
4820                 }
4821                 return $beans;
4822         }
4823
4824         /**
4825          * Retrieves an array of I-E beans that the user has team access to
4826          */
4827         function retrieveAllByGroupId($id, $includePersonal=true) {
4828                 global $current_user;
4829
4830                 $beans = ($includePersonal) ? $this->retrieveByGroupId($id) : array();
4831
4832                 $teamJoin = '';
4833
4834
4835
4836                 $q = "SELECT inbound_email.id FROM inbound_email {$teamJoin} WHERE is_personal = 0 AND groupfolder_id is null AND mailbox_type not like 'bounce' AND inbound_email.deleted = 0 AND status = 'Active' ";
4837
4838
4839
4840                 $r = $this->db->query($q, true);
4841
4842                 while($a = $this->db->fetchByAssoc($r)) {
4843                         $found = false;
4844                         foreach($beans as $bean) {
4845                                 if($bean->id == $a['id']) {
4846                                         $found = true;
4847                                 }
4848                         }
4849
4850                         if(!$found) {
4851                                 $ie = new InboundEmail();
4852                                 $ie->retrieve($a['id']);
4853                                 $beans[$a['id']] = $ie;
4854                         }
4855                 }
4856
4857                 return $beans;
4858         }
4859
4860         /**
4861          * Retrieves an array of I-E beans that the user has team access to including group
4862          */
4863         function retrieveAllByGroupIdWithGroupAccounts($id, $includePersonal=true) {
4864                 global $current_user;
4865
4866                 $beans = ($includePersonal) ? $this->retrieveByGroupId($id) : array();
4867
4868                 $teamJoin = '';
4869
4870
4871
4872
4873
4874
4875                 $q = "SELECT DISTINCT inbound_email.id FROM inbound_email {$teamJoin} WHERE is_personal = 0 AND mailbox_type not like 'bounce' AND status = 'Active' AND inbound_email.deleted = 0 ";
4876
4877                 $r = $this->db->query($q, true);
4878
4879                 while($a = $this->db->fetchByAssoc($r)) {
4880                         $found = false;
4881                         foreach($beans as $bean) {
4882                                 if($bean->id == $a['id']) {
4883                                         $found = true;
4884                                 }
4885                         }
4886
4887                         if(!$found) {
4888                                 $ie = new InboundEmail();
4889                                 $ie->retrieve($a['id']);
4890                                 $beans[$a['id']] = $ie;
4891                         }
4892                 }
4893
4894                 return $beans;
4895         }
4896
4897
4898         /**
4899          * returns the bean name - overrides SugarBean's
4900          */
4901         function get_summary_text() {
4902                 return $this->name;
4903         }
4904
4905         /**
4906          * Override's SugarBean's
4907          */
4908         function create_export_query($order_by, $where, $show_deleted = 0) {
4909                 return $this->create_new_list_query($order_by, $where, $show_deleted = 0);
4910         }
4911
4912         /**
4913          * Override's SugarBean's
4914          */
4915
4916         /**
4917          * Override's SugarBean's
4918          */
4919         function get_list_view_data(){
4920                 global $mod_strings;
4921                 global $app_list_strings;
4922                 $temp_array = $this->get_list_view_array();
4923                 $temp_array['MAILBOX_TYPE_NAME']= $app_list_strings['dom_mailbox_type'][$this->mailbox_type];
4924                 //cma, fix bug 21670.
4925         $temp_array['GLOBAL_PERSONAL_STRING']= ($this->is_personal ? $mod_strings['LBL_IS_PERSONAL'] : $mod_strings['LBL_IS_GROUP']);
4926         $temp_array['STATUS'] = ($this->status == 'Active') ? $mod_strings['LBL_STATUS_ACTIVE'] : $mod_strings['LBL_STATUS_INACTIVE'];
4927                 return $temp_array;
4928         }
4929
4930         /**
4931          * Override's SugarBean's
4932          */
4933         function fill_in_additional_list_fields() {
4934                 $this->fill_in_additional_detail_fields();
4935         }
4936
4937         /**
4938          * Override's SugarBean's
4939          */
4940         function fill_in_additional_detail_fields() {
4941                 if(!empty($this->service)) {
4942                         $exServ = explode('::', $this->service);
4943                         $this->tls              = $exServ[0];
4944                         if ( isset($exServ[1]) )
4945                             $this->ca           = $exServ[1];
4946                         if ( isset($exServ[2]) )
4947                             $this->ssl          = $exServ[2];
4948                         if ( isset($exServ[3]) )
4949                             $this->protocol     = $exServ[3];
4950                 }
4951         }
4952
4953
4954
4955
4956
4957
4958
4959
4960
4961
4962
4963
4964
4965
4966         ///////////////////////////////////////////////////////////////////////////
4967         ////    IN SUPPORT OF EMAIL 2.0
4968         /**
4969          * Checks for $user's autoImport setting and returns the current value
4970          * @param object $user User in focus, defaults to $current_user
4971          * @return bool
4972          */
4973         function isAutoImport($user=null) {
4974                 if(!empty($this->autoImport)) {
4975                         return $this->autoImport;
4976                 }
4977
4978                 global $current_user;
4979                 if(empty($user)) $user = $current_user;
4980
4981                 $emailSettings = $current_user->getPreference('emailSettings', 'Emails');
4982                 $emailSettings = is_string($emailSettings) ? unserialize($emailSettings) : $emailSettings;
4983
4984                 $this->autoImport = (isset($emailSettings['autoImport']) && !empty($emailSettings['autoImport'])) ? true : false;
4985                 return $this->autoImport;
4986         }
4987
4988         /**
4989          * Clears out cache files for a user
4990          */
4991         function cleanOutCache() {
4992                 $GLOBALS['log']->debug("INBOUNDEMAIL: at cleanOutCache()");
4993                 $this->deleteCache();
4994         }
4995
4996         /**
4997          * moves emails from folder to folder
4998          * @param string $fromIe I-E id
4999          * @param string $fromFolder IMAP path to folder in which the email lives
5000          * @param string $toIe I-E id
5001          * @param string $toFolder
5002          * @param string $uids UIDs of emails to move, either Sugar GUIDS or IMAP
5003          * UIDs
5004          */
5005         function copyEmails($fromIe, $fromFolder, $toIe, $toFolder, $uids) {
5006                 $this->moveEmails($fromIe, $fromFolder, $toIe, $toFolder, $uids, true);
5007         }
5008
5009         /**
5010          * moves emails from folder to folder
5011          * @param string $fromIe I-E id
5012          * @param string $fromFolder IMAP path to folder in which the email lives
5013          * @param string $toIe I-E id
5014          * @param string $toFolder
5015          * @param string $uids UIDs of emails to move, either Sugar GUIDS or IMAP
5016          * UIDs
5017          * @param bool $copy Default false
5018          * @return bool True on successful execution
5019          */
5020         function moveEmails($fromIe, $fromFolder, $toIe, $toFolder, $uids, $copy=false) {
5021                 global $app_strings;
5022                 global $current_user;
5023
5024
5025                 // same I-E server
5026                 if($fromIe == $toIe) {
5027                         $GLOBALS['log']->debug("********* SUGARFOLDER - moveEmails() moving email from I-E to I-E");
5028                         //$exDestFolder = explode("::", $toFolder);
5029                         //preserve $this->mailbox
5030                 if (isset($this->mailbox)) {
5031                     $oldMailbox = $this->mailbox;
5032                 }
5033
5034
5035                         $this->retrieve($fromIe);
5036                     $this->mailbox = $fromFolder;
5037                         $this->connectMailserver();
5038                         $exUids = explode('::;::', $uids);
5039                         $uids = implode(",", $exUids);
5040                         // imap_mail_move accepts comma-delimited lists of UIDs
5041                         if($copy) {
5042                                 if(imap_mail_copy($this->conn, $uids, $toFolder, CP_UID)) {
5043                                         $this->mailbox = $toFolder;
5044                                         $this->connectMailserver();
5045                                         $newOverviews = imap_fetch_overview($this->conn, $uids, FT_UID);
5046                                         $this->updateOverviewCacheFile($newOverviews, 'append');
5047                                     if (isset($oldMailbox)) {
5048                         $this->mailbox = $oldMailbox;
5049                     }
5050                                         return true;
5051                                 } else {
5052                                         $GLOBALS['log']->debug("INBOUNDEMAIL: could not imap_mail_copy() [ {$uids} ] to folder [ {$toFolder} ] from folder [ {$fromFolder} ]");
5053                                 }
5054                         } else {
5055                                 if(imap_mail_move($this->conn, $uids, $toFolder, CP_UID)) {
5056                                         $GLOBALS['log']->info("INBOUNDEMAIL: imap_mail_move() [ {$uids} ] to folder [ {$toFolder} ] from folder [ {$fromFolder} ]");
5057                                         imap_expunge($this->conn); // hard deletes moved messages
5058
5059                                         // update cache on fromFolder
5060                                         $newOverviews = $this->getOverviewsFromCacheFile($uids, $fromFolder, true);
5061                                         $this->deleteCachedMessages($uids, $fromFolder);
5062
5063                                         // update cache on toFolder
5064                                         $this->checkEmailOneMailbox($toFolder, true, true);
5065                                     if (isset($oldMailbox)) {
5066                         $this->mailbox = $oldMailbox;
5067                     }
5068
5069                                         return true;
5070                                 } else {
5071                                         $GLOBALS['log']->debug("INBOUNDEMAIL: could not imap_mail_move() [ {$uids} ] to folder [ {$toFolder} ] from folder [ {$fromFolder} ]");
5072                                 }
5073                         }
5074                 } elseif($toIe == 'folder' && $fromFolder == 'sugar::Emails') {
5075                         $GLOBALS['log']->debug("********* SUGARFOLDER - moveEmails() moving email from SugarFolder to SugarFolder");
5076                         // move from sugar folder to sugar folder
5077                         require_once("include/SugarFolders/SugarFolders.php");
5078                         $sugarFolder = new SugarFolder();
5079                         $exUids = explode($app_strings['LBL_EMAIL_DELIMITER'], $uids);
5080                         foreach($exUids as $id) {
5081                                 if($copy) {
5082                                         $sugarFolder->copyBean($fromIe, $toFolder, $id, "Emails");
5083                                 } else {
5084                                         $fromSugarFolder = new SugarFolder();
5085                                         $fromSugarFolder->retrieve($fromIe);
5086                                         $toSugarFolder = new SugarFolder();
5087                                         $toSugarFolder->retrieve($toFolder);
5088
5089                                         $email = new Email();
5090                                         $email->retrieve($id);
5091                                         $email->status = 'unread';
5092
5093                                         // when you move from My Emails to Group Folder, Assign To field for the Email should become null
5094                                         if ($fromSugarFolder->is_dynamic && $toSugarFolder->is_group) {
5095                                                 $email->assigned_user_id = "";
5096                                                 $email->save();
5097                                                 if (!$toSugarFolder->checkEmailExistForFolder($id)) {
5098                                                         $fromSugarFolder->deleteEmailFromAllFolder($id);
5099                                                         $toSugarFolder->addBean($email);
5100                                                 }
5101                                         } elseif ($fromSugarFolder->is_group && $toSugarFolder->is_dynamic) {
5102                                                 $fromSugarFolder->deleteEmailFromAllFolder($id);
5103                                                 $email->assigned_user_id = $current_user->id;
5104                                                 $email->save();
5105                                         } else {
5106                                                 // If you are moving something from personal folder then delete an entry from all folder
5107                                                 if (!$fromSugarFolder->is_dynamic && !$fromSugarFolder->is_group) {
5108                                                         $fromSugarFolder->deleteEmailFromAllFolder($id);
5109                                                 } // if
5110
5111                                                 if ($fromSugarFolder->is_dynamic && !$toSugarFolder->is_dynamic && !$toSugarFolder->is_group) {
5112                                                         $email->assigned_user_id = "";
5113                                                         $toSugarFolder->addBean($email);
5114                                                 } // if
5115                                                 if (!$toSugarFolder->checkEmailExistForFolder($id)) {
5116                                                         if (!$toSugarFolder->is_dynamic) {
5117                                                                 $fromSugarFolder->deleteEmailFromAllFolder($id);
5118                                                                 $toSugarFolder->addBean($email);
5119                                                         } else {
5120                                                                 $fromSugarFolder->deleteEmailFromAllFolder($id);
5121                                                                 $email->assigned_user_id = $current_user->id;
5122                                                         }
5123                                                 } else {
5124                                                         $sugarFolder->move($fromIe, $toFolder, $id);
5125                                                 } // else
5126                                                 $email->save();
5127                                         } // else
5128                                 }
5129                         }
5130
5131                         return true;
5132                 } elseif($toIe == 'folder') {
5133                         $GLOBALS['log']->debug("********* SUGARFOLDER - moveEmails() moving email from I-E to SugarFolder");
5134                         // move to Sugar folder
5135                         require_once("include/SugarFolders/SugarFolders.php");
5136                         $sugarFolder = new SugarFolder();
5137                         $sugarFolder->retrieve($toFolder);
5138                         //Show the import form if we don't have the required info
5139                         if (!isset($_REQUEST['delete'])) {
5140                                 $json = getJSONobj();
5141                                 if ($sugarFolder->is_group) {
5142                                         $_REQUEST['showTeam'] = false;
5143                                         $_REQUEST['showAssignTo'] = false;
5144                                 }
5145                     $ret = $this->email->et->getImportForm($_REQUEST, $this->email);
5146                     $ret['move'] = true;
5147                     $ret['srcFolder'] = $fromFolder;
5148                     $ret['srcIeId']   = $fromIe;
5149                     $ret['dstFolder'] = $toFolder;
5150                     $ret['dstIeId']   = $toIe;
5151                     $out = trim($json->encode($ret, false));
5152                     echo  $out;
5153                     return true;
5154                         }
5155
5156
5157                         // import to Sugar
5158                         $this->retrieve($fromIe);
5159                         $this->mailbox = $fromFolder;
5160                         $this->connectMailserver();
5161                         // If its a group folder the team should be of the folder team
5162                         if ($sugarFolder->is_group) {
5163                                 $_REQUEST['team_id'] = $sugarFolder->team_id;
5164                                 $_REQUEST['team_set_id'] = $sugarFolder->team_set_id;
5165                         } else {
5166                                 // TODO - set team_id, team_set for new UI
5167                         } // else
5168
5169                         $exUids = explode($app_strings['LBL_EMAIL_DELIMITER'], $uids);
5170
5171                         if(!empty($sugarFolder->id)) {
5172                                 $count = 1;
5173                                 $return = array();
5174                                 $json = getJSONobj();
5175                                 foreach($exUids as $k => $uid) {
5176                                         $msgNo = $uid;
5177                                         if ($this->isPop3Protocol()) {
5178                                                 $msgNo = $this->getCorrectMessageNoForPop3($uid);
5179                                         } else {
5180                                                 $msgNo = imap_msgno($this->conn, $uid);
5181                                         }
5182
5183                                         if(!empty($msgNo)) {
5184                                                 $importStatus = $this->importOneEmail($msgNo, $uid);
5185                                                 // add to folder
5186                                                 if($importStatus) {
5187                                                         $sugarFolder->addBean($this->email);
5188                                                         if(!$copy && isset($_REQUEST['delete']) && ($_REQUEST['delete'] == "true") && $importStatus) {
5189                                                                 $GLOBALS['log']->error("********* delete from mailserver [ {explode(",", $uids)} ]");
5190                                                                 // delete from mailserver
5191                                                                 $this->deleteMessageOnMailServer($uid);
5192                                                                 $this->deleteMessageFromCache($uid);
5193                                                         } // if
5194                                                 }
5195                                                 $return[] = $app_strings['LBL_EMAIL_MESSAGE_NO'] . " " . $count . ", " . $app_strings['LBL_STATUS'] . " " . ($importStatus ? $app_strings['LBL_EMAIL_IMPORT_SUCCESS'] : $app_strings['LBL_EMAIL_IMPORT_FAIL']);
5196                                                 $count++;
5197                                         } // if
5198                                 } // foreach
5199                                 echo $json->encode($return);
5200                                 return true;
5201                         } else {
5202                                 $GLOBALS['log']->error("********* SUGARFOLDER - failed to retrieve folder ID [ {$toFolder} ]");
5203                         }
5204                 } else {
5205                         $GLOBALS['log']->debug("********* SUGARFOLDER - moveEmails() called with no passing criteria");
5206                 }
5207
5208                 return false;
5209         }
5210
5211
5212         /**
5213          * Hard deletes an I-E account
5214          * @param string id GUID
5215          */
5216         function hardDelete($id) {
5217                 $q = "DELETE FROM inbound_email WHERE id = '{$id}'";
5218                 $r = $this->db->query($q, true);
5219         }
5220
5221         /**
5222          * Generate a unique filename for attachments based on the message id.  There are no maximum
5223          * specifications for the length of the message id, the only requirement is that it be globally unique.
5224          *
5225          * @param bool $nameOnly Whether or not the attachment count should be appended to the filename.
5226          * @return string The temp file name
5227          */
5228         function getTempFilename($nameOnly=false) {
5229
5230         $str = md5($this->compoundMessageId);
5231
5232                 if(!$nameOnly) {
5233                         $str = $str.$this->attachmentCount;
5234                         $this->attachmentCount++;
5235                 }
5236
5237                 return $str;
5238         }
5239
5240         /**
5241          * deletes and expunges emails on server
5242          * @param string $uid UID(s), comma delimited, of email(s) on server
5243          * @return bool true on success
5244          */
5245         function deleteMessageOnMailServer($uid) {
5246                 global $app_strings;
5247                 $this->connectMailserver();
5248
5249                 if(strpos($uid, $app_strings['LBL_EMAIL_DELIMITER']) !== false) {
5250                         $uids = explode($app_strings['LBL_EMAIL_DELIMITER'], $uid);
5251                 } else {
5252                         $uids[] = $uid;
5253                 }
5254
5255                 $return = true;
5256
5257                 if($this->protocol == 'imap') {
5258                         $trashFolder = $this->get_stored_options("trashFolder");
5259                         if (empty($trashFolder)) {
5260                                 $trashFolder = "INBOX.Trash";
5261                         }
5262                         foreach($uids as $uid) {
5263                         if($this->moveEmails($this->id, $this->mailbox, $this->id, $trashFolder, $uid))
5264                         $GLOBALS['log']->debug("INBOUNDEMAIL: MoveEmail to {$trashFolder} successful.");
5265                     else {
5266                         $GLOBALS['log']->debug("INBOUNDEMAIL: MoveEmail to {$trashFolder} FAILED - trying hard delete for message: $uid");
5267                         imap_delete($this->conn, $uid, FT_UID);
5268                                         $return = true;
5269                     }
5270                 }
5271                 }
5272         else {
5273             $msgnos = array();
5274                 foreach($uids as $uid) {
5275                 $msgnos[] = $this->getCorrectMessageNoForPop3($uid);
5276                         }
5277                         $msgnos = implode(',', $msgnos);
5278                         imap_delete($this->conn, $msgnos);
5279                         $return = true;
5280                 }
5281
5282                 if(!imap_expunge($this->conn)) {
5283             $GLOBALS['log']->debug("NOOP: could not expunge deleted email.");
5284             $return = false;
5285          }
5286          else
5287             $GLOBALS['log']->info("INBOUNDEMAIL: hard-deleted mail with MSgno's' [ {$msgnos} ]");
5288
5289                 return $return;
5290         }
5291
5292         /**
5293          * deletes and expunges emails on server
5294          * @param string $uid UID(s), comma delimited, of email(s) on server
5295          */
5296         function deleteMessageOnMailServerForPop3($uid) {
5297                 if(imap_delete($this->conn, $uid)) {
5298             if(!imap_expunge($this->conn)) {
5299                 $GLOBALS['log']->debug("NOOP: could not expunge deleted email.");
5300                 $return = false;
5301             } else {
5302                 $GLOBALS['log']->info("INBOUNDEMAIL: hard-deleted mail with MSgno's' [ {$uid} ]");
5303             }
5304                 }
5305         }
5306
5307         /**
5308          * Checks if this is a pop3 type of an account or not
5309          * @return boolean
5310          */
5311         function isPop3Protocol() {
5312                 return ($this->protocol == 'pop3');
5313         }
5314
5315         /**
5316          * Gets the UIDL from database for the corresponding msgno
5317          * @param int messageNo of a message
5318          * @return UIDL for the message
5319          */
5320         function getUIDLForMessage($msgNo) {
5321                 $query = "SELECT message_id FROM email_cache WHERE ie_id = '{$this->id}' AND msgno = '{$msgNo}'";
5322                 $r = $this->db->query($query);
5323                 $a = $this->db->fetchByAssoc($r);
5324                 return $a['message_id'];
5325         }
5326                 /**
5327          * Get the users default IE account id
5328          *
5329          * @param User $user
5330          * @return string
5331          */
5332         function getUsersDefaultOutboundServerId($user)
5333         {
5334                 $id =  $user->getPreference($this->keyForUsersDefaultIEAccount,'Emails',$user);
5335                 //If no preference has been set, grab the default system id.
5336                 if(empty($id))
5337                 {
5338                         $oe = new OutboundEmail();
5339                         $system = $oe->getSystemMailerSettings();
5340                         $id=empty($system->id) ? '' : $system->id;
5341                 }
5342
5343                 return $id;
5344         }
5345
5346         /**
5347          * Get the users default IE account id
5348          *
5349          * @param User $user
5350          */
5351         function setUsersDefaultOutboundServerId($user,$oe_id)
5352         {
5353                 $user->setPreference($this->keyForUsersDefaultIEAccount, $oe_id, '', 'Emails');
5354         }
5355         /**
5356          * Gets the UIDL from database for the corresponding msgno
5357          * @param int messageNo of a message
5358          * @return UIDL for the message
5359          */
5360         function getMsgnoForMessageID($messageid) {
5361                 $query = "SELECT msgno FROM email_cache WHERE ie_id = '{$this->id}' AND message_id = '{$messageid}'";
5362                 $r = $this->db->query($query);
5363                 $a = $this->db->fetchByAssoc($r);
5364                 return $a['message_id'];
5365         }
5366
5367         /**
5368          * fills InboundEmail->email with an email's details
5369          * @param int uid Unique ID of email
5370          * @param bool isMsgNo flag that passed ID is msgNo, default false
5371          * @param bool setRead Sets the 'seen' flag in cache
5372          * @param bool forceRefresh Skips cache file
5373          * @return string
5374          */
5375         function setEmailForDisplay($uid, $isMsgNo=false, $setRead=false, $forceRefresh=false) {
5376
5377                 if(empty($uid)) {
5378                         $GLOBALS['log']->debug("*** ERROR: INBOUNDEMAIL trying to setEmailForDisplay() with no UID");
5379                         return 'NOOP';
5380                 }
5381
5382                 global $sugar_config;
5383                 global $app_strings;
5384
5385                 // if its a pop3 then get the UIDL and see if this file name exist or not
5386                 if ($this->isPop3Protocol()) {
5387                         // get the UIDL from database;
5388                         $cachedUIDL = md5($uid);
5389                         $cache = "{$this->EmailCachePath}/{$this->id}/messages/{$this->mailbox}{$cachedUIDL}.php";
5390                 } else {
5391                         $cache = "{$this->EmailCachePath}/{$this->id}/messages/{$this->mailbox}{$uid}.php";
5392                 }
5393
5394                 if(file_exists($cache) && !$forceRefresh) {
5395                         $GLOBALS['log']->info("INBOUNDEMAIL: Using cache file for setEmailForDisplay()");
5396
5397                         include($cache); // profides $cacheFile
5398             /** @var $cacheFile array */
5399
5400             $metaOut = unserialize($cacheFile['out']);
5401                         $meta = $metaOut['meta']['email'];
5402                         $email = new Email();
5403
5404                         foreach($meta as $k => $v) {
5405                                 $email->$k = $v;
5406                         }
5407
5408                         $email->to_addrs = $meta['toaddrs'];
5409                         $email->date_sent = $meta['date_start'];
5410                         //_ppf($email,true);
5411
5412                         $this->email = $email;
5413                         $this->email->email2init();
5414                         $ret = 'cache';
5415                 } else {
5416                         $GLOBALS['log']->info("INBOUNDEMAIL: opening new connection for setEmailForDisplay()");
5417             if($this->isPop3Protocol()) {
5418                 $msgNo = $this->getCorrectMessageNoForPop3($uid);
5419             } else {
5420                                 if(empty($this->conn)) {
5421                                         $this->connectMailserver();
5422                                 }
5423                 $msgNo = ($isMsgNo) ? $uid : imap_msgno($this->conn, $uid);
5424             }
5425                         if(empty($this->conn)) {
5426                                 $status = $this->connectMailserver();
5427                                 if($status == "false") {
5428                                         $this->email = new Email();
5429                                         $this->email->name = $app_strings['LBL_EMAIL_ERROR_MAILSERVERCONNECTION'];
5430                                         $ret = 'error';
5431                                         return $ret;
5432                                 }
5433
5434                         }
5435
5436                         $this->importOneEmail($msgNo, $uid, true);
5437                         $this->email->id = '';
5438                         $this->email->new_with_id = false;
5439                         $ret = 'import';
5440                 }
5441
5442                 if($setRead) {
5443                         $this->setStatuses($uid, 'seen', 1);
5444                 }
5445
5446                 return $ret;
5447         }
5448
5449
5450         /**
5451          * Sets status for a particular attribute on the mailserver and the local cache file
5452          */
5453         function setStatuses($uid, $field, $value) {
5454                 global $sugar_config;
5455                 /** available status fields
5456                     [subject] => aaa
5457                     [from] => Some Name
5458                     [to] => Some Name
5459                     [date] => Mon, 22 Jan 2007 17:32:57 -0800
5460                     [message_id] =>
5461                     [size] => 718
5462                     [uid] => 191
5463                     [msgno] => 141
5464                     [recent] => 0
5465                     [flagged] => 0
5466                     [answered] => 0
5467                     [deleted] => 0
5468                     [seen] => 1
5469                     [draft] => 0
5470                 */
5471                 // local cache
5472                 $file = "{$this->mailbox}.imapFetchOverview.php";
5473                 $overviews = $this->getCacheValueForUIDs($this->mailbox, array($uid));
5474
5475                 if(!empty($overviews)) {
5476                         $updates = array();
5477
5478                         foreach($overviews['retArr'] as $k => $obj) {
5479                                 if($obj->imap_uid == $uid) {
5480                                         $obj->$field = $value;
5481                                         $updates[] = $obj;
5482                                 }
5483                         }
5484
5485                         if(!empty($updates)) {
5486                                 $this->setCacheValue($this->mailbox, array(), $updates);
5487                         }
5488                 }
5489         }
5490
5491         /**
5492          * Removes an email from the cache file, deletes the message from the cache too
5493          * @param string String of uids, comma delimited
5494          */
5495         function deleteMessageFromCache($uids) {
5496                 global $sugar_config;
5497                 global $app_strings;
5498
5499                 // delete message cache file and email_cache file
5500                 $exUids = explode($app_strings['LBL_EMAIL_DELIMITER'], $uids);
5501
5502                 foreach($exUids as $uid) {
5503                         // local cache
5504                         if ($this->isPop3Protocol()) {
5505                                 $q = "DELETE FROM email_cache WHERE message_id = '{$uid}' AND ie_id = '{$this->id}'";
5506                         } else {
5507                                 $q = "DELETE FROM email_cache WHERE imap_uid = {$uid} AND ie_id = '{$this->id}'";
5508                         }
5509                         $r = $this->db->query($q);
5510                         if ($this->isPop3Protocol()) {
5511                                 $uid = md5($uid);
5512                         } // if
5513                         $msgCacheFile = "{$this->EmailCachePath}/{$this->id}/messages/{$this->mailbox}{$uid}.php";
5514                         if(file_exists($msgCacheFile)) {
5515                                 if(!unlink($msgCacheFile)) {
5516                                         $GLOBALS['log']->error("***ERROR: InboundEmail could not delete the cache file [ {$msgCacheFile} ]");
5517                                 }
5518                         }
5519                 }
5520         }
5521
5522
5523         /**
5524          * Shows one email.
5525          * @param int uid UID of email to display
5526          * @param string mbox Mailbox to look in for the message
5527          * @param bool isMsgNo Flag to assume $uid is a MessageNo, not UniqueID, default false
5528          */
5529         function displayOneEmail($uid, $mbox, $isMsgNo=false) {
5530                 require_once("include/JSON.php");
5531
5532                 global $timedate;
5533                 global $app_strings;
5534                 global $app_list_strings;
5535                 global $sugar_smarty;
5536                 global $theme;
5537                 global $current_user;
5538                 global $sugar_config;
5539
5540                 $fetchedAttributes = array(
5541                         'name',
5542                         'from_name',
5543                         'from_addr',
5544                         'date_start',
5545                         'time_start',
5546                         'message_id',
5547                 );
5548
5549                 $souEmail = array();
5550                 foreach($fetchedAttributes as $k) {
5551                         if ($k == 'date_start') {
5552                                 $this->email->$k . " " . $this->email->time_start;
5553                                 $souEmail[$k] = $this->email->$k . " " . $this->email->time_start;
5554                         } elseif ($k == 'time_start') {
5555                                 $souEmail[$k] = "";
5556                         } else {
5557                                 $souEmail[$k] = trim($this->email->$k);
5558                         }
5559                 }
5560
5561                 // if a MsgNo is passed in, convert to UID
5562                 if($isMsgNo)
5563                         $uid = imap_uid($this->conn, $uid);
5564
5565                 // meta object to allow quick retrieval for replies
5566                 $meta = array();
5567                 $meta['type'] = $this->email->type;
5568                 $meta['uid'] = $uid;
5569                 $meta['ieId'] = $this->id;
5570                 $meta['email'] = $souEmail;
5571                 $meta['mbox'] = $this->mailbox;
5572                 $ccs = '';
5573                 // imap vs pop3
5574
5575                 // self mapping
5576                 $exMbox = explode("::", $mbox);
5577
5578                 // CC section
5579                 $cc = '';
5580                 if(!empty($this->email->cc_addrs)) {
5581                         //$ccs = $this->collapseLongMailingList($this->email->cc_addrs);
5582                         $ccs = to_html($this->email->cc_addrs_names);
5583                         $cc =<<<eoq
5584                                 <tr>
5585                                         <td NOWRAP valign="top" class="displayEmailLabel">
5586                                                 {$app_strings['LBL_EMAIL_CC']}:
5587                                         </td>
5588                                         <td class="displayEmailValue">
5589                                                 {$ccs}
5590                                         </td>
5591                                 </tr>
5592 eoq;
5593                 }
5594                 $meta['cc'] = $cc;
5595                 $meta['email']['cc_addrs'] = $ccs;
5596                 // attachments
5597                 $attachments = '';
5598                 if ($mbox == "sugar::Emails") {
5599
5600                         $q = "SELECT id, filename, file_mime_type FROM notes WHERE parent_id = '{$uid}' AND deleted = 0";
5601                         $r = $this->db->query($q);
5602                         $i = 0;
5603                         while($a = $this->db->fetchByAssoc($r)) {
5604                                 $url = "index.php?entryPoint=download&type=notes&id={$a['id']}";
5605                                 $lbl = ($i == 0) ? $app_strings['LBL_EMAIL_ATTACHMENTS'].":" : '';
5606                                 $i++;
5607                                 $attachments .=<<<EOQ
5608                                 <tr>
5609                                                         <td NOWRAP valign="top" class="displayEmailLabel">
5610                                                                 {$lbl}
5611                                                         </td>
5612                                                         <td NOWRAP valign="top" colspan="2" class="displayEmailValue">
5613                                                                 <a href="{$url}">{$a['filename']}</a>
5614                                                         </td>
5615                                                 </tr>
5616 EOQ;
5617                                 $this->email->cid2Link($a['id'], $a['file_mime_type']);
5618                     } // while
5619
5620
5621                 } else {
5622
5623                         if($this->attachmentCount > 0) {
5624                                 $theCount = $this->attachmentCount;
5625
5626                                 for($i=0; $i<$theCount; $i++) {
5627                                         $lbl = ($i == 0) ? $app_strings['LBL_EMAIL_ATTACHMENTS'].":" : '';
5628                                         $name = $this->getTempFilename(true).$i;
5629                                         $tempName = urlencode($this->tempAttachment[$name]);
5630
5631                                         $url = "index.php?entryPoint=download&type=temp&isTempFile=true&ieId={$this->id}&tempName={$tempName}&id={$name}";
5632
5633                                         $attachments .=<<<eoq
5634                                                 <tr>
5635                                                         <td NOWRAP valign="top" class="displayEmailLabel">
5636                                                                 {$lbl}
5637                                                         </td>
5638                                                         <td NOWRAP valign="top" colspan="2" class="displayEmailValue">
5639                                                                 <a href="{$url}">{$this->tempAttachment[$name]}</a>
5640                                                         </td>
5641                                                 </tr>
5642 eoq;
5643                                 } // for
5644                         } // if
5645                 } // else
5646                 $meta['email']['attachments'] = $attachments;
5647
5648                 // toasddrs
5649                 $meta['email']['toaddrs'] = $this->collapseLongMailingList($this->email->to_addrs);
5650                 $meta['email']['cc_addrs'] = $ccs;
5651
5652                 // body
5653                 $description = (empty($this->email->description_html)) ? nl2br($this->email->description) : $this->email->description_html;
5654                 $meta['email']['description'] = $description;
5655
5656                 // meta-metadata
5657                 $meta['is_sugarEmail'] = ($exMbox[0] == 'sugar') ? true : false;
5658
5659                 if(!$meta['is_sugarEmail']) {
5660                         if($this->isAutoImport) {
5661                                 $meta['is_sugarEmail'] = true;
5662                         }
5663                 } else {
5664                         if( $this->email->status != 'sent' ){
5665                                 // mark SugarEmail read
5666                                 $q = "UPDATE emails SET status = 'read' WHERE id = '{$uid}'";
5667                                 $r = $this->db->query($q);
5668                         }
5669                 }
5670
5671                 $return = array();
5672         $meta['email']['name'] = to_html($this->email->name);
5673         $meta['email']['from_addr'] = ( !empty($this->email->from_addr_name) ) ? to_html($this->email->from_addr_name) : to_html($this->email->from_addr);
5674         $meta['email']['toaddrs'] = ( !empty($this->email->to_addrs_names) ) ? to_html($this->email->to_addrs_names) : to_html($this->email->to_addrs);
5675         $meta['email']['cc_addrs'] = to_html($this->email->cc_addrs_names);
5676         $meta['email']['reply_to_addr'] = to_html($this->email->reply_to_addr);
5677                 $return['meta'] = $meta;
5678
5679                 return $return;
5680         }
5681
5682         /**
5683          * Takes a long list of email addresses from a To or CC field and shows the first 3, the rest hidden
5684          * @param string emails
5685          * @return string
5686          */
5687         function collapseLongMailingList($emails) {
5688                 global $app_strings;
5689
5690                 $ex = explode(",", $emails);
5691                 $i = 0;
5692                 $j = 0;
5693
5694                 if(count($ex) > 3) {
5695                         $emails = "";
5696                         $emailsHidden = "";
5697
5698                         foreach($ex as $email) {
5699                                 if($i < 2) {
5700                                         if(!empty($emails)) {
5701                                                 $emails .= ", ";
5702                                         }
5703                                         $emails .= trim($email);
5704                                 } else {
5705                                         if(!empty($emailsHidden)) {
5706                                                 $emailsHidden .= ", ";
5707                                         }
5708                                         $emailsHidden .= trim($email);
5709                                         $j++;
5710                                 }
5711                                 $i++;
5712                         }
5713
5714                         if(!empty($emailsHidden)) {
5715                                 $email2 = $emails;
5716                                 $emails = "<span onclick='javascript:SUGAR.email2.detailView.showFullEmailList(this);' style='cursor:pointer;'>{$emails} [...{$j} {$app_strings['LBL_MORE']}]</span>";
5717                                 $emailsHidden = "<span onclick='javascript:SUGAR.email2.detailView.showCroppedEmailList(this)' style='cursor:pointer; display:none;'>{$email2}, {$emailsHidden} [ {$app_strings['LBL_LESS']} ]</span>";
5718                         }
5719
5720                         $emails .= $emailsHidden;
5721                 }
5722
5723                 return $emails;
5724         }
5725
5726
5727         /**
5728          * Sorts IMAP's imap_fetch_overview() results
5729          * @param array $arr Array of standard objects
5730          * @param string $sort Column to sort by
5731          * @param string direction Direction to sort by (asc/desc)
5732          * @return array Sorted array of obj.
5733          */
5734         function sortFetchedOverview($arr, $sort=4, $direction='DESC', $forceSeen=false) {
5735                 global $current_user;
5736
5737                 $sortPrefs = $current_user->getPreference('folderSortOrder', 'Emails');
5738                 if(!empty($sortPrefs))
5739                         $listPrefs = $sortPrefs;
5740                 else
5741                         $listPrefs = array();
5742
5743                 if(isset($listPrefs[$this->id][$this->mailbox])) {
5744                         $currentNode = $listPrefs[$this->id][$this->mailbox];
5745                 }
5746
5747                 if(isset($currentNode['current']) && !empty($currentNode['current'])) {
5748                         $sort = $currentNode['current']['sort'];
5749                         $direction = $currentNode['current']['direction'];
5750                 }
5751
5752                 // sort defaults
5753                 if(empty($sort)) {
5754                         $sort = $this->defaultSort;//4;
5755                         $direction = $this->defaultDirection; //'DESC';
5756                 } elseif(!is_numeric($sort)) {
5757                         // handle bad sort index
5758                         $sort = $this->defaultSort;
5759                 } else {
5760                         // translate numeric index to human readable
5761             $sort = $this->hrSort[$sort];
5762                 }
5763                 if(empty($direction)) {
5764                         $direction = 'DESC';
5765                 }
5766
5767
5768
5769                 $retArr = array();
5770                 $sorts = array();
5771
5772                 foreach($arr as $k => $overview) {
5773                         $sorts['flagged'][$k] = $overview->flagged;
5774                         $sorts['status'][$k] = $overview->answered;
5775                         $sorts['from'][$k] = str_replace('"', "", $this->handleMimeHeaderDecode($overview->from));
5776                         $sorts['subj'][$k] = $this->handleMimeHeaderDecode(quoted_printable_decode($overview->subject));
5777                         $sorts['date'][$k] = $overview->date;
5778                 }
5779
5780                 // sort by column
5781                 natcasesort($sorts[$sort]);
5782                 //_ppd($sorts[$sort]);
5783                 // direction
5784                 if(strtolower($direction) == 'desc') {
5785                         $revSorts = array();
5786                         $keys = array_reverse(array_keys($sorts[$sort]));
5787 //                      _pp("count keys in DESC: ".count($keys));
5788 //                      _pp("count elements in sort[sort]: ".count($sorts[$sort]));
5789
5790                         for($i=0; $i<count($keys); $i++) {
5791                                 $v = $keys[$i];
5792                                 $revSorts[$v] = $sorts[$sort][$v];
5793                         }
5794
5795                         //_pp("count post-sort: ".count($revSorts));
5796                         $sorts[$sort] = $revSorts;
5797                 }
5798         $timedate = TimeDate::getInstance();
5799                 foreach($sorts[$sort] as $k2 => $overview2) {
5800                     $arr[$k2]->date = $timedate->fromString($arr[$k2]->date)->asDb();
5801                         $retArr[] = $arr[$k2];
5802                 }
5803                 //_pp("final count: ".count($retArr));
5804
5805                 $finalReturn = array();
5806                 $finalReturn['retArr'] = $retArr;
5807                 $finalReturn['sortBy'] = $sort;
5808                 $finalReturn['direction'] = $direction;
5809                 return $finalReturn;
5810         }
5811
5812
5813         function setReadFlagOnFolderCache($mbox, $uid) {
5814                 global $sugar_config;
5815
5816                 $this->mailbox = $mbox;
5817
5818                 // cache
5819                 if($this->validCacheExists($this->mailbox)) {
5820                         $ret = $this->getCacheValue($this->mailbox);
5821
5822                         $updates = array();
5823
5824                         foreach($ret as $k => $v) {
5825                                 if($v->imap_uid == $uid) {
5826                                         $v->seen = 1;
5827                                         $updates[] = $v;
5828                                         break;
5829                                 }
5830                         }
5831
5832                         $this->setCacheValue($this->mailbox, array(), $updates);
5833                 }
5834         }
5835
5836         /**
5837          * Returns a list of emails in a mailbox.
5838          * @param string mbox Name of mailbox using dot notation paths to display
5839          * @param string $forceRefresh Flag to use cache or not
5840          */
5841         function displayFolderContents($mbox, $forceRefresh='false', $page) {
5842                 global $current_user;
5843
5844                 $delimiter = $this->get_stored_options('folderDelimiter');
5845                 if ($delimiter) {
5846                         $mbox = str_replace('.', $delimiter, $mbox);
5847                 }
5848
5849                 $this->mailbox = $mbox;
5850
5851                 // jchi #9424, get sort and direction from user preference
5852                 $sort = 'date';
5853                 $direction = 'desc';
5854                 $sortSerial = $current_user->getPreference('folderSortOrder', 'Emails');
5855                 if(!empty($sortSerial) && !empty($_REQUEST['ieId']) && !empty($_REQUEST['mbox'])) {
5856                         $sortArray = unserialize($sortSerial);
5857                         $sort = $sortArray[$_REQUEST['ieId']][$_REQUEST['mbox']]['current']['sort'];
5858                         $direction = $sortArray[$_REQUEST['ieId']][$_REQUEST['mbox']]['current']['direction'];
5859                 }
5860                 //end
5861
5862                 // save sort order
5863                 if(!empty($_REQUEST['sort']) && !empty($_REQUEST['dir'])) {
5864                         $this->email->et->saveListViewSortOrder($_REQUEST['ieId'], $_REQUEST['mbox'], $_REQUEST['sort'], $_REQUEST['dir']);
5865                         $sort = $_REQUEST['sort'];
5866                         $direction = $_REQUEST['dir'];
5867                 } else {
5868                         $_REQUEST['sort'] = '';
5869                         $_REQUEST['dir'] = '';
5870                 }
5871
5872                 // cache
5873                 $ret = array();
5874                 $cacheUsed = false;
5875                 if($forceRefresh == 'false' && $this->validCacheExists($this->mailbox)) {
5876                         $emailSettings = $current_user->getPreference('emailSettings', 'Emails');
5877
5878                         // cn: default to a low number until user specifies otherwise
5879                         if(empty($emailSettings['showNumInList'])) {
5880                                 $emailSettings['showNumInList'] = 20;
5881                         }
5882
5883                         $ret = $this->getCacheValue($this->mailbox, $emailSettings['showNumInList'], $page, $sort, $direction);
5884                         $cacheUsed = true;
5885                 }
5886
5887                 $out = $this->displayFetchedSortedListXML($ret, $mbox);
5888
5889                 $metadata = array();
5890                 $metadata['mbox'] = $mbox;
5891                 $metadata['ieId'] = $this->id;
5892                 $metadata['name'] = $this->name;
5893                 $metadata['fromCache'] = $cacheUsed ? 1 : 0;
5894                 $metadata['out'] = $out;
5895
5896                 return $metadata;
5897         }
5898
5899         /**
5900          * For a group email account, create subscriptions for all users associated with the
5901          * team assigned to the account.
5902          *
5903          */
5904         function createUserSubscriptionsForGroupAccount()
5905         {
5906             $team = new Team();
5907             $team->retrieve($this->team_id);
5908             $usersList = $team->get_team_members(true);
5909             foreach($usersList as $userObject)
5910             {
5911                 $previousSubscriptions = unserialize(base64_decode($userObject->getPreference('showFolders', 'Emails',$userObject)));
5912                 if($previousSubscriptions === FALSE)
5913                     $previousSubscriptions = array();
5914
5915                 $previousSubscriptions[] = $this->id;
5916
5917                 $encodedSubs = base64_encode(serialize($previousSubscriptions));
5918                 $userObject->setPreference('showFolders',$encodedSubs , '', 'Emails');
5919                 $userObject->savePreferencesToDB();
5920             }
5921     }
5922         /**
5923     * Create a sugar folder for this inbound email account
5924     * if the Enable Auto Import option is selected
5925     *
5926     * @return String Id of the sugar folder created.
5927     */
5928         function createAutoImportSugarFolder()
5929         {
5930             global $current_user;
5931             $guid = create_guid();
5932             $GLOBALS['log']->debug("Creating Sugar Folder for IE with id $guid");
5933             $folder = new SugarFolder();
5934             $folder->id = $guid;
5935             $folder->new_with_id = TRUE;
5936             $folder->name = $this->name;
5937             $folder->has_child = 0;
5938             $folder->is_group = 1;
5939             $folder->assign_to_id = $current_user->id;
5940             $folder->parent_folder = "";
5941
5942
5943             //If this inbound email is marked as inactive, don't add subscriptions.
5944             $addSubscriptions = ($this->status == 'Inactive' || $this->mailbox_type == 'bounce') ? FALSE : TRUE;
5945             $folder->save($addSubscriptions);
5946
5947             return $guid;
5948         }
5949
5950         function validCacheExists($mbox) {
5951                 $q = "SELECT count(*) c FROM email_cache WHERE ie_id = '{$this->id}'";
5952                 $r = $this->db->query($q);
5953                 $a = $this->db->fetchByAssoc($r);
5954                 $count = $a['c'];
5955
5956                 if($count > 0) {
5957                         return true;
5958                 }
5959
5960                 return false;
5961         }
5962
5963
5964
5965
5966         function displayFetchedSortedListXML($ret, $mbox) {
5967
5968                 global $timedate;
5969                 global $current_user;
5970                 global $sugar_config;
5971
5972                 if(empty($ret['retArr'])) {
5973                     return array();
5974                 }
5975
5976                 $tPref = $current_user->getUserDateTimePreferences();
5977
5978                 $return = array();
5979
5980                 foreach($ret['retArr'] as $msg) {
5981
5982                         $flagged        = ($msg->flagged == 0) ? "" : $this->iconFlagged;
5983                         $status         = ($msg->deleted) ? $this->iconDeleted : "";
5984                         $status         = ($msg->draft == 0) ? $status : $this->iconDraft;
5985                         $status         = ($msg->answered == 0) ? $status : $this->iconAnswered;
5986                         $from           = $this->handleMimeHeaderDecode($msg->from);
5987                         $subject        = $this->handleMimeHeaderDecode($msg->subject);
5988                         //$date         = date($tPref['date']." ".$tPref['time'], $msg->date);
5989                         $date           = $timedate->to_display_date_time($this->db->fromConvert($msg->date, 'datetime'));
5990                         //$date         = date($tPref['date'], $this->getUnixHeaderDate($msg->date));
5991
5992                         $temp = array();
5993                         $temp['flagged'] = $flagged;
5994                         $temp['status'] = $status;
5995                         $temp['from']   = to_html($from);
5996                         $temp['subject'] = $subject;
5997                         $temp['date']   = $date;
5998                         $temp['uid'] = $msg->uid; // either from an imap_search() or massaged cache value
5999                         $temp['mbox'] = $this->mailbox;
6000                         $temp['ieId'] = $this->id;
6001                         $temp['site_url'] = $sugar_config['site_url'];
6002                         $temp['seen'] = $msg->seen;
6003                         $temp['type'] = (isset($msg->type)) ? $msg->type: 'remote';
6004                         $temp['to_addrs'] = to_html($msg->to);
6005                         $temp['hasAttach'] = '0';
6006
6007                         $return[] = $temp;
6008                 }
6009
6010                 return $return;
6011         }
6012
6013
6014
6015         /**
6016          * retrieves the mailboxes for a given account in the following format
6017          * Array(
6018             [INBOX] => Array
6019                 (
6020                     [Bugs] => Bugs
6021                     [Builder] => Builder
6022                     [DEBUG] => Array
6023                         (
6024                             [out] => out
6025                             [test] => test
6026                         )
6027                 )
6028          * @param bool $justRaw Default false
6029          * @return array
6030          */
6031         function getMailboxes($justRaw=false) {
6032                 if($justRaw == true) {
6033                         return $this->mailboxarray;
6034                 } // if
6035
6036                 return $this->generateMultiDimArrayFromFlatArray($this->mailboxarray, $this->retrieveDelimiter());
6037                 /*
6038                 $serviceString = $this->getConnectString('', '', false);
6039
6040                 if(strpos($serviceString, 'pop3')) {
6041                         $obj = new temp();
6042                         $obj->name = $serviceString."INBOX";
6043                         $boxes = array("INBOX" => $obj);
6044                 } else {
6045                         $boxes = imap_getmailboxes($this->conn, $serviceString, "*");
6046                 }
6047                 $raw = array();
6048                 //_ppd($boxes);
6049                 $delimiter = '.';
6050                 // clean MBOX path names
6051                 foreach($boxes as $k => $mbox) {
6052                         $raw[] = str_replace($serviceString, "", $mbox->name);
6053                         if ($mbox->delimiter) {
6054                                 $delimiter = $mbox->delimiter;
6055                         }
6056                 }
6057                 $storedOptions = unserialize(base64_decode($this->stored_options));
6058                 $storedOptions['folderDelimiter'] = $delimiter;
6059                 $this->stored_options = base64_encode(serialize($storedOptions));
6060         $this->save();
6061
6062                 sort($raw);
6063                 //_ppd($raw);
6064
6065                 // used by $this->search()
6066                 if($justRaw == true) {
6067                         return $raw;
6068                 }
6069
6070
6071                 // generate a multi-dimensional array to iterate through
6072                 $ret = array();
6073                 foreach($raw as $mbox) {
6074                         $ret = $this->sortMailboxes($mbox, $ret, $delimiter);
6075                 }
6076                 //_ppd($ret);
6077                 return $ret;
6078                 */
6079         }
6080
6081         function getMailBoxesForGroupAccount() {
6082                 $mailboxes = $this->generateMultiDimArrayFromFlatArray(explode(",", $this->mailbox), $this->retrieveDelimiter());
6083                 $mailboxesArray = $this->generateFlatArrayFromMultiDimArray($mailboxes, $this->retrieveDelimiter());
6084                 $mailboxesArray = $this->filterMailBoxFromRaw(explode(",", $this->mailbox), $mailboxesArray);
6085                 $this->saveMailBoxFolders($mailboxesArray);
6086                 /*
6087                 if ($this->mailbox != $this->$email_user) {
6088                         $mailboxes = $this->sortMailboxes($this->mailbox, $this->retrieveDelimiter());
6089                         $mailboxesArray = $this->generateFlatArrayFromMultiDimArray($mailboxes, $this->retrieveDelimiter());
6090                         $this->saveMailBoxFolders($mailboxesArray);
6091                         // save mailbox value of an inbound email account to email user
6092                         $this->saveMailBoxValueOfInboundEmail();
6093                 } else {
6094                         $mailboxes = $this->getMailboxes();
6095                 }
6096                 */
6097                 return $mailboxes;
6098         } // fn
6099
6100         function saveMailBoxFolders($value) {
6101                 if (is_array($value)) {
6102                         $value = implode(",", $value);
6103                 }
6104                 $this->mailboxarray = explode(",", $value);
6105                 $value = $this->db->quoted($value);
6106                 $query = "update inbound_email set mailbox = $value where id ='{$this->id}'";
6107                 $this->db->query($query);
6108         }
6109
6110         function insertMailBoxFolders($value) {
6111                 $query = "select value from config where category='InboundEmail' and name='{$this->id}'";
6112                 $r = $this->db->query($query);
6113                 $a = $this->db->fetchByAssoc($r);
6114                 if (empty($a['value'])) {
6115                         if (is_array($value)) {
6116                                 $value = implode(",", $value);
6117                         }
6118                         $this->mailboxarray = explode(",", $value);
6119                         $value = $this->db->quoted($value);
6120
6121                         $query = "INSERT INTO config VALUES('InboundEmail', '{$this->id}', $value)";
6122                         $this->db->query($query);
6123                 } // if
6124         }
6125
6126         function saveMailBoxValueOfInboundEmail() {
6127                 $query = "update Inbound_email set mailbox = '{$this->email_user}'";
6128                 $this->db->query($query);
6129         }
6130
6131         function retrieveMailBoxFolders() {
6132                 $this->mailboxarray = explode(",", $this->mailbox);
6133                 /*
6134                 $query = "select value from config where category='InboundEmail' and name='{$this->id}'";
6135                 $r = $this->db->query($query);
6136                 $a = $this->db->fetchByAssoc($r);
6137                 $this->mailboxarray = explode(",", $a['value']);
6138                 */
6139         } // fn
6140
6141
6142         function retrieveDelimiter() {
6143                 $delimiter = $this->get_stored_options('folderDelimiter');
6144         if (!$delimiter) {
6145                 $delimiter = '.';
6146         }
6147                 return $delimiter;
6148         } // fn
6149
6150         function generateFlatArrayFromMultiDimArray($arraymbox, $delimiter) {
6151                 $ret = array();
6152                 foreach($arraymbox as $key => $value) {
6153                         $this->generateArrayData($key, $value, $ret, $delimiter);
6154                 } // foreach
6155                 return $ret;
6156
6157         } // fn
6158
6159         function generateMultiDimArrayFromFlatArray($raw, $delimiter) {
6160                 // generate a multi-dimensional array to iterate through
6161                 $ret = array();
6162                 foreach($raw as $mbox) {
6163                         $ret = $this->sortMailboxes($mbox, $ret, $delimiter);
6164                 }
6165                 return $ret;
6166
6167         } // fn
6168
6169         function generateArrayData($key, $arraymbox, &$ret, $delimiter) {
6170                 $ret [] = $key;
6171                 if (is_array($arraymbox)) {
6172                         foreach($arraymbox as $mboxKey => $value) {
6173                                 $newKey = $key . $delimiter . $mboxKey;
6174                                 $this->generateArrayData($newKey, $value, $ret, $delimiter);
6175                         } // foreach
6176                 } // if
6177         }
6178
6179         /**
6180          * sorts the folders in a mailbox in a multi-dimensional array
6181          * @param string $MBOX
6182          * @param array $ret
6183          * @return array
6184          */
6185         function sortMailboxes($mbox, $ret, $delimeter = ".") {
6186                 if(strpos($mbox, $delimeter)) {
6187                         $node = substr($mbox, 0, strpos($mbox, $delimeter));
6188                         $nodeAfter = substr($mbox, strpos($mbox, $node) + strlen($node) + 1, strlen($mbox));
6189
6190                         if(!isset($ret[$node])) {
6191                                 $ret[$node] = array();
6192                         } elseif(isset($ret[$node]) && !is_array($ret[$node])) {
6193                                 $ret[$node] = array();
6194                         }
6195                         $ret[$node] = $this->sortMailboxes($nodeAfter, $ret[$node], $delimeter);
6196                 } else {
6197                         $ret[$mbox] = $mbox;
6198                 }
6199
6200                 return $ret;
6201         }
6202
6203         /**
6204          * parses Sugar's storage method for imap server service strings
6205          * @return string
6206          */
6207         function getServiceString() {
6208                 $service = '';
6209                 $exServ = explode('::', $this->service);
6210
6211                 foreach($exServ as $v) {
6212                         if(!empty($v) && ($v != 'imap' && $v !='pop3')) {
6213                                 $service .= '/'.$v;
6214                         }
6215                 }
6216                 return $service;
6217         }
6218
6219 } // end class definition
6220
6221
6222 /**
6223  * Simple class to mirror the passed object from an imap_fetch_overview() call
6224  */
6225 class Overview {
6226         var $subject;
6227         var $from;
6228         var $fromaddr;
6229         var $to;
6230         var $toaddr;
6231         var $date;
6232         var $message_id;
6233         var $size;
6234         var $uid;
6235         var $msgno;
6236         var $recent;
6237         var $flagged;
6238         var $answered;
6239         var $deleted;
6240         var $seen;
6241         var $draft;
6242         var $indices; /* = array(
6243
6244                         array(
6245                                 'name'                  => 'mail_date',
6246                                 'type'                  => 'index',
6247                                 'fields'                => array(
6248                                         'mbox',
6249                                         'senddate',
6250                                 )
6251                         ),
6252                         array(
6253                                 'name'                  => 'mail_from',
6254                                 'type'                  => 'index',
6255                                 'fields'                => array(
6256                                         'mbox',
6257                                         'fromaddr',
6258                                 )
6259                         ),
6260                         array(
6261                                 'name'                  => 'mail_subj',
6262                                 'type'                  => 'index',
6263                                 'fields'                => array(
6264                                         'mbox',
6265                                         'subject',
6266                                 )
6267                         ),
6268                 );
6269         */
6270         var $fieldDefs;/* = array(
6271                         'mbox' => array(
6272                                 'name'          => 'mbox',
6273                                 'type'          => 'varchar',
6274                                 'len'           => 60,
6275                                 'required'      => true,
6276                         ),
6277                         'subject' => array(
6278                                 'name'          => 'subject',
6279                                 'type'          => 'varchar',
6280                                 'len'           => 100,
6281                                 'required'      => false,
6282                         ),
6283                         'fromaddr' => array(
6284                                 'name'          => 'fromaddr',
6285                                 'type'          => 'varchar',
6286                                 'len'           => 100,
6287                                 'required'      => true,
6288                         ),
6289                         'toaddr' => array(
6290                                 'name'          => 'toaddr',
6291                                 'type'          => 'varchar',
6292                                 'len'           => 100,
6293                                 'required'      => true,
6294                         ),
6295                         'senddate' => array(
6296                                 'name'          => 'senddate',
6297                                 'type'          => 'datetime',
6298                                 'required'      => true,
6299                         ),
6300                         'message_id' => array(
6301                                 'name'          => 'message_id',
6302                                 'type'          => 'varchar',
6303                                 'len'           => 255,
6304                                 'required'      => false,
6305                         ),
6306                         'mailsize' => array(
6307                                 'name'          => 'mailsize',
6308                                 'type'          => 'uint',
6309                                 'len'           => 16,
6310                                 'required'      => true,
6311                         ),
6312                         'uid' => array(
6313                                 'name'          => 'uid',
6314                                 'type'          => 'uint',
6315                                 'len'           => 32,
6316                                 'required'      => true,
6317                         ),
6318                         'msgno' => array(
6319                                 'name'          => 'msgno',
6320                                 'type'          => 'uint',
6321                                 'len'           => 32,
6322                                 'required'      => false,
6323                         ),
6324                         'recent' => array(
6325                                 'name'          => 'recent',
6326                                 'type'          => 'tinyint',
6327                                 'len'           => 1,
6328                                 'required'      => true,
6329                         ),
6330                         'flagged' => array(
6331                                 'name'          => 'flagged',
6332                                 'type'          => 'tinyint',
6333                                 'len'           => 1,
6334                                 'required'      => true,
6335                         ),
6336                         'answered' => array(
6337                                 'name'          => 'answered',
6338                                 'type'          => 'tinyint',
6339                                 'len'           => 1,
6340                                 'required'      => true,
6341                         ),
6342                         'deleted' => array(
6343                                 'name'          => 'deleted',
6344                                 'type'          => 'tinyint',
6345                                 'len'           => 1,
6346                                 'required'      => true,
6347                         ),
6348                         'seen' => array(
6349                                 'name'          => 'seen',
6350                                 'type'          => 'tinyint',
6351                                 'len'           => 1,
6352                                 'required'      => true,
6353                         ),
6354                         'draft' => array(
6355                                 'name'          => 'draft',
6356                                 'type'          => 'tinyint',
6357                                 'len'           => 1,
6358                                 'required'      => true,
6359                         ),
6360                 );
6361         */
6362         function Overview() {
6363                 global $dictionary;
6364
6365                 if(!isset($dictionary['email_cache']) || empty($dictionary['email_cache'])) {
6366                         if(file_exists('custom/metadata/email_cacheMetaData.php')) {
6367                            include('custom/metadata/email_cacheMetaData.php');
6368                         } else {
6369                            include('metadata/email_cacheMetaData.php');
6370                         }
6371                 }
6372
6373                 $this->fieldDefs = $dictionary['email_cache']['fields'];
6374                 $this->indices = $dictionary['email_cache']['indices'];
6375         }
6376 }