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