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