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