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