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