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