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