2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 /*********************************************************************************
4 * SugarCRM is a customer relationship management program developed by
5 * SugarCRM, Inc. Copyright (C) 2004-2011 SugarCRM Inc.
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.
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
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
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.
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.
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 ********************************************************************************/
38 /*********************************************************************************
40 * Description: TODO: To be written.
41 * Portions created by SugarCRM are Copyright (C) SugarCRM, Inc.
42 * All Rights Reserved.
43 * Contributor(s): ______________________________________..
44 ********************************************************************************/
48 require_once('include/OutboundEmail/OutboundEmail.php');
49 require_once('include/Pear/HTML_Safe/Safe.php');
51 function this_callback($str) {
52 foreach($str as $match) {
53 $ret .= chr(hexdec(str_replace("%","",$match)));
59 * Stub for certain interactions;
65 class InboundEmail extends SugarBean {
68 var $purifier; // HTMLPurifier object placeholder
76 var $modified_user_id;
79 var $modified_by_name;
99 var $outboundInstance; // id to outbound_email instance
101 var $iconFlagged = "F";
102 var $iconDraft = "D";
103 var $iconAnswered = "A";
104 var $iconDeleted = "del";
105 var $isAutoImport = false;
107 var $attachmentCount = 0;
108 var $tempAttachment = array();
109 var $unsafeChars = array("&", "!", "'", '"', '\\', '/', '<', '>', '|', '$',);
111 var $defaultSort = 'date';
112 var $defaultDirection = "DESC";
120 var $hrSortLocal = array(
121 'flagged' => 'flagged',
122 'status' => 'answered',
123 'from' => 'fromaddr',
124 'subject' => 'subject',
125 'date' => 'senddate',
128 // default attributes
129 var $transferEncoding = array(0 => '7BIT',
133 4 => 'QUOTED-PRINTABLE',
137 var $safe; // place holder for HTML_Safe class
138 var $compoundMessageId; // concatenation of messageID and deliveredToEmail
139 var $serverConnectString;
140 var $disable_row_level_security = true;
141 var $InboundEmailCachePath;
142 var $InboundEmailCacheFile = 'InboundEmail.cache.php';
143 var $object_name = 'InboundEmail';
144 var $module_dir = 'InboundEmail';
145 var $table_name = 'inbound_email';
146 var $new_schema = true;
147 var $process_save_dates = true;
153 var $required_fields = array('name' => 'name',
154 'server_url' => 'server_url',
155 'mailbox' => 'mailbox',
159 var $imageTypes = array("JPG", "JPEG", "GIF");
160 var $inlineImages = array(); // temporary space to store ID of inlined images
161 var $defaultEmailNumAutoreplies24Hours = 10;
162 var $maxEmailNumAutoreplies24Hours = 10;
163 // custom ListView attributes
164 var $mailbox_type_name;
165 var $global_personal_string;
166 // service attributes
171 var $keyForUsersDefaultIEAccount = 'defaultIEAccount';
176 function InboundEmail() {
177 $this->InboundEmailCachePath = $GLOBALS['sugar_config']['cache_dir'].'modules/InboundEmail';
179 if(function_exists("imap_timeout")) {
191 $this->safe = new HTML_Safe();
192 $this->safe->clear();
193 $this->smarty = new Sugar_Smarty();
194 $this->overview = new Overview();
200 * @return object Bean
202 function retrieve($id) {
203 $ret = parent::retrieve($id);
204 $this->email_password = blowfishDecode(blowfishGetKey('InboundEmail'), $this->email_password);
205 $this->retrieveMailBoxFolders();
210 * wraps SugarBean->save()
211 * @param string ID of saved bean
213 function save($check_notify=false) {
214 // generate cache table for email 2.0
215 $multiDImArray = $this->generateMultiDimArrayFromFlatArray(explode(",", $this->mailbox), $this->retrieveDelimiter());
216 $raw = $this->generateFlatArrayFromMultiDimArray($multiDImArray, $this->retrieveDelimiter());
218 //_pp(explode(",", $this->mailbox));
220 $raw = $this->filterMailBoxFromRaw(explode(",", $this->mailbox), $raw);
221 $this->mailbox = implode(",", $raw);
222 $this->email_password = blowfishEncode(blowfishGetKey('InboundEmail'), $this->email_password);
223 $ret = parent::save($check_notify);
227 function filterMailBoxFromRaw($mailboxArray, $rawArray) {
228 $newArray = array_intersect($mailboxArray, $rawArray);
234 * Overrides SugarBean's mark_deleted() to drop the related cache table
235 * @param string $id GUID of I-E instance
237 function mark_deleted($id) {
238 parent::mark_deleted($id);
239 $q = "update inbound_email set groupfolder_id = null WHERE id = $id";
240 $r = $this->db->query($q);
241 $this->deleteCache();
245 * Mark cached email answered (replied)
246 * @param string $mailid (uid for imap, message_id for pop3)
248 function mark_answered($mailid, $type = 'smtp') {
251 $q = "update email_cache set answered = 1 WHERE imap_uid = $mailid and ie_id = '$this->id'";
252 $this->db->query($q);
255 $q = "update email_cache set answered = 1 WHERE message_id = '$mailid' and ie_id = '$this->id'";
256 $this->db->query($q);
262 * Renames an IMAP mailbox
263 * @param string $newName
265 function renameFolder($oldName, $newName) {
266 //$this->mailbox = "INBOX"
267 $this->connectMailserver();
268 $oldConnect = $this->getConnectString('', $oldName);
269 $newConnect = $this->getConnectString('', $newName);
270 if(!imap_renamemailbox($this->conn, $oldConnect , $newConnect)) {
271 $GLOBALS['log']->debug("***INBOUNDEMAIL: failed to rename mailbox [ {$oldConnect} ] to [ {$newConnect} ]");
273 $this->mailbox = str_replace($oldName, $newName, $this->mailbox);
275 $sessionFoldersString = $this->getSessionInboundFoldersString($this->server_url, $this->email_user, $this->port, $this->protocol);
276 $sessionFoldersString = str_replace($oldName, $newName, $sessionFoldersString);
277 $this->setSessionInboundFoldersString($this->server_url, $this->email_user, $this->port, $this->protocol, $sessionFoldersString);
282 ///////////////////////////////////////////////////////////////////////////
283 //// CUSTOM LOGIC HOOKS
285 * Called from $this->getMessageText()
286 * Allows upgrade-safe custom processing of message text.
289 * 1. Create a directory path: ./custom/modules/InboundEmail if it does not exist
290 * 2. Create a file in the ./custom/InboundEmail/ folder called "getMessageText.php"
291 * 3. Define a function named "custom_getMessageText()" that takes a string as an argument and returns a string
293 * @param string $msgPart
296 function customGetMessageText($msgPart) {
297 $custom = "custom/modules/InboundEmail/getMessageText.php";
299 if(file_exists($custom)) {
300 include_once($custom);
302 if(function_exists("custom_getMessageText")) {
303 $GLOBALS['log']->debug("*** INBOUND EMAIL-CUSTOM_LOGIC: calling custom_getMessageText()");
304 $msgPart = custom_getMessageText($msgPart);
310 //// END CUSTOM LOGIC HOOKS
311 ///////////////////////////////////////////////////////////////////////////
315 ///////////////////////////////////////////////////////////////////////////
316 //// EMAIL 2.0 SPECIFIC
318 * constructs a nicely formatted version of raw source
319 * @param int $uid UID of email
322 function getFormattedRawSource($uid) {
325 //if($this->protocol == 'pop3') {
326 //$raw = $app_strings['LBL_EMAIL_VIEW_UNSUPPORTED'];
329 if (empty($this->id)) {
330 $q = "SELECT raw_source FROM emails_text WHERE email_id = '{$uid}'";
331 $r = $this->db->query($q);
332 $a = $this->db->fetchByAssoc($r);
334 $raw = utf8_encode($a['raw_source']);
336 $raw = $app_strings['LBL_EMAIL_ERROR_VIEW_RAW_SOURCE'];
339 if ($this->isPop3Protocol()) {
340 $uid = $this->getCorrectMessageNoForPop3($uid);
342 $raw = imap_fetchheader($this->conn, $uid, FT_UID+FT_PREFETCHTEXT);
343 $raw .= utf8_encode(imap_body($this->conn, $uid, FT_UID));
345 $raw = to_html($raw);
353 * constructs a nicely formatted version of email headers.
357 function getFormattedHeaders($uid) {
360 //if($this->protocol == 'pop3') {
361 // $header = $app_strings['LBL_EMAIL_VIEW_UNSUPPORTED'];
363 if ($this->isPop3Protocol()) {
364 $uid = $this->getCorrectMessageNoForPop3($uid);
366 $headers = imap_fetchheader($this->conn, $uid, FT_UID);
368 $lines = explode("\n", $headers);
370 $header = "<table cellspacing='0' cellpadding='2' border='0' width='100%'>";
372 foreach($lines as $line) {
376 $key = trim(substr($line, 0, strpos($line, ":")));
377 $value = trim(substr($line, strpos($line, ":") + 1));
378 $value = to_html($value);
381 $header .= "<td class='displayEmailLabel' NOWRAP><b>{$key}</b> </td>";
382 $header .= "<td class='displayEmailValueWhite'>{$value} </td>";
387 $header .= "</table>";
393 * Empties Trash folders
395 function emptyTrash() {
396 global $sugar_config;
398 $this->mailbox = $this->get_stored_options("trashFolder");
399 if (empty($this->mailbox)) {
400 $this->mailbox = 'INBOX.Trash';
402 $this->connectMailserver();
404 $uids = imap_search($this->conn, "ALL", SE_UID);
406 foreach($uids as $uid) {
407 if(!imap_delete($this->conn, $uid, FT_UID)) {
408 $lastError = imap_last_error();
409 $GLOBALS['log']->warn("INBOUNDEMAIL: emptyTrash() Could not delete message [ {$uid} ] from [ {$this->mailbox} ]. IMAP_ERROR [ {$lastError} ]");
413 // remove local cache file
414 $q = "DELETE FROM email_cache WHERE mbox = '{$this->mailbox}' AND ie_id = '{$this->id}'";
415 $r = $this->db->query($q);
419 * Fetches a timestamp
421 function getCacheTimestamp($mbox) {
422 $key = $this->db->quote("{$this->id}_{$mbox}");
423 $q = "SELECT ie_timestamp FROM inbound_email_cache_ts WHERE id = '{$key}'";
424 $r = $this->db->query($q);
425 $a = $this->db->fetchByAssoc($r);
430 return $a['ie_timestamp'];
434 * sets the cache timestamp
437 function setCacheTimestamp($mbox) {
438 $key = $this->db->quote("{$this->id}_{$mbox}");
440 $tsOld = $this->getCacheTimestamp($mbox);
443 $q = "INSERT INTO inbound_email_cache_ts (id, ie_timestamp) VALUES ('{$key}', {$ts})";
445 $q = "UPDATE inbound_email_cache_ts SET ie_timestamp = {$ts} WHERE id = '{$key}'";
448 $r = $this->db->query($q, true);
449 $GLOBALS['log']->info("INBOUNDEMAIL-CACHE: setting timestamp query [ {$q} ]");
454 * Gets a count of all rows that are flagged seen = 0
455 * @param string $mbox
458 function getCacheUnreadCount($mbox) {
459 $q = "SELECT count(*) c FROM email_cache WHERE mbox = '{$mbox}' AND seen = 0 AND ie_id = '{$this->id}'";
460 $r = $this->db->query($q);
461 $a = $this->db->fetchByAssoc($r);
467 * Returns total number of emails for a mailbox
471 function getCacheCount($mbox) {
472 $q = "SELECT count(*) c FROM email_cache WHERE mbox = '{$mbox}' AND ie_id = '{$this->id}'";
473 $r = $this->db->query($q);
474 $a = $this->db->fetchByAssoc($r);
479 function getCacheUnread($mbox) {
480 $q = "SELECT count(*) c FROM email_cache WHERE mbox = '{$mbox}' AND ie_id = '{$this->id}' AND seen = '0'";
481 $r = $this->db->query($q);
482 $a = $this->db->fetchByAssoc($r);
489 * Deletes all rows for a given instance
491 function deleteCache() {
492 $q = "DELETE FROM email_cache WHERE ie_id = '{$this->id}'";
494 $GLOBALS['log']->info("INBOUNDEMAIL: deleting cache using query [ {$q} ]");
496 $r = $this->db->query($q);
500 * Deletes all the pop3 data which has been deleted from server
502 function deletePop3Cache() {
503 global $sugar_config;
504 $UIDLs = $this->pop3_getUIDL();
505 $cacheUIDLs = $this->pop3_getCacheUidls();
506 foreach($cacheUIDLs as $msgNo => $msgId) {
507 if (!in_array($msgId, $UIDLs)) {
508 $md5msgIds = md5($msgId);
509 $file = "{$sugar_config['cache_dir']}modules/Emails/{$this->id}/messages/INBOX{$md5msgIds}.PHP";
510 $GLOBALS['log']->debug("INBOUNDEMAIL: deleting file [ {$file} ] ");
511 if(file_exists($file)) {
513 $GLOBALS['log']->debug("INBOUNDEMAIL: Could not delete [ {$file} ] ");
516 $q = "DELETE from email_cache where imap_uid = {$msgNo} AND msgno = {$msgNo} AND ie_id = '{$this->id}' AND message_id = '{$msgId}'";
517 $r = $this->db->query($q);
523 * Retrieves cached headers
526 function getCacheValueForUIDs($mbox, $UIDs) {
527 if (!is_array($UIDs) || empty($UIDs)) {
531 $q = "SELECT * FROM email_cache WHERE ie_id = '{$this->id}' AND mbox = '{$mbox}' AND ";
535 $slicedArray = array_slice($UIDs, $startIndex ,$endIndex);
536 $columnName = ($this->isPop3Protocol() ? "message_id" : "imap_uid");
538 'timestamp' => $this->getCacheTimestamp($mbox),
542 while (!empty($slicedArray)) {
543 $messageIdString = implode(',', $slicedArray);
544 $GLOBALS['log']->debug("sliced array = {$messageIdString}");
545 $extraWhere = "{$columnName} IN (";
547 foreach($slicedArray as $UID) {
549 $extraWhere = $extraWhere . ",";
552 $extraWhere = "{$extraWhere} '{$UID}'";
554 $newQuery = $q . $extraWhere . ")";
555 $r = $this->db->query($newQuery);
557 while($a = $this->db->fetchByAssoc($r)) {
558 if (isset($a['uid'])) {
559 if ($this->isPop3Protocol()) {
560 $ret['uids'][] = $a['message_id'];
562 $ret['uids'][] = $a['uid'];
566 $overview = new Overview();
568 foreach($a as $k => $v) {
572 $overview->imap_uid = $v;
573 if ($this->isPop3Protocol()) {
574 $overview->uid = $a['message_id'];
580 $overview->to = from_html($v);
584 $overview->from = from_html($v);
588 $overview->size = $v;
592 //$overview->date = strtotime(date('r', strtotime($v)));
593 $overview->date = $v;
597 $overview->$k = from_html($v);
601 $ret['retArr'][] = $overview;
603 $startIndex = $startIndex + $endIndex;
604 $slicedArray = array_slice($UIDs, $startIndex ,$endIndex);
605 $messageIdString = implode(',', $slicedArray);
606 $GLOBALS['log']->debug("sliced array = {$messageIdString}");
612 * Retrieves cached headers
615 function getCacheValue($mbox, $limit = 20, $page = 1, $sort='', $direction='') {
616 // try optimizing this call as we don't want repeat queries
617 if(!empty($this->currentCache)) {
618 return $this->currentCache;
621 $sort = (empty($sort)) ? $this->defaultSort : $sort;
622 $direction = (empty($direction)) ? $this->defaultDirection : $direction;
623 $order = " ORDER BY {$this->hrSortLocal[$sort]} {$direction}";
625 $q = "SELECT * FROM email_cache WHERE ie_id = '{$this->id}' AND mbox = '{$mbox}' {$order}";
628 $start = ( $page - 1 ) * $limit;
629 $r = $this->db->limitQuery($q, $start, $limit);
631 $r = $this->db->query($q);
635 'timestamp' => $this->getCacheTimestamp($mbox),
640 while($a = $this->db->fetchByAssoc($r)) {
641 if (isset($a['uid'])) {
642 if ($this->isPop3Protocol()) {
643 $ret['uids'][] = $a['message_id'];
645 $ret['uids'][] = $a['uid'];
649 $overview = new Overview();
651 foreach($a as $k => $v) {
655 $overview->imap_uid = $v;
656 if ($this->isPop3Protocol()) {
657 $overview->uid = $a['message_id'];
663 $overview->to = from_html($v);
667 $overview->from = from_html($v);
671 $overview->size = $v;
675 //$overview->date = strtotime(date('r', strtotime($v)));
676 $overview->date = $v;
680 $overview->$k = from_html($v);
684 $ret['retArr'][] = $overview;
687 $this->currentCache = $ret;
695 function setCacheValue($mbox, $insert, $update=array(), $remove=array()) {
702 // reset in-memory cache
703 $this->currentCache = null;
705 $table = 'email_cache';
706 $where = "WHERE ie_id = '{$this->id}' AND mbox = '{$mbox}'";
708 // handle removed rows
709 if(!empty($remove)) {
711 foreach($remove as $overview) {
712 if(!empty($removeIds)) {
716 $removeIds .= "'{$overview->imap_uid}'";
719 $q = "DELETE FROM {$table} {$where} AND imap_uid IN ({$removeIds})";
721 $GLOBALS['log']->info("INBOUNDEMAIL-CACHE: delete query [ {$q} ]");
723 $r = $this->db->query($q, true, $q);
726 // handle insert rows
727 if(!empty($insert)) {
728 $q = "SELECT imap_uid FROM {$table} {$where}";
729 $GLOBALS['log']->info("INBOUNDEMAIL-CACHE: filter UIDs query [ {$q} ]");
730 $r = $this->db->query($q);
733 while($a = $this->db->fetchByAssoc($r)) {
734 $uids[] = $a['imap_uid'];
736 $count = count($uids);
737 $GLOBALS['log']->info("INBOUNDEMAIL-CACHE: found [ {$count} ] UIDs to filter against");
740 foreach($uids as $uid) {
745 $GLOBALS['log']->info("INBOUNDEMAIL-CACHE: filter UIDs: [ {$tmp} ]");
749 foreach($this->overview->fieldDefs as $colDef) {
753 $cols .= "{$colDef['name']}";
755 foreach($insert as $overview) {
756 if(in_array($overview->imap_uid, $uids)) {
757 $update[] = $overview;
763 foreach($this->overview->fieldDefs as $colDef) {
764 if(!empty($values)) {
768 // trim values for Oracle/MSSql
769 if( isset($colDef['len']) && !empty($colDef['len']) &&
770 isset($colDef['type']) && !empty($colDef['type']) &&
771 $colDef['type'] == 'varchar'
773 $overview->$colDef['name'] = substr($overview->$colDef['name'], 0, $colDef['len']);
776 switch($colDef['name']) {
778 if(isset($overview->uid) && !empty($overview->uid)) {
779 $this->imap_uid = $overview->uid;
781 $values .= "'{$this->imap_uid}'";
785 $values .= "'{$this->id}'";
789 $values .= "'".$this->db->helper->escape_quote($overview->to)."'";
793 $values .= "'".$this->db->helper->escape_quote($overview->from)."'";
797 $values .= "'".$this->db->helper->escape_quote($overview->message_id)."'";
801 $values .= $overview->size;
805 $conv=$this->getUnixHeaderDate($overview->date);
807 $values .= "'" . date($timedate->get_db_date_time_format(), $conv) ."'";
814 $values .= "'{$mbox}'";
818 $overview->$colDef['name'] = from_html($overview->$colDef['name']);
819 $overview->$colDef['name'] = $this->cleanContent($overview->$colDef['name']);
820 $values .= "'".$this->db->helper->escape_quote($overview->$colDef['name'])."'";
825 $q = "INSERT INTO {$table} ({$cols}) VALUES ({$values})";
826 $GLOBALS['log']->info("INBOUNDEMAIL-CACHE: insert query [ {$q} ]");
827 $r = $this->db->query($q, true, $q);
831 // handle update rows
832 if(!empty($update)) {
834 foreach($this->overview->fieldDefs as $colDef) {
838 $cols .= "{$colDef['name']}";
841 foreach($update as $overview) {
842 $q = "UPDATE {$table} SET ";
845 foreach($this->overview->fieldDefs as $colDef) {
847 switch($colDef['name']) {
849 //$set .= "toaddr = '".$this->db->helper->escape_quote($overview->to)."'";
853 //$set .= "fromaddr = '".$this->db->helper->escape_quote($overview->from)."'";
857 //$set .= "mailsize = {$overview->size}";
861 //$set .= "senddate = '".date("Y-m-d H:i:s", $overview->date)."'";
865 //$set .= "mbox = '{$mbox}'";
872 $set .= "{$colDef['name']} = '".$this->db->helper->escape_quote($overview->$colDef['name'])."'";
877 $q .= $set . " WHERE ie_id = '{$this->id}' AND mbox = '{$overview->mbox}' AND imap_uid = '{$overview->imap_uid}'";
878 $GLOBALS['log']->info("INBOUNDEMAIL-CACHE: update query [ {$q} ]");
879 $r = $this->db->query($q, true, $q);
886 * Opens a socket connection to the pop3 server
889 function pop3_open() {
890 if(!is_resource($this->pop3socket)) {
891 $GLOBALS['log']->info("*** INBOUNDEMAIL: opening socket connection");
892 $exServ = explode('::', $this->service);
893 $socket = ($exServ[2] == 'ssl') ? "ssl://" : "tcp://";
894 $socket .= $this->server_url;
895 $this->pop3socket = fsockopen($socket, $this->port);
897 $GLOBALS['log']->info("*** INBOUNDEMAIL: REUSING socket connection");
901 if(!is_resource($this->pop3socket)) {
902 $GLOBALS['log']->debug("*** INBOUNDEMAIL: unable to open socket connection");
907 $ret = trim(fgets($this->pop3socket, 1024));
908 $GLOBALS['log']->info("*** INBOUNDEMAIL: got socket connection [ {$ret} ]");
913 * Closes connections and runs clean-up routines
915 function pop3_cleanUp() {
916 $GLOBALS['log']->info("*** INBOUNDEMAIL: cleaning up socket connection");
917 fputs($this->pop3socket, "QUIT\r\n");
918 $buf = fgets($this->pop3socket, 1024);
919 fclose($this->pop3socket);
923 * sends a command down to the POP3 server
924 * @param string command
929 function pop3_sendCommand($command, $args='', $return=true) {
930 $command .= " {$args}";
931 $command = trim($command);
932 $GLOBALS['log']->info("*** INBOUNDEMAIL: pop3_sendCommand() SEND [ {$command} ]");
935 fputs($this->pop3socket, $command);
938 $ret = trim(fgets($this->pop3socket, 1024));
939 $GLOBALS['log']->info("*** INBOUNDEMAIL: pop3_sendCommand() RECEIVE [ {$ret} ]");
944 function getPop3NewMessagesToDownload() {
945 $pop3UIDL = $this->pop3_getUIDL();
946 $cacheUIDLs = $this->pop3_getCacheUidls();
947 // new email cache values we should deal with
948 $diff = array_diff_assoc($pop3UIDL, $cacheUIDLs);
949 // this is msgNo to UIDL array
950 $diff = $this->pop3_shiftCache($diff, $cacheUIDLs);
951 // get all the keys which are msgnos;
952 return array_keys($diff);
955 function getPop3NewMessagesToDownloadForCron() {
956 $pop3UIDL = $this->pop3_getUIDL();
957 $cacheUIDLs = $this->pop3_getCacheUidls();
958 // new email cache values we should deal with
959 $diff = array_diff_assoc($pop3UIDL, $cacheUIDLs);
960 // this is msgNo to UIDL array
961 $diff = $this->pop3_shiftCache($diff, $cacheUIDLs);
962 // insert data into email_cache
963 if ($this->groupfolder_id != null && $this->groupfolder_id != "" && $this->isPop3Protocol()) {
964 $searchResults = array_keys($diff);
965 $concatResults = implode(",", $searchResults);
966 if ($this->connectMailserver() == 'true') {
967 $fetchedOverviews = imap_fetch_overview($this->conn, $concatResults);
968 // clean up cache entry
969 foreach($fetchedOverviews as $k => $overview) {
970 $overview->message_id = trim($diff[$overview->msgno]);
971 $fetchedOverviews[$k] = $overview;
973 $this->updateOverviewCacheFile($fetchedOverviews);
980 * This method returns all the UIDL for this account. This should be called if the protocol is pop3
981 * @return array od messageno to UIDL array
983 function pop3_getUIDL() {
985 if($this->pop3_open()) {
987 $this->pop3_sendCommand("USER", $this->email_user);
988 $this->pop3_sendCommand("PASS", $this->email_password);
991 $this->pop3_sendCommand("UIDL", '', false); // leave socket buffer alone until the while()
992 fgets($this->pop3socket, 1024); // handle "OK+";
997 if(is_resource($this->pop3socket)) {
998 while(!feof($this->pop3socket)) {
999 $buf = fgets($this->pop3socket, 1024); // 8kb max buffer - shouldn't be more than 80 chars via pop3...
1002 if(trim($buf) == '.') {
1003 $GLOBALS['log']->debug("*** GOT '.'");
1007 // format is [msgNo] [UIDL]
1008 $exUidl = explode(" ", $buf);
1009 $UIDLs[$exUidl[0]] = trim($exUidl[1]);
1012 $this->pop3_cleanUp();
1018 * Special handler for POP3 boxes. Standard IMAP commands are useless.
1019 * This will fetch only partial emails for POP3 and hence needs to be call again and again based on status it returns
1021 function pop3_checkPartialEmail($synch = false) {
1022 require_once('include/utils/array_utils.php');
1023 global $current_user;
1024 global $sugar_config;
1026 $cacheDataExists = false;
1029 $cacheFilePath = clean_path("{$sugar_config['cache_dir']}modules/Emails/{$this->id}/folders/MsgNOToUIDLData.php");
1030 if(file_exists($cacheFilePath)) {
1031 $cacheDataExists = true;
1032 if($fh = @fopen($cacheFilePath, "rb")) {
1034 $chunksize = 1*(1024*1024); // how many bytes per chunk
1036 $buf = fgets($fh, $chunksize); // 8kb max buffer - shouldn't be more than 80 chars via pop3...
1037 $data = $data . $buf;
1041 $diff = unserialize($data);
1042 if (!empty($diff)) {
1043 if (count($diff)> 50) {
1044 $newDiff = array_slice($diff, 50, count($diff), true);
1048 $results = array_slice(array_keys($diff), 0 ,50);
1049 $data = serialize($newDiff);
1050 if($fh = @fopen($cacheFilePath, "w")) {
1057 if (!$cacheDataExists) {
1059 $this->deletePop3Cache();
1061 $UIDLs = $this->pop3_getUIDL();
1062 if(count($UIDLs) > 0) {
1064 $cacheUIDLs = $this->pop3_getCacheUidls();
1066 // new email cache values we should deal with
1067 $diff = array_diff_assoc($UIDLs, $cacheUIDLs);
1068 $diff = $this->pop3_shiftCache($diff, $cacheUIDLs);
1069 require_once('modules/Emails/EmailUI.php');
1070 EmailUI::preflightEmailCache("{$sugar_config['cache_dir']}modules/Emails/{$this->id}");
1072 if (count($diff)> 50) {
1073 $newDiff = array_slice($diff, 50, count($diff), true);
1078 $results = array_slice(array_keys($diff), 0 ,50);
1079 $data = serialize($newDiff);
1080 if($fh = @fopen($cacheFilePath, "w")) {
1085 $GLOBALS['log']->debug("*** INBOUNDEMAIL: could not open socket connection to POP3 server");
1086 return "could not open socket connection to POP3 server";
1090 // build up msgNo request
1091 if(count($diff) > 0) {
1092 // remove dirty cache entries
1094 if (isset($_REQUEST['currentCount']) && $_REQUEST['currentCount'] > -1) {
1095 $startingNo = $_REQUEST['currentCount'];
1098 $this->mailbox = 'INBOX';
1099 $this->connectMailserver();
1100 //$searchResults = array_keys($diff);
1101 //$fetchedOverviews = array();
1102 //$chunkArraySerachResults = array_chunk($searchResults, 50);
1103 $concatResults = implode(",", $results);
1104 $GLOBALS['log']->info('$$$$ '.$concatResults);
1105 $GLOBALS['log']->info("[EMAIL] Start POP3 fetch overview on mailbox [{$this->mailbox}] for user [{$current_user->user_name}] on 50 data");
1106 $fetchedOverviews = imap_fetch_overview($this->conn, $concatResults);
1107 $GLOBALS['log']->info("[EMAIL] End POP3 fetch overview on mailbox [{$this->mailbox}] for user [{$current_user->user_name}] on "
1108 . sizeof($fetchedOverviews) . " data");
1110 // clean up cache entry
1111 foreach($fetchedOverviews as $k => $overview) {
1112 $overview->message_id = trim($diff[$overview->msgno]);
1113 $fetchedOverviews[$k] = $overview;
1116 $GLOBALS['log']->info("[EMAIL] Start updating overview cache for pop3 mailbox [{$this->mailbox}] for user [{$current_user->user_name}]");
1117 $this->updateOverviewCacheFile($fetchedOverviews);
1118 $GLOBALS['log']->info("[EMAIL] Start updating overview cache for pop3 mailbox [{$this->mailbox}] for user [{$current_user->user_name}]");
1119 return array('status' => "In Progress", 'mbox' => $this->mailbox, 'count'=> (count($results) + $startingNo), 'totalcount' => count($diff), 'ieid' => $this->id);
1121 unlink($cacheFilePath);
1122 return array('status' => "done");
1127 * Special handler for POP3 boxes. Standard IMAP commands are useless.
1129 function pop3_checkEmail() {
1130 if($this->pop3_open()) {
1132 $this->pop3_sendCommand("USER", $this->email_user);
1133 $this->pop3_sendCommand("PASS", $this->email_password);
1136 $this->pop3_sendCommand("UIDL", '', false); // leave socket buffer alone until the while()
1137 fgets($this->pop3socket, 1024); // handle "OK+";
1142 if(is_resource($this->pop3socket)) {
1143 while(!feof($this->pop3socket)) {
1144 $buf = fgets($this->pop3socket, 1024); // 8kb max buffer - shouldn't be more than 80 chars via pop3...
1147 if(trim($buf) == '.') {
1148 $GLOBALS['log']->debug("*** GOT '.'");
1152 // format is [msgNo] [UIDL]
1153 $exUidl = explode(" ", $buf);
1154 $UIDLs[$exUidl[0]] = trim($exUidl[1]);
1158 $this->pop3_cleanUp();
1161 $cacheUIDLs = $this->pop3_getCacheUidls();
1162 // _pp($UIDLs);_pp($cacheUIDLs);
1164 // new email cache values we should deal with
1165 $diff = array_diff_assoc($UIDLs, $cacheUIDLs);
1167 // remove dirty cache entries
1168 $diff = $this->pop3_shiftCache($diff, $cacheUIDLs);
1170 // build up msgNo request
1172 $this->mailbox = 'INBOX';
1173 $this->connectMailserver();
1174 $searchResults = array_keys($diff);
1175 $concatResults = implode(",", $searchResults);
1176 $fetchedOverviews = imap_fetch_overview($this->conn, $concatResults);
1178 // clean up cache entry
1179 foreach($fetchedOverviews as $k => $overview) {
1180 $overview->message_id = trim($diff[$overview->msgno]);
1181 $fetchedOverviews[$k] = $overview;
1184 $this->updateOverviewCacheFile($fetchedOverviews);
1187 $GLOBALS['log']->debug("*** INBOUNDEMAIL: could not open socket connection to POP3 server");
1193 * Iterates through msgno and message_id to remove dirty cache entries
1196 function pop3_shiftCache($diff, $cacheUIDLs) {
1199 $newArray = array();
1200 foreach($diff as $msgNo => $msgId) {
1201 if (in_array($msgId, $cacheUIDLs)) {
1202 $q1 = "UPDATE email_cache SET imap_uid = {$msgNo}, msgno = {$msgNo} WHERE ie_id = '{$this->id}' AND message_id = '{$msgId}'";
1203 $this->db->query($q1);
1205 $newArray[$msgNo] = $msgId;
1210 foreach($diff as $msgNo => $msgId) {
1211 if(!empty($msgNos)) {
1214 if(!empty($msgIds)) {
1219 $msgIds .= "'{$msgId}'";
1222 if(!empty($msgNos)) {
1223 $q1 = "DELETE FROM email_cache WHERE ie_id = '{$this->id}' AND msgno IN ({$msgNos})";
1224 $this->db->query($q1);
1226 if(!empty($msgIds)) {
1227 $q2 = "DELETE FROM email_cache WHERE ie_id = '{$this->id}' AND message_id IN ({$msgIds})";
1228 $this->db->query($q2);
1234 * retrieves cached uidl values.
1235 * When dealing with POP3 accounts, the message_id column in email_cache will contain the UIDL.
1238 function pop3_getCacheUidls() {
1239 $q = "SELECT msgno, message_id FROM email_cache WHERE ie_id = '{$this->id}'";
1240 $r = $this->db->query($q);
1243 while($a = $this->db->fetchByAssoc($r)) {
1244 $ret[$a['msgno']] = $a['message_id'];
1251 * This function is used by cron job for group mailbox without group folder
1252 * @param string $msgno for pop
1253 * @param string $uid for imap
1255 function getMessagesInEmailCache($msgno, $uid) {
1256 $fetchedOverviews = array();
1257 if ($this->isPop3Protocol()) {
1258 $fetchedOverviews = imap_fetch_overview($this->conn, $msgno);
1259 foreach($fetchedOverviews as $k => $overview) {
1260 $overview->message_id = $uid;
1261 $fetchedOverviews[$k] = $overview;
1264 $fetchedOverviews = imap_fetch_overview($this->conn, $uid, FT_UID);
1266 $this->updateOverviewCacheFile($fetchedOverviews);
1271 * Checks email (local caching too) for one mailbox
1272 * @param string $mailbox IMAP Mailbox path
1273 * @param bool $prefetch Flag to prefetch email body on check
1275 function checkEmailOneMailbox($mailbox, $prefetch=true, $synchronize=false) {
1276 global $sugar_config;
1277 global $current_user;
1278 global $app_strings;
1280 $GLOBALS['log']->info("INBOUNDEMAIL: checking mailbox [ {$mailbox} ]");
1281 $this->mailbox = $mailbox;
1282 $this->connectMailserver();
1285 $shouldProcessRules = true;
1287 $timestamp = $this->getCacheTimestamp($mailbox);
1289 if($timestamp > 0) {
1290 $checkTime = date('r', $timestamp);
1293 /* first time through, process ALL emails */
1294 if(empty($checkTime) || $synchronize) {
1295 // do not process rules for the first time or sunchronize
1296 $shouldProcessRules = false;
1297 $criteria = "ALL UNDELETED";
1298 $prefetch = false; // do NOT prefetch emails on a brand new account - timeouts happen.
1299 $GLOBALS['log']->info("INBOUNDEMAIL: new account detected - not prefetching email bodies.");
1301 $criteria = "SINCE \"{$checkTime}\" UNDELETED"; // not using UNSEEN
1303 $this->setCacheTimestamp($mailbox);
1304 $GLOBALS['log']->info("[EMAIL] Performing IMAP search using criteria [{$criteria}] on mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1305 $searchResults = imap_search($this->conn, $criteria, SE_UID);
1306 $GLOBALS['log']->info("[EMAIL] Done IMAP search on mailbox [{$mailbox}] for user [{$current_user->user_name}]. Result count = ".count($searchResults));
1308 if(!empty($searchResults)) {
1310 $concatResults = implode(",", $searchResults);
1311 $GLOBALS['log']->info("[EMAIL] Start IMAP fetch overview on mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1312 $fetchedOverview = imap_fetch_overview($this->conn, $concatResults, FT_UID);
1313 $GLOBALS['log']->info("[EMAIL] Done IMAP fetch overview on mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1315 $GLOBALS['log']->info("[EMAIL] Start updating overview cache for mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1316 $this->updateOverviewCacheFile($fetchedOverview);
1317 $GLOBALS['log']->info("[EMAIL] Done updating overview cache for mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1320 if($prefetch == true) {
1321 $GLOBALS['log']->info("[EMAIL] Start fetching emails for mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1322 $this->fetchCheckedEmails($fetchedOverview);
1323 $GLOBALS['log']->info("[EMAIL] Done fetching emails for mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1326 $GLOBALS['log']->info("INBOUNDEMAIL: no results for mailbox [ {$mailbox} ]");
1330 * To handle the use case where an external client is also connected, deleting emails, we need to clear our
1331 * local cache of all emails with the "DELETED" flag
1333 $criteria = 'DELETED';
1334 $criteria .= (!empty($checkTime)) ? " SINCE \"{$checkTime}\"" : "";
1335 $GLOBALS['log']->info("INBOUNDEMAIL: checking for deleted emails using [ {$criteria} ]");
1337 $trashFolder = $this->get_stored_options("trashFolder");
1338 if (empty($trashFolder)) {
1339 $trashFolder = "INBOX.Trash";
1342 if($this->mailbox != $trashFolder) {
1343 $searchResults = imap_search($this->conn, $criteria, SE_UID);
1344 if(!empty($searchResults)) {
1345 $uids = implode($app_strings['LBL_EMAIL_DELIMITER'], $searchResults);
1346 $GLOBALS['log']->info("INBOUNDEMAIL: removing UIDs found deleted [ {$uids} ]");
1347 $this->getOverviewsFromCacheFile($uids, $mailbox, true);
1353 * Checks email (local caching too) for one mailbox
1354 * @param string $mailbox IMAP Mailbox path
1355 * @param bool $prefetch Flag to prefetch email body on check
1357 function checkEmailOneMailboxPartial($mailbox, $prefetch=true, $synchronize=false, $start = 0, $max = -1) {
1358 global $sugar_config;
1359 global $current_user;
1360 global $app_strings;
1362 $GLOBALS['log']->info("INBOUNDEMAIL: checking mailbox [ {$mailbox} ]");
1363 $this->mailbox = $mailbox;
1364 $this->connectMailserver();
1367 $shouldProcessRules = true;
1369 $timestamp = $this->getCacheTimestamp($mailbox);
1371 if($timestamp > 0) {
1372 $checkTime = date('r', $timestamp);
1375 /* first time through, process ALL emails */
1376 if(empty($checkTime) || $synchronize) {
1377 // do not process rules for the first time or sunchronize
1378 $shouldProcessRules = false;
1379 $criteria = "ALL UNDELETED";
1380 $prefetch = false; // do NOT prefetch emails on a brand new account - timeouts happen.
1381 $GLOBALS['log']->info("INBOUNDEMAIL: new account detected - not prefetching email bodies.");
1383 $criteria = "SINCE \"{$checkTime}\" UNDELETED"; // not using UNSEEN
1385 $this->setCacheTimestamp($mailbox);
1386 $GLOBALS['log']->info("[EMAIL] Performing IMAP search using criteria [{$criteria}] on mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1387 $searchResults = $this->getCachedIMAPSearch($criteria);
1389 if(!empty($searchResults)) {
1391 $total = sizeof($searchResults);
1392 $searchResults = array_slice($searchResults, $start, $max);
1394 $GLOBALS['log']->info("INBOUNDEMAIL: there are $total messages in [{$mailbox}], we are on $start");
1395 $GLOBALS['log']->info("INBOUNDEMAIL: getting the next " . sizeof($searchResults) . " messages");
1396 $concatResults = implode(",", $searchResults);
1397 $GLOBALS['log']->info("INBOUNDEMAIL: Start IMAP fetch overview on mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1398 $fetchedOverview = imap_fetch_overview($this->conn, $concatResults, FT_UID);
1399 $GLOBALS['log']->info("INBOUNDEMAIL: Done IMAP fetch overview on mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1401 $GLOBALS['log']->info("INBOUNDEMAIL: Start updating overview cache for mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1402 $this->updateOverviewCacheFile($fetchedOverview);
1403 $GLOBALS['log']->info("INBOUNDEMAIL: Done updating overview cache for mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1406 if($prefetch == true) {
1407 $GLOBALS['log']->info("INBOUNDEMAIL: Start fetching emails for mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1408 $this->fetchCheckedEmails($fetchedOverview);
1409 $GLOBALS['log']->info("INBOUNDEMAIL: Done fetching emails for mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1411 $status = ($total > $start + sizeof($searchResults)) ? 'continue' : 'done';
1412 $ret = array('status' => $status, 'count' => $start + sizeof($searchResults), 'mbox' => $mailbox, 'totalcount' => $total);
1413 $GLOBALS['log']->info("INBOUNDEMAIL: $status : Downloaded " . $start + sizeof($searchResults) . "messages of $total");
1416 $GLOBALS['log']->info("INBOUNDEMAIL: no results for mailbox [ {$mailbox} ]");
1417 $ret = array('status' =>'done');
1420 if ($ret['status'] == 'done') {
1421 //Remove the cached search if we are done with this mailbox
1422 $cacheFilePath = clean_path("{$sugar_config['cache_dir']}modules/Emails/{$this->id}/folders/SearchData.php");
1423 unlink($cacheFilePath);
1425 * To handle the use case where an external client is also connected, deleting emails, we need to clear our
1426 * local cache of all emails with the "DELETED" flag
1428 $criteria = 'DELETED';
1429 $criteria .= (!empty($checkTime)) ? " SINCE \"{$checkTime}\"" : "";
1430 $GLOBALS['log']->info("INBOUNDEMAIL: checking for deleted emails using [ {$criteria} ]");
1432 $trashFolder = $this->get_stored_options("trashFolder");
1433 if (empty($trashFolder)) {
1434 $trashFolder = "INBOX.Trash";
1437 if($this->mailbox != $trashFolder) {
1438 $searchResults = imap_search($this->conn, $criteria, SE_UID);
1439 if(!empty($searchResults)) {
1440 $uids = implode($app_strings['LBL_EMAIL_DELIMITER'], $searchResults);
1441 $GLOBALS['log']->info("INBOUNDEMAIL: removing UIDs found deleted [ {$uids} ]");
1442 $this->getOverviewsFromCacheFile($uids, $mailbox, true);
1449 function getCachedIMAPSearch($criteria) {
1450 global $current_user;
1451 global $sugar_config;
1453 $cacheDataExists = false;
1456 $cacheFolderPath = clean_path("{$sugar_config['cache_dir']}modules/Emails/{$this->id}/folders");
1457 if (!file_exists($cacheFolderPath)) {
1458 mkdir_recursive($cacheFolderPath);
1460 $cacheFilePath = $cacheFolderPath . '/SearchData.php';
1461 $GLOBALS['log']->info("INBOUNDEMAIL: Cache path is $cacheFilePath");
1462 if(file_exists($cacheFilePath)) {
1463 $cacheDataExists = true;
1464 if($fh = @fopen($cacheFilePath, "rb")) {
1466 $chunksize = 1*(1024*1024); // how many bytes per chunk
1468 $buf = fgets($fh, $chunksize); // 8kb max buffer - shouldn't be more than 80 chars via pop3...
1469 $data = $data . $buf;
1473 $results = unserialize($data);
1476 if (!$cacheDataExists) {
1477 $searchResults = imap_search($this->conn, $criteria, SE_UID);
1478 if(count($searchResults) > 0) {
1479 $results = $searchResults;
1480 $data = serialize($searchResults);
1481 if($fh = @fopen($cacheFilePath, "w")) {
1490 function checkEmailIMAPPartial($prefetch=true, $synch = false) {
1491 $GLOBALS['log']->info("*****************INBOUNDEMAIL: at IMAP check partial");
1492 global $sugar_config;
1493 $this->connectMailserver();
1494 $mailboxes = $this->getMailboxes(true);
1495 if (!in_array('INBOX', $mailboxes)) {
1496 $mailboxes[] = 'INBOX';
1499 if (isset($_REQUEST['mbox']) && !empty($_REQUEST['mbox']) && isset($_REQUEST['currentCount'])) {
1500 $GLOBALS['log']->info("INBOUNDEMAIL: Picking up from where we left off");
1501 $mbox = $_REQUEST['mbox'];
1502 $count = $_REQUEST['currentCount'];
1505 $GLOBALS['log']->info("INBOUNDEMAIL: Cleaning out the cache");
1506 $this->cleanOutCache();
1508 $mbox = $mailboxes[0];
1511 $GLOBALS['log']->info("INBOUNDEMAIL:found " . sizeof($mailboxes) . " Mailboxes");
1512 $index = array_search($mbox, $mailboxes) + 1;
1513 $ret = $this->checkEmailOneMailboxPartial($mbox, $prefetch, $synch, $count, 100);
1514 while($ret['status'] == 'done' && $index < sizeof($mailboxes)) {
1515 if ($ret['count'] > 100) {
1516 $ret['mbox'] = $mailboxes[$index];
1517 $ret['status'] = 'continue';
1520 $GLOBALS['log']->info("INBOUNDEMAIL: checking account [ $index => $mbox : $count]");
1521 $mbox = $mailboxes[$index];
1522 $ret = $this->checkEmailOneMailboxPartial($mbox, $prefetch, $synch, 0, 100);
1529 function checkEmail2_meta() {
1530 global $sugar_config;
1532 $this->connectMailserver();
1533 $mailboxes = $this->getMailboxes(true);
1534 $mailboxes[] = 'INBOX';
1537 $GLOBALS['log']->info("INBOUNDEMAIL: checking account [ {$this->name} ]");
1539 $mailboxes_meta = array();
1540 foreach($mailboxes as $mailbox) {
1541 $mailboxes_meta[$mailbox] = $this->getMailboxProcessCount($mailbox);
1545 $ret['mailboxes'] = $mailboxes_meta;
1547 foreach($mailboxes_meta as $count) {
1548 $ret['processCount'] += $count;
1553 function getMailboxProcessCount($mailbox) {
1554 global $sugar_config;
1556 $GLOBALS['log']->info("INBOUNDEMAIL: checking mailbox [ {$mailbox} ]");
1557 $this->mailbox = $mailbox;
1558 $this->connectMailserver();
1560 $timestamp = $this->getCacheTimestamp($mailbox);
1563 if($timestamp > 0) {
1564 $checkTime = date('r', $timestamp);
1567 /* first time through, process ALL emails */
1568 if(empty($checkTime)) {
1569 $criteria = "ALL UNDELETED";
1570 $prefetch = false; // do NOT prefetch emails on a brand new account - timeouts happen.
1571 $GLOBALS['log']->info("INBOUNDEMAIL: new account detected - not prefetching email bodies.");
1573 $criteria = "SINCE \"{$checkTime}\" UNDELETED"; // not using UNSEEN
1576 $GLOBALS['log']->info("INBOUNDEMAIL: using [ {$criteria} ]");
1577 $searchResults = imap_search($this->conn, $criteria, SE_UID);
1579 if(!empty($searchResults)) {
1580 $concatResults = implode(",", $searchResults);
1582 $GLOBALS['log']->info("INBOUNDEMAIL: no results for mailbox [ {$mailbox} ]");
1585 if(empty($searchResults)) {
1589 return count($searchResults);
1595 function checkEmail($prefetch=true, $synch = false) {
1596 global $sugar_config;
1598 if($this->protocol == 'pop3') {
1599 $this->pop3_checkEmail();
1601 $this->connectMailserver();
1602 $mailboxes = $this->getMailboxes(true);
1603 $mailboxes[] = 'INBOX';
1606 $GLOBALS['log']->info("INBOUNDEMAIL: checking account [ {$this->name} ]");
1608 foreach($mailboxes as $mailbox) {
1609 $this->checkEmailOneMailbox($mailbox, $prefetch, $synch);
1615 * full synchronization
1617 function syncEmail() {
1618 global $sugar_config;
1619 global $current_user;
1621 $showFolders = unserialize(base64_decode($current_user->getPreference('showFolders', 'Emails')));
1623 if(empty($showFolders)) {
1624 $showFolders = array();
1627 $email = new Email();
1628 $email->email2init();
1630 // personal accounts
1631 if($current_user->hasPersonalEmail()) {
1632 $personals = $this->retrieveByGroupId($current_user->id);
1634 foreach($personals as $personalAccount) {
1635 if(in_array($personalAccount->id, $showFolders)) {
1636 $personalAccount->email = $email;
1637 if ($personalAccount->isPop3Protocol()) {
1638 $personalAccount->deletePop3Cache();
1641 $personalAccount->cleanOutCache();
1642 $personalAccount->connectMailserver();
1643 $mailboxes = $personalAccount->getMailboxes(true);
1644 $mailboxes[] = 'INBOX';
1647 $GLOBALS['log']->info("[EMAIL] Start checking account [{$personalAccount->name}] for user [{$current_user->user_name}]");
1649 foreach($mailboxes as $mailbox) {
1650 $GLOBALS['log']->info("[EMAIL] Start checking mailbox [{$mailbox}] of account [{$personalAccount->name}] for user [{$current_user->user_name}]");
1651 $personalAccount->checkEmailOneMailbox($mailbox, false, true);
1652 $GLOBALS['log']->info("[EMAIL] Done checking mailbox [{$mailbox}] of account [{$personalAccount->name}] for user [{$current_user->user_name}]");
1654 $GLOBALS['log']->info("[EMAIL] Done checking account [{$personalAccount->name}] for user [{$current_user->user_name}]");
1660 $beans = $this->retrieveAllByGroupId($current_user->id, false);
1661 foreach($beans as $k => $groupAccount) {
1662 if(in_array($groupAccount->id, $showFolders)) {
1663 $groupAccount->email = $email;
1664 $groupAccount->cleanOutCache();
1665 $groupAccount->connectMailserver();
1666 $mailboxes = $groupAccount->getMailboxes(true);
1667 $mailboxes[] = 'INBOX';
1670 $GLOBALS['log']->info("INBOUNDEMAIL: checking account [ {$groupAccount->name} ]");
1672 foreach($mailboxes as $mailbox) {
1673 $groupAccount->checkEmailOneMailbox($mailbox, false, true);
1681 * Deletes cached messages when moving from folder to folder
1682 * @param string $uids
1683 * @param string $fromFolder
1684 * @param string $toFolder
1686 function deleteCachedMessages($uids, $fromFolder) {
1687 global $sugar_config;
1689 if(!isset($this->email) && !isset($this->email->et)) {
1690 if(!class_exists('Email')) {
1693 $this->email = new Email();
1694 $this->email->email2init();
1697 $uids = $this->email->et->_cleanUIDList($uids);
1699 foreach($uids as $uid) {
1700 $file = "{$sugar_config['cache_dir']}modules/Emails/{$this->id}/messages/{$fromFolder}{$uid}.php";
1702 if(file_exists($file)) {
1703 if(!unlink($file)) {
1704 $GLOBALS['log']->debug("INBOUNDEMAIL: Could not delete [ {$file} ] to [ {$newFile} ]");
1711 * similar to imap_fetch_overview, but it gets overviews from a local cache
1713 * @param string $uids UIDs in comma-delimited format
1714 * @param string $mailbox The mailbox in focus, will default to $this->mailbox
1715 * @param bool $remove Default false
1718 function getOverviewsFromCacheFile($uids, $mailbox='', $remove=false) {
1719 global $app_strings;
1720 if(!isset($this->email) && !isset($this->email->et)) {
1721 if(!class_exists('Email')) {
1724 $this->email = new Email();
1725 $this->email->email2init();
1728 $uids = $this->email->et->_cleanUIDList($uids, true);
1730 // load current cache file
1731 $mailbox = empty($mailbox) ? $this->mailbox : $mailbox;
1732 $cacheValue = $this->getCacheValue($mailbox);
1736 $exUids = explode($app_strings['LBL_EMAIL_DELIMITER'], $uids);
1737 foreach($exUids as $k => $uid) {
1738 $exUids[$k] = trim($uid);
1741 // fill $ret will requested $uids
1742 foreach($cacheValue['retArr'] as $k => $overview) {
1743 if(in_array($overview->imap_uid, $exUids)) {
1748 // remove requested $uids from current cache file (move_mail() type action)
1750 $this->setCacheValue($mailbox, array(), array(), $ret);
1756 * merges new info with the saved cached file
1757 * @param array $array Array of email Overviews
1758 * @param string $type 'append' or 'remove'
1759 * @param string $mailbox Target mailbox if not current assigned
1761 function updateOverviewCacheFile($array, $type='append', $mailbox='') {
1762 $mailbox = empty($mailbox) ? $this->mailbox : $mailbox;
1764 $cacheValue = $this->getCacheValue($mailbox);
1765 $uids = $cacheValue['uids'];
1767 $updateRows = array();
1768 $insertRows = array();
1769 $removeRows = array();
1772 if($type == 'append') { // append
1773 /* we are adding overviews to the cache file */
1774 foreach($array as $overview) {
1775 if(isset($overview->uid)) {
1776 $overview->imap_uid = $overview->uid; // coming from imap_fetch_overview() call
1779 if(!in_array($overview->imap_uid, $uids)) {
1780 $insertRows[] = $overview;
1784 $updatedCacheOverviews = array();
1785 // compare against generated list
1786 /* we are removing overviews from the cache file */
1787 foreach($cacheValue['retArr'] as $cacheOverview) {
1788 if(!in_array($cacheOverview->imap_uid, $uids)) {
1789 $insertRows[] = $cacheOverview;
1791 $removeRows[] = $cacheOverview;
1795 $cacheValue['retArr'] = $updatedCacheOverviews;
1798 $this->setCacheValue($mailbox, $insertRows, $updateRows, $removeRows);
1802 * Check email prefetches email bodies for quicker display
1803 * @param array array of fetched overviews
1805 function fetchCheckedEmails($fetchedOverviews) {
1806 global $sugar_config;
1808 if(is_array($fetchedOverviews) && !empty($fetchedOverviews)) {
1809 foreach($fetchedOverviews as $overview) {
1810 if($overview->size < 10000) {
1812 $uid = $overview->imap_uid;
1815 $file = "{$this->mailbox}{$uid}.php";
1816 $cacheFile = clean_path("{$sugar_config['cache_dir']}/modules/Emails/{$this->id}/messages/{$file}");
1818 if(!file_exists($cacheFile)) {
1819 $GLOBALS['log']->info("INBOUNDEMAIL: Prefetching email [ {$file} ]");
1820 $this->setEmailForDisplay($uid);
1821 $out = $this->displayOneEmail($uid, $this->mailbox);
1822 $this->email->et->writeCacheFile('out', $out, $this->id, 'messages', "{$this->mailbox}{$uid}.php");
1824 $GLOBALS['log']->debug("INBOUNDEMAIL: Trying to prefetch an email we already fetched! [ {$cacheFile} ]");
1827 $GLOBALS['log']->debug("*** INBOUNDEMAIL: prefetch has a message with no UID");
1830 $GLOBALS['log']->debug("INBOUNDEMAIL: skipping email prefetch - size too large [ {$overview->size} ]");
1837 * Sets flags on emails. Assumes that connection is live, correct folder is
1839 * @param string $uids Sequence of UIDs, comma separated
1840 * @param string $type Flag to mark
1842 function markEmails($uids, $type) {
1845 $result = imap_clearflag_full($this->conn, $uids, '\\SEEN', ST_UID);
1848 $result = imap_setflag_full($this->conn, $uids, '\\SEEN', ST_UID);
1851 $result = imap_setflag_full($this->conn, $uids, '\\FLAGGED', ST_UID);
1854 $result = imap_clearflag_full($this->conn, $uids, '\\FLAGGED', ST_UID);
1857 $result = imap_setflag_full($this->conn, $uids, '\\Answered', ST_UID);
1861 //// END EMAIL 2.0 SPECIFIC
1862 ///////////////////////////////////////////////////////////////////////////
1866 ///////////////////////////////////////////////////////////////////////////
1867 //// SERVER MANIPULATION METHODS
1869 * Deletes the specified folder
1870 * @param string $mbox "::" delimited IMAP mailbox path, ie, INBOX.saved.stuff
1873 function deleteFolder($mbox) {
1874 $returnArray = array();
1875 if ($this->getCacheCount($mbox) > 0) {
1876 $returnArray['status'] = false;
1877 $returnArray['errorMessage'] = "Can not delete {$mbox} as it has emails.";
1878 return $returnArray;
1880 $connectString = $this->getConnectString('', $mbox);
1881 //Remove Folder cache
1882 global $sugar_config;
1883 unlink("{$sugar_config['cache_dir']}modules/Emails/{$this->id}/folders/folders.php");
1885 if(imap_unsubscribe($this->conn, imap_utf7_encode($connectString))) {
1886 if(imap_deletemailbox($this->conn, $connectString)) {
1887 $this->mailbox = str_replace(("," . $mbox), "", $this->mailbox);
1889 $sessionFoldersString = $this->getSessionInboundFoldersString($this->server_url, $this->email_user, $this->port, $this->protocol);
1890 $sessionFoldersString = str_replace(("," . $mbox), "", $sessionFoldersString);
1891 $this->setSessionInboundFoldersString($this->server_url, $this->email_user, $this->port, $this->protocol, $sessionFoldersString);
1892 $returnArray['status'] = true;
1893 return $returnArray;
1895 $GLOBALS['log']->error("*** ERROR: EMAIL2.0 - could not delete IMAP mailbox with path: [ {$connectString} ]");
1896 $returnArray['status'] = false;
1897 $returnArray['errorMessage'] = "NOOP: could not delete folder: {$connectString}";
1898 return $returnArray;
1902 $GLOBALS['log']->error("*** ERROR: EMAIL2.0 - could not unsubscribe from folder, {$connectString} before deletion.");
1903 $returnArray['status'] = false;
1904 $returnArray['errorMessage'] = "NOOP: could not unsubscribe from folder, {$connectString} before deletion.";
1905 return $returnArray;
1911 * @param string $name Name of new IMAP mailbox
1912 * @param string $mbox "::" delimited IMAP mailbox path, ie, INBOX.saved.stuff
1913 * @return bool True on success
1915 function saveNewFolder($name, $mbox) {
1916 global $sugar_config;
1917 //Remove Folder cache
1918 global $sugar_config;
1919 //unlink("{$sugar_config['cache_dir']}modules/Emails/{$this->id}/folders/folders.php");
1921 //$mboxImap = $this->getImapMboxFromSugarProprietary($mbox);
1922 $delimiter = $this->get_stored_options('folderDelimiter');
1927 $newFolder = $mbox . $delimiter . $name;
1928 $mbox .= $delimiter.str_replace($delimiter, "_", $name);
1929 $connectString = $this->getConnectString('', $mbox);
1931 if(imap_createmailbox($this->conn, imap_utf7_encode($connectString))) {
1932 imap_subscribe($this->conn, imap_utf7_encode($connectString));
1933 $status = imap_status($this->conn, str_replace("{$delimiter}{$name}","",$connectString));
1934 $this->mailbox = $this->mailbox . "," . $newFolder;
1936 $sessionFoldersString = $this->getSessionInboundFoldersString($this->server_url, $this->email_user, $this->port, $this->protocol);
1937 $sessionFoldersString = $sessionFoldersString . "," . $newFolder;
1938 $this->setSessionInboundFoldersString($this->server_url, $this->email_user, $this->port, $this->protocol, $sessionFoldersString);
1943 echo "NOOP: could not create folder";
1944 $GLOBALS['log']->error("*** ERROR: EMAIL2.0 - could not create IMAP mailbox with path: [ {$connectString} ]");
1951 * Constructs an IMAP c-client compatible folder path from Sugar proprietary
1952 * @param string $mbox "::" delimited IMAP mailbox path, ie, INBOX.saved.stuff
1955 function getImapMboxFromSugarProprietary($mbox) {
1956 $exMbox = explode("::", $mbox);
1960 for($i=2; $i<count($exMbox); $i++) {
1961 if(!empty($mboxImap)) {
1964 $mboxImap .= $exMbox[$i];
1971 * Searches IMAP (and POP3?) accounts/folders for emails with qualifying criteria
1973 function search($ieId, $subject='', $from='', $to='', $body='', $dateFrom='', $dateTo='') {
1974 global $current_user;
1975 global $app_strings;
1978 $dateFrom = $timedate->to_db_date($dateFrom, false);
1979 $dateTo = $timedate->to_db_date($dateTo, false);
1981 $bean = new InboundEmail();
1982 $bean->retrieve($ieId);
1984 //$beans = $this->retrieveAllByGroupId($current_user->id, true);
1986 $subject = urldecode($subject);
1989 $criteria .= (!empty($subject)) ? 'SUBJECT '.from_html($subject).'' : "";
1990 //$criteria .= (!empty($subject)) ? 'SUBJECT "'.$GLOBALS['db']->helper->escape_quote($subject).'"' : "";
1991 $criteria .= (!empty($from)) ? ' FROM "'.$from.'"' : "";
1992 $criteria .= (!empty($to)) ? ' FROM "'.$to.'"' : "";
1993 $criteria .= (!empty($body)) ? ' TEXT "'.$body.'"' : "";
1994 $criteria .= (!empty($dateFrom)) ? ' SINCE "'.date('d-M-Y', strtotime($dateFrom)).'"' : "";
1995 $criteria .= (!empty($dateTo)) ? ' BEFORE "'.date('d-M-Y', strtotime($dateTo)).'"' : "";
1996 //$criteria .= (!empty($from)) ? ' FROM "'.$from.'"' : "";
1998 $showFolders = unserialize(base64_decode($current_user->getPreference('showFolders', 'Emails')));
2002 foreach($beans as $bean) {
2003 if(!in_array($bean->id, $showFolders)) {
2007 $GLOBALS['log']->info("*** INBOUNDEMAIL: searching [ {$bean->name} ] for [ {$subject}{$from}{$to}{$body}{$dateFrom}{$dateTo} ]");
2008 $group = (!$bean->is_personal) ? 'group.' : '';
2009 $bean->connectMailServer();
2010 $mailboxes = $bean->getMailboxes(true);
2011 if (!in_array('INBOX', $mailboxes)) {
2012 $mailboxes[] = 'INBOX';
2016 foreach($mailboxes as $mbox) {
2017 $bean->mailbox = $mbox;
2018 $searchOverviews = array();
2019 if ($bean->protocol == 'pop3') {
2020 $pop3Criteria = "SELECT * FROM email_cache WHERE ie_id = '{$bean->id}' AND mbox = '{$mbox}'";
2021 $pop3Criteria .= (!empty($subject)) ? ' AND subject like "%'.$bean->db->helper->escape_quote($subject).'%"' : "";
2022 $pop3Criteria .= (!empty($from)) ? ' AND fromaddr like "%'.$from.'%"' : "";
2023 $pop3Criteria .= (!empty($to)) ? ' AND toaddr like "%'.$to.'%"' : "";
2024 $pop3Criteria .= (!empty($dateFrom)) ? ' AND senddate > "'.$dateFrom.'"' : "";
2025 $pop3Criteria .= (!empty($dateTo)) ? ' AND senddate < "'.$dateTo.'"' : "";
2026 $GLOBALS['log']->info("*** INBOUNDEMAIL: searching [ {$mbox} ] using criteria [ {$pop3Criteria} ]");
2028 $r = $bean->db->query($pop3Criteria);
2029 while($a = $bean->db->fetchByAssoc($r)) {
2030 $overview = new Overview();
2032 foreach($a as $k => $v) {
2036 $overview->imap_uid = $v;
2037 $overview->uid = $a['message_id'];
2040 $overview->to = from_html($v);
2044 $overview->from = from_html($v);
2048 $overview->size = $v;
2052 $overview->date = date('r', strtotime($v));
2056 $overview->$k = from_html($v);
2060 $searchOverviews[] = $overview;
2063 $bean->connectMailServer();
2064 $searchResult = imap_search($bean->conn, $criteria, SE_UID);
2065 if (!empty($searchResult)) {
2066 $searchOverviews = imap_fetch_overview($bean->conn, implode(',', $searchResult), FT_UID);
2069 $numHits = count($searchOverviews);
2072 $totalHits = $totalHits + $numHits;
2073 $ret = $bean->sortFetchedOverview($searchOverviews, 'date', 'desc', true);
2074 $mbox = "{$bean->id}.SEARCH";
2075 $out = array_merge($out, $bean->displayFetchedSortedListXML($ret, $mbox, false));
2080 $metadata = array();
2081 $metadata['mbox'] = $app_strings['LBL_EMAIL_SEARCH_RESULTS_TITLE'];
2082 $metadata['ieId'] = $this->id;
2083 $metadata['name'] = $this->name;
2084 $metadata['unreadChecked'] = ($current_user->getPreference('showUnreadOnly', 'Emails') == 1) ? 'CHECKED' : '';
2085 $metadata['out'] = $out;
2091 * repairs the encrypted password for a given I-E account
2092 * @return bool True on success
2094 function repairAccount() {
2096 for($i=0; $i<3; $i++) {
2097 if($i != 0) { // decode is performed on retrieve already
2098 $this->email_password = blowfishDecode(blowfishGetKey('InboundEmail'), $this->email_password);
2101 if($this->connectMailserver() == 'true') {
2102 $this->save(); // save decoded password (is encoded on save())
2111 * soft deletes a User's personal inbox
2112 * @param string id I-E id
2113 * @param string user_name User name of User in focus, NOT current_user
2114 * @return bool True on success
2116 function deletePersonalEmailAccount($id, $user_name) {
2117 $q = "SELECT ie.id FROM inbound_email ie LEFT JOIN users u ON ie.group_id = u.id WHERE u.user_name = '{$user_name}'";
2118 $r = $this->db->query($q, true);
2120 while($a = $this->db->fetchByAssoc($r)) {
2121 if(!empty($a) && $a['id'] == $id) {
2122 $this->retrieve($id);
2131 function getTeamSetIdForTeams($teamIds) {
2132 if(!is_array($teamIds)){
2133 $teamIds = array($teamIds);
2135 $teamSet = new TeamSet();
2136 $team_set_id = $teamSet->addTeams($teamIds);
2137 return $team_set_id;
2141 * Saves Personal Inbox settings for Users
2142 * @param string userId ID of user to assign all emails for this account
2143 * @param strings userName Name of account, for Sugar purposes
2144 * @param bool forceSave Default true. Flag to save errored settings.
2145 * @return boolean true on success, false on fail
2147 function savePersonalEmailAccount($userId = '', $userName = '', $forceSave=true) {
2149 $accountExists = false;
2150 if(isset($_REQUEST['ie_id']) && !empty($_REQUEST['ie_id'])) {
2151 $this->retrieve($_REQUEST['ie_id']);
2152 $accountExists = true;
2154 $ie_name = $_REQUEST['ie_name'];
2156 $this->is_personal = 1;
2157 $this->name = $ie_name;
2158 $this->group_id = $groupId;
2159 $this->status = $_REQUEST['ie_status'];
2160 $this->server_url = trim($_REQUEST['server_url']);
2161 $this->email_user = trim($_REQUEST['email_user']);
2162 $this->email_password = $_REQUEST['email_password'];
2163 $this->port = trim($_REQUEST['port']);
2164 $this->protocol = $_REQUEST['protocol'];
2165 if ($this->protocol == "pop3") {
2166 $_REQUEST['mailbox'] = "INBOX";
2168 $this->mailbox = $_REQUEST['mailbox'];
2169 $this->mailbox_type = 'pick'; // forcing this
2172 if(isset($_REQUEST['ssl']) && $_REQUEST['ssl'] == 1) { $useSsl = true; }
2173 else $useSsl = false;
2174 $this->service = '::::::::::';
2177 $id = $this->save(); // saving here to prevent user from having to re-enter all the info in case of error
2178 $this->retrieve($id);
2181 $this->protocol = $_REQUEST['protocol']; // need to set this again since we safe the "service" string to empty explode values
2182 $opts = $this->getSessionConnectionString($this->server_url, $this->email_user, $this->port, $this->protocol);
2183 $detectedOpts = $this->findOptimumSettings($useSsl);
2185 //If $detectedOpts is empty, there was an error connecting, so clear $opts. If $opts was empty, use $detectedOpts
2186 if (empty($opts) || empty($detectedOpts) || (empty($detectedOpts['good']) && empty($detectedOpts['serial'])))
2188 $opts = $detectedOpts;
2190 $delimiter = $this->getSessionInboundDelimiterString($this->server_url, $this->email_user, $this->port, $this->protocol);
2192 if(isset($opts['serial']) && !empty($opts['serial'])) {
2193 $this->service = $opts['serial'];
2194 if(isset($_REQUEST['mark_read']) && $_REQUEST['mark_read'] == 1) {
2195 $this->delete_seen = 0;
2197 $this->delete_seen = 1;
2200 // handle stored_options serialization
2201 if(isset($_REQUEST['only_since']) && $_REQUEST['only_since'] == 1) {
2207 $focusUser = new User();
2208 $focusUser->retrieve($groupId);
2209 $mailerId = (isset($_REQUEST['outbound_email'])) ? $_REQUEST['outbound_email'] : "";
2211 $oe = new OutboundEmail();
2212 $oe->getSystemMailerSettings($focusUser, $mailerId);
2214 $stored_options = array();
2215 $stored_options['from_name'] = trim($_REQUEST['from_name']);
2216 $stored_options['from_addr'] = trim($_REQUEST['from_addr']);
2217 $stored_options['reply_to_addr'] = trim($_REQUEST['reply_to_addr']);
2219 if (!$this->isPop3Protocol()) {
2220 $stored_options['trashFolder'] = (isset($_REQUEST['trashFolder']) ? trim($_REQUEST['trashFolder']) : "");
2221 $stored_options['sentFolder'] = (isset($_REQUEST['sentFolder']) ? trim($_REQUEST['sentFolder']) : "");
2223 $stored_options['only_since'] = $onlySince;
2224 $stored_options['filter_domain'] = '';
2225 $storedOptions['folderDelimiter'] = $delimiter;
2226 $stored_options['outbound_email'] = (isset($_REQUEST['outbound_email'])) ? $_REQUEST['outbound_email'] : $oe->id;
2227 $this->stored_options = base64_encode(serialize($stored_options));
2229 $ieId = $this->save();
2231 //If this is the first personal account the user has setup mark it as default for them.
2232 $currentIECount = $this->getUserPersonalAccountCount($focusUser);
2233 if($currentIECount == 1)
2234 $this->setUsersDefaultOutboundServerId($focusUser, $ieId);
2238 // could not find opts, no save
2239 $GLOBALS['log']->debug('-----> InboundEmail could not find optimums for User: '.$ie_name);
2244 * Determines if this instance of I-E is for a Group Inbox or Personal Inbox
2246 function handleIsPersonal() {
2247 $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.'\'';
2248 $rp = $this->db->query($qp, true);
2249 $personalBox = array();
2250 while($ap = $this->db->fetchByAssoc($rp)) {
2251 $personalBox[] = array($ap['id'], $ap['user_name']);
2253 if(count($personalBox) > 0) {
2260 function getUserNameFromGroupId() {
2261 $r = $this->db->query('SELECT users.user_name FROM users WHERE deleted=0 AND id=\''.$this->group_id.'\'', true);
2262 while($a = $this->db->fetchByAssoc($r)) {
2263 return $a['user_name'];
2268 function getFoldersListForMailBox() {
2270 $foldersList = $this->getSessionInboundFoldersString($this->server_url, $this->email_user, $this->port, $this->protocol);
2271 if (empty($foldersList)) {
2272 global $mod_strings;
2273 $msg = $this->connectMailserver(true);
2274 if (strpos($msg, "successfully")) {
2275 $foldersList = $this->getSessionInboundFoldersString($this->server_url, $this->email_user, $this->port, $this->protocol);
2276 $return['status'] = true;
2277 $return['foldersList'] = $foldersList;
2278 $return['statusMessage'] = "";
2280 $return['status'] = false;
2281 $return['statusMessage'] = $msg;
2284 $return['status'] = true;
2285 $return['foldersList'] = $foldersList;
2286 $return['statusMessage'] = "";
2291 * Programatically determines best-case settings for imap_open()
2293 function findOptimumSettings($useSsl=false, $user='', $pass='', $server='', $port='', $prot='', $mailbox='') {
2294 global $mod_strings;
2295 $serviceArr = array();
2296 $returnService = array();
2297 $badService = array();
2298 $goodService = array();
2299 $errorArr = array();
2301 $retArray = array( 'good' => $goodService,
2302 'bad' => $badService,
2303 'err' => $errorArr);
2305 if(!function_exists('imap_open')) {
2306 $retArray['err'][0] = $mod_strings['ERR_NO_IMAP'];
2310 imap_errors(); // clearing error stack
2311 error_reporting(0); // turn off notices from IMAP
2313 if(isset($_REQUEST['ssl']) && $_REQUEST['ssl'] == 1) {
2317 $exServ = explode('::', $this->service);
2318 $service = '/'.$exServ[1];
2320 $nonSsl = array('both-secure' => '/notls/novalidate-cert/secure',
2321 'both' => '/notls/novalidate-cert',
2322 'nocert-secure' => '/novalidate-cert/secure',
2323 'nocert' => '/novalidate-cert',
2324 'notls-secure' => '/notls/secure',
2325 'secure' => '/secure', // for POP3 servers that force CRAM-MD5
2326 'notls' => '/notls',
2327 'none' => '', // try default nothing
2330 'ssl-both-on-secure' => '/ssl/tls/validate-cert/secure',
2331 'ssl-both-on' => '/ssl/tls/validate-cert',
2332 'ssl-cert-secure' => '/ssl/validate-cert/secure',
2333 'ssl-cert' => '/ssl/validate-cert',
2334 'ssl-tls-secure' => '/ssl/tls/secure',
2335 'ssl-tls' => '/ssl/tls',
2336 'ssl-both-off-secure' => '/ssl/notls/novalidate-cert/secure',
2337 'ssl-both-off' => '/ssl/notls/novalidate-cert',
2338 'ssl-nocert-secure' => '/ssl/novalidate-cert/secure',
2339 'ssl-nocert' => '/ssl/novalidate-cert',
2340 'ssl-notls-secure' => '/ssl/notls/secure',
2341 'ssl-notls' => '/ssl/notls',
2342 'ssl-secure' => '/ssl/secure',
2343 'ssl-none' => '/ssl',
2346 if(isset($user) && !empty($user) && isset($pass) && !empty($pass)) {
2347 $this->email_password = $pass;
2348 $this->email_user = $user;
2349 $this->server_url = $server;
2350 $this->port = $port;
2351 $this->protocol = $prot;
2352 $this->mailbox = $mailbox;
2355 // in case we flip from IMAP to POP3
2356 if($this->protocol == 'pop3')
2357 $this->mailbox = 'INBOX';
2359 //If user has selected multiple mailboxes, we only need to test the first mailbox for the connection string.
2360 $a_mailbox = explode(",", $this->mailbox);
2361 $tmpMailbox = isset($a_mailbox[0]) ? $a_mailbox[0] : "";
2365 foreach($ssl as $k => $service)
2367 $returnService[$k] = 'foo'.$service;
2368 $serviceArr[$k] = '{'.$this->server_url.':'.$this->port.'/service='.$this->protocol.$service.'}'.$tmpMailbox;
2373 foreach($nonSsl as $k => $service)
2375 $returnService[$k] = 'foo'.$service;
2376 $serviceArr[$k] = '{'.$this->server_url.':'.$this->port.'/service='.$this->protocol.$service.'}'.$tmpMailbox;
2380 $GLOBALS['log']->debug('---------------STARTING FINDOPTIMUMS LOOP----------------');
2383 //php imap library will capture c-client library warnings as errors causing good connections to be ignored.
2384 //Check against known warnings to ensure good connections are used.
2385 $acceptableWarnings = array("SECURITY PROBLEM: insecure server advertised AUTH=PLAIN", //c-client auth_pla.c
2386 "Mailbox is empty");
2387 $login = $this->email_user;
2388 $passw = $this->email_password;
2389 $foundGoodConnection = false;
2390 foreach($serviceArr as $k => $serviceTest) {
2393 $GLOBALS['log']->debug($l.': I-E testing string: '.$serviceTest);
2395 // open the connection and try the test string
2396 $this->conn = imap_open($serviceTest, $login, $passw);
2398 if(($errors = imap_last_error()) || ($alerts = imap_alerts())) {
2399 if($errors == 'Too many login failures' || $errors == '[CLOSED] IMAP connection broken (server response)') { // login failure means don't bother trying the rest
2400 $GLOBALS['log']->debug($l.': I-E failed using ['.$serviceTest.']');
2401 $retArray['err'][$k] = $mod_strings['ERR_BAD_LOGIN_PASSWORD'];
2402 $retArray['bad'][$k] = $serviceTest;
2403 $GLOBALS['log']->debug($l.': I-E ERROR: $ie->findOptimums() failed due to bad user credentials for user login: '.$this->email_user);
2405 } elseif( in_array($errors, $acceptableWarnings, TRUE)) { // false positive
2406 $GLOBALS['log']->debug($l.': I-E found good connection but with warnings ['.$serviceTest.'] Errors:' . $errors);
2407 $retArray['good'][$k] = $returnService[$k];
2408 $foundGoodConnection = true;
2411 $GLOBALS['log']->debug($l.': I-E failed using ['.$serviceTest.'] - error: '.$errors);
2412 $retArray['err'][$k] = $errors;
2413 $retArray['bad'][$k] = $serviceTest;
2416 $GLOBALS['log']->debug($l.': I-E found good connect using ['.$serviceTest.']');
2417 $retArray['good'][$k] = $returnService[$k];
2418 $foundGoodConnection = true;
2421 if(is_resource($this->conn)) {
2422 if (!$this->isPop3Protocol()) {
2423 $serviceTest = str_replace("INBOX", "", $serviceTest);
2424 $boxes = imap_getmailboxes($this->conn, $serviceTest, "*");
2426 // clean MBOX path names
2427 foreach($boxes as $k => $mbox) {
2428 $raw[] = $mbox->name;
2429 if ($mbox->delimiter) {
2430 $delimiter = $mbox->delimiter;
2433 $this->setSessionInboundDelimiterString($this->server_url, $this->email_user, $this->port, $this->protocol, $delimiter);
2436 if(!imap_close($this->conn)) $GLOBALS['log']->debug('imap_close() failed!');
2439 $GLOBALS['log']->debug($l.': I-E clearing error and alert stacks.');
2440 imap_errors(); // clear stacks
2442 // If you find a good connection, then don't do any further testing to find URL
2443 if ($foundGoodConnection) {
2448 $GLOBALS['log']->debug('---------------end FINDOPTIMUMS LOOP----------------');
2450 if(!empty($retArray['good'])) {
2455 $newNovalidate_cert = '';
2456 $good = array_pop($retArray['good']); // get most complete string
2457 $exGood = explode('/', $good);
2458 foreach($exGood as $v) {
2467 $newNotls = 'notls';
2470 $newCert = 'validate-cert';
2472 case 'novalidate-cert':
2473 $newNovalidate_cert = 'novalidate-cert';
2481 $goodStr['serial'] = $newTls.'::'.$newCert.'::'.$newSsl.'::'.$this->protocol.'::'.$newNovalidate_cert.'::'.$newNotls.'::'.$secure;
2482 $goodStr['service'] = $good;
2483 $testConnectString = str_replace('foo','', $good);
2484 $testConnectString = '{'.$this->server_url.':'.$this->port.'/service='.$this->protocol.$testConnectString.'}';
2485 $this->setSessionConnectionString($this->server_url, $this->email_user, $this->port, $this->protocol, $goodStr);
2487 foreach($raw as $mbox)
2489 $raw[$i] = str_replace($testConnectString, "", mb_convert_encoding($mbox, "UTF8", "UTF7-IMAP" ));
2493 $this->setSessionInboundFoldersString($this->server_url, $this->email_user, $this->port, $this->protocol, implode(",", $raw));
2500 function getSessionConnectionString($server_url, $email_user, $port, $protocol) {
2501 $sessionConnectionString = $server_url . $email_user . $port . $protocol;
2502 return (isset($_SESSION[$sessionConnectionString]) ? $_SESSION[$sessionConnectionString] : "");
2505 function setSessionConnectionString($server_url, $email_user, $port, $protocol, $goodStr) {
2506 $sessionConnectionString = $server_url . $email_user . $port . $protocol;
2507 $_SESSION[$sessionConnectionString] = $goodStr;
2510 function getSessionInboundDelimiterString($server_url, $email_user, $port, $protocol) {
2511 $sessionInboundDelimiterString = $server_url . $email_user . $port . $protocol . "delimiter";
2512 return (isset($_SESSION[$sessionInboundDelimiterString]) ? $_SESSION[$sessionInboundDelimiterString] : "");
2515 function setSessionInboundDelimiterString($server_url, $email_user, $port, $protocol, $delimiter) {
2516 $sessionInboundDelimiterString = $server_url . $email_user . $port . $protocol . "delimiter";
2517 $_SESSION[$sessionInboundDelimiterString] = $delimiter;
2520 function getSessionInboundFoldersString($server_url, $email_user, $port, $protocol) {
2521 $sessionInboundFoldersListString = $server_url . $email_user . $port . $protocol . "foldersList";
2522 return (isset($_SESSION[$sessionInboundFoldersListString]) ? $_SESSION[$sessionInboundFoldersListString] : "");
2525 function setSessionInboundFoldersString($server_url, $email_user, $port, $protocol, $foldersList) {
2526 $sessionInboundFoldersListString = $server_url . $email_user . $port . $protocol . "foldersList";
2527 $_SESSION[$sessionInboundFoldersListString] = $foldersList;
2531 * Checks for duplicate Group User names when creating a new one at save()
2532 * @return GUID returns GUID of Group User if user_name match is
2534 * @return boolean false if NO DUPE IS FOUND
2536 function groupUserDupeCheck() {
2537 $q = "SELECT u.id FROM users u WHERE u.deleted=0 AND u.is_group=1 AND u.user_name = '".$this->name."'";
2538 $r = $this->db->query($q, true);
2540 while($a = $this->db->fetchByAssoc($r)) {
2544 if(strlen($uid) > 0) {
2552 * Returns <option> markup with the contents of Group users
2553 * @param array $groups default empty array
2554 * @return string HTML options
2556 function getGroupsWithSelectOptions($groups = array()) {
2557 $r = $this->db->query('SELECT id, user_name FROM users WHERE users.is_group = 1 AND deleted = 0', true);
2558 if(is_resource($r)) {
2559 while($a = $this->db->fetchByAssoc($r)) {
2560 $groups[$a['id']] = $a['user_name'];
2564 $selectOptions = get_select_options_with_id_separate_key($groups, $groups, $this->group_id);
2565 return $selectOptions;
2569 * handles auto-responses to inbound emails
2571 * @param object email Email passed as reference
2573 function handleAutoresponse(&$email, &$contactAddr) {
2574 if($this->template_id) {
2575 $GLOBALS['log']->debug('found auto-reply template id - prefilling and mailing response');
2577 if($this->getAutoreplyStatus($contactAddr)
2578 && $this->checkOutOfOffice($email->name)
2579 && $this->checkFilterDomain($email)) { // if we haven't sent this guy 10 replies in 24hours
2581 if(!empty($this->stored_options)) {
2582 $storedOptions = unserialize(base64_decode($this->stored_options));
2585 if(!empty($storedOptions['from_name'])) {
2586 $from_name = $storedOptions['from_name'];
2587 $GLOBALS['log']->debug('got from_name from storedOptions: '.$from_name);
2588 } else { // use system default
2589 $rName = $this->db->query('SELECT value FROM config WHERE name = \'fromname\'', true);
2590 if(is_resource($rName)) {
2591 $aName = $this->db->fetchByAssoc($rName);
2593 if(!empty($aName['value'])) {
2594 $from_name = $aName['value'];
2600 if(!empty($storedOptions['from_addr'])) {
2601 $from_addr = $storedOptions['from_addr'];
2603 $rAddr = $this->db->query('SELECT value FROM config WHERE name = \'fromaddress\'', true);
2604 if(is_resource($rAddr)) {
2605 $aAddr = $this->db->fetchByAssoc($rAddr);
2607 if(!empty($aAddr['value'])) {
2608 $from_addr = $aAddr['value'];
2614 $replyToName = (!empty($storedOptions['reply_to_name']))? from_html($storedOptions['reply_to_name']) :$fromName ;
2615 $replyToAddr = (!empty($storedOptions['reply_to_addr'])) ? $storedOptions['reply_to_addr'] : $fromAddress;
2617 if(!empty($email->reply_to_email)) {
2618 $to[0]['email'] = $email->reply_to_email;
2620 $to[0]['email'] = $email->from_addr;
2622 // handle to name: address, prefer reply-to
2623 if(!empty($email->reply_to_name)) {
2624 $to[0]['display'] = $email->reply_to_name;
2625 } elseif(!empty($email->from_name)) {
2626 $to[0]['display'] = $email->from_name;
2629 if(!class_exists('EmailTemplate')) {
2632 $et = new EmailTemplate();
2633 $et->retrieve($this->template_id);
2634 if(empty($et->subject)) { $et->subject = ''; }
2635 if(empty($et->body)) { $et->body = ''; }
2636 if(empty($et->body_html)) { $et->body_html = ''; }
2638 $reply = new Email();
2639 $reply->type = 'out';
2640 $reply->to_addrs = $to[0]['email'];
2641 $reply->to_addrs_arr = $to;
2642 $reply->cc_addrs_arr = array();
2643 $reply->bcc_addrs_arr = array();
2644 $reply->from_name = $from_name;
2645 $reply->from_addr = $from_addr;
2646 $reply->name = $et->subject;
2647 $reply->description = $et->body;
2648 $reply->description_html = $et->body_html;
2649 $reply->reply_to_name = $replyToName;
2650 $reply->reply_to_addr = $replyToAddr;
2652 $GLOBALS['log']->debug('saving and sending auto-reply email');
2653 //$reply->save(); // don't save the actual email.
2655 $this->setAutoreplyStatus($contactAddr);
2657 $GLOBALS['log']->debug('InboundEmail: auto-reply threshold reached for email ('.$contactAddr.') - not sending auto-reply');
2662 function handleCaseAssignment($email) {
2663 if(!class_exists('aCase')) {
2667 if($caseId = $this->getCaseIdFromCaseNumber($email->name, $c)) {
2668 $c->retrieve($caseId);
2669 $c->load_relationship('emails');
2670 $c->emails->add($email->id);
2672 $email->retrieve($email->id);
2673 $email->parent_type = "Cases";
2674 $email->parent_id = $caseId;
2675 // assign the email to the case owner
2676 $email->assigned_user_id = $c->assigned_user_id;
2678 $GLOBALS['log']->debug('InboundEmail found exactly 1 match for a case: '.$c->name);
2685 * handles functionality specific to the Mailbox type (Cases, bounced
2688 * @param object email Email object passed as a reference
2689 * @param object header Header object generated by imap_headerinfo();
2691 function handleMailboxType(&$email, &$header) {
2692 switch($this->mailbox_type) {
2694 $this->handleCaseAssignment($email);
2701 // do something with this?
2704 // do something with leads? we don't have an email_leads table
2710 require_once('modules/Campaigns/ProcessBouncedEmails.php');
2711 campaign_process_bounced_emails($email, $header);
2713 case 'pick': // do all except bounce handling
2714 $GLOBALS['log']->debug('looking for a case for '.$email->name);
2715 $this->handleCaseAssignment($email);
2720 function isMailBoxTypeCreateCase() {
2721 return ($this->mailbox_type == 'createcase' && !empty($this->groupfolder_id));
2724 function handleCreateCase($email, $userId) {
2725 global $current_user, $mod_strings, $current_language;
2726 $mod_strings = return_module_language($current_language, "Emails");
2727 $GLOBALS['log']->debug('In handleCreateCase');
2728 if(!class_exists('aCase')) {
2732 $this->getCaseIdFromCaseNumber($email->name, $c);
2734 if (!$this->handleCaseAssignment($email) && $this->isMailBoxTypeCreateCase()) {
2736 $GLOBALS['log']->debug('retrieveing email');
2737 $email->retrieve($email->id);
2739 $c->description = $email->description;
2740 $c->assigned_user_id = $userId;
2741 $c->name = $email->name;
2743 $c->priority = 'P1';
2745 if(!empty($email->reply_to_email)) {
2746 $contactAddr = $email->reply_to_email;
2748 $contactAddr = $email->from_addr;
2751 $GLOBALS['log']->debug('finding related accounts with address ' . $contactAddr);
2752 if($accountIds = $this->getRelatedId($contactAddr, 'accounts')) {
2753 if (sizeof($accountIds) == 1) {
2754 $c->account_id = $accountIds[0];
2756 $acct = new Account();
2757 $acct->retrieve($c->account_id);
2758 $c->account_name = $acct->name;
2764 $c->retrieve($caseId);
2765 if($c->load_relationship('emails')) {
2766 $c->emails->add($email->id);
2768 if($contactIds = $this->getRelatedId($contactAddr, 'contacts')) {
2769 if(!empty($contactIds) && $c->load_relationship('contacts')) {
2770 $c->contacts->add($contactIds);
2773 $c->email_id = $email->id;
2774 $email->parent_type = "Cases";
2775 $email->parent_id = $caseId;
2776 // assign the email to the case owner
2777 $email->assigned_user_id = $c->assigned_user_id;
2778 $email->name = str_replace('%1', $c->case_number, $c->getEmailSubjectMacro()) . " ". $email->name;
2780 $GLOBALS['log']->debug('InboundEmail created one case with number: '.$c->case_number);
2781 $createCaseTemplateId = $this->get_stored_options('create_case_email_template', "");
2782 if(!empty($this->stored_options)) {
2783 $storedOptions = unserialize(base64_decode($this->stored_options));
2785 if(!empty($createCaseTemplateId)) {
2788 if (!empty($this->stored_options)) {
2789 $fromAddress = $storedOptions['from_addr'];
2790 $fromName = from_html($storedOptions['from_name']);
2791 $replyToName = (!empty($storedOptions['reply_to_name']))? from_html($storedOptions['reply_to_name']) :$fromName ;
2792 $replyToAddr = (!empty($storedOptions['reply_to_addr'])) ? $storedOptions['reply_to_addr'] : $fromAddress;
2794 $defaults = $current_user->getPreferredEmail();
2795 $fromAddress = (!empty($fromAddress)) ? $fromAddress : $defaults['email'];
2796 $fromName = (!empty($fromName)) ? $fromName : $defaults['name'];
2797 $to[0]['email'] = $contactAddr;
2799 // handle to name: address, prefer reply-to
2800 if(!empty($email->reply_to_name)) {
2801 $to[0]['display'] = $email->reply_to_name;
2802 } elseif(!empty($email->from_name)) {
2803 $to[0]['display'] = $email->from_name;
2806 if(!class_exists('EmailTemplate')) {
2809 $et = new EmailTemplate();
2810 $et->retrieve($createCaseTemplateId);
2811 if(empty($et->subject)) { $et->subject = ''; }
2812 if(empty($et->body)) { $et->body = ''; }
2813 if(empty($et->body_html)) { $et->body_html = ''; }
2815 $et->subject = "Re:" . " " . str_replace('%1', $c->case_number, $c->getEmailSubjectMacro() . " ". $c->name);
2817 $html = trim($email->description_html);
2818 $plain = trim($email->description);
2820 $email->email2init();
2821 $email->from_addr = $email->from_addr_name;
2822 $email->to_addrs = $email->to_addrs_names;
2823 $email->cc_addrs = $email->cc_addrs_names;
2824 $email->bcc_addrs = $email->bcc_addrs_names;
2825 $email->from_name = $email->from_addr;
2827 $email = $email->et->handleReplyType($email, "reply");
2828 $ret = $email->et->displayComposeEmail($email);
2829 $ret['description'] = empty($email->description_html) ? str_replace("\n", "\n<BR/>", $email->description) : $email->description_html;
2831 $reply = new Email();
2832 $reply->type = 'out';
2833 $reply->to_addrs = $to[0]['email'];
2834 $reply->to_addrs_arr = $to;
2835 $reply->cc_addrs_arr = array();
2836 $reply->bcc_addrs_arr = array();
2837 $reply->from_name = $fromName;
2838 $reply->from_addr = $fromAddress;
2839 $reply->reply_to_name = $replyToName;
2840 $reply->reply_to_addr = $replyToAddr;
2841 $reply->name = $et->subject;
2842 $reply->description = $et->body . "<div><hr /></div>" . $email->description;
2843 if (!$et->text_only) {
2844 $reply->description_html = $et->body_html . "<div><hr /></div>" . $email->description;
2846 $GLOBALS['log']->debug('saving and sending auto-reply email');
2847 //$reply->save(); // don't save the actual email.
2852 if(!empty($email->reply_to_email)) {
2853 $contactAddr = $email->reply_to_email;
2855 $contactAddr = $email->from_addr;
2857 $this->handleAutoresponse($email, $contactAddr);
2863 * handles linking contacts, accounts, etc. to an email
2865 * @param object Email bean to be linked against
2866 * @return string contactAddr is the email address of the sender
2868 function handleLinking(&$email) {
2869 // link email to an User if emails match TO addr
2870 if($userIds = $this->getRelatedId($email->to_addrs, 'users')) {
2871 $GLOBALS['log']->debug('I-E linking email to User');
2872 // link the user to the email
2873 $email->load_relationship('users');
2874 $email->users->add($userIds);
2877 // link email to a Contact, Lead, or Account if the emails match
2878 // give precedence to REPLY-TO above FROM
2879 if(!empty($email->reply_to_email)) {
2880 $contactAddr = $email->reply_to_email;
2882 $contactAddr = $email->from_addr;
2885 // Samir Gandhi : 12/06/07
2886 // This changes has been done because the linking was done only with the from address and
2887 // not with to address
2888 $relationShipAddress = $contactAddr;
2889 if (empty($relationShipAddress)) {
2890 $relationShipAddress .= $email->to_addrs;
2892 $relationShipAddress = $relationShipAddress . "," . $email->to_addrs;
2894 if($leadIds = $this->getRelatedId($relationShipAddress, 'leads')) {
2895 $GLOBALS['log']->debug('I-E linking email to Lead');
2896 $email->load_relationship('leads');
2897 $email->leads->add($leadIds);
2899 if(!class_exists('Lead')) {
2902 foreach($leadIds as $leadId) {
2904 $lead->retrieve($leadId);
2905 $lead->load_relationship('emails');
2906 $lead->emails->add($email->id);
2910 if($contactIds = $this->getRelatedId($relationShipAddress, 'contacts')) {
2911 $GLOBALS['log']->debug('I-E linking email to Contact');
2912 // link the contact to the email
2913 $email->load_relationship('contacts');
2914 $email->contacts->add($contactIds);
2917 if($accountIds = $this->getRelatedId($relationShipAddress, 'accounts')) {
2918 $GLOBALS['log']->debug('I-E linking email to Account');
2919 // link the account to the email
2920 $email->load_relationship('accounts');
2921 $email->accounts->add($accountIds);
2923 if(!class_exists('Account')) {
2927 /* cn: bug 9171 another cause of dying I-E - bad linking
2928 foreach($accountIds as $accountId) {
2929 $GLOBALS['log']->debug('I-E reverse-linking Accounts to Emails');
2930 $acct = new Account();
2931 $acct->retrieve($accountId);
2932 $acct->load_relationship('emails');
2933 $acct->account_emails->add($email->id);
2937 return $contactAddr;
2940 * takes a breadcrumb and returns the encoding at that level
2941 * @param string bc the breadcrumb string in format (1.1.1)
2942 * @param array parts the root level parts array
2943 * @return int retInt Int key to transfer encoding (see handleTranserEncoding())
2945 function getEncodingFromBreadCrumb($bc, $parts) {
2946 if(strstr($bc,'.')) {
2947 $exBc = explode('.', $bc);
2952 $depth = count($exBc);
2954 for($i=0; $i<$depth; $i++) {
2955 $tempObj[$i] = $parts[($exBc[$i]-1)];
2956 $retInt = imap_utf8($tempObj[$i]->encoding);
2957 if(!empty($tempObj[$i]->parts)) {
2958 $parts = $tempObj[$i]->parts;
2965 * retrieves the charset for a given part of an email body
2967 * @param string bc target part of the message in format (1.1.1)
2968 * @param array parts 1 level above ROOT array of Objects representing a multipart body
2969 * @return string charset name
2971 function getCharsetFromBreadCrumb($bc, $parts) {
2972 if(strstr($bc,'.')) {
2973 $exBc = explode('.', $bc);
2978 foreach($exBc as $crumb) {
2979 $tempObj = $parts[$crumb-1];
2980 if(is_array($tempObj->parts)) {
2981 $parts = $tempObj->parts;
2985 // now we have the tempObj at the end of the breadCrumb trail
2986 //$GLOBALS['log']->fatal(print_r(debug_backtrace(), true));
2988 if($tempObj->ifparameters) {
2989 foreach($tempObj->parameters as $param) {
2990 if(strtolower($param->attribute) == 'charset') {
2991 return $param->value;
3000 * Get the message text from a single mime section, html or plain.
3002 * @param string $msgNo
3003 * @param string $section
3004 * @param stdObject $structure
3007 function getMessageTextFromSingleMimePart($msgNo,$section,$structure)
3009 $msgPartTmp = imap_fetchbody($this->conn, $msgNo, $section);
3010 $enc = $this->getEncodingFromBreadCrumb($section, $structure->parts);
3011 $charset = $this->getCharsetFromBreadCrumb($section, $structure->parts);
3012 $msgPartTmp = $this->handleTranserEncoding($msgPartTmp, $enc);
3013 return $this->handleCharsetTranslation($msgPartTmp, $charset);
3017 * Givin an existing breadcrumb add a cooresponding offset
3020 * @param string $offset
3023 function addBreadCrumbOffset($bc, $offset)
3025 if( (empty($bc) || is_null($bc)) && !empty($offset) )
3028 $a_bc = explode(".", $bc);
3029 $a_offset = explode(".",$offset);
3030 if(count($a_bc) < count($a_offset))
3031 $a_bc = array_merge($a_bc,array_fill( count($a_bc), count($a_offset) - count($a_bc), 0));
3034 for($i=0;$i < count($a_bc); $i++)
3036 if(isset($a_offset[$i]))
3037 $results[] = $a_bc[$i] + $a_offset[$i];
3039 $results[] = $a_bc[$i];
3041 return implode(".", $results);
3045 * returns the HTML text part of a multi-part message
3047 * @param int msgNo the relative message number for the monitored mailbox
3048 * @param string $type the type of text processed, either 'PLAIN' or 'HTML'
3049 * @return string UTF-8 encoded version of the requested message text
3051 function getMessageText($msgNo, $type, $structure, $fullHeader,$clean_email=true, $bcOffset = "") {
3052 global $sugar_config;
3055 $bc = $this->buildBreadCrumbs($structure->parts, $type);
3056 //Add an offset if specified
3057 if(!empty($bcOffset))
3058 $bc = $this->addBreadCrumbOffset($bc, $bcOffset);
3060 if(!empty($bc)) { // multi-part
3061 // HUGE difference between PLAIN and HTML
3062 if($type == 'PLAIN') {
3063 $msgPart = $this->getMessageTextFromSingleMimePart($msgNo,$bc,$structure);
3065 // get part of structure that will
3067 $bcArray = $this->buildBreadCrumbsHTML($structure->parts,$bcOffset);
3068 // construct inline HTML/Rich msg
3069 foreach($bcArray as $bcArryKey => $bcArr) {
3070 foreach($bcArr as $type => $bcTrail) {
3072 $msgPartRaw .= $this->getMessageTextFromSingleMimePart($msgNo,$bcTrail,$structure);
3074 // deal with inline image
3075 if(count($this->inlineImages > 0)) {
3076 $imageName = array_shift($this->inlineImages);
3077 $newImagePath = "class='image' src='{$sugar_config['site_url']}/{$sugar_config['cache_dir']}/images/{$imageName}'";
3078 $preImagePath = 'src="cid:'.substr($structure->parts[$bcArryKey]->id, 1, -1).'"';
3079 $msgPartRaw = str_replace($preImagePath, $newImagePath, $msgPartRaw);
3084 $msgPart = $msgPartRaw;
3086 } else { // either PLAIN message type (flowed) or b0rk3d RFC
3087 // make sure we're working on valid data here.
3088 if($structure->subtype != $type) {
3092 $decodedHeader = $this->decodeHeader($fullHeader);
3094 // now get actual body contents
3095 $text = imap_body($this->conn, $msgNo);
3097 $upperCaseKeyDecodeHeader = array();
3098 if (is_array($decodedHeader)) {
3099 $upperCaseKeyDecodeHeader = array_change_key_case($decodedHeader, CASE_UPPER);
3101 if(isset($upperCaseKeyDecodeHeader[strtoupper('Content-Transfer-Encoding')])) {
3102 $flip = array_flip($this->transferEncoding);
3103 $text = $this->handleTranserEncoding($text, $flip[strtoupper($upperCaseKeyDecodeHeader[strtoupper('Content-Transfer-Encoding')])]);
3107 if(isset($upperCaseKeyDecodeHeader[strtoupper('Content-Type')]['charset']) && !empty($upperCaseKeyDecodeHeader[strtoupper('Content-Type')]['charset'])) {
3108 $msgPart = $this->handleCharsetTranslation($text, $upperCaseKeyDecodeHeader[strtoupper('Content-Type')]['charset']);
3110 } // end else clause
3112 $msgPart = $this->customGetMessageText($msgPart);
3113 /* cn: bug 9176 - htmlEntitites hide XSS attacks.
3114 * decode to pass refreshed HTML to HTML_Safe */
3115 if ($type == 'PLAIN')
3116 return $this->cleanXssContent(to_html($msgPart));
3119 $safedMsgPart = $this->cleanContent($msgPart);
3120 return str_replace("<img />", '', $safedMsgPart);
3125 * decodes raw header information and passes back an associative array with
3126 * the important elements key'd by name
3127 * @param header string the raw header
3128 * @return decodedHeader array the associative array
3130 function decodeHeader($fullHeader) {
3131 $decodedHeader = array();
3132 $exHeaders = explode("\r", $fullHeader);
3133 if (!is_array($exHeaders)) {
3134 $exHeaders = explode("\r\n", $fullHeader);
3136 $quotes = array('"', "'");
3138 foreach($exHeaders as $lineNum => $head) {
3140 $key = trim(substr($head, 0, strpos($head, ':')));
3142 $value = trim(substr($head, (strpos($head, ':') + 1), strlen($head)));
3144 // handle content-type section in headers
3145 if(strtolower($key) == 'content-type' && strpos($value, ';')) { // ";" means something follows related to (such as Charset)
3146 $semiColPos = mb_strpos($value, ';');
3147 $strLenVal = mb_strlen($value);
3148 if(($semiColPos + 4) >= $strLenVal) {
3149 // the charset="[something]" is on the next line
3150 $value .= str_replace($quotes, "", trim($exHeaders[$lineNum+1]));
3153 $newValue = array();
3154 $exValue = explode(';', $value);
3155 $newValue['type'] = $exValue[0];
3157 for($i=1; $i<count($exValue); $i++) {
3158 $exContent = explode('=', $exValue[$i]);
3159 $newValue[trim($exContent[0])] = trim($exContent[1]);
3164 if(!empty($key) && !empty($value)) {
3165 $decodedHeader[$key] = $value;
3169 return $decodedHeader;
3173 * handles translating message text from orignal encoding into UTF-8
3175 * @param string text test to be re-encoded
3176 * @param string charset original character set
3177 * @return string utf8 re-encoded text
3179 function handleCharsetTranslation($text, $charset) {
3182 if(empty($charset)) {
3183 $GLOBALS['log']->debug("***ERROR: InboundEmail::handleCharsetTranslation() called without a \$charset!");
3184 $GLOBALS['log']->debug("***STACKTRACE: ".print_r(debug_backtrace(), true));
3188 // typical headers have no charset - let destination pick (since it's all ASCII anyways)
3189 if(strtolower($charset) == 'default' || strtolower($charset) == 'utf-8') {
3193 return $locale->translateCharset($text, $charset);
3199 * Builds up the "breadcrumb" trail that imap_fetchbody() uses to return
3200 * parts of an email message, including attachments and inline images
3201 * @param $parts array of objects
3202 * @param $subtype what type of trail to return? HTML? Plain? binaries?
3203 * @param $breadcrumb text trail to build up
3205 function buildBreadCrumbs($parts, $subtype, $breadcrumb = '0') {
3206 //_pp('buildBreadCrumbs building for '.$subtype.' with BC at '.$breadcrumb);
3207 // loop through available parts in the array
3208 foreach($parts as $k => $part) {
3209 // mark passage through level
3211 // if this is not the first time through, start building the map
3212 if($breadcrumb != 0) {
3213 $thisBc = $breadcrumb.'.'.$thisBc;
3216 // found a multi-part/mixed 'part' - keep digging
3217 if($part->type == 1 && (strtoupper($part->subtype) == 'RELATED' || strtoupper($part->subtype) == 'ALTERNATIVE' || strtoupper($part->subtype) == 'MIXED')) {
3218 //_pp('in loop: going deeper with subtype: '.$part->subtype.' $k is: '.$k);
3219 $thisBc = $this->buildBreadCrumbs($part->parts, $subtype, $thisBc);
3222 } elseif(strtolower($part->subtype) == strtolower($subtype)) { // found the subtype we want, return the breadcrumb value
3223 //_pp('found '.$subtype.' bc! returning: '.$thisBc);
3226 //_pp('found '.$part->subtype.' instead');
3232 * Similar to buildBreadCrumbs() but returns an ordered array containing all parts of the message that would be
3233 * considered "HTML" or Richtext (embedded images, formatting, etc.).
3234 * @param array parts Array of parts of a message
3235 * @param int breadcrumb Passed integer value to start breadcrumb trail
3236 * @param array stackedBreadcrumbs Persistent trail of breadcrumbs
3237 * @return array Ordered array of parts to retrieve via imap_fetchbody()
3239 function buildBreadCrumbsHTML($parts, $breadcrumb = '0', $stackedBreadcrumbs = array()) {
3241 $disposition = 'inline';
3243 foreach($parts as $k => $part) {
3244 // mark passage through level
3247 if($breadcrumb != 0) {
3248 $thisBc = $breadcrumb.'.'.$thisBc;
3250 // found a multi-part/mixed 'part' - keep digging
3251 if($part->type == 1 && (strtoupper($part->subtype) == 'RELATED' || strtoupper($part->subtype) == 'ALTERNATIVE' || strtoupper($part->subtype) == 'MIXED')) {
3252 $stackedBreadcrumbs = $this->buildBreadCrumbsHTML($part->parts, $thisBc, $stackedBreadcrumbs);
3254 (strtolower($part->subtype) == strtolower($subtype)) ||
3256 isset($part->disposition) && strtolower($part->disposition) == 'inline' &&
3257 in_array(strtoupper($part->subtype), $this->imageTypes)
3260 // found the subtype we want, return the breadcrumb value
3261 $stackedBreadcrumbs[] = array(strtolower($part->subtype) => $thisBc);
3262 } elseif($part->type == 5) {
3263 $stackedBreadcrumbs[] = array(strtolower($part->subtype) => $thisBc);
3267 return $stackedBreadcrumbs;
3271 * Takes a PHP imap_* object's to/from/cc/bcc address field and converts it
3272 * to a standard string that SugarCRM expects
3273 * @param $arr an array of email address objects
3275 function convertImapToSugarEmailAddress($arr) {
3276 if(is_array($arr)) {
3278 foreach($arr as $key => $obj) {
3279 $addr .= $obj->mailbox.'@'.$obj->host.', ';
3282 $ret = substr_replace($addr,'',-2,-1);
3288 * tries to figure out what character set a given filename is using and
3289 * decode based on that
3291 * @param string name Name of attachment
3292 * @return string decoded name
3294 function handleEncodedFilename($name) {
3295 $imapDecode = imap_mime_header_decode($name);
3296 /******************************
3297 $imapDecode => stdClass Object
3300 [text] => w�hlen.php
3305 $imapDecode => stdClass Object
3307 [charset] => default
3308 [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
3310 *******************************/
3311 if($imapDecode[0]->charset != 'default') { // mime-header encoded charset
3312 $encoding = $imapDecode[0]->charset;
3313 $name = $imapDecode[0]->text; // encoded in that charset
3315 /* encoded filenames are formatted as [encoding]''[filename] */
3316 if(strpos($name, "''") !== false) {
3318 $encoding = substr($name, 0, strpos($name, "'"));
3320 while(strpos($name, "'") !== false) {
3321 $name = trim(substr($name, (strpos($name, "'")+1), strlen($name)));
3324 $name = urldecode($name);
3326 return (strtolower($encoding) == 'utf-8') ? $name : mb_convert_encoding($name, 'UTF-8', $encoding);
3330 Primary body types for a part of a mail structure (imap_fetchstructure returned object)
3340 public $imap_types = array(
3350 public function getMimeType($type, $subtype)
3352 if(isset($this->imap_types[$type])) {
3353 return $this->imap_types[$type]."/$subtype";
3355 return "other/$subtype";
3361 * Takes the "parts" attribute of the object that imap_fetchbody() method
3362 * returns, and recursively goes through looking for objects that have a
3363 * disposition of "attachement" or "inline"
3364 * @param int $msgNo The relative message number for the monitored mailbox
3365 * @param object $parts Array of objects to examine
3366 * @param string $emailId The GUID of the email saved prior to calling this method
3367 * @param array $breadcrumb Default 0, build up of the parts mapping
3368 * @param bool $forDisplay Default false
3370 function saveAttachments($msgNo, $parts, $emailId, $breadcrumb='0', $forDisplay) {
3371 global $sugar_config;
3373 foreach($parts as $k => $part) {
3375 if($breadcrumb != '0') {
3376 $thisBc = $breadcrumb.'.'.$thisBc;
3378 // check if we need to recurse into the object
3379 //if($part->type == 1 && !empty($part->parts)) {
3380 if(isset($part->parts) && !empty($part->parts) && !( isset($part->subtype) && strtolower($part->subtype) == 'rfc822') ) {
3381 $this->saveAttachments($msgNo, $part->parts, $emailId, $thisBc, $forDisplay);
3382 } elseif($part->ifdisposition) {
3383 // we will take either 'attachments' or 'inline'
3384 if(strtolower($part->disposition) == 'attachment' || ((strtolower($part->disposition) == 'inline') && $part->type != 0)) {
3385 $attach = $this->getNoteBeanForAttachment($emailId);
3386 $fname = $this->handleEncodedFilename($this->retrieveAttachmentNameFromStructure($part->dparameters));
3388 if(!empty($fname)) {//assign name to attachment
3389 $attach->name = $fname;
3390 } else {//if name is empty, default to filename
3391 $attach->name = urlencode($this->retrieveAttachmentNameFromStructure($part->dparameters));
3393 $attach->filename = $attach->name;
3394 if (empty($attach->filename)) {
3398 // deal with the MIME types email has
3399 $attach->file_mime_type = $this->getMimeType($part->type, $part->subtype);
3400 $attach->safeAttachmentName();
3402 $attach->id = $this->getTempFilename();
3404 // only save if doing a full import, else we want only the binaries
3407 $this->saveAttachmentBinaries($attach, $msgNo, $thisBc, $part, $forDisplay);
3408 } // end if disposition type 'attachment'
3409 }// end ifdisposition
3410 //Retrieve contents of subtype rfc8822
3411 elseif ($part->type == 2 && isset($part->subtype) && strtolower($part->subtype) == 'rfc822' )
3413 $tmp_eml = imap_fetchbody($this->conn, $msgNo, $thisBc);
3414 $attach = $this->getNoteBeanForAttachment($emailId);
3415 $attach->file_mime_type = 'messsage/rfc822';
3416 $attach->description = $tmp_eml;
3417 $attach->filename = 'bounce.eml';
3418 $attach->safeAttachmentName();
3420 $attach->id = $this->getTempFilename();
3422 // only save if doing a full import, else we want only the binaries
3425 $this->saveAttachmentBinaries($attach, $msgNo, $thisBc, $part, $forDisplay);
3426 } elseif(!$part->ifdisposition && $part->type != 1 && $part->type != 2 && $thisBc != '1') {
3427 // No disposition here, but some IMAP servers lie about disposition headers, try to find the truth
3428 // Also Outlook puts inline attachments as type 5 (image) without a disposition
3429 if($part->ifparameters) {
3430 foreach($part->parameters as $param) {
3431 if(strtolower($param->attribute) == "name") {
3432 $fname = $this->handleEncodedFilename($param->value);
3435 if(!isset($fname)) continue;
3436 // we assume that named parts are attachments too
3437 $attach = $this->getNoteBeanForAttachment($emailId);
3439 $attach->filename = $attach->name = $fname;
3440 $attach->file_mime_type = $this->getMimeType($part->type, $part->subtype);
3442 $attach->safeAttachmentName();
3444 $attach->id = $this->getTempFilename();
3446 // only save if doing a full import, else we want only the binaries
3449 $this->saveAttachmentBinaries($attach, $msgNo, $thisBc, $part, $forDisplay);
3456 * Return a new note object for attachments.
3458 * @param string $emailId
3461 function getNoteBeanForAttachment($emailId)
3463 $attach = new Note();
3464 $attach->parent_id = $emailId;
3465 $attach->parent_type = 'Emails';
3471 * Return the filename of the attachment by examining the dparameters returned from imap_fetch_structure which
3472 * represet the content-disposition of the MIME header.
3474 * @param array $dparamaters
3477 function retrieveAttachmentNameFromStructure($dparamaters)
3481 foreach ($dparamaters as $k => $v)
3483 if( strtolower($v->attribute) == 'filename')
3485 $result = $v->value;
3494 * saves the actual binary file of a given attachment
3495 * @param object attach Note object that is attached to the binary file
3496 * @param string msgNo Message Number on IMAP/POP3 server
3497 * @param string thisBc Breadcrumb to navigate email structure to find the content
3498 * @param object part IMAP standard object that contains the "parts" of this section of email
3499 * @param bool $forDisplay
3501 function saveAttachmentBinaries($attach, $msgNo, $thisBc, $part, $forDisplay) {
3502 global $sugar_config;
3504 // decide where to place the file temporarily
3505 $uploadDir = $sugar_config['upload_dir']; // typically "cache/uploads/"
3506 $uploadDir = ($forDisplay) ? "{$sugar_config['cache_dir']}modules/Emails/{$this->id}/attachments/" : $uploadDir;
3508 // decide what name to save file as
3509 $fileName = $attach->id;
3511 // deal with attachment encoding and decode the text string
3512 if(!file_exists($uploadDir.$fileName)) {
3513 $msgPartRaw = imap_fetchbody($this->conn, $msgNo, $thisBc);
3514 $msgPart = $this->handleTranserEncoding($msgPartRaw, $part->encoding);
3516 if($fp = fopen($uploadDir.$fileName, 'wb')) {
3517 if(fwrite($fp, $msgPart)) {
3518 $this->tempAttachment[$fileName] = urldecode($attach->filename);
3519 $GLOBALS['log']->debug('InboundEmail saved attachment file: '.$attach->filename);
3521 $GLOBALS['log']->debug('InboundEmail could not create attachment file: '.$attach->filename ." - temp file target: [ {$uploadDir}{$fileName} ]");
3525 // if all was successful, feel for inline and cache Note ID for display:
3526 if((strtolower($part->disposition) == 'inline' && in_array($part->subtype, $this->imageTypes))
3527 || ($part->type == 5)) {
3528 if(copy($uploadDir.$fileName,
3529 "{$sugar_config['cache_dir']}/images/{$fileName}.".strtolower($part->subtype))) {
3530 $this->inlineImages[] = $attach->id.".".strtolower($part->subtype);
3534 $GLOBALS['log']->debug('InboundEmail could not open a filepointer to: '.$uploadDir.$fileName);
3537 // binary is in cache already, just prep necessary metadata
3538 $this->tempAttachment[$fileName] = urldecode($attach->filename);
3543 * decodes a string based on its associated encoding
3544 * if nothing is passed, we default to no-encoding type
3545 * @param $str encoded string
3546 * @param $enc detected encoding
3548 function handleTranserEncoding($str, $enc=0) {
3554 $ret = base64_decode($str);
3556 case 4:// QUOTED-PRINTABLE
3557 $ret = quoted_printable_decode($str);
3559 case 0:// 7BIT or 8BIT
3560 case 1:// already in a string-useable format - do nothing
3562 default:// catch all
3572 * Some emails do not get assigned a message_id, specifically from
3575 * We need to derive a reliable one for duplicate import checking.
3577 function getMessageId($header) {
3578 $message_id = md5(print_r($header, true));
3583 * checks for duplicate emails on polling. The uniqueness of a given email message is determined by a concatenation
3584 * of 2 values, the messageID and the delivered-to field. This allows multiple To: and B/CC: destination addresses
3585 * to be imported by Sugar without violating the true duplicate-email issues.
3587 * @param string message_id message ID generated by sending server
3588 * @param int message number (mailserver's key) of email
3589 * @param object header object generated by imap_headerinfo()
3590 * @param string textHeader Headers in normal text format
3593 function importDupeCheck($message_id, $header, $textHeader) {
3594 $GLOBALS['log']->debug('*********** InboundEmail doing dupe check.');
3596 // generate "delivered-to" seed for email duplicate check
3597 $deliveredTo = $this->id; // cn: bug 12236 - cc's failing dupe check
3598 $exHeader = explode("\n", $textHeader);
3600 foreach($exHeader as $headerLine) {
3601 if(strpos(strtolower($headerLine), 'delivered-to:') !== false) {
3602 $deliveredTo = substr($headerLine, strpos($headerLine, " "), strlen($headerLine));
3603 $GLOBALS['log']->debug('********* InboundEmail found [ '.$deliveredTo.' ] as the destination address for email [ '.$message_id.' ]');
3604 } elseif(strpos(strtolower($headerLine), 'x-real-to:') !== false) {
3605 $deliveredTo = substr($headerLine, strpos($headerLine, " "), strlen($headerLine));
3606 $GLOBALS['log']->debug('********* InboundEmail found [ '.$deliveredTo.' ] for non-standards compliant email x-header [ '.$message_id.' ]');
3610 //if(empty($message_id) && !isset($message_id)) {
3611 if(empty($message_id) || !isset($message_id)) {
3612 $GLOBALS['log']->debug('*********** NO MESSAGE_ID.');
3613 $message_id = $this->getMessageId($header);
3616 // generate compound messageId
3617 $this->compoundMessageId = trim($message_id).trim($deliveredTo);
3618 // if the length > 255 then md5 it so that the data will be of smaller length
3619 if (strlen($this->compoundMessageId) > 255) {
3620 $this->compoundMessageId = md5($this->compoundMessageId);
3623 if (empty($this->compoundMessageId)) {
3624 $GLOBALS['log']->error('Inbound Email found a message without a header and message_id');
3628 $potentials = clean_xss($this->compoundMessageId, false);
3630 if(is_array($potentials) && !empty($potentials)) {
3631 foreach($potentials as $bad) {
3632 $this->compoundMessageId = str_replace($bad, "", $this->compoundMessageId);
3636 $query = 'SELECT count(emails.id) AS c FROM emails WHERE emails.message_id = \''.$this->compoundMessageId.'\' and emails.deleted = 0';
3637 $r = $this->db->query($query, true);
3638 $a = $this->db->fetchByAssoc($r);
3641 $GLOBALS['log']->debug('InboundEmail found a duplicate email with ID ('.$this->compoundMessageId.')');
3642 return false; // we have a dupe and don't want to import the email'
3649 * takes the output from imap_mime_hader_decode() and handles multiple types of encoding
3650 * @param string subject Raw subject string from email
3651 * @return string ret properly formatted UTF-8 string
3653 function handleMimeHeaderDecode($subject) {
3654 $subjectDecoded = imap_mime_header_decode($subject);
3657 foreach($subjectDecoded as $object) {
3658 if($object->charset != 'default') {
3659 $ret .= $this->handleCharsetTranslation($object->text, $object->charset);
3661 $ret .= $object->text;
3668 * Cleans content for XSS and other types of attack vectors
3669 * @param string str String to clean
3672 function cleanContent($str) {
3674 $this->safe->clear();
3675 $str = $this->safe->parse($str);
3676 return $this->cleanXssContent($str);
3680 * Cleans content for XSS
3681 * @param string str String to clean
3684 function cleanXssContent($str) {
3686 $potentials = clean_xss($str, false);
3687 if(is_array($potentials) && !empty($potentials)) {
3688 foreach($potentials as $bad) {
3689 $str = str_replace($bad, "", $str);
3695 * Calculates the appropriate display date/time sent for an email.
3696 * @param string headerDate The date sent of email in MIME header format
3697 * @return string GMT-0 Unix timestamp
3699 function getUnixHeaderDate($headerDate) {
3702 if (empty($headerDate)) {
3705 ///////////////////////////////////////////////////////////////////
3706 //// CALCULATE CORRECT SENT DATE/TIME FOR EMAIL
3707 if(!empty($headerDate)) {
3708 // need to hack PHP/windows' bad handling of strings when using POP3
3709 if(strstr($headerDate,'+0000 GMT')) {
3710 $headerDate = str_replace('GMT','', $headerDate);
3711 } elseif(!strtotime($headerDate)) {
3712 $headerDate = 'now'; // catch non-standard format times.
3714 // cn: bug 9196 parse the GMT offset
3715 if(strpos($headerDate, '-') || strpos($headerDate, '+')) {
3716 // cn: bug make sure last 5 chars are [+|-]nnnn
3717 if(strpos($headerDate, "(")) {
3718 $headerDate = preg_replace("/\([\w]+\)/i", "", $headerDate);
3719 $headerDate = trim($headerDate);
3722 // parse mailserver time
3723 $gmtEmail = trim(substr($headerDate, -5, 5));
3724 $posNeg = substr($gmtEmail, 0, 1);
3725 $gmtHours = substr($gmtEmail, 1, 2);
3726 $gmtMins = substr($gmtEmail, -2, 2);
3729 $secsHours = $gmtHours * 60 * 60;
3730 $secsTotal = $secsHours + ($gmtMins * 60);
3731 $secsTotal = ($posNeg == '-') ? $secsTotal : -1 * $secsTotal;
3733 $headerDate = trim(substr_replace($headerDate, '', -5)); // mfh: bug 10961/12855 - date time values with GMT offsets not properly formatted
3737 $headerDate = 'now';
3740 $unixHeaderDate = strtotime($headerDate);
3742 if(isset($secsTotal)) {
3743 // this gets the timestamp to true GMT-0
3744 $unixHeaderDate += $secsTotal;
3747 if(strtotime('Jan 1, 2001') > $unixHeaderDate) {
3748 $unixHeaderDate = strtotime('now');
3751 // now get it to user's datetime format for save
3752 // $gmt0dateTime = $timedate->to_display_date_time(date('Y-m-d H:i:s', $unixHeaderDate));
3753 // $gmt0dateTime = $timedate->swap_formats($gmt0dateTime, $timedate->get_date_time_format(), $timedate->get_db_date_time_format());
3754 // $unixHeaderDate = strtotime($gmt0dateTime);
3756 return $unixHeaderDate;
3757 //// END CALCULATE CORRECT SENT DATE/TIME FOR EMAIL
3758 ///////////////////////////////////////////////////////////////////
3762 * This method returns the correct messageno for the pop3 protocol
3763 * @param String UIDL
3764 * @return returnMsgNo
3766 function getCorrectMessageNoForPop3($messageId) {
3768 if ($this->protocol == 'pop3') {
3769 if($this->pop3_open()) {
3770 // get the UIDL from database;
3771 $query = "SELECT msgno FROM email_cache WHERE ie_id = '{$this->id}' AND message_id = '{$messageId}'";
3772 $r = $this->db->query($query);
3773 $a = $this->db->fetchByAssoc($r);
3774 $msgNo = $a['msgno'];
3775 $returnMsgNo = $msgNo;
3778 $this->pop3_sendCommand("USER", $this->email_user);
3779 $this->pop3_sendCommand("PASS", $this->email_password);
3781 // get UIDL for this msgNo
3782 $this->pop3_sendCommand("UIDL {$msgNo}", '', false); // leave socket buffer alone until the while()
3783 $buf = fgets($this->pop3socket, 1024); // handle "OK+ msgNo UIDL(UIDL for this messageno)";
3785 // if it returns OK then we have found the message else get all the UIDL
3786 // and search for the correct msgNo;
3787 $foundMessageNo = false;
3788 if (preg_match("/OK/", $buf) > 0) {
3789 $mailserverResponse = explode(" ", $buf);
3790 // if the cachedUIDL and the UIDL from mail server matches then its the correct messageno
3791 if (trim($mailserverResponse[sizeof($mailserverResponse) - 1]) == $messageId) {
3792 $foundMessageNo = true;
3796 //get all the UIDL and then find the correct messageno
3797 if (!$foundMessageNo) {
3799 $this->pop3_sendCommand("UIDL", '', false); // leave socket buffer alone until the while()
3800 fgets($this->pop3socket, 1024); // handle "OK+";
3803 if(is_resource($this->pop3socket)) {
3804 while(!feof($this->pop3socket)) {
3805 $buf = fgets($this->pop3socket, 1024); // 8kb max buffer - shouldn't be more than 80 chars via pop3...
3806 if(trim($buf) == '.') {
3807 $GLOBALS['log']->debug("*** GOT '.'");
3810 // format is [msgNo] [UIDL]
3811 $exUidl = explode(" ", $buf);
3812 $UIDLs[trim($exUidl[1])] = trim($exUidl[0]);
3814 if (array_key_exists($messageId, $UIDLs)) {
3815 $returnMsgNo = $UIDLs[$messageId];
3817 // message could not be found on server
3823 $this->pop3_cleanUp();
3826 return $returnMsgNo;
3830 * If the importOneEmail returns false, then findout if the duplicate email
3832 function getDuplicateEmailId($msgNo, $uid) {
3833 if(!class_exists('Email')) {
3837 global $app_strings;
3838 global $app_list_strings;
3839 global $sugar_config;
3840 global $current_user;
3842 $header = imap_headerinfo($this->conn, $msgNo);
3843 $fullHeader = imap_fetchheader($this->conn, $msgNo); // raw headers
3845 // reset inline images cache
3846 $this->inlineImages = array();
3848 // handle messages deleted on server
3849 if(empty($header)) {
3850 if(!isset($this->email) || empty($this->email)) {
3851 $this->email = new Email();
3855 $dupeCheckResult = $this->importDupeCheck($header->message_id, $header, $fullHeader);
3856 if (!$dupeCheckResult && !empty($this->compoundMessageId)) {
3857 // we have a duplicate email
3858 $query = 'SELECT id FROM emails WHERE emails.message_id = \''.$this->compoundMessageId.'\' and emails.deleted = 0';
3859 $r = $this->db->query($query, true);
3860 $a = $this->db->fetchByAssoc($r);
3862 $this->email = new Email();
3863 $this->email->id = $a['id'];
3872 * shiny new importOneEmail() method
3874 * @param bool forDisplay
3875 * @param clean_email boolean, default true,
3877 function importOneEmail($msgNo, $uid, $forDisplay=false, $clean_email=true) {
3878 if(!class_exists('Email')) {
3882 $GLOBALS['log']->debug("InboundEmail processing 1 email {$msgNo}-----------------------------------------------------------------------------------------");
3884 global $app_strings;
3885 global $app_list_strings;
3886 global $sugar_config;
3887 global $current_user;
3889 $header = imap_headerinfo($this->conn, $msgNo);
3890 $fullHeader = imap_fetchheader($this->conn, $msgNo); // raw headers
3892 // reset inline images cache
3893 $this->inlineImages = array();
3895 // handle messages deleted on server
3896 if(empty($header)) {
3897 if(!isset($this->email) || empty($this->email)) {
3898 $this->email = new Email();
3902 if ($this->isPop3Protocol()) {
3903 $this->email->name = $app_strings['LBL_EMAIL_ERROR_MESSAGE_DELETED'];
3904 $q = "DELETE FROM email_cache WHERE message_id = '{$uid}' AND ie_id = '{$this->id}' AND mbox = '{$this->mailbox}'";
3906 $this->email->name = $app_strings['LBL_EMAIL_ERROR_IMAP_MESSAGE_DELETED'];
3907 $q = "DELETE FROM email_cache WHERE imap_uid = {$uid} AND ie_id = '{$this->id}' AND mbox = '{$this->mailbox}'";
3909 // delete local cache
3910 $r = $this->db->query($q);
3912 $this->email->date_sent = date('Y-m-d H:i:s');
3914 //return "Message deleted from server.";
3917 ///////////////////////////////////////////////////////////////////////
3918 //// DUPLICATE CHECK
3919 $dupeCheckResult = $this->importDupeCheck($header->message_id, $header, $fullHeader);
3920 if($forDisplay || $dupeCheckResult) {
3921 $GLOBALS['log']->debug('*********** NO duplicate found, continuing with processing.');
3923 $structure = imap_fetchstructure($this->conn, $msgNo); // map of email
3925 ///////////////////////////////////////////////////////////////////
3926 //// CREATE SEED EMAIL OBJECT
3927 $email = new Email();
3928 $email->isDuplicate = ($dupeCheckResult) ? false : true;
3929 $email->mailbox_id = $this->id;
3931 $email->id = create_guid();
3932 $email->new_with_id = true; //forcing a GUID here to prevent double saves.
3933 //// END CREATE SEED EMAIL
3934 ///////////////////////////////////////////////////////////////////
3936 ///////////////////////////////////////////////////////////////////
3937 //// PREP SYSTEM USER
3938 if(empty($current_user)) {
3939 // I-E runs as admin, get admin prefs
3941 $current_user = new User();
3942 $current_user->getSystemUser();
3944 $tPref = $current_user->getUserDateTimePreferences();
3946 ///////////////////////////////////////////////////////////////////
3948 $unixHeaderDate = $this->getUnixHeaderDate($header->date);
3950 ///////////////////////////////////////////////////////////////////
3951 //// HANDLE EMAIL ATTACHEMENTS OR HTML TEXT
3952 //// Inline images require that I-E handle attachments before body text
3953 // parts defines attachments - be mindful of .html being interpreted as an attachment
3954 if($structure->type == 1 && !empty($structure->parts)) {
3955 $GLOBALS['log']->debug('InboundEmail found multipart email - saving attachments if found.');
3956 $this->saveAttachments($msgNo, $structure->parts, $email->id, 0, $forDisplay);
3957 } elseif($structure->type == 0) {
3958 $uuemail = ($this->isUuencode($email->description)) ? true : false;
3960 * UUEncoded attachments - legacy, but still have to deal with it
3962 * begin 777 filename.txt
3967 // set body to the filtered one
3969 $email->description = $this->handleUUEncodedEmailBody($email->description, $email->id);
3970 $email->retrieve($email->id);
3974 if($this->port != 110) {
3975 $GLOBALS['log']->debug('InboundEmail found a multi-part email (id:'.$msgNo.') with no child parts to parse.');
3978 //// END HANDLE EMAIL ATTACHEMENTS OR HTML TEXT
3979 ///////////////////////////////////////////////////////////////////
3981 ///////////////////////////////////////////////////////////////////
3982 //// ASSIGN APPROPRIATE ATTRIBUTES TO NEW EMAIL OBJECT
3983 // handle UTF-8/charset encoding in the ***headers***
3985 //bug #33929 added quoteForEmail() to replace single quote
3986 $email->name = $this->handleMimeHeaderDecode($header->subject);
3987 $unixHeaderDate = (!empty($unixHeaderDate)) ? date($timedate->get_db_date_time_format(), $unixHeaderDate) : "";
3988 $email->date_start = (!empty($unixHeaderDate)) ? $timedate->to_display_date($unixHeaderDate) : "";
3989 $email->time_start = (!empty($unixHeaderDate)) ? $timedate->to_display_time($unixHeaderDate) : "";
3990 $email->type = 'inbound';
3991 $email->date_created = (!empty($unixHeaderDate)) ? $timedate->to_display_date_time($unixHeaderDate) : "";
3992 $email->status = 'unread'; // this is used in Contacts' Emails SubPanel
3993 if(!empty($header->toaddress)) {
3994 $email->to_name = $this->handleMimeHeaderDecode($header->toaddress);
3995 $email->to_addrs_names = $email->to_name;
3997 if(!empty($header->to)) {
3998 $email->to_addrs = $this->convertImapToSugarEmailAddress($header->to);
4000 $email->from_name = $this->handleMimeHeaderDecode($header->fromaddress);
4001 $email->from_addr_name = $email->from_name;
4002 $email->from_addr = $this->convertImapToSugarEmailAddress($header->from);
4003 if(!empty($header->cc)) {
4004 $email->cc_addrs = $this->convertImapToSugarEmailAddress($header->cc);
4006 if(!empty($header->ccaddress)) {
4007 $email->cc_addrs_names = $this->handleMimeHeaderDecode($header->ccaddress);
4009 $email->reply_to_name = $this->handleMimeHeaderDecode($header->reply_toaddress);
4010 $email->reply_to_email = $this->convertImapToSugarEmailAddress($header->reply_to);
4011 if (!empty($email->reply_to_email)) {
4012 $email->reply_to_addr = $email->reply_to_name;
4014 $email->intent = $this->mailbox_type;
4016 $email->message_id = $this->compoundMessageId; // filled by importDupeCheck();
4018 // handle multi-part email bodies
4019 $email->description_html= $this->getMessageText($msgNo, 'HTML', $structure, $fullHeader,$clean_email); // runs through handleTranserEncoding() already
4020 $email->description = $this->getMessageText($msgNo, 'PLAIN', $structure, $fullHeader,$clean_email); // runs through handleTranserEncoding() already
4022 // empty() check for body content
4023 if(empty($email->description)) {
4024 $GLOBALS['log']->debug('InboundEmail Message (id:'.$email->message_id.') has no body');
4028 if (!empty($_REQUEST['user_id'])) {
4029 $email->assigned_user_id = $_REQUEST['user_id'];
4031 // Samir Gandhi : Commented out this code as its not needed
4032 //$email->assigned_user_id = $this->group_id;
4035 //Assign Parent Values if set
4036 if (!empty($_REQUEST['parent_id']) && !empty($_REQUEST['parent_type'])) {
4037 $email->parent_id = $_REQUEST['parent_id'];
4038 $email->parent_type = $_REQUEST['parent_type'];
4039 $mod = strtolower($email->parent_type);
4040 $rel = array_key_exists($mod, $email->field_defs) ? $mod : $mod . "_activities_emails"; //Custom modules rel name
4042 if(! $email->load_relationship($rel) )
4044 $email->$rel->add($email->parent_id);
4047 // override $forDisplay w/user pref
4049 if($this->isAutoImport()) {
4050 $forDisplay = false; // triggers save of imported email
4057 $email->new_with_id = false; // to allow future saves by UPDATE, instead of INSERT
4058 //// ASSIGN APPROPRIATE ATTRIBUTES TO NEW EMAIL OBJECT
4059 ///////////////////////////////////////////////////////////////////
4061 ///////////////////////////////////////////////////////////////////
4062 //// LINK APPROPRIATE BEANS TO NEWLY SAVED EMAIL
4063 //$contactAddr = $this->handleLinking($email);
4064 //// END LINK APPROPRIATE BEANS TO NEWLY SAVED EMAIL
4065 ///////////////////////////////////////////////////////////////////
4067 ///////////////////////////////////////////////////////////////////
4068 //// MAILBOX TYPE HANDLING
4069 $this->handleMailboxType($email, $header);
4070 //// END MAILBOX TYPE HANDLING
4071 ///////////////////////////////////////////////////////////////////
4073 ///////////////////////////////////////////////////////////////////
4074 //// SEND AUTORESPONSE
4075 if(!empty($email->reply_to_email)) {
4076 $contactAddr = $email->reply_to_email;
4078 $contactAddr = $email->from_addr;
4080 if (!$this->isMailBoxTypeCreateCase()) {
4081 $this->handleAutoresponse($email, $contactAddr);
4083 //// END SEND AUTORESPONSE
4084 ///////////////////////////////////////////////////////////////////
4085 //// END IMPORT ONE EMAIL
4086 ///////////////////////////////////////////////////////////////////
4089 // only log if not POP3; pop3 iterates through ALL mail
4090 if($this->protocol != 'pop3') {
4091 $GLOBALS['log']->info("InboundEmail found a duplicate email: ".$header->message_id);
4092 //echo "This email has already been imported";
4096 //// END DUPLICATE CHECK
4097 ///////////////////////////////////////////////////////////////////////
4099 ///////////////////////////////////////////////////////////////////////
4100 //// DEAL WITH THE MAILBOX
4102 imap_setflag_full($this->conn, $msgNo, '\\SEEN');
4104 // if delete_seen, mark msg as deleted
4105 if($this->delete_seen == 1 && !$forDisplay) {
4106 $GLOBALS['log']->info("INBOUNDEMAIL: delete_seen == 1 - deleting email");
4107 imap_setflag_full($this->conn, $msgNo, '\\DELETED');
4110 // for display - don't touch server files?
4111 //imap_setflag_full($this->conn, $msgNo, '\\UNSEEN');
4114 $GLOBALS['log']->debug('********************************* InboundEmail finished import of 1 email: '.$email->name);
4115 //// END DEAL WITH THE MAILBOX
4116 ///////////////////////////////////////////////////////////////////////
4118 ///////////////////////////////////////////////////////////////////////
4119 //// TO SUPPORT EMAIL 2.0
4120 $this->email = $email;
4122 if(empty($this->email->et)) {
4123 $this->email->email2init();
4130 * figures out if a plain text email body has UUEncoded attachments
4131 * @param string string The email body
4132 * @return bool True if UUEncode is detected.
4134 function isUuencode($string) {
4135 $rx = "begin [0-9]{3} .*";
4137 $exBody = explode("\r", $string);
4138 foreach($exBody as $line) {
4139 if(preg_match("/begin [0-9]{3} .*/i", $line)) {
4148 * handles UU Encoded emails - a legacy from pre-RFC 822 which must still be supported (?)
4149 * @param string raw The raw email body
4150 * @param string id Parent email ID
4151 * @return string The filtered email body, stripped of attachments
4153 function handleUUEncodedEmailBody($raw, $id) {
4157 $attachmentBody = '';
4158 $inAttachment = false;
4160 $exRaw = explode("\n", $raw);
4162 foreach($exRaw as $k => $line) {
4163 $line = trim($line);
4165 if(preg_match("/begin [0-9]{3} .*/i", $line, $m)) {
4166 $inAttachment = true;
4167 $fileName = $this->handleEncodedFilename(substr($m[0], 10, strlen($m[0])));
4169 $attachmentBody = ''; // reset for next part of loop;
4174 if(strpos($line, "end") === 0) {
4175 if(!empty($fileName) && !empty($attachmentBody)) {
4176 $this->handleUUDecode($id, $fileName, trim($attachmentBody));
4177 $attachmentBody = ''; // reset for next part of loop;
4181 if($inAttachment === false) {
4182 $emailBody .= "\n".$line;
4184 $attachmentBody .= "\n".$line;
4188 /* 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 */
4189 $emailBody = $locale->translateCharset($emailBody, $locale->getExportCharset(), 'UTF-8');
4194 * wrapper for UUDecode
4195 * @param string id Id of the email
4196 * @param string UUEncode Encode US-ASCII
4198 function handleUUDecode($id, $fileName, $UUEncode) {
4199 global $sugar_config;
4200 /* include PHP_Compat library; it auto-feels for PHP5's compiled convert_uuencode() function */
4201 require_once('include/PHP_Compat/convert_uudecode.php');
4204 $attach = new Note();
4205 $attach->parent_id = $id;
4206 $attach->parent_type = 'Emails';
4208 $fname = $this->handleEncodedFilename($fileName);
4210 if(!empty($fname)) {//assign name to attachment
4211 $attach->name = $fname;
4212 } else {//if name is empty, default to filename
4213 $attach->name = urlencode($fileName);
4216 $attach->filename = urlencode($attach->name);
4218 //get position of last "." in file name
4219 $file_ext_beg = strrpos($attach->filename,".");
4221 //get file extension
4222 if($file_ext_beg >0) {
4223 $file_ext = substr($attach->filename, $file_ext_beg+1);
4225 //check to see if this is a file with extension located in "badext"
4226 foreach($sugar_config['upload_badext'] as $badExt) {
4227 if(strtolower($file_ext) == strtolower($badExt)) {
4228 //if found, then append with .txt and break out of lookup
4229 $attach->name = $attach->name . ".txt";
4230 $attach->file_mime_type = 'text/';
4231 $attach->filename = $attach->filename . ".txt";
4232 break; // no need to look for more
4237 $bin = convert_uudecode($UUEncode);
4238 if($fp = fopen($sugar_config['upload_dir'].$attach->id, 'wb')) {
4239 if(fwrite($fp, $bin)) {
4240 $GLOBALS['log']->debug('InboundEmail saved attachment file: '.$attach->filename);
4242 $GLOBALS['log']->debug('InboundEmail could not create attachment file: '.$attach->filename);
4247 $GLOBALS['log']->debug('InboundEmail could not open a filepointer to: '.$sugar_config['upload_dir'].$attach->id);
4252 * returns true if the email's domain is NOT in the filter domain string
4254 * @param object email Email object in question
4255 * @return bool true if not filtered, false if filtered
4257 function checkFilterDomain($email) {
4258 $filterDomain = $this->get_stored_options('filter_domain');
4259 if(!isset($filterDomain) || empty($filterDomain)) {
4260 return true; // nothing set for this
4262 $replyTo = strtolower($email->reply_to_email);
4263 $from = strtolower($email->from_addr);
4264 $filterDomain = '@'.strtolower($filterDomain);
4265 if(strpos($replyTo, $filterDomain) !== false) {
4266 $GLOBALS['log']->debug('Autoreply cancelled - [reply to] address domain matches filter domain.');
4268 } elseif(strpos($from, $filterDomain) !== false) {
4269 $GLOBALS['log']->debug('Autoreply cancelled - [from] address domain matches filter domain.');
4272 return true; // no match
4278 * returns true if subject is NOT "out of the office" type
4280 * @param string subject Subject line of email in question
4281 * @return bool returns false if OOTO found
4283 function checkOutOfOffice($subject) {
4284 $ooto = array("Out of the Office", "Out of Office");
4286 foreach($ooto as $str) {
4287 if(preg_replace('/'.$str.'/i', $subject)) {
4288 $GLOBALS['log']->debug('Autoreply cancelled - found "Out of Office" type of subject.');
4292 return true; // no matches to ooto strings
4297 * sets a timestamp for an autoreply to a single email addy
4299 * @param string addr Address of auto-replied target
4301 function setAutoreplyStatus($addr) {
4302 $this->db->query( 'INSERT INTO inbound_email_autoreply (id, deleted, date_entered, date_modified, autoreplied_to, ie_id) VALUES (
4303 \''.create_guid().'\',
4305 \''.gmdate('Y-m-d H:i:s', strtotime('now')).'\',
4306 \''.gmdate('Y-m-d H:i:s', strtotime('now')).'\',
4308 \''.$this->id.'\') ', true);
4313 * returns true if recipient has NOT received 10 auto-replies in 24 hours
4315 * @param string from target address for auto-reply
4316 * @return bool true if target is valid/under limit
4318 function getAutoreplyStatus($from) {
4319 global $sugar_config;
4321 $q_clean = 'UPDATE inbound_email_autoreply SET deleted = 1 WHERE date_entered < \''.gmdate($GLOBALS['timedate']->get_db_date_time_format(), strtotime('now -24 hours')).'\'';
4322 $r_clean = $this->db->query($q_clean, true);
4324 $q = 'SELECT count(*) AS c FROM inbound_email_autoreply WHERE deleted = 0 AND autoreplied_to = \''.$from.'\' AND ie_id = \''.$this->id.'\'';
4325 $r = $this->db->query($q, true);
4326 $a = $this->db->fetchByAssoc($r);
4328 $email_num_autoreplies_24_hours = $this->get_stored_options('email_num_autoreplies_24_hours');
4329 $maxReplies = (isset($email_num_autoreplies_24_hours)) ? $email_num_autoreplies_24_hours : $this->maxEmailNumAutoreplies24Hours;
4331 if($a['c'] >= $maxReplies) {
4332 $GLOBALS['log']->debug('Autoreply cancelled - more than ' . $maxReplies . ' replies sent in 24 hours.');
4340 * returns exactly 1 id match. if more than one, than returns false
4341 * @param $emailName the subject of the email to match
4342 * @param $tableName the table of the matching bean type
4344 function getSingularRelatedId($emailName, $tableName) {
4345 $repStrings = array('RE:','Re:','re:');
4346 $preppedName = str_replace($repStrings,'',trim($emailName));
4348 //TODO add team security to this query
4349 $q = 'SELECT count(id) AS c FROM '.$tableName.' WHERE deleted = 0 AND name LIKE \'%'.$preppedName.'%\'';
4350 $r = $this->db->query($q, true);
4351 $a = $this->db->fetchByAssoc($r);
4354 $q = 'SELECT id FROM '.$tableName.' WHERE deleted = 0 AND name LIKE \'%'.$preppedName.'%\'';
4355 $r = $this->db->query($q, true);
4356 $a = $this->db->fetchByAssoc($r);
4364 * saves InboundEmail parse macros to config.php
4365 * @param string type Bean to link
4366 * @param string macro The new macro
4368 function saveInboundEmailSystemSettings($type, $macro) {
4369 global $sugar_config;
4371 // inbound_email_case_subject_macro
4372 $var = "inbound_email_".strtolower($type)."_subject_macro";
4373 $sugar_config[$var] = $macro;
4375 ksort($sugar_config);
4377 $sugar_config_string = "<?php\n" .
4378 '// created: ' . date('Y-m-d H:i:s') . "\n" .
4379 '$sugar_config = ' .
4380 var_export($sugar_config, true) .
4383 write_array_to_file("sugar_config", $sugar_config, "config.php");
4387 * returns the HTML for InboundEmail system settings
4388 * @return string HTML
4390 function getSystemSettingsForm() {
4391 global $sugar_config;
4392 global $mod_strings;
4393 global $app_strings;
4394 global $app_list_strings;
4397 if(!class_exists('aCase')) {
4402 $macro = $c->getEmailSubjectMacro();
4405 <form action="index.php" method="post" name="Macro" id="form">
4406 <input type="hidden" name="module" value="InboundEmail">
4407 <input type="hidden" name="action" value="ListView">
4408 <input type="hidden" name="save" value="true">
4410 <table width="100%" cellpadding="0" cellspacing="0" border="0">
4413 <input title="{$app_strings['LBL_SAVE_BUTTON_TITLE']}"
4414 accessKey="{$app_strings['LBL_SAVE_BUTTON_KEY']}"
4416 onclick="this.form.return_module.value='InboundEmail'; this.form.return_action.value='ListView';"
4417 type="submit" name="Edit" value=" {$app_strings['LBL_SAVE_BUTTON_LABEL']} ">
4422 <table width="100%" border="0" cellspacing="0" cellpadding="0" class="detail view">
4424 <td valign="top" width='10%' NOWRAP scope="row">
4426 <b>{$mod_strings['LBL_CASE_MACRO']}:</b>
4429 <td valign="top" width='20%'>
4431 <input name="inbound_email_case_macro" type="text" value="{$macro}">
4434 <td valign="top" width='70%'>
4436 {$mod_strings['LBL_CASE_MACRO_DESC']}
4438 <i>{$mod_strings['LBL_CASE_MACRO_DESC2']}</i>
4449 * for mailboxes of type "Support" parse for '[CASE:%1]'
4450 * @param $emailName the subject line of the email
4451 * @param $aCase a Case object
4453 function getCaseIdFromCaseNumber($emailName, $aCase) {
4454 //$emailSubjectMacro
4455 $exMacro = explode('%1', $aCase->getEmailSubjectMacro());
4456 $open = $exMacro[0];
4457 $close = $exMacro[1];
4459 if($sub = stristr($emailName, $open)) { // eliminate everything up to the beginning of the macro and return the rest
4460 // $sub is [CASE:XX] xxxxxxxxxxxxxxxxxxxxxx
4461 $sub2 = str_replace($open, '', $sub);
4462 // $sub2 is XX] xxxxxxxxxxxxxx
4463 $sub3 = substr($sub2, 0, strpos($sub2, $close));
4465 $r = $this->db->query("SELECT id FROM cases WHERE case_number = '{$sub3}'", true);
4466 $a = $this->db->fetchByAssoc($r);
4467 if(!empty($a['id'])) {
4477 function get_stored_options($option_name,$default_value=null,$stored_options=null) {
4478 if (empty($stored_options)) {
4479 $stored_options=$this->stored_options;
4481 if(!empty($stored_options)) {
4482 $storedOptions = unserialize(base64_decode($stored_options));
4483 if (isset($storedOptions[$option_name])) {
4484 $default_value=$storedOptions[$option_name];
4487 return $default_value;
4492 * This function returns a contact or user ID if a matching email is found
4493 * @param $email the email address to match
4494 * @param $table which table to query
4496 function getRelatedId($email, $module) {
4497 $email = trim(strtoupper($email));
4498 if(strpos($email, ',') !== false) {
4499 $emailsArray = explode(',', $email);
4500 $emailAddressString = "";
4501 foreach($emailsArray as $emailAddress) {
4502 if (!empty($emailAddressString)) {
4503 $emailAddressString .= ",";
4505 $emailAddressString .= "'" . $emailAddress. "'";
4507 $email = $emailAddressString;
4509 $email = "'" . $email . "'";
4511 $module = ucfirst($module);
4513 $q = "SELECT bean_id FROM email_addr_bean_rel eabr
4514 JOIN email_addresses ea ON (eabr.email_address_id = ea.id)
4515 WHERE bean_module = '{$module}' AND ea.email_address_caps in ( {$email} ) AND eabr.deleted=0";
4517 $r = $this->db->query($q, true);
4520 while($a = $this->db->fetchByAssoc($r)) {
4521 $retArr[] = $a['bean_id'];
4523 if(count($retArr) > 0) {
4531 * finds emails tagged "//UNSEEN" on mailserver and "SINCE: [date]" if that
4534 * @return array Array of messageNumbers (mail server's internal keys)
4536 function getNewMessageIds() {
4537 $storedOptions = unserialize(base64_decode($this->stored_options));
4539 //TODO figure out if the since date is UDT
4540 if($storedOptions['only_since']) {// POP3 does not support Unseen flags
4541 if(!isset($storedOptions['only_since_last']) && !empty($storedOptions['only_since_last'])) {
4542 $q = 'SELECT last_run FROM schedulers WHERE job = \'function::pollMonitoredInboxes\'';
4543 $r = $this->db->query($q, true);
4544 $a = $this->db->fetchByAssoc($r);
4546 $date = date('r', strtotime($a['last_run']));
4548 $date = $storedOptions['only_since_last'];
4550 $ret = imap_search($this->conn, 'SINCE "'.$date.'" UNDELETED UNSEEN');
4551 $check = imap_check($this->conn);
4552 $storedOptions['only_since_last'] = $check->Date;
4553 $this->stored_options = base64_encode(serialize($storedOptions));
4556 $ret = imap_search($this->conn, 'UNDELETED UNSEEN');
4559 $GLOBALS['log']->debug('-----> getNewMessageIds() got '.count($ret).' new Messages');
4564 * Constructs the resource connection string that IMAP needs
4565 * @param string $service Service string, will generate if not passed
4568 function getConnectString($service='', $mbox='', $includeMbox=true) {
4569 $service = empty($service) ? $this->getServiceString() : $service;
4570 $mbox = empty($mbox) ? $this->mailbox : $mbox;
4572 $connectString = '{'.$this->server_url.':'.$this->port.'/service='.$this->protocol.$service.'}';
4573 $connectString .= ($includeMbox) ? $mbox : "";
4575 return $connectString;
4578 function disconnectMailserver() {
4579 if(is_resource($this->conn)) {
4580 imap_close($this->conn);
4585 * Connects to mailserver. If an existing IMAP resource is available, it
4586 * will attempt to reuse the connection, updating the mailbox path.
4588 * @param bool test Flag to test connection
4589 * @param bool force Force reconnect
4590 * @return string "true" on success, "false" or $errorMessage on failure
4592 function connectMailserver($test=false, $force=false) {
4593 global $mod_strings;
4594 if(!function_exists("imap_open")) {
4595 $GLOBALS['log']->debug('------------------------- IMAP libraries NOT available!!!! die()ing thread.----');
4596 return $mod_strings['LBL_WARN_NO_IMAP'];
4599 imap_errors(); // clearing error stack
4600 error_reporting(0); // turn off notices from IMAP
4602 // tls::ca::ssl::protocol::novalidate-cert::notls
4603 $useSsl = ($_REQUEST['ssl'] == 'true') ? true : false;
4605 imap_timeout(1, 15); // 60 secs is the default
4606 imap_timeout(2, 15);
4607 imap_timeout(3, 15);
4609 $opts = $this->findOptimumSettings($useSsl);
4610 if(isset($opts['good']) && empty($opts['good'])) {
4611 return array_pop($opts['err']);
4613 $service = $opts['service'];
4614 $service = str_replace('foo','', $service); // foo there to support no-item explodes
4617 $service = $this->getServiceString();
4620 $connectString = $this->getConnectString($service, $this->mailbox);
4623 * Try to recycle the current connection to reduce response times
4625 if(is_resource($this->conn)) {
4628 imap_close($this->conn);
4631 if(imap_ping($this->conn)) {
4632 // we have a live connection
4633 imap_reopen($this->conn, $connectString, CL_EXPUNGE);
4638 if(!is_resource($this->conn) && !$test) {
4639 $this->conn = imap_open($connectString, $this->email_user, $this->email_password, CL_EXPUNGE);
4643 if ($opts == false && !is_resource($this->conn)) {
4644 $this->conn = imap_open($connectString, $this->email_user, $this->email_password, CL_EXPUNGE);
4648 $successful = false;
4649 if(($errors = imap_last_error()) || ($alerts = imap_alerts())) {
4650 if($errors == 'Mailbox is empty') { // false positive
4654 $msg .= '<p>'.$alerts.'<p>';
4655 $msg .= '<p>'.$mod_strings['ERR_TEST_MAILBOX'];
4662 if($this->protocol == 'imap') {
4663 $msg .= $mod_strings['LBL_TEST_SUCCESSFUL'];
4665 $testConnectString = '{'.$this->server_url.':'.$this->port.'/service='.$this->protocol.$service.'}';
4666 if (!is_resource($this->conn)) {
4667 $this->conn = imap_open($connectString, $this->email_user, $this->email_password, CL_EXPUNGE);
4669 $list = imap_getmailboxes($this->conn, $testConnectString, "*");
4670 if(isset($_REQUEST['personal']) && $_REQUEST['personal'] == 'true') {
4671 $msg .= $mod_strings['LBL_TEST_SUCCESSFUL'];
4672 } elseif (is_array($list)) {
4676 $msg .= '<b>'.$mod_strings['LBL_FOUND_MAILBOXES'].'</b><p>';
4677 foreach ($list as $key => $val) {
4678 $mb = imap_utf7_decode(str_replace($testConnectString,'',$val->name));
4679 $msg .= '<a onClick=\'setMailbox(\"'.$mb.'\"); window.close();\'>';
4685 $msg .= '<p>'.$mod_strings['ERR_MAILBOX_FAIL'].imap_last_error().'</p>';
4686 $msg .= '<p>'.$mod_strings['ERR_TEST_MAILBOX'].'</p>';
4690 $msg .= $mod_strings['LBL_POP3_SUCCESS'];
4694 imap_errors(); // collapse error stack
4695 imap_close($this->conn);
4697 } elseif(!is_resource($this->conn)) {
4706 function checkImap() {
4707 global $mod_strings;
4709 if(!function_exists('imap_open')) {
4711 <table cellpadding="0" cellspacing="0" width="100%" border="0" class="list view">
4713 <td scope="col" width="25%" colspan="2"><slot>
4714 '.$mod_strings['LBL_WARN_IMAP_TITLE'].'
4718 <td scope="row" valign=TOP bgcolor="#fdfdfd" width="20%"><slot>
4719 '.$mod_strings['LBL_WARN_IMAP'].'
4720 <td scope="row" valign=TOP class="oddListRowS1" bgcolor="#fdfdfd" width="80%"><slot>
4721 <span class=error>'.$mod_strings['LBL_WARN_NO_IMAP'].'</span>
4730 * retrieves an array of I-E beans based on the group_id
4731 * @param string $groupId GUID of the group user or Individual
4732 * @return array $beans array of beans
4733 * @return boolean false if none returned
4735 function retrieveByGroupId($groupId) {
4736 $q = 'SELECT id FROM inbound_email WHERE group_id = \''.$groupId.'\' AND deleted = 0 AND status = \'Active\'';
4737 $r = $this->db->query($q, true);
4740 while($a = $this->db->fetchByAssoc($r)) {
4741 $ie = new InboundEmail();
4742 $ie->retrieve($a['id']);
4743 $beans[$a['id']] = $ie;
4749 * Retrieves the current count of personal accounts for the user specified.
4751 * @param unknown_type $user
4753 function getUserPersonalAccountCount($user = null)
4756 $user = $GLOBALS['current_user'];
4758 $query = "SELECT count(*) as c FROM inbound_email WHERE deleted=0 AND is_personal='1' AND group_id='{$user->id}' AND status='Active'";
4760 $rs = $this->db->query($query);
4761 $row = $this->db->fetchByAssoc($rs);
4766 * retrieves an array of I-E beans based on the group folder id
4767 * @param string $groupFolderId GUID of the group folder
4768 * @return array $beans array of beans
4769 * @return boolean false if none returned
4771 function retrieveByGroupFolderId($groupFolderId) {
4772 $q = 'SELECT id FROM inbound_email WHERE groupfolder_id = \''.$groupFolderId.'\' AND deleted = 0 ';
4773 $r = $this->db->query($q, true);
4776 while($a = $this->db->fetchByAssoc($r)) {
4777 $ie = new InboundEmail();
4778 $ie->retrieve($a['id']);
4785 * Retrieves an array of I-E beans that the user has team access to
4787 function retrieveAllByGroupId($id, $includePersonal=true) {
4788 global $current_user;
4790 $beans = ($includePersonal) ? $this->retrieveByGroupId($id) : array();
4796 $q = "SELECT inbound_email.id FROM inbound_email {$teamJoin} WHERE is_personal = 0 AND groupfolder_id is null AND mailbox_type not like 'bounce' AND inbound_email.deleted = 0 AND status = 'Active' ";
4800 $r = $this->db->query($q, true);
4802 while($a = $this->db->fetchByAssoc($r)) {
4804 foreach($beans as $bean) {
4805 if($bean->id == $a['id']) {
4811 $ie = new InboundEmail();
4812 $ie->retrieve($a['id']);
4813 $beans[$a['id']] = $ie;
4821 * Retrieves an array of I-E beans that the user has team access to including group
4823 function retrieveAllByGroupIdWithGroupAccounts($id, $includePersonal=true) {
4824 global $current_user;
4826 $beans = ($includePersonal) ? $this->retrieveByGroupId($id) : array();
4835 $q = "SELECT DISTINCT inbound_email.id FROM inbound_email {$teamJoin} WHERE is_personal = 0 AND mailbox_type not like 'bounce' AND status = 'Active' AND inbound_email.deleted = 0 ";
4837 $r = $this->db->query($q, true);
4839 while($a = $this->db->fetchByAssoc($r)) {
4841 foreach($beans as $bean) {
4842 if($bean->id == $a['id']) {
4848 $ie = new InboundEmail();
4849 $ie->retrieve($a['id']);
4850 $beans[$a['id']] = $ie;
4859 * returns the bean name - overrides SugarBean's
4861 function get_summary_text() {
4866 * Override's SugarBean's
4868 function create_export_query($order_by, $where, $show_deleted = 0) {
4869 return $this->create_new_list_query($order_by, $where, $show_deleted = 0);
4873 * Override's SugarBean's
4877 * Override's SugarBean's
4879 function get_list_view_data(){
4880 global $mod_strings;
4881 global $app_list_strings;
4882 $temp_array = $this->get_list_view_array();
4883 $temp_array['MAILBOX_TYPE_NAME']= $app_list_strings['dom_mailbox_type'][$this->mailbox_type];
4884 //cma, fix bug 21670.
4885 $temp_array['GLOBAL_PERSONAL_STRING']= ($this->is_personal ? $mod_strings['LBL_IS_PERSONAL'] : $mod_strings['LBL_IS_GROUP']);
4886 $temp_array['STATUS'] = ($this->status == 'Active') ? $mod_strings['LBL_STATUS_ACTIVE'] : $mod_strings['LBL_STATUS_INACTIVE'];
4891 * Override's SugarBean's
4893 function fill_in_additional_list_fields() {
4894 $this->fill_in_additional_detail_fields();
4898 * Override's SugarBean's
4900 function fill_in_additional_detail_fields() {
4901 if(!empty($this->service)) {
4902 $exServ = explode('::', $this->service);
4903 $this->tls = $exServ[0];
4904 if ( isset($exServ[1]) )
4905 $this->ca = $exServ[1];
4906 if ( isset($exServ[2]) )
4907 $this->ssl = $exServ[2];
4908 if ( isset($exServ[3]) )
4909 $this->protocol = $exServ[3];
4926 ///////////////////////////////////////////////////////////////////////////
4927 //// IN SUPPORT OF EMAIL 2.0
4929 * Checks for $user's autoImport setting and returns the current value
4930 * @param object $user User in focus, defaults to $current_user
4933 function isAutoImport($user=null) {
4934 if(!empty($this->autoImport)) {
4935 return $this->autoImport;
4938 global $current_user;
4939 if(empty($user)) $user = $current_user;
4941 $emailSettings = $current_user->getPreference('emailSettings', 'Emails');
4942 $emailSettings = is_string($emailSettings) ? unserialize($emailSettings) : $emailSettings;
4944 $this->autoImport = (isset($emailSettings['autoImport']) && !empty($emailSettings['autoImport'])) ? true : false;
4945 return $this->autoImport;
4949 * Clears out cache files for a user
4951 function cleanOutCache() {
4952 $GLOBALS['log']->debug("INBOUNDEMAIL: at cleanOutCache()");
4953 //global $current_user;
4954 //global $sugar_config;
4956 //$showFolders = unserialize(base64_decode($current_user->getPreference('showFolders', 'Emails')));
4957 //$personals = $this->retrieveAllByGroupId($current_user->id, true);
4959 //foreach($personals as $k => $personalAccount) {
4960 // if(in_array($personalAccount->id, $showFolders)) {
4961 // // check for cache value
4962 // $cacheRoot = getcwd()."/{$sugar_config['cache_dir']}modules/Emails/{$personalAccount->id}";
4964 // // on new account creation, this is not created yet
4965 // if(!file_exists($cacheRoot)) {
4966 // require_once("modules/Emails/EmailUI.php");
4967 // $et = new EmailUI();
4968 // $et->preflightEmailCache($cacheRoot);
4969 // $et->preflightUserCache();
4970 // continue; // no need to find files, there are none.
4973 // // destroy overviews, keep messages
4974 // $cacheFiles = findAllFiles($cacheRoot."/folders/", array());
4976 // foreach($cacheFiles as $cFile) {
4977 // if(preg_match("/imapFetchOverview/", $cFile)) {
4978 // _ppl("***InboundEmail deleting cache file [ {$cFile} ]");
4979 // $GLOBALS['log']->debug("***InboundEmail deleting cache file [ {$cFile} ]");
4980 // if(!unlink($cFile)) {
4981 // $GLOBALS['log']->error("*** ERROR: InboundEmail could not delete cache file [ {$cFile} ]");
4986 // delete the tables
4987 $this->deleteCache();
4993 * moves emails from folder to folder
4994 * @param string $fromIe I-E id
4995 * @param string $fromFolder IMAP path to folder in which the email lives
4996 * @param string $toIe I-E id
4997 * @param string $toFolder
4998 * @param string $uids UIDs of emails to move, either Sugar GUIDS or IMAP
5001 function copyEmails($fromIe, $fromFolder, $toIe, $toFolder, $uids) {
5002 $this->moveEmails($fromIe, $fromFolder, $toIe, $toFolder, $uids, true);
5006 * moves emails from folder to folder
5007 * @param string $fromIe I-E id
5008 * @param string $fromFolder IMAP path to folder in which the email lives
5009 * @param string $toIe I-E id
5010 * @param string $toFolder
5011 * @param string $uids UIDs of emails to move, either Sugar GUIDS or IMAP
5013 * @param bool $copy Default false
5014 * @return bool True on successful execution
5016 function moveEmails($fromIe, $fromFolder, $toIe, $toFolder, $uids, $copy=false) {
5017 global $app_strings;
5018 global $current_user;
5022 if($fromIe == $toIe) {
5023 $GLOBALS['log']->debug("********* SUGARFOLDER - moveEmails() moving email from I-E to I-E");
5024 //$exDestFolder = explode("::", $toFolder);
5025 //preserve $this->mailbox
5026 if (isset($this->mailbox)) {
5027 $oldMailbox = $this->mailbox;
5031 $this->retrieve($fromIe);
5032 $this->mailbox = $fromFolder;
5033 $this->connectMailserver();
5034 $exUids = explode('::;::', $uids);
5035 $uids = implode(",", $exUids);
5036 // imap_mail_move accepts comma-delimited lists of UIDs
5038 if(imap_mail_copy($this->conn, $uids, $toFolder, CP_UID)) {
5039 $this->mailbox = $toFolder;
5040 $this->connectMailserver();
5041 $newOverviews = imap_fetch_overview($this->conn, $uids, FT_UID);
5042 $this->updateOverviewCacheFile($newOverviews, 'append');
5043 if (isset($oldMailbox)) {
5044 $this->mailbox = $oldMailbox;
5048 $GLOBALS['log']->debug("INBOUNDEMAIL: could not imap_mail_copy() [ {$uids} ] to folder [ {$toFolder} ] from folder [ {$fromFolder} ]");
5051 if(imap_mail_move($this->conn, $uids, $toFolder, CP_UID)) {
5052 $GLOBALS['log']->info("INBOUNDEMAIL: imap_mail_move() [ {$uids} ] to folder [ {$toFolder} ] from folder [ {$fromFolder} ]");
5053 imap_expunge($this->conn); // hard deletes moved messages
5055 // update cache on fromFolder
5056 $newOverviews = $this->getOverviewsFromCacheFile($uids, $fromFolder, true);
5057 $this->deleteCachedMessages($uids, $fromFolder);
5059 // update cache on toFolder
5060 $this->checkEmailOneMailbox($toFolder, true, true);
5061 if (isset($oldMailbox)) {
5062 $this->mailbox = $oldMailbox;
5067 $GLOBALS['log']->debug("INBOUNDEMAIL: could not imap_mail_move() [ {$uids} ] to folder [ {$toFolder} ] from folder [ {$fromFolder} ]");
5070 } elseif($toIe == 'folder' && $fromFolder == 'sugar::Emails') {
5071 $GLOBALS['log']->debug("********* SUGARFOLDER - moveEmails() moving email from SugarFolder to SugarFolder");
5072 // move from sugar folder to sugar folder
5073 require_once("include/SugarFolders/SugarFolders.php");
5074 $sugarFolder = new SugarFolder();
5075 $exUids = explode($app_strings['LBL_EMAIL_DELIMITER'], $uids);
5076 foreach($exUids as $id) {
5078 $sugarFolder->copyBean($fromIe, $toFolder, $id, "Emails");
5080 $fromSugarFolder = new SugarFolder();
5081 $fromSugarFolder->retrieve($fromIe);
5082 $toSugarFolder = new SugarFolder();
5083 $toSugarFolder->retrieve($toFolder);
5085 $email = new Email();
5086 $email->retrieve($id);
5087 $email->status = 'unread';
5089 // when you move from My Emails to Group Folder, Assign To field for the Email should become null
5090 if ($fromSugarFolder->is_dynamic && $toSugarFolder->is_group) {
5091 $email->assigned_user_id = "";
5093 if (!$toSugarFolder->checkEmailExistForFolder($id)) {
5094 $fromSugarFolder->deleteEmailFromAllFolder($id);
5095 $toSugarFolder->addBean($email);
5097 } elseif ($fromSugarFolder->is_group && $toSugarFolder->is_dynamic) {
5098 $fromSugarFolder->deleteEmailFromAllFolder($id);
5099 $email->assigned_user_id = $current_user->id;
5102 // If you are moving something from personal folder then delete an entry from all folder
5103 if (!$fromSugarFolder->is_dynamic && !$fromSugarFolder->is_group) {
5104 $fromSugarFolder->deleteEmailFromAllFolder($id);
5107 if ($fromSugarFolder->is_dynamic && !$toSugarFolder->is_dynamic && !$toSugarFolder->is_group) {
5108 $email->assigned_user_id = "";
5109 $toSugarFolder->addBean($email);
5111 if (!$toSugarFolder->checkEmailExistForFolder($id)) {
5112 if (!$toSugarFolder->is_dynamic) {
5113 $fromSugarFolder->deleteEmailFromAllFolder($id);
5114 $toSugarFolder->addBean($email);
5116 $fromSugarFolder->deleteEmailFromAllFolder($id);
5117 $email->assigned_user_id = $current_user->id;
5120 $sugarFolder->move($fromIe, $toFolder, $id);
5128 } elseif($toIe == 'folder') {
5129 $GLOBALS['log']->debug("********* SUGARFOLDER - moveEmails() moving email from I-E to SugarFolder");
5130 // move to Sugar folder
5131 require_once("include/SugarFolders/SugarFolders.php");
5132 $sugarFolder = new SugarFolder();
5133 $sugarFolder->retrieve($toFolder);
5134 //Show the import form if we don't have the required info
5135 if (!isset($_REQUEST['delete'])) {
5136 $json = getJSONobj();
5137 if ($sugarFolder->is_group) {
5138 $_REQUEST['showTeam'] = false;
5139 $_REQUEST['showAssignTo'] = false;
5141 $ret = $this->email->et->getImportForm($_REQUEST, $this->email);
5142 $ret['move'] = true;
5143 $ret['srcFolder'] = $fromFolder;
5144 $ret['srcIeId'] = $fromIe;
5145 $ret['dstFolder'] = $toFolder;
5146 $ret['dstIeId'] = $toIe;
5147 $out = trim($json->encode($ret, false));
5154 $this->retrieve($fromIe);
5155 $this->mailbox = $fromFolder;
5156 $this->connectMailserver();
5157 // If its a group folder the team should be of the folder team
5158 if ($sugarFolder->is_group) {
5159 $_REQUEST['team_id'] = $sugarFolder->team_id;
5160 $_REQUEST['team_set_id'] = $sugarFolder->team_set_id;
5162 // TODO - set team_id, team_set for new UI
5165 $exUids = explode($app_strings['LBL_EMAIL_DELIMITER'], $uids);
5167 if(!empty($sugarFolder->id)) {
5170 $json = getJSONobj();
5171 foreach($exUids as $k => $uid) {
5173 if ($this->isPop3Protocol()) {
5174 $msgNo = $this->getCorrectMessageNoForPop3($uid);
5176 $msgNo = imap_msgno($this->conn, $uid);
5179 if(!empty($msgNo)) {
5180 $importStatus = $this->importOneEmail($msgNo, $uid);
5183 $sugarFolder->addBean($this->email);
5184 if(!$copy && isset($_REQUEST['delete']) && ($_REQUEST['delete'] == "true") && $importStatus) {
5185 $GLOBALS['log']->error("********* delete from mailserver [ {explode(",", $uids)} ]");
5186 // delete from mailserver
5187 $this->deleteMessageOnMailServer($uid);
5188 $this->deleteMessageFromCache($uid);
5191 $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']);
5195 echo $json->encode($return);
5198 $GLOBALS['log']->error("********* SUGARFOLDER - failed to retrieve folder ID [ {$toFolder} ]");
5201 $GLOBALS['log']->debug("********* SUGARFOLDER - moveEmails() called with no passing criteria");
5209 * Hard deletes an I-E account
5210 * @param string id GUID
5212 function hardDelete($id) {
5213 $q = "DELETE FROM inbound_email WHERE id = '{$id}'";
5214 $r = $this->db->query($q, true);
5218 * Generate a unique filename for attachments based on the message id. There are no maximum
5219 * specifications for the length of the message id, the only requirement is that it be globally unique.
5221 * @param bool $nameOnly Whether or not the attachment count should be appended to the filename.
5222 * @return string The temp file name
5224 function getTempFilename($nameOnly=false) {
5226 $str = md5($this->compoundMessageId);
5229 $str = $str.$this->attachmentCount;
5230 $this->attachmentCount++;
5237 * deletes and expunges emails on server
5238 * @param string $uid UID(s), comma delimited, of email(s) on server
5239 * @return bool true on success
5241 function deleteMessageOnMailServer($uid) {
5242 global $app_strings;
5243 $this->connectMailserver();
5245 if(strpos($uid, $app_strings['LBL_EMAIL_DELIMITER']) !== false) {
5246 $uids = explode($app_strings['LBL_EMAIL_DELIMITER'], $uid);
5253 if($this->protocol == 'imap') {
5254 $trashFolder = $this->get_stored_options("trashFolder");
5255 if (empty($trashFolder)) {
5256 $trashFolder = "INBOX.Trash";
5258 foreach($uids as $uid) {
5259 if($this->moveEmails($this->id, $this->mailbox, $this->id, $trashFolder, $uid))
5260 $GLOBALS['log']->debug("INBOUNDEMAIL: MoveEmail to {$trashFolder} successful.");
5262 $GLOBALS['log']->debug("INBOUNDEMAIL: MoveEmail to {$trashFolder} FAILED - trying hard delete for message: $uid");
5263 imap_delete($this->conn, $uid, FT_UID);
5270 foreach($uids as $uid) {
5271 $msgnos[] = $this->getCorrectMessageNoForPop3($uid);
5273 $msgnos = implode(',', $msgnos);
5274 imap_delete($this->conn, $msgnos);
5278 if(!imap_expunge($this->conn)) {
5279 $GLOBALS['log']->debug("NOOP: could not expunge deleted email.");
5283 $GLOBALS['log']->info("INBOUNDEMAIL: hard-deleted mail with MSgno's' [ {$msgnos} ]");
5289 * deletes and expunges emails on server
5290 * @param string $uid UID(s), comma delimited, of email(s) on server
5292 function deleteMessageOnMailServerForPop3($uid) {
5293 if(imap_delete($this->conn, $uid)) {
5294 if(!imap_expunge($this->conn)) {
5295 $GLOBALS['log']->debug("NOOP: could not expunge deleted email.");
5298 $GLOBALS['log']->info("INBOUNDEMAIL: hard-deleted mail with MSgno's' [ {$uid} ]");
5304 * Checks if this is a pop3 type of an account or not
5307 function isPop3Protocol() {
5308 return ($this->protocol == 'pop3');
5312 * Gets the UIDL from database for the corresponding msgno
5313 * @param int messageNo of a message
5314 * @return UIDL for the message
5316 function getUIDLForMessage($msgNo) {
5317 $query = "SELECT message_id FROM email_cache WHERE ie_id = '{$this->id}' AND msgno = '{$msgNo}'";
5318 $r = $this->db->query($query);
5319 $a = $this->db->fetchByAssoc($r);
5320 return $a['message_id'];
5323 * Get the users default IE account id
5328 function getUsersDefaultOutboundServerId($user)
5330 $id = $user->getPreference($this->keyForUsersDefaultIEAccount,'Emails',$user);
5331 //If no preference has been set, grab the default system id.
5334 $oe = new OutboundEmail();
5335 $system = $oe->getSystemMailerSettings();
5336 $id=empty($system->id) ? '' : $system->id;
5343 * Get the users default IE account id
5347 function setUsersDefaultOutboundServerId($user,$oe_id)
5349 $user->setPreference($this->keyForUsersDefaultIEAccount, $oe_id, '', 'Emails');
5352 * Gets the UIDL from database for the corresponding msgno
5353 * @param int messageNo of a message
5354 * @return UIDL for the message
5356 function getMsgnoForMessageID($messageid) {
5357 $query = "SELECT msgno FROM email_cache WHERE ie_id = '{$this->id}' AND message_id = '{$messageid}'";
5358 $r = $this->db->query($query);
5359 $a = $this->db->fetchByAssoc($r);
5360 return $a['message_id'];
5364 * fills InboundEmail->email with an email's details
5365 * @param int uid Unique ID of email
5366 * @param bool isMsgNo flag that passed ID is msgNo, default false
5367 * @param bool setRead Sets the 'seen' flag in cache
5368 * @param bool forceRefresh Skips cache file
5371 function setEmailForDisplay($uid, $isMsgNo=false, $setRead=false, $forceRefresh=false) {
5374 $GLOBALS['log']->debug("*** ERROR: INBOUNDEMAIL trying to setEmailForDisplay() with no UID");
5378 global $sugar_config;
5379 global $app_strings;
5381 // if its a pop3 then get the UIDL and see if this file name exist or not
5382 if ($this->isPop3Protocol()) {
5383 // get the UIDL from database;
5384 $cachedUIDL = md5($uid);
5385 $cache = "{$sugar_config['cache_dir']}modules/Emails/{$this->id}/messages/{$this->mailbox}{$cachedUIDL}.php";
5387 $cache = "{$sugar_config['cache_dir']}modules/Emails/{$this->id}/messages/{$this->mailbox}{$uid}.php";
5390 if(file_exists($cache) && !$forceRefresh) {
5391 $GLOBALS['log']->info("INBOUNDEMAIL: Using cache file for setEmailForDisplay()");
5393 include($cache); // profides $cacheFile
5396 $metaOut = unserialize($cacheFile['out']);
5397 $meta = $metaOut['meta']['email'];
5398 $email = new Email();
5400 foreach($meta as $k => $v) {
5404 $email->to_addrs = $meta['toaddrs'];
5405 $email->date_sent = $meta['date_start'];
5406 //_ppf($email,true);
5408 $this->email = $email;
5409 $this->email->email2init();
5412 $GLOBALS['log']->info("INBOUNDEMAIL: opening new connection for setEmailForDisplay()");
5413 if($this->isPop3Protocol()) {
5414 $msgNo = $this->getCorrectMessageNoForPop3($uid);
5416 if(empty($this->conn)) {
5417 $this->connectMailserver();
5419 $msgNo = ($isMsgNo) ? $uid : imap_msgno($this->conn, $uid);
5421 if(empty($this->conn)) {
5422 $status = $this->connectMailserver();
5423 if($status == "false") {
5424 $this->email = new Email();
5425 $this->email->name = $app_strings['LBL_EMAIL_ERROR_MAILSERVERCONNECTION'];
5432 $this->importOneEmail($msgNo, $uid, true);
5433 $this->email->id = '';
5434 $this->email->new_with_id = false;
5439 $this->setStatuses($uid, 'seen', 1);
5447 * Sets status for a particular attribute on the mailserver and the local cache file
5449 function setStatuses($uid, $field, $value) {
5450 global $sugar_config;
5451 /** available status fields
5455 [date] => Mon, 22 Jan 2007 17:32:57 -0800
5468 $file = "{$this->mailbox}.imapFetchOverview.php";
5469 $overviews = $this->getCacheValueForUIDs($this->mailbox, array($uid));
5471 if(!empty($overviews)) {
5474 foreach($overviews['retArr'] as $k => $obj) {
5475 if($obj->imap_uid == $uid) {
5476 $obj->$field = $value;
5481 if(!empty($updates)) {
5482 $this->setCacheValue($this->mailbox, array(), $updates);
5488 * Removes an email from the cache file, deletes the message from the cache too
5489 * @param string String of uids, comma delimited
5491 function deleteMessageFromCache($uids) {
5492 global $sugar_config;
5493 global $app_strings;
5495 // delete message cache file and email_cache file
5496 $exUids = explode($app_strings['LBL_EMAIL_DELIMITER'], $uids);
5498 foreach($exUids as $uid) {
5500 if ($this->isPop3Protocol()) {
5501 $q = "DELETE FROM email_cache WHERE message_id = '{$uid}' AND ie_id = '{$this->id}'";
5503 $q = "DELETE FROM email_cache WHERE imap_uid = {$uid} AND ie_id = '{$this->id}'";
5505 $r = $this->db->query($q);
5506 if ($this->isPop3Protocol()) {
5509 $msgCacheFile = "{$sugar_config['cache_dir']}modules/Emails/{$this->id}/messages/{$this->mailbox}{$uid}.php";
5510 if(file_exists($msgCacheFile)) {
5511 if(!unlink($msgCacheFile)) {
5512 $GLOBALS['log']->error("***ERROR: InboundEmail could not delete the cache file [ {$msgCacheFile} ]");
5521 * @param int uid UID of email to display
5522 * @param string mbox Mailbox to look in for the message
5523 * @param bool isMsgNo Flag to assume $uid is a MessageNo, not UniqueID, default false
5525 function displayOneEmail($uid, $mbox, $isMsgNo=false) {
5526 require_once("include/JSON.php");
5529 global $app_strings;
5530 global $app_list_strings;
5531 global $sugar_smarty;
5533 global $current_user;
5535 $fetchedAttributes = array(
5544 $souEmail = array();
5545 foreach($fetchedAttributes as $k) {
5546 if ($k == 'date_start') {
5547 $this->email->$k . " " . $this->email->time_start;
5548 //$timedate->to_display_date_time($msg->date)
5549 //$souEmail[$k] = $timedate->to_display_date_time(date('r', strtotime($this->email->$k . " " . $this->email->time_start)));
5550 //$souEmail[$k] = $timedate->swap_formats($this->email->$k . " " . $this->email->time_start, $timedate->get_date_time_format(true,$current_user), $timedate->get_db_date_time_format());
5551 //$souEmail[$k] = $timedate->to_display_date_time($souEmail[$k]);
5552 $souEmail[$k] = $this->email->$k . " " . $this->email->time_start;
5553 } elseif ($k == 'time_start') {
5556 $souEmail[$k] = trim($this->email->$k);
5560 // if a MsgNo is passed in, convert to UID
5562 $uid = imap_uid($this->conn, $uid);
5564 // meta object to allow quick retrieval for replies
5566 $meta['type'] = $this->email->type;
5567 $meta['uid'] = $uid;
5568 $meta['ieId'] = $this->id;
5569 $meta['email'] = $souEmail;
5570 $meta['mbox'] = $this->mailbox;
5575 $exMbox = explode("::", $mbox);
5579 if(!empty($this->email->cc_addrs)) {
5580 //$ccs = $this->collapseLongMailingList($this->email->cc_addrs);
5581 $ccs = to_html($this->email->cc_addrs_names);
5584 <td NOWRAP valign="top" class="displayEmailLabel">
5585 {$app_strings['LBL_EMAIL_CC']}:
5587 <td class="displayEmailValue">
5594 $meta['email']['cc_addrs'] = $ccs;
5597 if ($mbox == "sugar::Emails") {
5599 $q = "SELECT id, filename FROM notes WHERE parent_id = '{$uid}' AND deleted = 0";
5600 $r = $this->db->query($q);
5602 while($a = $this->db->fetchByAssoc($r)) {
5603 $url = "index.php?entryPoint=download&id={$a['id']}&type=notes";
5604 $lbl = ($i == 0) ? $app_strings['LBL_EMAIL_ATTACHMENTS'].":" : '';
5606 $attachments .=<<<EOQ
5608 <td NOWRAP valign="top" class="displayEmailLabel">
5611 <td NOWRAP valign="top" colspan="2" class="displayEmailValue">
5612 <a href="{$url}">{$a['filename']}</a>
5622 if($this->attachmentCount > 0) {
5623 $theCount = $this->attachmentCount;
5625 for($i=0; $i<$theCount; $i++) {
5626 $lbl = ($i == 0) ? $app_strings['LBL_EMAIL_ATTACHMENTS'].":" : '';
5627 $name = $this->getTempFilename(true).$i;
5628 $tempName = urlencode($this->tempAttachment[$name]);
5630 $url = "index.php?entryPoint=download&id={$name}&type=temp&isTempFile=true&ieId={$this->id}&tempName={$tempName}";
5632 $attachments .=<<<eoq
5634 <td NOWRAP valign="top" class="displayEmailLabel">
5637 <td NOWRAP valign="top" colspan="2" class="displayEmailValue">
5638 <a href="{$url}">{$this->tempAttachment[$name]}</a>
5645 $meta['email']['attachments'] = $attachments;
5648 $meta['email']['toaddrs'] = $this->collapseLongMailingList($this->email->to_addrs);
5649 $meta['email']['cc_addrs'] = $ccs;
5652 $description = (empty($this->email->description_html)) ? nl2br($this->email->description) : $this->email->description_html;
5653 $meta['email']['description'] = $description;
5656 $meta['is_sugarEmail'] = ($exMbox[0] == 'sugar') ? true : false;
5658 if(!$meta['is_sugarEmail']) {
5659 if($this->isAutoImport) {
5660 $meta['is_sugarEmail'] = true;
5663 // mark SugarEmail read
5664 $q = "UPDATE emails SET status = 'read' WHERE id = '{$uid}'";
5665 $r = $this->db->query($q);
5669 $meta['email']['name'] = to_html($this->email->name);
5670 $meta['email']['from_addr'] = ( !empty($this->email->from_addr_name) ) ? to_html($this->email->from_addr_name) : to_html($this->email->from_addr);
5671 $meta['email']['toaddrs'] = ( !empty($this->email->to_addrs_names) ) ? to_html($this->email->to_addrs_names) : to_html($this->email->to_addrs);
5672 $meta['email']['cc_addrs'] = to_html($this->email->cc_addrs_names);
5673 $meta['email']['reply_to_addr'] = to_html($this->email->reply_to_addr);
5674 $return['meta'] = $meta;
5680 * Takes a long list of email addresses from a To or CC field and shows the first 3, the rest hidden
5681 * @param string emails
5684 function collapseLongMailingList($emails) {
5685 global $app_strings;
5687 $ex = explode(",", $emails);
5691 if(count($ex) > 3) {
5695 foreach($ex as $email) {
5697 if(!empty($emails)) {
5700 $emails .= trim($email);
5702 if(!empty($emailsHidden)) {
5703 $emailsHidden .= ", ";
5705 $emailsHidden .= trim($email);
5711 if(!empty($emailsHidden)) {
5713 $emails = "<span onclick='javascript:SUGAR.email2.detailView.showFullEmailList(this);' style='cursor:pointer;'>{$emails} [...{$j} {$app_strings['LBL_MORE']}]</span>";
5714 $emailsHidden = "<span onclick='javascript:SUGAR.email2.detailView.showCroppedEmailList(this)' style='cursor:pointer; display:none;'>{$email2}, {$emailsHidden} [ {$app_strings['LBL_LESS']} ]</span>";
5717 $emails .= $emailsHidden;
5725 * Sorts IMAP's imap_fetch_overview() results
5726 * @param array $arr Array of standard objects
5727 * @param string $sort Column to sort by
5728 * @param string direction Direction to sort by (asc/desc)
5729 * @return array Sorted array of obj.
5731 function sortFetchedOverview($arr, $sort=4, $direction='DESC', $forceSeen=false) {
5732 global $current_user;
5734 $sortPrefs = $current_user->getPreference('folderSortOrder', 'Emails');
5735 if(!empty($sortPrefs))
5736 $listPrefs = $sortPrefs;
5738 $listPrefs = array();
5740 if(isset($listPrefs[$this->id][$this->mailbox])) {
5741 $currentNode = $listPrefs[$this->id][$this->mailbox];
5744 if(isset($currentNode['current']) && !empty($currentNode['current'])) {
5745 $sort = $currentNode['current']['sort'];
5746 $direction = $currentNode['current']['direction'];
5751 $sort = $this->defaultSort;//4;
5752 $direction = $this->defaultDirection; //'DESC';
5753 } elseif(!is_numeric($sort)) {
5754 // handle bad sort index
5755 $sort = $this->defaultSort;
5757 // translate numeric index to human readable
5758 $sort = $this->hrSort[$sort];
5760 if(empty($direction)) {
5761 $direction = 'DESC';
5769 foreach($arr as $k => $overview) {
5770 $sorts['flagged'][$k] = $overview->flagged;
5771 $sorts['status'][$k] = $overview->answered;
5772 $sorts['from'][$k] = str_replace('"', "", $this->handleMimeHeaderDecode($overview->from));
5773 $sorts['subj'][$k] = $this->handleMimeHeaderDecode(quoted_printable_decode($overview->subject));
5774 $sorts['date'][$k] = $overview->date;
5778 natcasesort($sorts[$sort]);
5779 //_ppd($sorts[$sort]);
5781 if(strtolower($direction) == 'desc') {
5782 $revSorts = array();
5783 $keys = array_reverse(array_keys($sorts[$sort]));
5784 // _pp("count keys in DESC: ".count($keys));
5785 // _pp("count elements in sort[sort]: ".count($sorts[$sort]));
5787 for($i=0; $i<count($keys); $i++) {
5789 $revSorts[$v] = $sorts[$sort][$v];
5792 //_pp("count post-sort: ".count($revSorts));
5793 $sorts[$sort] = $revSorts;
5796 foreach($sorts[$sort] as $k2 => $overview2) {
5797 $conv = $this->getUnixHeaderDate($arr[$k2]->date);
5798 $arr[$k2]->date = date('Y-m-d H:i:s', $conv);
5799 $arr[$k2]->date = $arr[$k2]->date;
5800 $retArr[] = $arr[$k2];
5802 //_pp("final count: ".count($retArr));
5804 $finalReturn = array();
5805 $finalReturn['retArr'] = $retArr;
5806 $finalReturn['sortBy'] = $sort;
5807 $finalReturn['direction'] = $direction;
5808 return $finalReturn;
5812 function setReadFlagOnFolderCache($mbox, $uid) {
5813 global $sugar_config;
5815 $this->mailbox = $mbox;
5818 if($this->validCacheExists($this->mailbox)) {
5819 $ret = $eui->getCacheValue($this->mailbox);
5823 foreach($ret as $k => $v) {
5824 if($v->imap_uid == $uid) {
5831 $this->setCacheValue($this->mailbox, array(), $updates);
5836 * Returns a list of emails in a mailbox.
5837 * @param string mbox Name of mailbox using dot notation paths to display
5838 * @param string $forceRefresh Flag to use cache or not
5840 function displayFolderContents($mbox, $forceRefresh='false', $page) {
5841 global $current_user;
5843 $delimiter = $this->get_stored_options('folderDelimiter');
5845 $mbox = str_replace('.', $delimiter, $mbox);
5848 $this->mailbox = $mbox;
5850 // jchi #9424, get sort and direction from user preference
5852 $direction = 'desc';
5853 $sortSerial = $current_user->getPreference('folderSortOrder', 'Emails');
5854 if(!empty($sortSerial) && !empty($_REQUEST['ieId']) && !empty($_REQUEST['mbox'])) {
5855 $sortArray = unserialize($sortSerial);
5856 $sort = $sortArray[$_REQUEST['ieId']][$_REQUEST['mbox']]['current']['sort'];
5857 $direction = $sortArray[$_REQUEST['ieId']][$_REQUEST['mbox']]['current']['direction'];
5862 if(!empty($_REQUEST['sort']) && !empty($_REQUEST['dir'])) {
5863 $this->email->et->saveListViewSortOrder($_REQUEST['ieId'], $_REQUEST['mbox'], $_REQUEST['sort'], $_REQUEST['dir']);
5864 $sort = $_REQUEST['sort'];
5865 $direction = $_REQUEST['dir'];
5867 $_REQUEST['sort'] = '';
5868 $_REQUEST['dir'] = '';
5874 if($forceRefresh == 'false' && $this->validCacheExists($this->mailbox)) {
5875 $emailSettings = $current_user->getPreference('emailSettings', 'Emails');
5877 // cn: default to a low number until user specifies otherwise
5878 if(empty($emailSettings['showNumInList'])) {
5879 $emailSettings['showNumInList'] = 20;
5882 $ret = $this->getCacheValue($this->mailbox, $emailSettings['showNumInList'], $page, $sort, $direction);
5886 $out = $this->displayFetchedSortedListXML($ret, $mbox);
5888 $metadata = array();
5889 $metadata['mbox'] = $mbox;
5890 $metadata['ieId'] = $this->id;
5891 $metadata['name'] = $this->name;
5892 $metadata['fromCache'] = $cacheUsed ? 1 : 0;
5893 $metadata['out'] = $out;
5899 * For a group email account, create subscriptions for all users associated with the
5900 * team assigned to the account.
5903 function createUserSubscriptionsForGroupAccount()
5906 $team->retrieve($this->team_id);
5907 $usersList = $team->get_team_members(true);
5908 foreach($usersList as $userObject)
5910 $previousSubscriptions = unserialize(base64_decode($userObject->getPreference('showFolders', 'Emails',$userObject)));
5911 if($previousSubscriptions === FALSE)
5912 $previousSubscriptions = array();
5914 $previousSubscriptions[] = $this->id;
5916 $encodedSubs = base64_encode(serialize($previousSubscriptions));
5917 $userObject->setPreference('showFolders',$encodedSubs , '', 'Emails');
5918 $userObject->savePreferencesToDB();
5922 * Create a sugar folder for this inbound email account
5923 * if the Enable Auto Import option is selected
5925 * @return String Id of the sugar folder created.
5927 function createAutoImportSugarFolder()
5929 global $current_user;
5930 $guid = create_guid();
5931 $GLOBALS['log']->debug("Creating Sugar Folder for IE with id $guid");
5932 $folder = new SugarFolder();
5933 $folder->id = $guid;
5934 $folder->new_with_id = TRUE;
5935 $folder->name = $this->name;
5936 $folder->has_child = 0;
5937 $folder->is_group = 1;
5938 $folder->assign_to_id = $current_user->id;
5939 $folder->parent_folder = "";
5942 //If this inbound email is marked as inactive, don't add subscriptions.
5943 $addSubscriptions = ($this->status == 'Inactive' || $this->mailbox_type == 'bounce') ? FALSE : TRUE;
5944 $folder->save($addSubscriptions);
5949 function validCacheExists($mbox) {
5950 $q = "SELECT count(*) c FROM email_cache WHERE ie_id = '{$this->id}'";
5951 $r = $this->db->query($q);
5952 $a = $this->db->fetchByAssoc($r);
5965 function displayFetchedSortedListXML($ret, $mbox) {
5968 global $current_user;
5969 global $sugar_config;
5971 if(empty($ret['retArr'])) {
5975 $tPref = $current_user->getUserDateTimePreferences();
5979 foreach($ret['retArr'] as $msg) {
5981 $flagged = ($msg->flagged == 0) ? "" : $this->iconFlagged;
5982 $status = ($msg->deleted) ? $this->iconDeleted : "";
5983 $status = ($msg->draft == 0) ? $status : $this->iconDraft;
5984 $status = ($msg->answered == 0) ? $status : $this->iconAnswered;
5985 $from = $this->handleMimeHeaderDecode($msg->from);
5986 $subject = $this->handleMimeHeaderDecode($msg->subject);
5987 //$date = date($tPref['date']." ".$tPref['time'], $msg->date);
5988 $date = $timedate->to_display_date_time($msg->date);
5989 //$date = date($tPref['date'], $this->getUnixHeaderDate($msg->date));
5992 $temp['flagged'] = $flagged;
5993 $temp['status'] = $status;
5994 $temp['from'] = $from;
5995 $temp['subject'] = $subject;
5996 $temp['date'] = $date;
5997 $temp['uid'] = $msg->uid; // either from an imap_search() or massaged cache value
5998 $temp['mbox'] = $this->mailbox;
5999 $temp['ieId'] = $this->id;
6000 $temp['site_url'] = $sugar_config['site_url'];
6001 $temp['seen'] = $msg->seen;
6002 $temp['type'] = (isset($msg->type)) ? $msg->type: 'remote';
6003 $temp['to_addrs'] = $msg->to;
6004 $temp['hasAttach'] = '0';
6015 * retrieves the mailboxes for a given account in the following format
6020 [Builder] => Builder
6027 * @param bool $justRaw Default false
6030 function getMailboxes($justRaw=false) {
6031 if($justRaw == true) {
6032 return $this->mailboxarray;
6035 return $this->generateMultiDimArrayFromFlatArray($this->mailboxarray, $this->retrieveDelimiter());
6037 $serviceString = $this->getConnectString('', '', false);
6039 if(strpos($serviceString, 'pop3')) {
6041 $obj->name = $serviceString."INBOX";
6042 $boxes = array("INBOX" => $obj);
6044 $boxes = imap_getmailboxes($this->conn, $serviceString, "*");
6049 // clean MBOX path names
6050 foreach($boxes as $k => $mbox) {
6051 $raw[] = str_replace($serviceString, "", $mbox->name);
6052 if ($mbox->delimiter) {
6053 $delimiter = $mbox->delimiter;
6056 $storedOptions = unserialize(base64_decode($this->stored_options));
6057 $storedOptions['folderDelimiter'] = $delimiter;
6058 $this->stored_options = base64_encode(serialize($storedOptions));
6064 // used by $this->search()
6065 if($justRaw == true) {
6070 // generate a multi-dimensional array to iterate through
6072 foreach($raw as $mbox) {
6073 $ret = $this->sortMailboxes($mbox, $ret, $delimiter);
6080 function getMailBoxesForGroupAccount() {
6081 $mailboxes = $this->generateMultiDimArrayFromFlatArray(explode(",", $this->mailbox), $this->retrieveDelimiter());
6082 $mailboxesArray = $this->generateFlatArrayFromMultiDimArray($mailboxes, $this->retrieveDelimiter());
6083 $mailboxesArray = $this->filterMailBoxFromRaw(explode(",", $this->mailbox), $mailboxesArray);
6084 $this->saveMailBoxFolders($mailboxesArray);
6086 if ($this->mailbox != $this->$email_user) {
6087 $mailboxes = $this->sortMailboxes($this->mailbox, $this->retrieveDelimiter());
6088 $mailboxesArray = $this->generateFlatArrayFromMultiDimArray($mailboxes, $this->retrieveDelimiter());
6089 $this->saveMailBoxFolders($mailboxesArray);
6090 // save mailbox value of an inbound email account to email user
6091 $this->saveMailBoxValueOfInboundEmail();
6093 $mailboxes = $this->getMailboxes();
6099 function saveMailBoxFolders($value) {
6100 if (is_array($value)) {
6101 $value = implode(",", $value);
6103 $this->mailboxarray = explode(",", $value);
6104 $value = $this->db->helper->escape_quote($value);
6105 //$query = "update config set value = '{$value}' where category='InboundEmail' and name='{$this->id}' )";
6106 $query = "update inbound_email set mailbox = '{$value}' where id ='{$this->id}'";
6107 $this->db->query($query);
6110 function insertMailBoxFolders($value) {
6111 $query = "select value from config where category='InboundEmail' and name='{$this->id}'";
6112 $r = $this->db->query($query);
6113 $a = $this->db->fetchByAssoc($r);
6114 if (empty($a['value'])) {
6115 if (is_array($value)) {
6116 $value = implode(",", $value);
6118 $this->mailboxarray = explode(",", $value);
6119 $value = $this->db->helper->escape_quote($value);
6121 $query = "INSERT INTO config VALUES('InboundEmail', '{$this->id}', '{$value}')";
6122 $this->db->query($query);
6126 function saveMailBoxValueOfInboundEmail() {
6127 $query = "update Inbound_email set mailbox = '{$this->email_user}'";
6128 $this->db->query($query);
6131 function retrieveMailBoxFolders() {
6132 $this->mailboxarray = explode(",", $this->mailbox);
6134 $query = "select value from config where category='InboundEmail' and name='{$this->id}'";
6135 $r = $this->db->query($query);
6136 $a = $this->db->fetchByAssoc($r);
6137 $this->mailboxarray = explode(",", $a['value']);
6142 function retrieveDelimiter() {
6143 $delimiter = $this->get_stored_options('folderDelimiter');
6150 function generateFlatArrayFromMultiDimArray($arraymbox, $delimiter) {
6152 foreach($arraymbox as $key => $value) {
6153 $this->generateArrayData($key, $value, $ret, $delimiter);
6159 function generateMultiDimArrayFromFlatArray($raw, $delimiter) {
6160 // generate a multi-dimensional array to iterate through
6162 foreach($raw as $mbox) {
6163 $ret = $this->sortMailboxes($mbox, $ret, $delimiter);
6169 function generateArrayData($key, $arraymbox, &$ret, $delimiter) {
6171 if (is_array($arraymbox)) {
6172 foreach($arraymbox as $mboxKey => $value) {
6173 $newKey = $key . $delimiter . $mboxKey;
6174 $this->generateArrayData($newKey, $value, $ret, $delimiter);
6180 * sorts the folders in a mailbox in a multi-dimensional array
6181 * @param string $MBOX
6185 function sortMailboxes($mbox, $ret, $delimeter = ".") {
6186 if(strpos($mbox, $delimeter)) {
6187 $node = substr($mbox, 0, strpos($mbox, $delimeter));
6188 $nodeAfter = substr($mbox, strpos($mbox, $node) + strlen($node) + 1, strlen($mbox));
6190 if(!isset($ret[$node])) {
6191 $ret[$node] = array();
6192 } elseif(isset($ret[$node]) && !is_array($ret[$node])) {
6193 $ret[$node] = array();
6195 $ret[$node] = $this->sortMailboxes($nodeAfter, $ret[$node], $delimeter);
6197 $ret[$mbox] = $mbox;
6204 * parses Sugar's storage method for imap server service strings
6207 function getServiceString() {
6209 $exServ = explode('::', $this->service);
6211 foreach($exServ as $v) {
6212 if(!empty($v) && ($v != 'imap' && $v !='pop3')) {
6219 } // end class definition
6223 * Simple class to mirror the passed object from an imap_fetch_overview() call
6242 var $indices; /* = array(
6245 'name' => 'mail_date',
6253 'name' => 'mail_from',
6261 'name' => 'mail_subj',
6270 var $fieldDefs;/* = array(
6273 'type' => 'varchar',
6278 'name' => 'subject',
6279 'type' => 'varchar',
6281 'required' => false,
6283 'fromaddr' => array(
6284 'name' => 'fromaddr',
6285 'type' => 'varchar',
6291 'type' => 'varchar',
6295 'senddate' => array(
6296 'name' => 'senddate',
6297 'type' => 'datetime',
6300 'message_id' => array(
6301 'name' => 'message_id',
6302 'type' => 'varchar',
6304 'required' => false,
6306 'mailsize' => array(
6307 'name' => 'mailsize',
6322 'required' => false,
6326 'type' => 'tinyint',
6331 'name' => 'flagged',
6332 'type' => 'tinyint',
6336 'answered' => array(
6337 'name' => 'answered',
6338 'type' => 'tinyint',
6343 'name' => 'deleted',
6344 'type' => 'tinyint',
6350 'type' => 'tinyint',
6356 'type' => 'tinyint',
6362 function Overview() {
6365 if(!isset($dictionary['email_cache']) || empty($dictionary['email_cache'])) {
6366 if(file_exists('custom/metadata/email_cacheMetaData.php')) {
6367 include('custom/metadata/email_cacheMetaData.php');
6369 include('metadata/email_cacheMetaData.php');
6373 $this->fieldDefs = $dictionary['email_cache']['fields'];
6374 $this->indices = $dictionary['email_cache']['indices'];