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