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