InboundEmail::retrieve()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2
Metric Value
cc 2
eloc 6
nc 2
nop 3
dl 0
loc 9
ccs 6
cts 6
cp 1
crap 2
rs 9.6666
1
<?php
2 1
if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3
/*********************************************************************************
4
 * SugarCRM Community Edition is a customer relationship management program developed by
5
 * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
6
7
 * SuiteCRM is an extension to SugarCRM Community Edition developed by Salesagility Ltd.
8
 * Copyright (C) 2011 - 2014 Salesagility Ltd.
9
 *
10
 * This program is free software; you can redistribute it and/or modify it under
11
 * the terms of the GNU Affero General Public License version 3 as published by the
12
 * Free Software Foundation with the addition of the following permission added
13
 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
14
 * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
15
 * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
16
 *
17
 * This program is distributed in the hope that it will be useful, but WITHOUT
18
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19
 * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
20
 * details.
21
 *
22
 * You should have received a copy of the GNU Affero General Public License along with
23
 * this program; if not, see http://www.gnu.org/licenses or write to the Free
24
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
25
 * 02110-1301 USA.
26
 *
27
 * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
28
 * SW2-130, Cupertino, CA 95014, USA. or at email address [email protected].
29
 *
30
 * The interactive user interfaces in modified source and object code versions
31
 * of this program must display Appropriate Legal Notices, as required under
32
 * Section 5 of the GNU Affero General Public License version 3.
33
 *
34
 * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
35
 * these Appropriate Legal Notices must retain the display of the "Powered by
36
 * SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not
37
 * reasonably feasible for  technical reasons, the Appropriate Legal Notices must
38
 * display the words  "Powered by SugarCRM" and "Supercharged by SuiteCRM".
39
 ********************************************************************************/
40
41
42 1
require_once('include/OutboundEmail/OutboundEmail.php');
43
44 1
function this_callback($str) {
45
	foreach($str as $match) {
46
		$ret .= chr(hexdec(str_replace("%","",$match)));
47
	}
48
	return $ret;
49
}
50
51
/**
52
 * Stub for certain interactions;
53
 */
54
class temp {
55
	var $name;
56
}
57
58
class InboundEmail extends SugarBean {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
59
	// module specific
60
	var $conn;
61
	var $purifier; // HTMLPurifier object placeholder
62
	var $email;
63
64
	// fields
65
	var $id;
66
	var $deleted;
67
	var $date_entered;
68
	var $date_modified;
69
	var $modified_user_id;
70
	var $created_by;
71
	var $created_by_name;
72
	var $modified_by_name;
73
	var $name;
74
	var $status;
75
	var $server_url;
76
	var $email_user;
77
	var $email_password;
78
	var $port;
79
	var $service;
80
	var $mailbox;
81
	var $mailboxarray;
82
	var $delete_seen;
83
	var $mailbox_type;
84
	var $template_id;
85
	var $stored_options;
86
	var $group_id;
87
	var $is_personal;
88
	var $groupfolder_id;
89
90
	// email 2.0
91
	var $pop3socket;
92
	var $outboundInstance; // id to outbound_email instance
93
	var $autoImport;
94
	var $iconFlagged = "F";
95
	var $iconDraft = "D";
96
	var $iconAnswered = "A";
97
	var $iconDeleted = "del";
98
	var $isAutoImport = false;
99
	var $smarty;
100
	var $attachmentCount = 0;
101
	var $tempAttachment = array();
102
	var $unsafeChars = array("&", "!", "'", '"', '\\', '/', '<', '>', '|', '$',);
103
	var $currentCache;
104
	var $defaultSort = 'date';
105
	var $defaultDirection = "DESC";
106
	var $hrSort = array(
107
			0 => 'flagged',
108
			1 => 'status',
109
			2 => 'from',
110
			3 => 'subj',
111
			4 => 'date',
112
		);
113
	var $hrSortLocal = array(
114
			'flagged' => 'flagged',
115
			'status'  => 'answered',
116
			'from'    => 'fromaddr',
117
			'subject' => 'subject',
118
			'date'    => 'senddate',
119
		);
120
121
	// default attributes
122
	var $transferEncoding				  = array(0 => '7BIT',
123
												1 => '8BIT',
124
												2 => 'BINARY',
125
												3 => 'BASE64',
126
												4 => 'QUOTED-PRINTABLE',
127
												5 => 'OTHER'
128
											);
129
	// object attributes
130
	var $compoundMessageId; // concatenation of messageID and deliveredToEmail
131
	var $serverConnectString;
132
	var $disable_row_level_security	= true;
133
	var $InboundEmailCachePath;
134
	var $InboundEmailCacheFile			= 'InboundEmail.cache.php';
135
	var $object_name					= 'InboundEmail';
136
	var $module_dir					= 'InboundEmail';
137
	var $table_name					= 'inbound_email';
138
	var $new_schema					= true;
139
	var $process_save_dates 			= true;
140
	var $order_by;
141
	var $dbManager;
142
	var $field_defs;
143
	var $column_fields;
144
	var $required_fields				= array('name'			=> 'name',
145
												'server_url' 	=> 'server_url',
146
												'mailbox'		=> 'mailbox',
147
												'user'			=> 'user',
148
												'port'			=> 'port',
149
											);
150
	var $imageTypes					= array("JPG", "JPEG", "GIF", "PNG");
151
	var $inlineImages					= array();  // temporary space to store ID of inlined images
152
	var $defaultEmailNumAutoreplies24Hours = 10;
153
	var $maxEmailNumAutoreplies24Hours = 10;
154
	// custom ListView attributes
155
	var $mailbox_type_name;
156
	var $global_personal_string;
157
	// service attributes
158
	var $tls;
159
	var $ca;
160
	var $ssl;
161
	var $protocol;
162
	var $keyForUsersDefaultIEAccount = 'defaultIEAccount';
163
	// prefix to use when importing inlinge images in emails
164
	public $imagePrefix;
165
166
	/**
167
	 * Sole constructor
168
	 */
169 142
    public function __construct() {
170 142
	    $this->InboundEmailCachePath = sugar_cached('modules/InboundEmail');
171 142
	    $this->EmailCachePath = sugar_cached('modules/Emails');
172 142
	    parent::__construct();
173 142
		if(function_exists("imap_timeout")) {
174
			/*
175
			 * 1: Open
176
			 * 2: Read
177
			 * 3: Write
178
			 * 4: Close
179
			 */
180 142
			imap_timeout(1, 60);
181 142
			imap_timeout(2, 60);
182 142
			imap_timeout(3, 60);
183
		}
184
185 142
		$this->smarty = new Sugar_Smarty();
186 142
		$this->overview = new Overview();
187 142
		$this->imagePrefix = "{$GLOBALS['sugar_config']['site_url']}/cache/images/";
188 142
	}
189
190
	/**
191
	 * retrieves I-E bean
192
	 * @param string id
193
	 * @return object Bean
194
	 */
195 3
	function retrieve($id = -1, $encode=true, $deleted=true) {
196 3
		$ret = parent::retrieve($id,$encode,$deleted);
197
		// if I-E bean exist
198 3
		if (!is_null($ret)) {
199 1
		    $this->email_password = blowfishDecode(blowfishGetKey('InboundEmail'), $this->email_password);
200 1
		    $this->retrieveMailBoxFolders();
201
		}
202 3
		return $ret;
203
	}
204
205
	/**
206
	 * wraps SugarBean->save()
207
	 * @param string ID of saved bean
208
	 */
209 1
	function save($check_notify=false) {
210
		// generate cache table for email 2.0
211 1
		$multiDImArray = $this->generateMultiDimArrayFromFlatArray(explode(",", $this->mailbox), $this->retrieveDelimiter());
212 1
		$raw = $this->generateFlatArrayFromMultiDimArray($multiDImArray, $this->retrieveDelimiter());
213 1
		sort($raw);
214
		//_pp(explode(",", $this->mailbox));
215
		//_ppd($raw);
216 1
		$raw = $this->filterMailBoxFromRaw(explode(",", $this->mailbox), $raw);
217 1
		$this->mailbox = implode(",", $raw);
218 1
		if(!empty($this->email_password)) {
219 1
		    $this->email_password = blowfishEncode(blowfishGetKey('InboundEmail'), $this->email_password);
220
		}
221 1
		$ret = parent::save($check_notify);
222 1
		return $ret;
223
	}
224
225 3
	function filterMailBoxFromRaw($mailboxArray, $rawArray) {
226 3
		$newArray = array_intersect($mailboxArray, $rawArray);
227 3
		sort($newArray);
228 3
		return $newArray;
229
	} // fn
230
231
	/**
232
	 * Overrides SugarBean's mark_deleted() to drop the related cache table
233
	 * @param string $id GUID of I-E instance
234
	 */
235
	function mark_deleted($id) {
236
		parent::mark_deleted($id);
237
238
		//bug52021  we need to keep the reference to the folders table in order for emails module to function properly
239
		//$q = "update inbound_email set groupfolder_id = null WHERE id = '{$id}'";
240
		//$r = $this->db->query($q);
241
		$this->deleteCache();
242
	}
243
244
	/**
245
	 * Mark cached email answered (replied)
246
	 * @param string $mailid (uid for imap, message_id for pop3)
247
	 */
248 1
	function mark_answered($mailid, $type = 'smtp') {
249
		switch ($type) {
250 1
			case 'smtp' :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
251
				$q = "update email_cache set answered = 1 WHERE imap_uid = $mailid and ie_id = '{$this->id}'";
252
				$this->db->query($q);
253
				break;
254 1
			case 'pop3' :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
255 1
				$q = "update email_cache set answered = 1 WHERE message_id = '$mailid' and ie_id = '{$this->id}'";
256 1
				$this->db->query($q);
257 1
				break;
258
		}
259 1
	}
260
261
	/**
262
	 * Renames an IMAP mailbox
263
	 * @param string $newName
264
	 */
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} ]");
272
		} else {
273
        	$this->mailbox = str_replace($oldName, $newName, $this->mailbox);
274
        	$this->save();
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);
278
279
		}
280
	}
281
282
	///////////////////////////////////////////////////////////////////////////
283
	////	CUSTOM LOGIC HOOKS
284
	/**
285
	 * Called from $this->getMessageText()
286
	 * Allows upgrade-safe custom processing of message text.
287
	 *
288
	 * To use:
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
292
	 *
293
	 * @param string $msgPart
294
	 * @return string
295
	 */
296 1
	function customGetMessageText($msgPart) {
297 1
		$custom = "custom/modules/InboundEmail/getMessageText.php";
298
299 1
		if(file_exists($custom)) {
300
			include_once($custom);
301
302
			if(function_exists("custom_getMessageText")) {
303
				$GLOBALS['log']->debug("*** INBOUND EMAIL-CUSTOM_LOGIC: calling custom_getMessageText()");
304
				$msgPart = custom_getMessageText($msgPart);
305
			}
306
		}
307
308 1
		return $msgPart;
309
	}
310
	////	END CUSTOM LOGIC HOOKS
311
	///////////////////////////////////////////////////////////////////////////
312
313
314
315
	///////////////////////////////////////////////////////////////////////////
316
	////	EMAIL 2.0 SPECIFIC
317
	/**
318
	 * constructs a nicely formatted version of raw source
319
	 * @param int $uid UID of email
320
	 * @return string
321
	 */
322 1
	function getFormattedRawSource($uid) {
323 1
		global $app_strings;
324
325
		//if($this->protocol == 'pop3') {
326
		//$raw = $app_strings['LBL_EMAIL_VIEW_UNSUPPORTED'];
327
		//} else {
328 1
			if (empty($this->id)) {
329 1
				$q = "SELECT raw_source FROM emails_text WHERE email_id = '{$uid}'";
330 1
				$r = $this->db->query($q);
331 1
				$a = $this->db->fetchByAssoc($r);
332 1
				$ret = array();
333 1
				$raw = $this->convertToUtf8($a['raw_source']);
334 1
				if (empty($raw)) {
335 1
					$raw = $app_strings['LBL_EMAIL_ERROR_VIEW_RAW_SOURCE'];
336
				}
337
			} else {
338 1
				if ($this->isPop3Protocol()) {
339
					$uid = $this->getCorrectMessageNoForPop3($uid);
340
				}
341 1
				$raw  = imap_fetchheader($this->conn, $uid, FT_UID+FT_PREFETCHTEXT);
342 1
				$raw .= $this->convertToUtf8(imap_body($this->conn, $uid, FT_UID));
343
			} // else
344 1
			$raw = to_html($raw);
345 1
			$raw = nl2br($raw);
346
		//}
347
348 1
		return $raw;
349
	}
350
351
352
    /**
353
     * helper method to convert text to utf-8 if necessary
354
     *
355
     * @param string $input text
356
     * @return string output text
357
     */
358 2
    function convertToUtf8($input)
359
    {
360 2
       $charset = $GLOBALS['locale']->detectCharset($input, true);
361
362
       // we haven't a clue due to missing package?, just return as itself
363 2
       if ($charset === FALSE) {
364
           return $input;
365
       }
366
367
       // convert if we can or must
368 2
       return $this->handleCharsetTranslation($input, $charset);
369
    }
370
371
	/**
372
	 * constructs a nicely formatted version of email headers.
373
	 * @param int $uid
374
	 * @return string
375
	 */
376 1
	function getFormattedHeaders($uid) {
377 1
		global $app_strings;
378
379
		//if($this->protocol == 'pop3') {
380
		//	$header = $app_strings['LBL_EMAIL_VIEW_UNSUPPORTED'];
381
		//} else {
382 1
			if ($this->isPop3Protocol()) {
383 1
				$uid = $this->getCorrectMessageNoForPop3($uid);
384
			}
385 1
			$headers = imap_fetchheader($this->conn, $uid, FT_UID);
386
387 1
			$lines = explode("\n", $headers);
388
389 1
			$header = "<table cellspacing='0' cellpadding='2' border='0' width='100%'>";
390
391 1
			foreach($lines as $line) {
392 1
				$line = trim($line);
393
394 1
				if(!empty($line)) {
395
					$key = trim(substr($line, 0, strpos($line, ":")));
396
					$key = strip_tags($key);
397
					$value = trim(substr($line, strpos($line, ":") + 1));
398
					$value = to_html($value);
399
400
					$header .= "<tr>";
401
					$header .= "<td class='displayEmailLabel' NOWRAP><b>{$key}</b>&nbsp;</td>";
402
					$header .= "<td class='displayEmailValueWhite'>{$value}&nbsp;</td>";
403 1
					$header .= "</tr>";
404
				}
405
			}
406
407 1
			$header .= "</table>";
408
		//}
409 1
		return $header;
410
	}
411
412
	/**
413
	 * Empties Trash folders
414
	 */
415 1
	function emptyTrash() {
416 1
		global $sugar_config;
417
418 1
		$this->mailbox = $this->get_stored_options("trashFolder");
419 1
		if (empty($this->mailbox)) {
420 1
			$this->mailbox = 'INBOX.Trash';
421
		}
422 1
		$this->connectMailserver();
423
424 1
		$uids = imap_search($this->conn, "ALL", SE_UID);
425
426 1
		foreach($uids as $uid) {
427
			if(!imap_delete($this->conn, $uid, FT_UID)) {
428
				$lastError = imap_last_error();
429
				$GLOBALS['log']->warn("INBOUNDEMAIL: emptyTrash() Could not delete message [ {$uid} ] from [ {$this->mailbox} ].  IMAP_ERROR [ {$lastError} ]");
430
			}
431
		}
432
433
		// remove local cache file
434 1
		$q = "DELETE FROM email_cache WHERE mbox = '{$this->mailbox}' AND ie_id = '{$this->id}'";
435 1
		$r = $this->db->query($q);
436 1
	}
437
438
	/**
439
	 * Fetches a timestamp
440
	 */
441 16
	function getCacheTimestamp($mbox) {
442 16
		$key = $this->db->quote("{$this->id}_{$mbox}");
443 16
		$q = "SELECT ie_timestamp FROM inbound_email_cache_ts WHERE id = '{$key}'";
444 16
		$r = $this->db->query($q);
445 16
		$a = $this->db->fetchByAssoc($r);
446
447 16
		if(empty($a)) {
448 6
			return -1;
449
		}
450 11
		return $a['ie_timestamp'];
451
	}
452
453
	/**
454
	 * sets the cache timestamp
455
	 * @param string mbox
456
	 */
457 3
	function setCacheTimestamp($mbox) {
458 3
		$key = $this->db->quote("{$this->id}_{$mbox}");
459 3
		$ts = mktime();
460 3
		$tsOld = $this->getCacheTimestamp($mbox);
461
462 3
		if($tsOld < 0) {
463 2
			$q = "INSERT INTO inbound_email_cache_ts (id, ie_timestamp) VALUES ('{$key}', {$ts})";
464
		} else {
465 1
			$q = "UPDATE inbound_email_cache_ts SET ie_timestamp = {$ts} WHERE id = '{$key}'";
466
		}
467
468 3
		$r = $this->db->query($q, true);
469 3
		$GLOBALS['log']->info("INBOUNDEMAIL-CACHE: setting timestamp query [ {$q} ]");
470 3
	}
471
472
473
	/**
474
	 * Gets a count of all rows that are flagged seen = 0
475
	 * @param string $mbox
476
	 * @return int
477
	 */
478 1
	function getCacheUnreadCount($mbox) {
479 1
		$q = "SELECT count(*) c FROM email_cache WHERE mbox = '{$mbox}' AND seen = 0 AND ie_id = '{$this->id}'";
480 1
		$r = $this->db->query($q);
481 1
		$a = $this->db->fetchByAssoc($r);
482
483 1
		return $a['c'];
484
	}
485
486
	/**
487
	 * Returns total number of emails for a mailbox
488
	 * @param string mbox
489
	 * @return int
490
	 */
491 2
	function getCacheCount($mbox) {
492 2
		$q = "SELECT count(*) c FROM email_cache WHERE mbox = '{$mbox}' AND ie_id = '{$this->id}'";
493 2
		$r = $this->db->query($q);
494 2
		$a = $this->db->fetchByAssoc($r);
495
496 2
		return $a['c'];
497
	}
498
499 1
    function getCacheUnread($mbox) {
500 1
        $q = "SELECT count(*) c FROM email_cache WHERE mbox = '{$mbox}' AND ie_id = '{$this->id}' AND seen = '0'";
501 1
        $r = $this->db->query($q);
502 1
        $a = $this->db->fetchByAssoc($r);
503
504 1
        return $a['c'];
505
    }
506
507
508
	/**
509
	 * Deletes all rows for a given instance
510
	 */
511 2
	function deleteCache() {
512 2
		$q = "DELETE FROM email_cache WHERE ie_id = '{$this->id}'";
513
514 2
		$GLOBALS['log']->info("INBOUNDEMAIL: deleting cache using query [ {$q} ]");
515
516 2
		$r = $this->db->query($q);
517 2
	}
518
519
	/**
520
	 * Deletes all the pop3 data which has been deleted from server
521
	 */
522 1
	function deletePop3Cache() {
523 1
		global $sugar_config;
524 1
		$UIDLs = $this->pop3_getUIDL();
525 1
		$cacheUIDLs = $this->pop3_getCacheUidls();
526 1
		foreach($cacheUIDLs as $msgNo => $msgId) {
527
			if (!in_array($msgId, $UIDLs)) {
528
				$md5msgIds = md5($msgId);
529
				$file = "{$this->EmailCachePath}/{$this->id}/messages/INBOX{$md5msgIds}.PHP";
530
				$GLOBALS['log']->debug("INBOUNDEMAIL: deleting file [ {$file} ] ");
531
				if(file_exists($file)) {
532
					if(!unlink($file)) {
533
						$GLOBALS['log']->debug("INBOUNDEMAIL: Could not delete [ {$file} ] ");
534
					} // if
535
				} // if
536
				$q = "DELETE from email_cache where imap_uid = {$msgNo} AND msgno = {$msgNo} AND ie_id = '{$this->id}' AND message_id = '{$msgId}'";
537
				$r = $this->db->query($q);
538
			} // if
539
		} // for
540 1
	} // fn
541
542
	/**
543
	 * Retrieves cached headers
544
	 * @return array
545
	 */
546 1
	function getCacheValueForUIDs($mbox, $UIDs) {
547 1
		if (!is_array($UIDs) || empty($UIDs)) {
548
			return array();
549
		}
550
551 1
		$q = "SELECT * FROM email_cache WHERE ie_id = '{$this->db->quote($this->id)}' AND mbox = '{$this->db->quote($mbox)}' AND ";
552 1
		$startIndex = 0;
553 1
		$endIndex = 5;
554
555 1
		$slicedArray = array_slice($UIDs, $startIndex ,$endIndex);
556 1
		$columnName = ($this->isPop3Protocol() ? "message_id" : "imap_uid");
557
		$ret = array(
558 1
			'timestamp'	=> $this->getCacheTimestamp($mbox),
559
			'uids'		=> array(),
560
			'retArr'	=> array(),
561
		);
562 1
		while (!empty($slicedArray)) {
563 1
			$messageIdString = implode(',', $slicedArray);
564 1
			$GLOBALS['log']->debug("sliced array = {$messageIdString}");
565 1
			$extraWhere = "{$columnName} IN (";
566 1
			$i = 0;
567 1
			foreach($slicedArray as $UID) {
568 1
				if($i != 0) {
569 1
					$extraWhere = $extraWhere . ",";
570
				} // if
571 1
				$i++;
572 1
				$extraWhere = "{$extraWhere} '{$UID}'";
573
			} // foreach
574 1
			$newQuery = $q . $extraWhere . ")";
575 1
			$r = $this->db->query($newQuery);
576
577 1
			while($a = $this->db->fetchByAssoc($r)) {
578
				if (isset($a['uid'])) {
579
					if ($this->isPop3Protocol()) {
580
						$ret['uids'][] = $a['message_id'];
581
					} else {
582
				    	$ret['uids'][] = $a['uid'];
583
					}
584
				}
585
586
				$overview = new Overview();
587
588
				foreach($a as $k => $v) {
589
					$k=strtolower($k);
590
					switch($k) {
591
						case "imap_uid":
592
							$overview->imap_uid = $v;
593
							if ($this->isPop3Protocol()) {
594
								$overview->uid = $a['message_id'];
595
							} else {
596
								$overview->uid = $v;
597
							}
598
						break;
599
						case "toaddr":
600
							$overview->to = from_html($v);
601
						break;
602
603
						case "fromaddr":
604
							$overview->from = from_html($v);
605
						break;
606
607
						case "mailsize":
608
							$overview->size = $v;
609
						break;
610
611
						case "senddate":
612
							$overview->date = $v;
613
						break;
614
615
						default:
616
							$overview->$k = from_html($v);
617
						break;
618
					} // switch
619
				} // foreach
620
				$ret['retArr'][] = $overview;
621
			} // while
622 1
			$startIndex = $startIndex + $endIndex;
623 1
			$slicedArray = array_slice($UIDs, $startIndex ,$endIndex);
624 1
			$messageIdString = implode(',', $slicedArray);
625 1
			$GLOBALS['log']->debug("sliced array = {$messageIdString}");
626
		} // while
627 1
		return $ret;
628
	}
629
630
	/**
631
	 * Retrieves cached headers
632
	 * @return array
633
	 */
634 10
	function getCacheValue($mbox, $limit = 20, $page = 1, $sort='', $direction='') {
635
		// try optimizing this call as we don't want repeat queries
636 10
		if(!empty($this->currentCache)) {
637 1
			return $this->currentCache;
638
		}
639
640 10
		$sort = (empty($sort)) ? $this->defaultSort : $sort;
641 10
        if (!in_array(strtolower($direction), array('asc', 'desc'))) {
642 10
            $direction = $this->defaultDirection;
643
        }
644
645 10
        if (!empty($this->hrSortLocal[$sort])) {
646 10
            $order = " ORDER BY {$this->db->quote($this->hrSortLocal[$sort])} {$this->db->quote($direction)}";
647
        } else {
648
            $order = "";
649
        }
650
651 10
		$q = "SELECT * FROM email_cache WHERE ie_id = '{$this->db->quote($this->id)}' AND mbox = '{$this->db->quote($mbox)}' {$order}";
652
653 10
		if(!empty($limit)) {
654 10
			$start = ( $page - 1 ) * $limit;
655 10
			$r = $this->db->limitQuery($q, $start, $limit);
656
		} else {
657
			$r = $this->db->query($q);
658
		}
659
660
		$ret = array(
661 10
			'timestamp'	=> $this->getCacheTimestamp($mbox),
662
			'uids'		=> array(),
663
			'retArr'	=> array(),
664
		);
665
666 10
		while($a = $this->db->fetchByAssoc($r)) {
667 4
			if (isset($a['uid'])) {
668
				if ($this->isPop3Protocol()) {
669
					$ret['uids'][] = $a['message_id'];
670
				} else {
671
			    	$ret['uids'][] = $a['uid'];
672
				}
673
			}
674
675 4
			$overview = new Overview();
676
677 4
			foreach($a as $k => $v) {
678 4
				$k=strtolower($k);
679
				switch($k) {
680 4
					case "imap_uid":
681 4
						$overview->imap_uid = $v;
682 4
						if ($this->isPop3Protocol()) {
683
							$overview->uid = $a['message_id'];
684
						} else {
685 4
							$overview->uid = $v;
686
						}
687 4
					break;
688 4
					case "toaddr":
689 4
						$overview->to = from_html($v);
690 4
					break;
691
692 4
					case "fromaddr":
693 4
						$overview->from = from_html($v);
694 4
					break;
695
696 4
					case "mailsize":
697 4
						$overview->size = $v;
698 4
					break;
699
700 4
					case "senddate":
701 4
						$overview->date = $v;
702 4
					break;
703
704
					default:
705 4
						$overview->$k = from_html($v);
706 4
					break;
707
				}
708
			}
709 4
			$ret['retArr'][] = $overview;
710
		}
711
712 10
		$this->currentCache = $ret;
713
714 10
		return $ret;
715
	}
716
717
	/**
718
	 * Sets cache values
719
	 */
720 2
	function setCacheValue($mbox, $insert, $update=array(), $remove=array()) {
721 2
		if(empty($mbox)) {
722
			return;
723
		}
724 2
		global $timedate;
725
726
727
		// reset in-memory cache
728 2
		$this->currentCache = null;
729
730 2
		$table = $this->db->quote('email_cache');
731 2
		$where = "WHERE ie_id = '{$this->db->quote($this->id)}' AND mbox = '{$this->db->quote($mbox)}'";
732
733
		// handle removed rows
734 2
		if(!empty($remove)) {
735
			$removeIds = '';
736
			foreach($remove as $overview) {
737
				if(!empty($removeIds)) {
738
					$removeIds .= ",";
739
				}
740
741
				$removeIds .= "'{$this->db->quote($overview->imap_uid)}'";
742
			}
743
744
			$q = "DELETE FROM {$table} {$where} AND imap_uid IN ({$removeIds})";
745
746
			$GLOBALS['log']->info("INBOUNDEMAIL-CACHE: delete query [ {$q} ]");
747
748
			$r = $this->db->query($q, true, $q);
749
		}
750
751
		// handle insert rows
752 2
		if(!empty($insert)) {
753 1
			$q = "SELECT imap_uid FROM {$table} {$where}";
754 1
			$GLOBALS['log']->info("INBOUNDEMAIL-CACHE: filter UIDs query [ {$q} ]");
755 1
			$r = $this->db->query($q);
756 1
			$uids = array();
757
758 1
			while($a = $this->db->fetchByAssoc($r)) {
759
				$uids[] = $a['imap_uid'];
760
			}
761 1
			$count = count($uids);
762 1
			$GLOBALS['log']->info("INBOUNDEMAIL-CACHE: found [ {$count} ] UIDs to filter against");
763
764 1
			$tmp = '';
765 1
			foreach($uids as $uid) {
766
				if(!empty($tmp))
767
					$tmp .= ", ";
768
				$tmp .= "{$uid}";
769
			}
770 1
			$GLOBALS['log']->info("INBOUNDEMAIL-CACHE: filter UIDs: [ {$tmp} ]");
771
772 1
			$cols = "";
773
774 1
			foreach($this->overview->fieldDefs as $colDef) {
775 1
				if(!empty($cols))
776 1
					$cols .= ",";
777
778 1
				$cols .= "{$colDef['name']}";
779
			}
780 1
			foreach($insert as $overview) {
781 1
                if(in_array($overview->imap_uid, $uids))
782
                {
783
                    // fixing bug #49543: setting 'mbox' property for the following updating of other items in this box
784
                    if (!isset($overview->mbox))
785
                    {
786
                        $overview->mbox = $mbox;
787
                    }
788
                    $update[] = $overview;
789
                    continue;
790
                }
791
792 1
				$values = '';
793
794 1
				foreach($this->overview->fieldDefs as $colDef) {
795 1
					if(!empty($values)) {
796 1
						$values .= ", ";
797
					}
798
799 1
					$fieldName = $colDef['name'];
800
					// trim values for Oracle/MSSql
801 1
					if(	isset($colDef['len']) && !empty($colDef['len']) &&
802 1
						isset($colDef['type']) && !empty($colDef['type']) &&
803 1
						$colDef['type'] == 'varchar'
804
					)
805
                    {
806 1
                        if (isset($overview->$fieldName))
807
                        {
808 1
                            $overview->$fieldName = substr($overview->$fieldName, 0, $colDef['len']);
809
                        }
810
                    }
811
812
					switch($fieldName) {
813 1
						case "imap_uid":
814 1
							if(isset($overview->uid) && !empty($overview->uid)) {
815
								$this->imap_uid = $overview->uid;
816
							}
817 1
							$values .= "'{$this->imap_uid}'";
818 1
						break;
819
820 1
						case "ie_id":
821 1
							$values .= "'{$this->id}'";
822 1
						break;
823
824 1
						case "toaddr":
825 1
							$values .= $this->db->quoted($overview->to);
826 1
						break;
827
828 1
						case "fromaddr":
829 1
							$values .= $this->db->quoted($overview->from);
830 1
						break;
831
832 1
						case "message_id" :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
833 1
							$values .= $this->db->quoted($overview->message_id);
834 1
						break;
835
836 1
						case "mailsize":
837 1
							$values .= $overview->size;
838 1
						break;
839
840 1
						case "senddate":
841 1
							$conv=$timedate->fromString($overview->date);
842 1
							if (!empty($conv)) {
843 1
								$values .= $this->db->quoted($conv->asDb());
844
							} else {
845
								$values .= "NULL";
846
							}
847 1
						break;
848
849 1
						case "mbox":
850 1
							$values .= "'{$mbox}'";
851 1
						break;
852
853
						default:
854 1
							$overview->$fieldName = SugarCleaner::cleanHtml(from_html($overview->$fieldName));
855 1
							$values .= $this->db->quoted($overview->$fieldName);
856 1
						break;
857
					}
858
				}
859
860 1
				$q = "INSERT INTO {$table} ({$cols}) VALUES ({$values})";
861 1
				$GLOBALS['log']->info("INBOUNDEMAIL-CACHE: insert query [ {$q} ]");
862 1
				$r = $this->db->query($q, true, $q);
863
			}
864
		}
865
866
		// handle update rows
867 2
		if(!empty($update)) {
868
			$cols = "";
869
			foreach($this->overview->fieldDefs as $colDef) {
870
				if(!empty($cols))
871
					$cols .= ",";
872
873
				$cols .= "{$colDef['name']}";
874
			}
875
876
			foreach($update as $overview) {
877
				$q = "UPDATE {$table} SET ";
878
879
				$set = '';
880
				foreach($this->overview->fieldDefs as $colDef) {
881
882
					$fieldName = $colDef['name'];
883
					switch($fieldName) {
884
						case "toaddr":
885
						case "fromaddr":
886
						case "mailsize":
887
						case "senddate":
888
						case "mbox":
889
						case "ie_id":
890
						break;
891
892
						default:
893
                            if(!empty($set))
894
                            {
895
                                $set .= ",";
896
                            }
897
                            $value = '';
898
                            if (isset($overview->$fieldName))
899
                            {
900
                                $value = $this->db->quoted($overview->$fieldName);
901
                            }
902
                            else
903
                            {
904
                                $value = $this->db->quoted($value);
905
                            }
906
                            $set .= "{$fieldName} = " . $value;
907
						break;
908
					}
909
				}
910
911
				$q .= $set . " WHERE ie_id = '{$this->db->quote($this->id)}' AND mbox = '{$this->db->quote($overview->mbox)}' AND imap_uid = '{$overview->imap_uid}'";
912
				$GLOBALS['log']->info("INBOUNDEMAIL-CACHE: update query [ {$q} ]");
913
				$r = $this->db->query($q, true, $q);
914
			}
915
		}
916
917 2
	}
918
919
	/**
920
	 * Opens a socket connection to the pop3 server
921
	 * @return bool
922
	 */
923 11
	function pop3_open() {
924 11
		if(!is_resource($this->pop3socket)) {
925 11
			$GLOBALS['log']->info("*** INBOUNDEMAIL: opening socket connection");
926 11
			$exServ = explode('::', $this->service);
927 11
			$socket  = ($exServ[2] == 'ssl') ? "ssl://" : "tcp://";
928 11
			$socket .= $this->server_url;
929 11
			$this->pop3socket = fsockopen($socket, $this->port);
930
		} else {
931
			$GLOBALS['log']->info("*** INBOUNDEMAIL: REUSING socket connection");
932
			return true;
933
		}
934
935 11
		if(!is_resource($this->pop3socket)) {
936 11
			$GLOBALS['log']->debug("*** INBOUNDEMAIL: unable to open socket connection");
937 11
			return false;
938
		}
939
940
		// clear buffer
941
		$ret = trim(fgets($this->pop3socket, 1024));
942
		$GLOBALS['log']->info("*** INBOUNDEMAIL: got socket connection [ {$ret} ]");
943
		return true;
944
	}
945
946
	/**
947
	 * Closes connections and runs clean-up routines
948
	 */
949 1
	function pop3_cleanUp() {
950 1
		$GLOBALS['log']->info("*** INBOUNDEMAIL: cleaning up socket connection");
951 1
		fputs($this->pop3socket, "QUIT\r\n");
952 1
		$buf = fgets($this->pop3socket, 1024);
953 1
		fclose($this->pop3socket);
954 1
	}
955
956
	/**
957
	 * sends a command down to the POP3 server
958
	 * @param string command
959
	 * @param string args
960
	 * @param bool return
961
	 * @return string
962
	 */
963 1
	function pop3_sendCommand($command, $args='', $return=true) {
964 1
		$command .= " {$args}";
965 1
		$command = trim($command);
966 1
		$GLOBALS['log']->info("*** INBOUNDEMAIL: pop3_sendCommand() SEND [ {$command} ]");
967 1
		$command .= "\r\n";
968
969 1
		fputs($this->pop3socket, $command);
970
971 1
		if($return) {
972 1
			$ret = trim(fgets($this->pop3socket, 1024));
973 1
			$GLOBALS['log']->info("*** INBOUNDEMAIL: pop3_sendCommand() RECEIVE [ {$ret} ]");
974 1
			return $ret;
975
		}
976
	}
977
978 2
	function getPop3NewMessagesToDownload() {
979 2
		$pop3UIDL = $this->pop3_getUIDL();
980 2
		$cacheUIDLs = $this->pop3_getCacheUidls();
981
		// new email cache values we should deal with
982 2
		$diff = array_diff_assoc($pop3UIDL, $cacheUIDLs);
983
		// this is msgNo to UIDL array
984 2
		$diff = $this->pop3_shiftCache($diff, $cacheUIDLs);
985
		// get all the keys which are msgnos;
986 2
		return array_keys($diff);
987
	}
988
989 2
	function getPop3NewMessagesToDownloadForCron() {
990 2
		$pop3UIDL = $this->pop3_getUIDL();
991 2
		$cacheUIDLs = $this->pop3_getCacheUidls();
992
		// new email cache values we should deal with
993 2
		$diff = array_diff_assoc($pop3UIDL, $cacheUIDLs);
994
		// this is msgNo to UIDL array
995 2
		$diff = $this->pop3_shiftCache($diff, $cacheUIDLs);
996
		// insert data into email_cache
997 2
		if ($this->groupfolder_id != null && $this->groupfolder_id != "" && $this->isPop3Protocol()) {
998
			$searchResults = array_keys($diff);
999
			$concatResults = implode(",", $searchResults);
1000
			if ($this->connectMailserver() == 'true') {
1001
				$fetchedOverviews = imap_fetch_overview($this->conn, $concatResults);
1002
				// clean up cache entry
1003
				foreach($fetchedOverviews as $k => $overview) {
1004
					$overview->message_id = trim($diff[$overview->msgno]);
1005
					$fetchedOverviews[$k] = $overview;
1006
				}
1007
				$this->updateOverviewCacheFile($fetchedOverviews);
1008
			}
1009
		} // if
1010 2
		return $diff;
1011
	}
1012
1013
	/**
1014
	 * This method returns all the UIDL for this account. This should be called if the protocol is pop3
1015
	 * @return array od messageno to UIDL array
1016
	 */
1017 6
	function pop3_getUIDL() {
1018 6
		$UIDLs = array();
1019 6
		if($this->pop3_open()) {
1020
			// authenticate
1021
			$this->pop3_sendCommand("USER", $this->email_user);
1022
			$this->pop3_sendCommand("PASS", $this->email_password);
1023
1024
			// get UIDLs
1025
			$this->pop3_sendCommand("UIDL", '', false); // leave socket buffer alone until the while()
1026
			fgets($this->pop3socket, 1024); // handle "OK+";
1027
			$UIDLs = array();
1028
1029
			$buf = '!';
1030
1031
			if(is_resource($this->pop3socket)) {
1032
				while(!feof($this->pop3socket)) {
1033
					$buf = fgets($this->pop3socket, 1024); // 8kb max buffer - shouldn't be more than 80 chars via pop3...
1034
					//_pp(trim($buf));
1035
1036
					if(trim($buf) == '.') {
1037
						$GLOBALS['log']->debug("*** GOT '.'");
1038
						break;
1039
					}
1040
1041
					// format is [msgNo] [UIDL]
1042
					$exUidl = explode(" ", $buf);
1043
					$UIDLs[$exUidl[0]] = trim($exUidl[1]);
1044
				} // while
1045
			} // if
1046
			$this->pop3_cleanUp();
1047
		} // if
1048 6
		return $UIDLs;
1049
	} // fn
1050
1051
	/**
1052
	 * Special handler for POP3 boxes.  Standard IMAP commands are useless.
1053
	 * This will fetch only partial emails for POP3 and hence needs to be call again and again based on status it returns
1054
	 */
1055 1
	function pop3_checkPartialEmail($synch = false) {
1056 1
		require_once('include/utils/array_utils.php');
1057 1
		global $current_user;
1058 1
		global $sugar_config;
1059
1060 1
		$cacheDataExists = false;
1061 1
		$diff = array();
1062 1
		$results = array();
1063 1
		$cacheFilePath = clean_path("{$this->EmailCachePath}/{$this->id}/folders/MsgNOToUIDLData.php");
1064 1
		if(file_exists($cacheFilePath)) {
1065
			$cacheDataExists = true;
1066
			if($fh = @fopen($cacheFilePath, "rb")) {
1067
				$data = "";
1068
				$chunksize = 1*(1024*1024); // how many bytes per chunk
1069
				while(!feof($fh)) {
1070
					$buf = fgets($fh, $chunksize); // 8kb max buffer - shouldn't be more than 80 chars via pop3...
1071
					$data = $data . $buf;
1072
	    			flush();
1073
				} // while
1074
				fclose($fh);
1075
				$diff = unserialize($data);
1076
				if (!empty($diff)) {
1077
					if (count($diff)> 50) {
1078
	                	$newDiff = array_slice($diff, 50, count($diff), true);
1079
					} else {
1080
						$newDiff=array();
1081
					}
1082
	                $results = array_slice(array_keys($diff), 0 ,50);
1083
					$data = serialize($newDiff);
1084
				    if($fh = @fopen($cacheFilePath, "w")) {
1085
				        fputs($fh, $data);
1086
				        fclose($fh);
1087
				    } // if
1088
				}
1089
			} // if
1090
		} // if
1091 1
		if (!$cacheDataExists) {
1092 1
			if ($synch) {
1093
			    $this->deletePop3Cache();
1094
			}
1095 1
			$UIDLs = $this->pop3_getUIDL();
1096 1
			if(count($UIDLs) > 0) {
1097
				// get cached UIDLs
1098
				$cacheUIDLs = $this->pop3_getCacheUidls();
1099
1100
				// new email cache values we should deal with
1101
				$diff = array_diff_assoc($UIDLs, $cacheUIDLs);
1102
				$diff = $this->pop3_shiftCache($diff, $cacheUIDLs);
1103
				require_once('modules/Emails/EmailUI.php');
1104
				EmailUI::preflightEmailCache("{$this->EmailCachePath}/{$this->id}");
1105
1106
				if (count($diff)> 50) {
1107
                	$newDiff = array_slice($diff, 50, count($diff), true);
1108
				} else {
1109
					$newDiff=array();
1110
				}
1111
1112
				$results = array_slice(array_keys($diff), 0 ,50);
1113
				$data = serialize($newDiff);
1114
			    if($fh = @fopen($cacheFilePath, "w")) {
1115
			        fputs($fh, $data);
1116
			        fclose($fh);
1117
			    } // if
1118
			} else {
1119 1
				$GLOBALS['log']->debug("*** INBOUNDEMAIL: could not open socket connection to POP3 server");
1120 1
				return "could not open socket connection to POP3 server";
1121
			} // else
1122
		} // if
1123
1124
		// build up msgNo request
1125
		if(count($diff) > 0) {
1126
			// remove dirty cache entries
1127
			$startingNo = 0;
1128
			if (isset($_REQUEST['currentCount']) && $_REQUEST['currentCount'] > -1) {
1129
			     $startingNo = $_REQUEST['currentCount'];
1130
			}
1131
1132
			$this->mailbox = 'INBOX';
1133
			$this->connectMailserver();
1134
			//$searchResults = array_keys($diff);
1135
			//$fetchedOverviews = array();
1136
			//$chunkArraySerachResults = array_chunk($searchResults, 50);
1137
			$concatResults = implode(",", $results);
1138
			$GLOBALS['log']->info('$$$$ '.$concatResults);
1139
			$GLOBALS['log']->info("[EMAIL] Start POP3 fetch overview on mailbox [{$this->mailbox}] for user [{$current_user->user_name}] on 50 data");
1140
			$fetchedOverviews = imap_fetch_overview($this->conn, $concatResults);
1141
			$GLOBALS['log']->info("[EMAIL] End POP3 fetch overview on mailbox [{$this->mailbox}] for user [{$current_user->user_name}] on "
1142
			. sizeof($fetchedOverviews) . " data");
1143
1144
			// clean up cache entry
1145
			foreach($fetchedOverviews as $k => $overview) {
1146
				$overview->message_id = trim($diff[$overview->msgno]);
1147
				$fetchedOverviews[$k] = $overview;
1148
			}
1149
1150
			$GLOBALS['log']->info("[EMAIL] Start updating overview cache for pop3 mailbox [{$this->mailbox}] for user [{$current_user->user_name}]");
1151
			$this->updateOverviewCacheFile($fetchedOverviews);
1152
			$GLOBALS['log']->info("[EMAIL] Start updating overview cache for pop3 mailbox [{$this->mailbox}] for user [{$current_user->user_name}]");
1153
			return array('status' => "In Progress", 'mbox' => $this->mailbox, 'count'=> (count($results) + $startingNo), 'totalcount' => count($diff), 'ieid' => $this->id);
1154
		} // if
1155
		unlink($cacheFilePath);
1156
		return  array('status' => "done");
1157
	}
1158
1159
1160
	/**
1161
	 * Special handler for POP3 boxes.  Standard IMAP commands are useless.
1162
	 */
1163 2
	function pop3_checkEmail() {
1164 2
		if($this->pop3_open()) {
1165
			// authenticate
1166
			$this->pop3_sendCommand("USER", $this->email_user);
1167
			$this->pop3_sendCommand("PASS", $this->email_password);
1168
1169
			// get UIDLs
1170
			$this->pop3_sendCommand("UIDL", '', false); // leave socket buffer alone until the while()
1171
			fgets($this->pop3socket, 1024); // handle "OK+";
1172
			$UIDLs = array();
1173
1174
			$buf = '!';
1175
1176
			if(is_resource($this->pop3socket)) {
1177
				while(!feof($this->pop3socket)) {
1178
					$buf = fgets($this->pop3socket, 1024); // 8kb max buffer - shouldn't be more than 80 chars via pop3...
1179
					//_pp(trim($buf));
1180
1181
					if(trim($buf) == '.') {
1182
						$GLOBALS['log']->debug("*** GOT '.'");
1183
						break;
1184
					}
1185
1186
					// format is [msgNo] [UIDL]
1187
					$exUidl = explode(" ", $buf);
1188
					$UIDLs[$exUidl[0]] = trim($exUidl[1]);
1189
				}
1190
			}
1191
1192
			$this->pop3_cleanUp();
1193
1194
			// get cached UIDLs
1195
			$cacheUIDLs = $this->pop3_getCacheUidls();
1196
//			_pp($UIDLs);_pp($cacheUIDLs);
1197
1198
			// new email cache values we should deal with
1199
			$diff = array_diff_assoc($UIDLs, $cacheUIDLs);
1200
1201
			// remove dirty cache entries
1202
			$diff = $this->pop3_shiftCache($diff, $cacheUIDLs);
1203
1204
			// build up msgNo request
1205
			if(!empty($diff)) {
1206
				$this->mailbox = 'INBOX';
1207
				$this->connectMailserver();
1208
				$searchResults = array_keys($diff);
1209
				$concatResults = implode(",", $searchResults);
1210
				$fetchedOverviews = imap_fetch_overview($this->conn, $concatResults);
1211
1212
				// clean up cache entry
1213
				foreach($fetchedOverviews as $k => $overview) {
1214
					$overview->message_id = trim($diff[$overview->msgno]);
1215
					$fetchedOverviews[$k] = $overview;
1216
				}
1217
1218
				$this->updateOverviewCacheFile($fetchedOverviews);
1219
			}
1220
		} else {
1221 2
			$GLOBALS['log']->debug("*** INBOUNDEMAIL: could not open socket connection to POP3 server");
1222 2
			return false;
1223
		}
1224
	}
1225
1226
	/**
1227
	 * Iterates through msgno and message_id to remove dirty cache entries
1228
	 * @param array diff
1229
	 */
1230 5
	function pop3_shiftCache($diff, $cacheUIDLs) {
1231 5
		$msgNos = "";
1232 5
		$msgIds = "";
1233 5
		$newArray = array();
1234 5
		foreach($diff as $msgNo => $msgId) {
1235 1
			if (in_array($msgId, $cacheUIDLs)) {
1236 1
				$q1 = "UPDATE email_cache SET imap_uid = {$msgNo}, msgno = {$msgNo} WHERE ie_id = '{$this->id}' AND message_id = '{$msgId}'";
1237 1
				$this->db->query($q1);
1238
			} else {
1239 1
				$newArray[$msgNo] = $msgId;
1240
			}
1241
		}
1242 5
		return $newArray;
1243
		/*
1244
		foreach($diff as $msgNo => $msgId) {
1245
			if(!empty($msgNos)) {
1246
				$msgNos .= ", ";
1247
			}
1248
			if(!empty($msgIds)) {
1249
				$msgIds .= ", ";
1250
			}
1251
1252
			$msgNos .= $msgNo;
1253
			$msgIds .= "'{$msgId}'";
1254
		}
1255
1256
		if(!empty($msgNos)) {
1257
			$q1 = "DELETE FROM email_cache WHERE ie_id = '{$this->id}' AND msgno IN ({$msgNos})";
1258
			$this->db->query($q1);
1259
		}
1260
		if(!empty($msgIds)) {
1261
			$q2 = "DELETE FROM email_cache WHERE ie_id = '{$this->id}' AND message_id IN ({$msgIds})";
1262
			$this->db->query($q2);
1263
		}
1264
		*/
1265
	}
1266
1267
	/**
1268
	 * retrieves cached uidl values.
1269
	 * When dealing with POP3 accounts, the message_id column in email_cache will contain the UIDL.
1270
	 * @return array
1271
	 */
1272 6
	function pop3_getCacheUidls() {
1273 6
		$q = "SELECT msgno, message_id FROM email_cache WHERE ie_id = '{$this->id}'";
1274 6
		$r = $this->db->query($q);
1275
1276 6
		$ret = array();
1277 6
		while($a = $this->db->fetchByAssoc($r)) {
1278 1
			$ret[$a['msgno']] = $a['message_id'];
1279
		}
1280
1281 6
		return $ret;
1282
	}
1283
1284
	/**
1285
	 * This function is used by cron job for group mailbox without group folder
1286
	 * @param string $msgno for pop
1287
	 * @param string $uid for imap
1288
	 */
1289 1
	function getMessagesInEmailCache($msgno, $uid) {
1290 1
		$fetchedOverviews = array();
1291 1
		if ($this->isPop3Protocol()) {
1292 1
			$fetchedOverviews = imap_fetch_overview($this->conn, $msgno);
1293 1
			foreach($fetchedOverviews as $k => $overview) {
1294
				$overview->message_id = $uid;
1295 1
				$fetchedOverviews[$k] = $overview;
1296
			}
1297
		} else {
1298 1
			$fetchedOverviews = imap_fetch_overview($this->conn, $uid, FT_UID);
1299
		} // else
1300 1
		$this->updateOverviewCacheFile($fetchedOverviews);
1301
1302 1
	} // fn
1303
1304
	/**
1305
	 * Checks email (local caching too) for one mailbox
1306
	 * @param string $mailbox IMAP Mailbox path
1307
	 * @param bool $prefetch Flag to prefetch email body on check
1308
	 */
1309 1
	function checkEmailOneMailbox($mailbox, $prefetch=true, $synchronize=false) {
1310 1
		global $sugar_config;
1311 1
		global $current_user;
1312 1
		global $app_strings;
1313
1314 1
        $result = 1;
1315
1316 1
		$GLOBALS['log']->info("INBOUNDEMAIL: checking mailbox [ {$mailbox} ]");
1317 1
		$this->mailbox = $mailbox;
1318 1
		$this->connectMailserver();
1319
1320 1
		$checkTime = '';
1321 1
		$shouldProcessRules = true;
1322
1323 1
		$timestamp = $this->getCacheTimestamp($mailbox);
1324
1325 1
		if($timestamp > 0) {
1326
			$checkTime = date('r', $timestamp);
1327
		}
1328
1329
		/* first time through, process ALL emails */
1330 1
		if(empty($checkTime) || $synchronize) {
1331
			// do not process rules for the first time or sunchronize
1332 1
			$shouldProcessRules = false;
1333 1
			$criteria = "ALL UNDELETED";
1334 1
			$prefetch = false; // do NOT prefetch emails on a brand new account - timeouts happen.
1335 1
			$GLOBALS['log']->info("INBOUNDEMAIL: new account detected - not prefetching email bodies.");
1336
		} else {
1337
			$criteria = "SINCE \"{$checkTime}\" UNDELETED"; // not using UNSEEN
1338
		}
1339 1
		$this->setCacheTimestamp($mailbox);
1340 1
		$GLOBALS['log']->info("[EMAIL] Performing IMAP search using criteria [{$criteria}] on mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1341 1
		$searchResults = imap_search($this->conn, $criteria, SE_UID);
1342 1
		$GLOBALS['log']->info("[EMAIL] Done IMAP search on mailbox [{$mailbox}] for user [{$current_user->user_name}]. Result count = ".count($searchResults));
1343
1344 1
		if(!empty($searchResults)) {
1345
1346
			$concatResults = implode(",", $searchResults);
1347
			$GLOBALS['log']->info("[EMAIL] Start IMAP fetch overview on mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1348
			$fetchedOverview = imap_fetch_overview($this->conn, $concatResults, FT_UID);
1349
			$GLOBALS['log']->info("[EMAIL] Done IMAP fetch overview on mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1350
1351
			$GLOBALS['log']->info("[EMAIL] Start updating overview cache for mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1352
			$this->updateOverviewCacheFile($fetchedOverview);
1353
			$GLOBALS['log']->info("[EMAIL] Done updating overview cache for mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1354
1355
			// prefetch emails
1356
			if($prefetch == true) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1357
				$GLOBALS['log']->info("[EMAIL] Start fetching emails for mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1358
				if(!$this->fetchCheckedEmails($fetchedOverview))
1359
                {
1360
                    $result = 0;
1361
                }
1362
				$GLOBALS['log']->info("[EMAIL] Done fetching emails for mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1363
			}
1364
		} else {
1365 1
			$GLOBALS['log']->info("INBOUNDEMAIL: no results for mailbox [ {$mailbox} ]");
1366 1
            $result = 1;
1367
		}
1368
1369
		/**
1370
		 * To handle the use case where an external client is also connected, deleting emails, we need to clear our
1371
		 * local cache of all emails with the "DELETED" flag
1372
		 */
1373 1
		$criteria  = 'DELETED';
1374 1
		$criteria .= (!empty($checkTime)) ? " SINCE \"{$checkTime}\"" : "";
1375 1
		$GLOBALS['log']->info("INBOUNDEMAIL: checking for deleted emails using [ {$criteria} ]");
1376
1377 1
		$trashFolder = $this->get_stored_options("trashFolder");
1378 1
		if (empty($trashFolder)) {
1379 1
			$trashFolder = "INBOX.Trash";
1380
		}
1381
1382 1
		if($this->mailbox != $trashFolder) {
1383 1
			$searchResults = imap_search($this->conn, $criteria, SE_UID);
1384 1
			if(!empty($searchResults)) {
1385
				$uids = implode($app_strings['LBL_EMAIL_DELIMITER'], $searchResults);
1386
				$GLOBALS['log']->info("INBOUNDEMAIL: removing UIDs found deleted [ {$uids} ]");
1387
				$this->getOverviewsFromCacheFile($uids, $mailbox, true);
1388
			}
1389
		}
1390 1
        return $result;
1391
	}
1392
1393
	   /**
1394
     * Checks email (local caching too) for one mailbox
1395
     * @param string $mailbox IMAP Mailbox path
1396
     * @param bool $prefetch Flag to prefetch email body on check
1397
     */
1398 1
    function checkEmailOneMailboxPartial($mailbox, $prefetch=true, $synchronize=false, $start = 0, $max = -1) {
1399 1
        global $sugar_config;
1400 1
        global $current_user;
1401 1
        global $app_strings;
1402
1403 1
        $GLOBALS['log']->info("INBOUNDEMAIL: checking mailbox [ {$mailbox} ]");
1404 1
        $this->mailbox = $mailbox;
1405 1
        $this->connectMailserver();
1406
1407 1
        $checkTime = '';
1408 1
        $shouldProcessRules = true;
1409
1410 1
        $timestamp = $this->getCacheTimestamp($mailbox);
1411
1412 1
        if($timestamp > 0) {
1413 1
            $checkTime = date('r', $timestamp);
1414
        }
1415
1416
        /* first time through, process ALL emails */
1417 1
        if(empty($checkTime) || $synchronize) {
1418
            // do not process rules for the first time or sunchronize
1419
            $shouldProcessRules = false;
1420
            $criteria = "ALL UNDELETED";
1421
            $prefetch = false; // do NOT prefetch emails on a brand new account - timeouts happen.
1422
            $GLOBALS['log']->info("INBOUNDEMAIL: new account detected - not prefetching email bodies.");
1423
        } else {
1424 1
            $criteria = "SINCE \"{$checkTime}\" UNDELETED"; // not using UNSEEN
1425
        }
1426 1
        $this->setCacheTimestamp($mailbox);
1427 1
        $GLOBALS['log']->info("[EMAIL] Performing IMAP search using criteria [{$criteria}] on mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1428 1
        $searchResults = $this->getCachedIMAPSearch($criteria);
1429
1430 1
        if(!empty($searchResults)) {
1431
1432
            $total = sizeof($searchResults);
1433
            $searchResults = array_slice($searchResults, $start, $max);
1434
1435
            $GLOBALS['log']->info("INBOUNDEMAIL: there are  $total messages in [{$mailbox}], we are on $start");
1436
            $GLOBALS['log']->info("INBOUNDEMAIL: getting the next " . sizeof($searchResults) . " messages");
1437
            $concatResults = implode(",", $searchResults);
1438
            $GLOBALS['log']->info("INBOUNDEMAIL: Start IMAP fetch overview on mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1439
            $fetchedOverview = imap_fetch_overview($this->conn, $concatResults, FT_UID);
1440
            $GLOBALS['log']->info("INBOUNDEMAIL: Done IMAP fetch overview on mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1441
1442
            $GLOBALS['log']->info("INBOUNDEMAIL: Start updating overview cache for mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1443
            $this->updateOverviewCacheFile($fetchedOverview);
1444
            $GLOBALS['log']->info("INBOUNDEMAIL: Done updating overview cache for mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1445
1446
            // prefetch emails
1447
            if($prefetch == true) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1448
                $GLOBALS['log']->info("INBOUNDEMAIL: Start fetching emails for mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1449
                $this->fetchCheckedEmails($fetchedOverview);
1450
                $GLOBALS['log']->info("INBOUNDEMAIL: Done fetching emails for mailbox [{$mailbox}] for user [{$current_user->user_name}]");
1451
            }
1452
            $status = ($total > $start + sizeof($searchResults)) ? 'continue' : 'done';
1453
            $ret = array('status' => $status, 'count' => $start + sizeof($searchResults), 'mbox' => $mailbox, 'totalcount' => $total);
1454
            $GLOBALS['log']->info("INBOUNDEMAIL: $status : Downloaded " . $start + sizeof($searchResults) . "messages of $total");
1455
1456
        } else {
1457 1
            $GLOBALS['log']->info("INBOUNDEMAIL: no results for mailbox [ {$mailbox} ]");
1458 1
            $ret = array('status' =>'done');
1459
        }
1460
1461 1
        if ($ret['status'] == 'done') {
1462
        	//Remove the cached search if we are done with this mailbox
1463 1
        	$cacheFilePath = clean_path("{$this->EmailCachePath}/{$this->id}/folders/SearchData.php");
1464 1
            unlink($cacheFilePath);
1465
	        /**
1466
	         * To handle the use case where an external client is also connected, deleting emails, we need to clear our
1467
	         * local cache of all emails with the "DELETED" flag
1468
	         */
1469 1
	        $criteria  = 'DELETED';
1470 1
	        $criteria .= (!empty($checkTime)) ? " SINCE \"{$checkTime}\"" : "";
1471 1
	        $GLOBALS['log']->info("INBOUNDEMAIL: checking for deleted emails using [ {$criteria} ]");
1472
1473 1
			$trashFolder = $this->get_stored_options("trashFolder");
1474 1
			if (empty($trashFolder)) {
1475 1
				$trashFolder = "INBOX.Trash";
1476
			}
1477
1478 1
	        if($this->mailbox != $trashFolder) {
1479 1
	            $searchResults = imap_search($this->conn, $criteria, SE_UID);
1480 1
	            if(!empty($searchResults)) {
1481
	                $uids = implode($app_strings['LBL_EMAIL_DELIMITER'], $searchResults);
1482
	                $GLOBALS['log']->info("INBOUNDEMAIL: removing UIDs found deleted [ {$uids} ]");
1483
	                $this->getOverviewsFromCacheFile($uids, $mailbox, true);
1484
	            }
1485
	        }
1486
        }
1487 1
        return $ret;
1488
    }
1489
1490 2
    function getCachedIMAPSearch($criteria) {
1491 2
    	global $current_user;
1492 2
        global $sugar_config;
1493
1494 2
    	$cacheDataExists = false;
1495 2
        $diff = array();
1496 2
        $results = array();
1497 2
        $cacheFolderPath = clean_path("{$this->EmailCachePath}/{$this->id}/folders");
1498 2
        if (!file_exists($cacheFolderPath)) {
1499 1
        	mkdir_recursive($cacheFolderPath);
1500
        }
1501 2
        $cacheFilePath = $cacheFolderPath . '/SearchData.php';
1502 2
        $GLOBALS['log']->info("INBOUNDEMAIL: Cache path is $cacheFilePath");
1503 2
        if(file_exists($cacheFilePath)) {
1504
            $cacheDataExists = true;
1505
            if($fh = @fopen($cacheFilePath, "rb")) {
1506
                $data = "";
1507
                $chunksize = 1*(1024*1024); // how many bytes per chunk
1508
                while(!feof($fh)) {
1509
                    $buf = fgets($fh, $chunksize); // 8kb max buffer - shouldn't be more than 80 chars via pop3...
1510
                    $data = $data . $buf;
1511
                    flush();
1512
                } // while
1513
                fclose($fh);
1514
                $results = unserialize($data);
1515
            } // if
1516
        } // if
1517 2
        if (!$cacheDataExists) {
1518 2
            $searchResults = imap_search($this->conn, $criteria, SE_UID);
1519 2
            if(count($searchResults) > 0) {
1520
                $results = $searchResults;
1521
                $data = serialize($searchResults);
1522
                if($fh = @fopen($cacheFilePath, "w")) {
1523
                    fputs($fh, $data);
1524
                    fclose($fh);
1525
                } // if
1526
            }
1527
        } // if
1528 2
        return $results;
1529
    }
1530
1531 1
    function checkEmailIMAPPartial($prefetch=true, $synch = false) {
1532 1
    	$GLOBALS['log']->info("*****************INBOUNDEMAIL: at IMAP check partial");
1533 1
        global $sugar_config;
1534 1
        $result = $this->connectMailserver();
1535 1
        if ($result == 'false')
1536
        {
1537
            return array(
1538 1
                'status' => 'error',
1539
                'message' => 'Email server is down'
1540
            );
1541
        }
1542
        $mailboxes = $this->getMailboxes(true);
1543
        if (!in_array('INBOX', $mailboxes)) {
1544
            $mailboxes[] = 'INBOX';
1545
        }
1546
        sort($mailboxes);
1547
        if (isset($_REQUEST['mbox']) && !empty($_REQUEST['mbox']) && isset($_REQUEST['currentCount'])) {
1548
        	$GLOBALS['log']->info("INBOUNDEMAIL: Picking up from where we left off");
1549
            $mbox = $_REQUEST['mbox'];
1550
            $count = $_REQUEST['currentCount'];
1551
        } else {
1552
        	if ($synch) {
1553
        		$GLOBALS['log']->info("INBOUNDEMAIL: Cleaning out the cache");
1554
        		$this->cleanOutCache();
1555
        	}
1556
            $mbox = $mailboxes[0];
1557
            $count = 0;
1558
        }
1559
        $GLOBALS['log']->info("INBOUNDEMAIL:found " . sizeof($mailboxes) . " Mailboxes");
1560
        $index = array_search($mbox, $mailboxes) + 1;
1561
        $ret = $this->checkEmailOneMailboxPartial($mbox, $prefetch, $synch, $count, 100);
1562
        while($ret['status'] == 'done' && $index < sizeof($mailboxes)) {
1563
            if ($ret['count'] > 100) {
1564
                $ret['mbox'] = $mailboxes[$index];
1565
                $ret['status'] = 'continue';
1566
                return $ret;
1567
            }
1568
            $GLOBALS['log']->info("INBOUNDEMAIL: checking account [ $index => $mbox : $count]");
1569
            $mbox = $mailboxes[$index];
1570
            $ret = $this->checkEmailOneMailboxPartial($mbox, $prefetch, $synch, 0, 100);
1571
            $index++;
1572
        }
1573
1574
        return $ret;
1575
    }
1576
1577 1
	function checkEmail2_meta() {
1578 1
		global $sugar_config;
1579
1580 1
		$this->connectMailserver();
1581 1
		$mailboxes = $this->getMailboxes(true);
1582 1
		$mailboxes[] = 'INBOX';
1583 1
		sort($mailboxes);
1584
1585 1
		$GLOBALS['log']->info("INBOUNDEMAIL: checking account [ {$this->name} ]");
1586
1587 1
		$mailboxes_meta = array();
1588 1
		foreach($mailboxes as $mailbox) {
1589 1
			$mailboxes_meta[$mailbox] = $this->getMailboxProcessCount($mailbox);
1590
		}
1591
1592 1
		$ret = array();
1593 1
		$ret['mailboxes'] = $mailboxes_meta;
1594
1595 1
		foreach($mailboxes_meta as $count) {
1596 1
			$ret['processCount'] += $count;
1597
		}
1598 1
		return $ret;
1599
	}
1600
1601 2
	function getMailboxProcessCount($mailbox) {
1602 2
		global $sugar_config;
1603
1604 2
		$GLOBALS['log']->info("INBOUNDEMAIL: checking mailbox [ {$mailbox} ]");
1605 2
		$this->mailbox = $mailbox;
1606 2
		$this->connectMailserver();
1607
1608 2
		$timestamp = $this->getCacheTimestamp($mailbox);
1609
1610 2
		$checkTime = '';
1611 2
		if($timestamp > 0) {
1612 2
			$checkTime = date('r', $timestamp);
1613
		}
1614
1615
		/* first time through, process ALL emails */
1616 2
		if(empty($checkTime)) {
1617
			$criteria = "ALL UNDELETED";
1618
			$prefetch = false; // do NOT prefetch emails on a brand new account - timeouts happen.
1619
			$GLOBALS['log']->info("INBOUNDEMAIL: new account detected - not prefetching email bodies.");
1620
		} else {
1621 2
			$criteria = "SINCE \"{$checkTime}\" UNDELETED"; // not using UNSEEN
1622
		}
1623
1624 2
		$GLOBALS['log']->info("INBOUNDEMAIL: using [ {$criteria} ]");
1625 2
		$searchResults = imap_search($this->conn, $criteria, SE_UID);
1626
1627 2
		if(!empty($searchResults)) {
1628
			$concatResults = implode(",", $searchResults);
1629
		} else {
1630 2
			$GLOBALS['log']->info("INBOUNDEMAIL: no results for mailbox [ {$mailbox} ]");
1631
		}
1632
1633 2
		if(empty($searchResults)) {
1634 2
			return 0;
1635
		}
1636
1637
		return count($searchResults);
1638
	}
1639
1640
	/**
1641
	 * update INBOX
1642
	 */
1643 1
	function checkEmail($prefetch=true, $synch = false) {
1644 1
		global $sugar_config;
1645
1646 1
		if($this->protocol == 'pop3') {
1647 1
			$this->pop3_checkEmail();
1648
		} else {
1649 1
			$this->connectMailserver();
1650 1
			$mailboxes = $this->getMailboxes(true);
1651 1
			sort($mailboxes);
1652
1653 1
			$GLOBALS['log']->info("INBOUNDEMAIL: checking account [ {$this->name} ]");
1654
1655 1
			foreach($mailboxes as $mailbox) {
1656
				$this->checkEmailOneMailbox($mailbox, $prefetch, $synch);
1657
			}
1658
		}
1659 1
	}
1660
1661
	/**
1662
	 * full synchronization
1663
	 */
1664 1
	function syncEmail() {
1665 1
		global $sugar_config;
1666 1
		global $current_user;
1667
1668 1
		$showFolders = unserialize(base64_decode($current_user->getPreference('showFolders', 'Emails')));
1669
1670 1
		if(empty($showFolders)) {
1671 1
			$showFolders = array();
1672
		}
1673
1674 1
		$email = new Email();
1675 1
		$email->email2init();
1676
1677
		// personal accounts
1678 1
		if($current_user->hasPersonalEmail()) {
1679
			$personals = $this->retrieveByGroupId($current_user->id);
1680
1681
			foreach($personals as $personalAccount) {
1682
				if(in_array($personalAccount->id, $showFolders)) {
1683
					$personalAccount->email = $email;
1684
					if ($personalAccount->isPop3Protocol()) {
1685
						$personalAccount->deletePop3Cache();
1686
						continue;
1687
					}
1688
					$personalAccount->cleanOutCache();
1689
					$personalAccount->connectMailserver();
1690
					$mailboxes = $personalAccount->getMailboxes(true);
1691
					$mailboxes[] = 'INBOX';
1692
					sort($mailboxes);
1693
1694
					$GLOBALS['log']->info("[EMAIL] Start checking account [{$personalAccount->name}] for user [{$current_user->user_name}]");
1695
1696
					foreach($mailboxes as $mailbox) {
1697
						$GLOBALS['log']->info("[EMAIL] Start checking mailbox [{$mailbox}] of account [{$personalAccount->name}] for user [{$current_user->user_name}]");
1698
						$personalAccount->checkEmailOneMailbox($mailbox, false, true);
1699
						$GLOBALS['log']->info("[EMAIL] Done checking mailbox [{$mailbox}] of account [{$personalAccount->name}] for user [{$current_user->user_name}]");
1700
					}
1701
					$GLOBALS['log']->info("[EMAIL] Done checking account [{$personalAccount->name}] for user [{$current_user->user_name}]");
1702
				}
1703
			}
1704
		}
1705
1706
		// group accounts
1707 1
		$beans = $this->retrieveAllByGroupId($current_user->id, false);
1708 1
		foreach($beans as $k => $groupAccount) {
1709
			if(in_array($groupAccount->id, $showFolders)) {
1710
				$groupAccount->email = $email;
1711
				$groupAccount->cleanOutCache();
1712
				$groupAccount->connectMailserver();
1713
				$mailboxes = $groupAccount->getMailboxes(true);
1714
				$mailboxes[] = 'INBOX';
1715
				sort($mailboxes);
1716
1717
				$GLOBALS['log']->info("INBOUNDEMAIL: checking account [ {$groupAccount->name} ]");
1718
1719
				foreach($mailboxes as $mailbox) {
1720
					$groupAccount->checkEmailOneMailbox($mailbox, false, true);
1721
				}
1722
			}
1723
		}
1724 1
	}
1725
1726
1727
	/**
1728
	 * Deletes cached messages when moving from folder to folder
1729
	 * @param string $uids
1730
	 * @param string $fromFolder
1731
	 * @param string $toFolder
0 ignored issues
show
Bug introduced by
There is no parameter named $toFolder. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
1732
	 */
1733 1
	function deleteCachedMessages($uids, $fromFolder) {
1734 1
		global $sugar_config;
1735
1736 1
		if(!isset($this->email) && !isset($this->email->et)) {
1737 1
			$this->email = new Email();
1738 1
			$this->email->email2init();
1739
		}
1740
1741 1
		$uids = $this->email->et->_cleanUIDList($uids);
1742
1743 1
		foreach($uids as $uid) {
1744
			$file = "{$this->EmailCachePath}/{$this->id}/messages/{$fromFolder}{$uid}.php";
1745
1746
			if(file_exists($file)) {
1747
				if(!unlink($file)) {
1748
					$GLOBALS['log']->debug("INBOUNDEMAIL: Could not delete [ {$file} ]");
1749
				}
1750
			}
1751
		}
1752 1
	}
1753
1754
	/**
1755
	 * similar to imap_fetch_overview, but it gets overviews from a local cache
1756
	 * file.
1757
	 * @param string $uids UIDs in comma-delimited format
1758
	 * @param string $mailbox The mailbox in focus, will default to $this->mailbox
1759
	 * @param bool $remove Default false
1760
	 * @return array
1761
	 */
1762 1
	function getOverviewsFromCacheFile($uids, $mailbox='', $remove=false) {
1763 1
		global $app_strings;
1764 1
		if(!isset($this->email) && !isset($this->email->et)) {
1765 1
			$this->email = new Email();
1766 1
			$this->email->email2init();
1767
		}
1768
1769 1
		$uids = $this->email->et->_cleanUIDList($uids, true);
1770
1771
		// load current cache file
1772 1
		$mailbox = empty($mailbox) ? $this->mailbox : $mailbox;
1773 1
		$cacheValue = $this->getCacheValue($mailbox);
1774 1
		$ret = array();
1775
1776
		// prep UID array
1777 1
		$exUids = explode($app_strings['LBL_EMAIL_DELIMITER'], $uids);
1778 1
		foreach($exUids as $k => $uid) {
1779 1
			$exUids[$k] = trim($uid);
1780
		}
1781
1782
		// fill $ret will requested $uids
1783 1
		foreach($cacheValue['retArr'] as $k => $overview) {
1784
			if(in_array($overview->imap_uid, $exUids)) {
1785
				$ret[] = $overview;
1786
			}
1787
		}
1788
1789
		// remove requested $uids from current cache file (move_mail() type action)
1790 1
		if($remove) {
1791
			$this->setCacheValue($mailbox, array(), array(), $ret);
1792
		}
1793 1
		return $ret;
1794
	}
1795
1796
	/**
1797
	 * merges new info with the saved cached file
1798
	 * @param array $array Array of email Overviews
1799
	 * @param string $type 'append' or 'remove'
1800
	 * @param string $mailbox Target mailbox if not current assigned
1801
	 */
1802 1
	function updateOverviewCacheFile($array, $type='append', $mailbox='') {
1803 1
		$mailbox = empty($mailbox) ? $this->mailbox : $mailbox;
1804
1805 1
		$cacheValue = $this->getCacheValue($mailbox);
1806 1
		$uids = $cacheValue['uids'];
1807
1808 1
		$updateRows = array();
1809 1
		$insertRows = array();
1810 1
		$removeRows = array();
1811
1812
		// update values
1813 1
		if($type == 'append') { // append
1814
			/* we are adding overviews to the cache file */
1815 1
			foreach($array as $overview) {
1816
				if(isset($overview->uid)) {
1817
					$overview->imap_uid = $overview->uid; // coming from imap_fetch_overview() call
1818
				}
1819
1820
				if(!in_array($overview->imap_uid, $uids)) {
1821 1
					$insertRows[] = $overview;
1822
				}
1823
			}
1824
		} else {
1825
			$updatedCacheOverviews = array();
1826
			// compare against generated list
1827
			/* we are removing overviews from the cache file */
1828
			foreach($cacheValue['retArr'] as $cacheOverview) {
1829
				if(!in_array($cacheOverview->imap_uid, $uids)) {
1830
					$insertRows[] = $cacheOverview;
1831
				} else {
1832
					$removeRows[] = $cacheOverview;
1833
				}
1834
			}
1835
1836
			$cacheValue['retArr'] = $updatedCacheOverviews;
1837
		}
1838
1839 1
		$this->setCacheValue($mailbox, $insertRows, $updateRows, $removeRows);
1840 1
	}
1841
1842
	/**
1843
	 * Check email prefetches email bodies for quicker display
1844
	 * @param array array of fetched overviews
1845
	 */
1846 1
	function fetchCheckedEmails($fetchedOverviews) {
1847 1
		global $sugar_config;
1848
1849 1
		if(is_array($fetchedOverviews) && !empty($fetchedOverviews)) {
1850 1
			foreach($fetchedOverviews as $overview) {
1851 1
				if($overview->size < 10000) {
1852
1853 1
					$uid = $overview->imap_uid;
1854
1855 1
					if(!empty($uid)) {
1856
						$file = "{$this->mailbox}{$uid}.php";
1857
						$cacheFile = clean_path("{$this->EmailCachePath}/{$this->id}/messages/{$file}");
1858
1859
						if(!file_exists($cacheFile)) {
1860
							$GLOBALS['log']->info("INBOUNDEMAIL: Prefetching email [ {$file} ]");
1861
							$this->setEmailForDisplay($uid);
1862
							$out = $this->displayOneEmail($uid, $this->mailbox);
1863
							$this->email->et->writeCacheFile('out', $out, $this->id, 'messages', "{$this->mailbox}{$uid}.php");
1864
						} else {
1865
							$GLOBALS['log']->debug("INBOUNDEMAIL: Trying to prefetch an email we already fetched! [ {$cacheFile} ]");
1866
						}
1867
					} else {
1868 1
						$GLOBALS['log']->debug("*** INBOUNDEMAIL: prefetch has a message with no UID");
1869
					}
1870 1
                    return true;
1871
				} else {
1872 1
					$GLOBALS['log']->debug("INBOUNDEMAIL: skipping email prefetch - size too large [ {$overview->size} ]");
1873
				}
1874
			}
1875
		}
1876 1
        return false;
1877
	}
1878
1879
	/**
1880
	 * Sets flags on emails.  Assumes that connection is live, correct folder is
1881
	 * set.
1882
	 * @param string $uids Sequence of UIDs, comma separated
1883
	 * @param string $type Flag to mark
1884
	 */
1885 1
	function markEmails($uids, $type) {
1886
		switch($type) {
1887 1
			case 'unread':
1888 1
				$result = imap_clearflag_full($this->conn, $uids, '\\SEEN', ST_UID);
1889 1
			break;
1890 1
			case 'read':
1891 1
				$result = imap_setflag_full($this->conn, $uids, '\\SEEN', ST_UID);
1892 1
			break;
1893 1
			case 'flagged':
1894 1
				$result = imap_setflag_full($this->conn, $uids, '\\FLAGGED', ST_UID);
1895 1
			break;
1896 1
			case 'unflagged':
1897 1
				$result = imap_clearflag_full($this->conn, $uids, '\\FLAGGED', ST_UID);
1898 1
			break;
1899 1
			case 'answered':
1900 1
				$result = imap_setflag_full($this->conn, $uids, '\\Answered', ST_UID);
1901 1
			break;
1902
		}
1903 1
	}
1904
	////	END EMAIL 2.0 SPECIFIC
1905
	///////////////////////////////////////////////////////////////////////////
1906
1907
1908
1909
	///////////////////////////////////////////////////////////////////////////
1910
	////	SERVER MANIPULATION METHODS
1911
	/**
1912
	 * Deletes the specified folder
1913
	 * @param string $mbox "::" delimited IMAP mailbox path, ie, INBOX.saved.stuff
1914
	 * @return bool
1915
	 */
1916 1
	function deleteFolder($mbox) {
1917 1
		$returnArray = array();
1918 1
		if ($this->getCacheCount($mbox) > 0) {
1919
			$returnArray['status'] = false;
1920
			$returnArray['errorMessage'] = "Can not delete {$mbox} as it has emails.";
1921
			return $returnArray;
1922
		}
1923 1
		$connectString = $this->getConnectString('', $mbox);
1924
		//Remove Folder cache
1925 1
		global $sugar_config;
1926 1
		unlink("{$this->EmailCachePath}/{$this->id}/folders/folders.php");
1927
1928 1
		if(imap_unsubscribe($this->conn, imap_utf7_encode($connectString))) {
1929
			if(imap_deletemailbox($this->conn, $connectString)) {
1930
	        	$this->mailbox = str_replace(("," . $mbox), "", $this->mailbox);
1931
	        	$this->save();
1932
	        	$sessionFoldersString  = $this->getSessionInboundFoldersString($this->server_url, $this->email_user, $this->port, $this->protocol);
1933
	        	$sessionFoldersString = str_replace(("," . $mbox), "", $sessionFoldersString);
1934
				$this->setSessionInboundFoldersString($this->server_url, $this->email_user, $this->port, $this->protocol, $sessionFoldersString);
1935
				$returnArray['status'] = true;
1936
				return $returnArray;
1937
			} else {
1938
				$GLOBALS['log']->error("*** ERROR: EMAIL2.0 - could not delete IMAP mailbox with path: [ {$connectString} ]");
1939
				$returnArray['status'] = false;
1940
				$returnArray['errorMessage'] = "NOOP: could not delete folder: {$connectString}";
1941
				return $returnArray;
1942
				return false;
0 ignored issues
show
Unused Code introduced by
return false; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
1943
			}
1944
		} else {
1945 1
			$GLOBALS['log']->error("*** ERROR: EMAIL2.0 - could not unsubscribe from folder, {$connectString} before deletion.");
1946 1
			$returnArray['status'] = false;
1947 1
			$returnArray['errorMessage'] = "NOOP: could not unsubscribe from folder, {$connectString} before deletion.";
1948 1
			return $returnArray;
1949
		}
1950
	}
1951
1952
	/**
1953
	 * Saves new folders
1954
	 * @param string $name Name of new IMAP mailbox
1955
	 * @param string $mbox "::" delimited IMAP mailbox path, ie, INBOX.saved.stuff
1956
	 * @return bool True on success
1957
	 */
1958 1
	function saveNewFolder($name, $mbox) {
1959 1
		global $sugar_config;
1960
        //Remove Folder cache
1961 1
        global $sugar_config;
1962
        //unlink("{$this->EmailCachePath}/{$this->id}/folders/folders.php");
1963
1964
        //$mboxImap = $this->getImapMboxFromSugarProprietary($mbox);
1965 1
        $delimiter = $this->get_stored_options('folderDelimiter');
1966 1
        if (!$delimiter) {
1967 1
        	$delimiter = '.';
1968
        }
1969
1970 1
        $newFolder = $mbox . $delimiter . $name;
1971 1
        $mbox .= $delimiter.str_replace($delimiter, "_", $name);
1972 1
        $connectString = $this->getConnectString('', $mbox);
1973
1974 1
		if(imap_createmailbox($this->conn, imap_utf7_encode($connectString))) {
1975
			imap_subscribe($this->conn, imap_utf7_encode($connectString));
1976
			$status = imap_status($this->conn, str_replace("{$delimiter}{$name}","",$connectString), SA_ALL);
1977
        	$this->mailbox = $this->mailbox . "," . $newFolder;
1978
        	$this->save();
1979
        	$sessionFoldersString  = $this->getSessionInboundFoldersString($this->server_url, $this->email_user, $this->port, $this->protocol);
1980
        	$sessionFoldersString = $sessionFoldersString . "," . $newFolder;
1981
			$this->setSessionInboundFoldersString($this->server_url, $this->email_user, $this->port, $this->protocol, $sessionFoldersString);
1982
1983
			echo json_encode($status);
1984
			return true;
1985
		} else {
1986 1
			echo "NOOP: could not create folder";
1987 1
			$GLOBALS['log']->error("*** ERROR: EMAIL2.0 - could not create IMAP mailbox with path: [ {$connectString} ]");
1988 1
			return false;
1989
		}
1990
1991
	}
1992
1993
	/**
1994
	 * Constructs an IMAP c-client compatible folder path from Sugar proprietary
1995
	 * @param string $mbox "::" delimited IMAP mailbox path, ie, INBOX.saved.stuff
1996
	 * @return string
1997
	 */
1998 1
	function getImapMboxFromSugarProprietary($mbox) {
1999 1
		$exMbox = explode("::", $mbox);
2000
2001 1
		$mboxImap = '';
2002
2003 1
		for($i=2; $i<count($exMbox); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
2004 1
			if(!empty($mboxImap)) {
2005
				$mboxImap .= ".";
2006
			}
2007 1
			$mboxImap .= $exMbox[$i];
2008
		}
2009
2010 1
		return $mboxImap;
2011
	}
2012
2013
	/**
2014
	 * Searches IMAP (and POP3?) accounts/folders for emails with qualifying criteria
2015
	 */
2016
	function search($ieId, $subject='', $from='', $to='', $body='', $dateFrom='', $dateTo='') {
2017
		global $current_user;
2018
		global $app_strings;
2019
		global $timedate;
2020
2021
		$beans = array();
2022
		$bean = new InboundEmail();
2023
		$bean->retrieve($ieId);
2024
		$beans[] = $bean;
2025
		//$beans = $this->retrieveAllByGroupId($current_user->id, true);
2026
2027
		$subject = urldecode($subject);
2028
2029
		$criteria  = "";
2030
		$criteria .= (!empty($subject)) ? 'SUBJECT '.from_html($subject).'' : "";
2031
		$criteria .= (!empty($from)) ? ' FROM "'.$from.'"' : "";
2032
		$criteria .= (!empty($to)) ? ' FROM "'.$to.'"' : "";
2033
		$criteria .= (!empty($body)) ? ' TEXT "'.$body.'"' : "";
2034
		$criteria .= (!empty($dateFrom)) ? ' SINCE "'.$timedate->fromString($dateFrom)->format('d-M-Y').'"' : "";
2035
		$criteria .= (!empty($dateTo)) ? ' BEFORE "'.$timedate->fromString($dateTo)->format('d-M-Y').'"' : "";
2036
		//$criteria .= (!empty($from)) ? ' FROM "'.$from.'"' : "";
2037
2038
		$showFolders = unserialize(base64_decode($current_user->getPreference('showFolders', 'Emails')));
2039
2040
		$out = array();
2041
2042
		foreach($beans as $bean) {
2043
			if(!in_array($bean->id, $showFolders)) {
2044
				continue;
2045
			}
2046
2047
			$GLOBALS['log']->info("*** INBOUNDEMAIL: searching [ {$bean->name} ] for [ {$subject}{$from}{$to}{$body}{$dateFrom}{$dateTo} ]");
2048
			$group = (!$bean->is_personal) ? 'group.' : '';
2049
			$bean->connectMailServer();
2050
			$mailboxes = $bean->getMailboxes(true);
2051
			if (!in_array('INBOX', $mailboxes)) {
2052
				$mailboxes[] = 'INBOX';
2053
			}
2054
			$totalHits = 0;
2055
2056
			foreach($mailboxes as $mbox) {
2057
				$bean->mailbox = $mbox;
2058
				$searchOverviews = array();
2059
				if ($bean->protocol == 'pop3') {
2060
					$pop3Criteria = "SELECT * FROM email_cache WHERE ie_id = '{$bean->id}' AND mbox = '{$mbox}'";
2061
					$pop3Criteria .= (!empty($subject)) ? ' AND subject like "%'.$bean->db->quote($subject).'%"' : "";
2062
					$pop3Criteria .= (!empty($from)) ? ' AND fromaddr like "%'.$from.'%"' : "";
2063
					$pop3Criteria .= (!empty($to)) ? ' AND toaddr like "%'.$to.'%"' : "";
2064
					$pop3Criteria .= (!empty($dateFrom)) ? ' AND senddate > "'.$dateFrom.'"' : "";
2065
					$pop3Criteria .= (!empty($dateTo)) ? ' AND senddate < "'.$dateTo.'"' : "";
2066
					$GLOBALS['log']->info("*** INBOUNDEMAIL: searching [ {$mbox} ] using criteria [ {$pop3Criteria} ]");
2067
2068
					$r = $bean->db->query($pop3Criteria);
2069
					while($a = $bean->db->fetchByAssoc($r)) {
2070
						$overview = new Overview();
2071
2072
						foreach($a as $k => $v) {
2073
							$k=strtolower($k);
2074
							switch($k) {
2075
								case "imap_uid":
2076
									$overview->imap_uid = $v;
2077
									$overview->uid = $a['message_id'];
2078
								break;
2079
								case "toaddr":
2080
									$overview->to = from_html($v);
2081
								break;
2082
2083
								case "fromaddr":
2084
									$overview->from = from_html($v);
2085
								break;
2086
2087
								case "mailsize":
2088
									$overview->size = $v;
2089
								break;
2090
2091
								case "senddate":
2092
									$overview->date = $timedate->fromString($v)->format('r');
2093
								break;
2094
2095
								default:
2096
									$overview->$k = from_html($v);
2097
								break;
2098
							} // sqitch
2099
						} // foreach
2100
						$searchOverviews[] = $overview;
2101
					} // while
2102
				} else {
2103
					$bean->connectMailServer();
2104
					$searchResult = imap_search($bean->conn, $criteria, SE_UID);
2105
					if (!empty($searchResult)) {
2106
						$searchOverviews = imap_fetch_overview($bean->conn, implode(',', $searchResult), FT_UID);
2107
					} // if
2108
				} // else
2109
				$numHits = count($searchOverviews);
2110
2111
				if($numHits > 0) {
2112
					$totalHits = $totalHits + $numHits;
2113
					$ret = $bean->sortFetchedOverview($searchOverviews, 'date', 'desc', true);
2114
					$mbox = "{$bean->id}.SEARCH";
2115
					$out = array_merge($out, $bean->displayFetchedSortedListXML($ret, $mbox, false));
0 ignored issues
show
Unused Code introduced by
The call to InboundEmail::displayFetchedSortedListXML() has too many arguments starting with false.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
2116
				}
2117
			}
2118
		}
2119
2120
		$metadata = array();
2121
		$metadata['mbox'] = $app_strings['LBL_EMAIL_SEARCH_RESULTS_TITLE'];
2122
		$metadata['ieId'] = $this->id;
2123
		$metadata['name'] = $this->name;
2124
		$metadata['unreadChecked'] = ($current_user->getPreference('showUnreadOnly', 'Emails') == 1) ? 'CHECKED' : '';
2125
		$metadata['out'] = $out;
2126
2127
		return $metadata;
2128
	}
2129
2130
	/**
2131
	 * repairs the encrypted password for a given I-E account
2132
	 * @return bool True on success
2133
	 */
2134 1
	function repairAccount() {
2135
2136 1
		for($i=0; $i<3; $i++) {
2137 1
			if($i != 0) { // decode is performed on retrieve already
2138 1
				$this->email_password = blowfishDecode(blowfishGetKey('InboundEmail'), $this->email_password);
2139
			}
2140
2141 1
			if($this->connectMailserver() == 'true') {
2142
				$this->save(); // save decoded password (is encoded on save())
2143
				return true;
2144
			}
2145
		}
2146
2147 1
		return false;
2148
	}
2149
2150
	/**
2151
	 * soft deletes a User's personal inbox
2152
	 * @param string id I-E id
2153
	 * @param string user_name User name of User in focus, NOT current_user
2154
	 * @return bool True on success
2155
	 */
2156 1
	function deletePersonalEmailAccount($id, $user_name) {
2157 1
		$q = "SELECT ie.id FROM inbound_email ie LEFT JOIN users u ON ie.group_id = u.id WHERE u.user_name = '{$user_name}'";
2158 1
		$r = $this->db->query($q, true);
2159
2160 1
		while($a = $this->db->fetchByAssoc($r)) {
2161 1
			if(!empty($a) && $a['id'] == $id) {
2162 1
				$this->retrieve($id);
2163 1
				$this->deleted = 1;
2164 1
				$this->save();
2165 1
				return true;
2166
			}
2167
		}
2168 1
		return false;
2169
	}
2170
2171
	function getTeamSetIdForTeams($teamIds) {
2172
		if(!is_array($teamIds)){
2173
		   $teamIds = array($teamIds);
2174
		} // if
2175
		$teamSet = new TeamSet();
2176
		$team_set_id = $teamSet->addTeams($teamIds);
2177
		return $team_set_id;
2178
	} // fn
2179
2180
	/**
2181
	 * Saves Personal Inbox settings for Users
2182
	 * @param string userId ID of user to assign all emails for this account
2183
	 * @param strings userName Name of account, for Sugar purposes
2184
	 * @param bool forceSave Default true.  Flag to save errored settings.
2185
	 * @return boolean true on success, false on fail
2186
	 */
2187 1
	function savePersonalEmailAccount($userId = '', $userName = '', $forceSave=true) {
2188 1
		$groupId = $userId;
2189 1
		$accountExists = false;
2190 1
		if(isset($_REQUEST['ie_id']) && !empty($_REQUEST['ie_id'])) {
2191
			$this->retrieve($_REQUEST['ie_id']);
2192
			$accountExists = true;
2193
		}
2194 1
		$ie_name = $_REQUEST['ie_name'];
2195
2196 1
		$this->is_personal = 1;
2197 1
		$this->name = $ie_name;
2198 1
		$this->group_id = $groupId;
2199 1
		$this->status = $_REQUEST['ie_status'];
2200 1
		$this->server_url = trim($_REQUEST['server_url']);
2201 1
		$this->email_user = trim($_REQUEST['email_user']);
2202 1
		if(!empty($_REQUEST['email_password'])) {
2203 1
		    $this->email_password = html_entity_decode($_REQUEST['email_password'], ENT_QUOTES);
2204
		}
2205 1
		$this->port = trim($_REQUEST['port']);
2206 1
		$this->protocol = $_REQUEST['protocol'];
2207 1
		if ($this->protocol == "pop3") {
2208
			$_REQUEST['mailbox'] = "INBOX";
2209
		}
2210 1
		$this->mailbox = $_REQUEST['mailbox'];
2211 1
		$this->mailbox_type = 'pick'; // forcing this
2212
2213
2214 1
		if(isset($_REQUEST['ssl']) && $_REQUEST['ssl'] == 1) { $useSsl = true; }
2215 1
		else $useSsl = false;
2216 1
		$this->service = '::::::::::';
2217
2218 1
		if($forceSave) {
2219 1
			$id = $this->save(); // saving here to prevent user from having to re-enter all the info in case of error
2220 1
			$this->retrieve($id);
2221
		}
2222
2223 1
		$this->protocol = $_REQUEST['protocol']; // need to set this again since we safe the "service" string to empty explode values
2224 1
		$opts = $this->getSessionConnectionString($this->server_url, $this->email_user, $this->port, $this->protocol);
2225 1
		$detectedOpts = $this->findOptimumSettings($useSsl);
2226
2227
		//If $detectedOpts is empty, there was an error connecting, so clear $opts. If $opts was empty, use $detectedOpts
2228 1
		if (empty($opts) || empty($detectedOpts) || (empty($detectedOpts['good']) && empty($detectedOpts['serial'])))
2229
		{
2230 1
		  $opts = $detectedOpts;
2231
		}
2232 1
		$delimiter = $this->getSessionInboundDelimiterString($this->server_url, $this->email_user, $this->port, $this->protocol);
2233
2234 1
		if(isset($opts['serial']) && !empty($opts['serial'])) {
2235
			$this->service = $opts['serial'];
2236
			if(isset($_REQUEST['mark_read']) && $_REQUEST['mark_read'] == 1) {
2237
				$this->delete_seen = 0;
2238
			} else {
2239
				$this->delete_seen = 1;
2240
			}
2241
2242
			// handle stored_options serialization
2243
			if(isset($_REQUEST['only_since']) && $_REQUEST['only_since'] == 1) {
2244
				$onlySince = true;
2245
			} else {
2246
				$onlySince = false;
2247
			}
2248
2249
			$focusUser = new User();
2250
			$focusUser->retrieve($groupId);
2251
			$mailerId = (isset($_REQUEST['outbound_email'])) ? $_REQUEST['outbound_email'] : "";
2252
2253
			$oe = new OutboundEmail();
2254
			$oe->getSystemMailerSettings($focusUser, $mailerId);
0 ignored issues
show
Unused Code introduced by
The call to OutboundEmail::getSystemMailerSettings() has too many arguments starting with $focusUser.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
2255
2256
			$stored_options = array();
2257
			$stored_options['from_name'] = trim($_REQUEST['from_name']);
2258
			$stored_options['from_addr'] = trim($_REQUEST['from_addr']);
2259
			$stored_options['reply_to_addr'] = trim($_REQUEST['reply_to_addr']);
2260
2261
			if (!$this->isPop3Protocol()) {
2262
				$stored_options['trashFolder'] = (isset($_REQUEST['trashFolder']) ? trim($_REQUEST['trashFolder']) : "");
2263
				$stored_options['sentFolder'] = (isset($_REQUEST['sentFolder']) ? trim($_REQUEST['sentFolder']) : "");
2264
			} // if
2265
			$stored_options['only_since'] = $onlySince;
2266
			$stored_options['filter_domain'] = '';
2267
			$storedOptions['folderDelimiter'] = $delimiter;
2268
			$stored_options['outbound_email'] = (isset($_REQUEST['outbound_email'])) ? $_REQUEST['outbound_email'] : $oe->id;
2269
			$this->stored_options = base64_encode(serialize($stored_options));
2270
2271
			$ieId = $this->save();
2272
2273
			//If this is the first personal account the user has setup mark it as default for them.
2274
			$currentIECount = $this->getUserPersonalAccountCount($focusUser);
2275
			if($currentIECount == 1)
2276
			    $this->setUsersDefaultOutboundServerId($focusUser, $ieId);
2277
2278
			return true;
2279
		} else {
2280
			// could not find opts, no save
2281 1
			$GLOBALS['log']->debug('-----> InboundEmail could not find optimums for User: '.$ie_name);
2282 1
			return false;
2283
		}
2284
	}
2285
	/**
2286
	 * Determines if this instance of I-E is for a Group Inbox or Personal Inbox
2287
	 */
2288 1
	function handleIsPersonal() {
2289 1
		$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.'\'';
2290 1
		$rp = $this->db->query($qp, true);
2291 1
		$personalBox = array();
2292 1
		while($ap = $this->db->fetchByAssoc($rp)) {
2293 1
			$personalBox[] = array($ap['id'], $ap['user_name']);
2294
		}
2295 1
		if(count($personalBox) > 0) {
2296 1
			return true;
2297
		} else {
2298 1
			return false;
2299
		}
2300
	}
2301
2302 1
	function getUserNameFromGroupId() {
2303 1
		$r = $this->db->query('SELECT users.user_name FROM users WHERE deleted=0 AND id=\''.$this->group_id.'\'', true);
2304 1
		while($a = $this->db->fetchByAssoc($r)) {
2305 1
			return $a['user_name'];
2306
		}
2307 1
		return '';
2308
	}
2309
2310 1
	function getFoldersListForMailBox() {
2311 1
		$return = array();
2312 1
		$foldersList = $this->getSessionInboundFoldersString($this->server_url, $this->email_user, $this->port, $this->protocol);
2313 1
		if (empty($foldersList)) {
2314 1
			global $mod_strings;
2315 1
			$msg = $this->connectMailserver(true);
2316 1
			if (strpos($msg, "successfully")) {
2317
				$foldersList = $this->getSessionInboundFoldersString($this->server_url, $this->email_user, $this->port, $this->protocol);
2318
				$return['status'] = true;
2319
				$return['foldersList'] = $foldersList;
2320
				$return['statusMessage'] = "";
2321
			} else {
2322 1
				$return['status'] = false;
2323 1
				$return['statusMessage'] = $msg;
2324
			} // else
2325
		} else {
2326
			$return['status'] = true;
2327
			$return['foldersList'] = $foldersList;
2328
			$return['statusMessage'] = "";
2329
		}
2330 1
		return $return;
2331
	} // fn
2332
	/**
2333
	 * Programatically determines best-case settings for imap_open()
2334
	 */
2335 4
	function findOptimumSettings($useSsl=false, $user='', $pass='', $server='', $port='', $prot='', $mailbox='') {
2336 4
		global $mod_strings;
2337 4
		$serviceArr = array();
2338 4
		$returnService = array();
2339 4
		$badService = array();
2340 4
		$goodService = array();
2341 4
		$errorArr = array();
2342 4
		$raw = array();
2343 4
		$retArray = array(	'good' => $goodService,
2344 4
							'bad' => $badService,
2345 4
							'err' => $errorArr);
2346
2347 4
		if(!function_exists('imap_open')) {
2348
			$retArray['err'][0] = $mod_strings['ERR_NO_IMAP'];
2349
			return $retArray;
2350
		}
2351
2352 4
		imap_errors(); // clearing error stack
2353 4
		error_reporting(0); // turn off notices from IMAP
2354
2355 4
		if(isset($_REQUEST['ssl']) && $_REQUEST['ssl'] == 1) {
2356
			$useSsl = true;
2357
		}
2358
2359 4
		$exServ = explode('::', $this->service);
2360 4
		$service = '/'.$exServ[1];
2361
2362 4
		$nonSsl = array('both-secure'			=> '/notls/novalidate-cert/secure',
2363
						'both'					=> '/notls/novalidate-cert',
2364
						'nocert-secure'			=> '/novalidate-cert/secure',
2365
						'nocert'				=> '/novalidate-cert',
2366
						'notls-secure'			=> '/notls/secure',
2367
						'secure'				=> '/secure', // for POP3 servers that force CRAM-MD5
2368
						'notls'					=> '/notls',
2369
						'none'					=> '', // try default nothing
2370
					);
2371
		$ssl = array(
2372 4
						'ssl-both-on-secure'	=> '/ssl/tls/validate-cert/secure',
2373
						'ssl-both-on'			=> '/ssl/tls/validate-cert',
2374
						'ssl-cert-secure'		=> '/ssl/validate-cert/secure',
2375
						'ssl-cert'				=> '/ssl/validate-cert',
2376
						'ssl-tls-secure'		=> '/ssl/tls/secure',
2377
						'ssl-tls'				=> '/ssl/tls',
2378
						'ssl-both-off-secure'	=> '/ssl/notls/novalidate-cert/secure',
2379
						'ssl-both-off'			=> '/ssl/notls/novalidate-cert',
2380
						'ssl-nocert-secure'		=> '/ssl/novalidate-cert/secure',
2381
						'ssl-nocert'			=> '/ssl/novalidate-cert',
2382
						'ssl-notls-secure'		=> '/ssl/notls/secure',
2383
						'ssl-notls'				=> '/ssl/notls',
2384
						'ssl-secure'			=> '/ssl/secure',
2385
						'ssl-none'				=> '/ssl',
2386
					);
2387
2388 4
		if(isset($user) && !empty($user) && isset($pass) && !empty($pass)) {
2389 1
			$this->email_password = $pass;
2390 1
			$this->email_user = $user;
2391 1
			$this->server_url = $server;
2392 1
			$this->port = $port;
2393 1
			$this->protocol = $prot;
2394 1
			$this->mailbox = $mailbox;
2395
		}
2396
2397
		// in case we flip from IMAP to POP3
2398 4
		if($this->protocol == 'pop3')
2399
		  $this->mailbox = 'INBOX';
2400
2401
		//If user has selected multiple mailboxes, we only need to test the first mailbox for the connection string.
2402 4
		$a_mailbox = explode(",", $this->mailbox);
2403 4
		$tmpMailbox = isset($a_mailbox[0]) ? $a_mailbox[0] : "";
2404
2405 4
		if($useSsl == true)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
2406
		{
2407 1
			foreach($ssl as $k => $service)
2408
			{
2409 1
				$returnService[$k] = 'foo'.$service;
2410 1
				$serviceArr[$k] = '{'.$this->server_url.':'.$this->port.'/service='.$this->protocol.$service.'}'.$tmpMailbox;
2411
			}
2412
		}
2413
		else
2414
		{
2415 4
			foreach($nonSsl as $k => $service)
2416
			{
2417 4
				$returnService[$k] = 'foo'.$service;
2418 4
				$serviceArr[$k] = '{'.$this->server_url.':'.$this->port.'/service='.$this->protocol.$service.'}'.$tmpMailbox;
2419
			}
2420
		}
2421
2422 4
		$GLOBALS['log']->debug('---------------STARTING FINDOPTIMUMS LOOP----------------');
2423 4
		$l = 1;
2424
2425
		//php imap library will capture c-client library warnings as errors causing good connections to be ignored.
2426
		//Check against known warnings to ensure good connections are used.
2427 4
		$acceptableWarnings = array("SECURITY PROBLEM: insecure server advertised AUTH=PLAIN", //c-client auth_pla.c
2428
			                        "Mailbox is empty");
2429 4
		$login = $this->email_user;
2430 4
		$passw = $this->email_password;
2431 4
		$foundGoodConnection = false;
2432 4
		foreach($serviceArr as $k => $serviceTest) {
2433 4
			$errors = '';
2434 4
			$alerts = '';
2435 4
			$GLOBALS['log']->debug($l.': I-E testing string: '.$serviceTest);
2436
2437
            // open the connection and try the test string
2438 4
            $this->conn = $this->getImapConnection($serviceTest, $login, $passw);
2439
2440 4
			if(($errors = imap_last_error()) || ($alerts = imap_alerts())) {
2441
                // login failure means don't bother trying the rest
2442 4
                if ($errors == 'Too many login failures'
2443 4
                    || $errors == '[CLOSED] IMAP connection broken (server response)'
2444
                    // @link http://tools.ietf.org/html/rfc5530#section-3
2445 4
                    || strpos($errors, '[AUTHENTICATIONFAILED]') !== false
2446
                    // MS Exchange 2010
2447 4
                    || (strpos($errors, 'AUTHENTICATE') !== false && strpos($errors, 'failed') !== false)
2448
                ) {
2449
					$GLOBALS['log']->debug($l.': I-E failed using ['.$serviceTest.']');
2450
					$retArray['err'][$k] = $mod_strings['ERR_BAD_LOGIN_PASSWORD'];
2451
					$retArray['bad'][$k] = $serviceTest;
2452
					$GLOBALS['log']->debug($l.': I-E ERROR: $ie->findOptimums() failed due to bad user credentials for user login: '.$this->email_user);
2453
					return $retArray;
2454 4
				} elseif( in_array($errors, $acceptableWarnings, TRUE)) { // false positive
2455
					$GLOBALS['log']->debug($l.': I-E found good connection but with warnings ['.$serviceTest.'] Errors:' . $errors);
2456
					$retArray['good'][$k] = $returnService[$k];
2457
					$foundGoodConnection = true;
2458
				}
2459
				else {
2460 4
					$GLOBALS['log']->debug($l.': I-E failed using ['.$serviceTest.'] - error: '.$errors);
2461 4
					$retArray['err'][$k] = $errors;
2462 4
					$retArray['bad'][$k] = $serviceTest;
2463
				}
2464
			} else {
2465
				$GLOBALS['log']->debug($l.': I-E found good connect using ['.$serviceTest.']');
2466
				$retArray['good'][$k] = $returnService[$k];
2467
				$foundGoodConnection = true;
2468
			}
2469
2470 4
			if(is_resource($this->conn)) {
2471
				if (!$this->isPop3Protocol()) {
2472
					$serviceTest = str_replace("INBOX", "", $serviceTest);
2473
					$boxes = imap_getmailboxes($this->conn, $serviceTest, "*");
2474
					$delimiter = '.';
2475
					// clean MBOX path names
2476
					foreach($boxes as $k => $mbox) {
2477
						$raw[] = $mbox->name;
2478
						if ($mbox->delimiter) {
2479
							$delimiter = $mbox->delimiter;
2480
						} // if
2481
					} // foreach
2482
					$this->setSessionInboundDelimiterString($this->server_url, $this->email_user, $this->port, $this->protocol, $delimiter);
2483
				} // if
2484
2485
				if(!imap_close($this->conn)) $GLOBALS['log']->debug('imap_close() failed!');
2486
			}
2487
2488 4
			$GLOBALS['log']->debug($l.': I-E clearing error and alert stacks.');
2489 4
			imap_errors(); // clear stacks
2490 4
			imap_alerts();
2491
			// If you find a good connection, then don't do any further testing to find URL
2492 4
			if ($foundGoodConnection) {
2493
				break;
2494
			} // if
2495 4
			$l++;
2496
		}
2497 4
		$GLOBALS['log']->debug('---------------end FINDOPTIMUMS LOOP----------------');
2498
2499 4
		if(!empty($retArray['good'])) {
2500
			$newTls				= '';
2501
			$newCert			= '';
2502
			$newSsl				= '';
2503
			$newNotls			= '';
2504
			$newNovalidate_cert	= '';
2505
			$good = array_pop($retArray['good']); // get most complete string
2506
			$exGood = explode('/', $good);
2507
			foreach($exGood as $v) {
2508
				switch($v) {
2509
					case 'ssl':
2510
						$newSsl = 'ssl';
2511
					break;
2512
					case 'tls':
2513
						$newTls = 'tls';
2514
					break;
2515
					case 'notls':
2516
						$newNotls = 'notls';
2517
					break;
2518
					case 'cert':
2519
						$newCert = 'validate-cert';
2520
					break;
2521
					case 'novalidate-cert':
2522
						$newNovalidate_cert = 'novalidate-cert';
2523
					break;
2524
					case 'secure':
2525
						$secure = 'secure';
2526
					break;
2527
				}
2528
			}
2529
2530
			$goodStr['serial'] = $newTls.'::'.$newCert.'::'.$newSsl.'::'.$this->protocol.'::'.$newNovalidate_cert.'::'.$newNotls.'::'.$secure;
2531
			$goodStr['service'] = $good;
2532
			$testConnectString = str_replace('foo','', $good);
2533
			$testConnectString = '{'.$this->server_url.':'.$this->port.'/service='.$this->protocol.$testConnectString.'}';
2534
			$this->setSessionConnectionString($this->server_url, $this->email_user, $this->port, $this->protocol, $goodStr);
2535
			$i = 0;
2536
			foreach($raw as $mbox)
2537
			{
2538
				$raw[$i] = str_replace($testConnectString, "", $GLOBALS['locale']->translateCharset($mbox, "UTF7-IMAP", "UTF8" ));
2539
				$i++;
2540
			} // foreach
2541
			sort($raw);
2542
			$this->setSessionInboundFoldersString($this->server_url, $this->email_user, $this->port, $this->protocol, implode(",", $raw));
2543
			return $goodStr;
2544
		} else {
2545 4
			return false;
2546
		}
2547
	}
2548
2549 2
	function getSessionConnectionString($server_url, $email_user, $port, $protocol) {
2550 2
		$sessionConnectionString = $server_url . $email_user . $port . $protocol;
2551 2
		return (isset($_SESSION[$sessionConnectionString]) ? $_SESSION[$sessionConnectionString] : "");
2552
	}
2553
2554 1
	function setSessionConnectionString($server_url, $email_user, $port, $protocol, $goodStr) {
2555 1
		$sessionConnectionString = $server_url . $email_user . $port . $protocol;
2556 1
		$_SESSION[$sessionConnectionString] = $goodStr;
2557 1
	}
2558
2559 2
	function getSessionInboundDelimiterString($server_url, $email_user, $port, $protocol) {
2560 2
		$sessionInboundDelimiterString = $server_url . $email_user . $port . $protocol . "delimiter";
2561 2
		return (isset($_SESSION[$sessionInboundDelimiterString]) ? $_SESSION[$sessionInboundDelimiterString] : "");
2562
	}
2563
2564 1
	function setSessionInboundDelimiterString($server_url, $email_user, $port, $protocol, $delimiter) {
2565 1
		$sessionInboundDelimiterString = $server_url . $email_user . $port . $protocol . "delimiter";
2566 1
		$_SESSION[$sessionInboundDelimiterString] = $delimiter;
2567 1
	}
2568
2569 2
	function getSessionInboundFoldersString($server_url, $email_user, $port, $protocol) {
2570 2
		$sessionInboundFoldersListString = $server_url . $email_user . $port . $protocol . "foldersList";
2571 2
		return (isset($_SESSION[$sessionInboundFoldersListString]) ? $_SESSION[$sessionInboundFoldersListString] : "");
2572
	}
2573
2574 1
	function setSessionInboundFoldersString($server_url, $email_user, $port, $protocol, $foldersList) {
2575 1
		$sessionInboundFoldersListString = $server_url . $email_user . $port . $protocol . "foldersList";
2576 1
		$_SESSION[$sessionInboundFoldersListString] = $foldersList;
2577 1
	}
2578
2579
	/**
2580
	 * Checks for duplicate Group User names when creating a new one at save()
2581
	 * @return	GUID		returns GUID of Group User if user_name match is
2582
	 * found
2583
	 * @return	boolean		false if NO DUPE IS FOUND
2584
	 */
2585 1
	function groupUserDupeCheck() {
2586 1
		$q = "SELECT u.id FROM users u WHERE u.deleted=0 AND u.is_group=1 AND u.user_name = '".$this->name."'";
2587 1
		$r = $this->db->query($q, true);
2588 1
		$uid = '';
2589 1
		while($a = $this->db->fetchByAssoc($r)) {
2590
			$uid = $a['id'];
2591
		}
2592
2593 1
		if(strlen($uid) > 0) {
2594
			return $uid;
2595
		} else {
2596 1
			return false;
2597
		}
2598
	}
2599
2600
	/**
2601
	 * Returns <option> markup with the contents of Group users
2602
	 * @param array $groups default empty array
2603
	 * @return string HTML options
2604
	 */
2605 1
	function getGroupsWithSelectOptions($groups = array()) {
2606 1
		$r = $this->db->query('SELECT id, user_name FROM users WHERE users.is_group = 1 AND deleted = 0', true);
2607 1
		if(is_resource($r)) {
2608
			while($a = $this->db->fetchByAssoc($r)) {
2609
				$groups[$a['id']] = $a['user_name'];
2610
			}
2611
		}
2612
2613 1
		$selectOptions = get_select_options_with_id_separate_key($groups, $groups, $this->group_id);
2614 1
		return $selectOptions;
2615
	}
2616
2617
	/**
2618
	 * handles auto-responses to inbound emails
2619
	 *
2620
	 * @param object email Email passed as reference
2621
	 */
2622 2
	function handleAutoresponse(&$email, &$contactAddr) {
2623 2
		if($this->template_id) {
2624 1
			$GLOBALS['log']->debug('found auto-reply template id - prefilling and mailing response');
2625
2626 1
			if($this->getAutoreplyStatus($contactAddr)
2627 1
			&& $this->checkOutOfOffice($email->name)
2628 1
			&& $this->checkFilterDomain($email)) { // if we haven't sent this guy 10 replies in 24hours
2629
2630 1
				if(!empty($this->stored_options)) {
2631
					$storedOptions = unserialize(base64_decode($this->stored_options));
2632
				}
2633
				// get FROM NAME
2634 1
				if(!empty($storedOptions['from_name'])) {
2635
					$from_name = $storedOptions['from_name'];
2636
					$GLOBALS['log']->debug('got from_name from storedOptions: '.$from_name);
2637
				} else { // use system default
2638 1
					$rName = $this->db->query('SELECT value FROM config WHERE name = \'fromname\'', true);
2639 1
					if(is_resource($rName)) {
2640
						$aName = $this->db->fetchByAssoc($rName);
2641
					}
2642 1
					if(!empty($aName['value'])) {
2643
						$from_name = $aName['value'];
2644
					} else {
2645 1
						$from_name = '';
2646
					}
2647
				}
2648
				// get FROM ADDRESS
2649 1
				if(!empty($storedOptions['from_addr'])) {
2650
					$from_addr = $storedOptions['from_addr'];
2651
				} else {
2652 1
					$rAddr = $this->db->query('SELECT value FROM config WHERE name = \'fromaddress\'', true);
2653 1
					if(is_resource($rAddr)) {
2654
						$aAddr = $this->db->fetchByAssoc($rAddr);
2655
					}
2656 1
					if(!empty($aAddr['value'])) {
2657
						$from_addr = $aAddr['value'];
2658
					} else {
2659 1
						$from_addr = '';
2660
					}
2661
				}
2662
2663 1
				$replyToName = (!empty($storedOptions['reply_to_name']))? from_html($storedOptions['reply_to_name']) :$from_name ;
2664 1
				$replyToAddr = (!empty($storedOptions['reply_to_addr'])) ? $storedOptions['reply_to_addr'] : $from_addr;
2665
2666
2667 1
				if(!empty($email->reply_to_email)) {
2668
					$to[0]['email'] = $email->reply_to_email;
2669
				} else {
2670 1
					$to[0]['email'] = $email->from_addr;
2671
				}
2672
				// handle to name: address, prefer reply-to
2673 1
				if(!empty($email->reply_to_name)) {
2674
					$to[0]['display'] = $email->reply_to_name;
2675 1
				} elseif(!empty($email->from_name)) {
2676
					$to[0]['display'] = $email->from_name;
2677
				}
2678
2679 1
				$et = new EmailTemplate();
2680 1
				$et->retrieve($this->template_id);
2681 1
				if(empty($et->subject))		{ $et->subject = ''; }
2682 1
				if(empty($et->body))		{ $et->body = ''; }
2683 1
				if(empty($et->body_html))	{ $et->body_html = ''; }
2684
2685 1
				$reply = new Email();
2686 1
				$reply->type				= 'out';
2687 1
				$reply->to_addrs			= $to[0]['email'];
2688 1
				$reply->to_addrs_arr		= $to;
2689 1
				$reply->cc_addrs_arr		= array();
2690 1
				$reply->bcc_addrs_arr		= array();
2691 1
				$reply->from_name			= $from_name;
2692 1
				$reply->from_addr			= $from_addr;
2693 1
				$reply->name				= $et->subject;
2694 1
				$reply->description			= $et->body;
2695 1
				$reply->description_html	= $et->body_html;
2696 1
				$reply->reply_to_name		= $replyToName;
2697 1
				$reply->reply_to_addr		= $replyToAddr;
2698
2699 1
				$GLOBALS['log']->debug('saving and sending auto-reply email');
2700
				//$reply->save(); // don't save the actual email.
2701 1
				$reply->send();
2702 1
				$this->setAutoreplyStatus($contactAddr);
2703
			} else {
2704
				$GLOBALS['log']->debug('InboundEmail: auto-reply threshold reached for email ('.$contactAddr.') - not sending auto-reply');
2705
			}
2706
		}
2707 2
	}
2708
2709 3
	function handleCaseAssignment($email) {
2710 3
		$c = new aCase();
2711 3
		if($caseId = $this->getCaseIdFromCaseNumber($email->name, $c)) {
2712
			$c->retrieve($caseId);
2713
			$email->retrieve($email->id);
2714
            //assign the case info to parent id and parent type so that the case can be linked to the email on Email Save
2715
			$email->parent_type = "Cases";
2716
			$email->parent_id = $caseId;
2717
			// assign the email to the case owner
2718
			$email->assigned_user_id = $c->assigned_user_id;
2719
			$email->save();
2720
			$GLOBALS['log']->debug('InboundEmail found exactly 1 match for a case: '.$c->name);
2721
			return true;
2722
		} // if
2723 3
		return false;
2724
	} // fn
2725
2726
	/**
2727
	 * handles functionality specific to the Mailbox type (Cases, bounced
2728
	 * campaigns, etc.)
2729
	 *
2730
	 * @param object email Email object passed as a reference
2731
	 * @param object header Header object generated by imap_headerinfo();
2732
	 */
2733 1
	function handleMailboxType(&$email, &$header) {
2734 1
		switch($this->mailbox_type) {
2735 1
			case 'support':
2736 1
				$this->handleCaseAssignment($email);
2737 1
				break;
2738
			case 'bug':
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
2739
2740
				break;
2741
2742
			case 'info':
2743
				// do something with this?
2744
				break;
2745
			case 'sales':
2746
				// do something with leads? we don't have an email_leads table
2747
				break;
2748
			case 'task':
2749
				// do something?
2750
				break;
2751
			case 'bounce':
2752
				require_once('modules/Campaigns/ProcessBouncedEmails.php');
2753
				campaign_process_bounced_emails($email, $header);
2754
				break;
2755
			case 'pick': // do all except bounce handling
2756
				$GLOBALS['log']->debug('looking for a case for '.$email->name);
2757
				$this->handleCaseAssignment($email);
2758
				break;
2759
		}
2760 1
	}
2761
2762 2
	function isMailBoxTypeCreateCase() {
2763 2
		return ($this->mailbox_type == 'createcase' && !empty($this->groupfolder_id));
2764
	} // fn
2765
2766 1
	function handleCreateCase($email, $userId) {
2767 1
		global $current_user, $mod_strings, $current_language;
2768 1
		$mod_strings = return_module_language($current_language, "Emails");
2769 1
		$GLOBALS['log']->debug('In handleCreateCase');
2770 1
		$c = new aCase();
2771 1
		$this->getCaseIdFromCaseNumber($email->name, $c);
2772
2773 1
		if (!$this->handleCaseAssignment($email) && $this->isMailBoxTypeCreateCase()) {
2774
			// create a case
2775
			$GLOBALS['log']->debug('retrieveing email');
2776
			$email->retrieve($email->id);
2777
			$c = new aCase();
2778
			$c->description = $email->description;
2779
			$c->assigned_user_id = $userId;
2780
			$c->name = $email->name;
2781
			$c->status = 'New';
2782
			$c->priority = 'P1';
2783
2784
			if(!empty($email->reply_to_email)) {
2785
				$contactAddr = $email->reply_to_email;
2786
			} else {
2787
				$contactAddr = $email->from_addr;
2788
			}
2789
2790
			$GLOBALS['log']->debug('finding related accounts with address ' . $contactAddr);
2791
			if($accountIds = $this->getRelatedId($contactAddr, 'accounts')) {
2792
				if (sizeof($accountIds) == 1) {
2793
					$c->account_id = $accountIds[0];
2794
2795
					$acct = new Account();
2796
					$acct->retrieve($c->account_id);
2797
					$c->account_name = $acct->name;
2798
				} // if
2799
			} // if
2800
			$c->save(true);
2801
			$caseId = $c->id;
2802
			$c = new aCase();
2803
			$c->retrieve($caseId);
2804
			if($c->load_relationship('emails')) {
2805
				$c->emails->add($email->id);
2806
			} // if
2807
			if($contactIds = $this->getRelatedId($contactAddr, 'contacts')) {
2808
				if(!empty($contactIds) && $c->load_relationship('contacts')) {
2809
                    if (!$accountIds && count($contactIds) == 1) {
2810
                        $contact = BeanFactory::getBean('Contacts', $contactIds[0]);
2811
                        if ($contact->load_relationship('accounts')) {
2812
                            $acct = $contact->accounts->get();
2813
                            if ($c->load_relationship('accounts') && !empty($acct[0])) {
2814
                                $c->accounts->add($acct[0]);
2815
                            }
2816
                        }
2817
                    }
2818
					$c->contacts->add($contactIds);
2819
				} // if
2820
			} // if
2821
			$c->email_id = $email->id;
2822
			$email->parent_type = "Cases";
2823
			$email->parent_id = $caseId;
2824
			// assign the email to the case owner
2825
			$email->assigned_user_id = $c->assigned_user_id;
2826
			$email->name = str_replace('%1', $c->case_number, $c->getEmailSubjectMacro()) . " ". $email->name;
2827
			$email->save();
2828
			$GLOBALS['log']->debug('InboundEmail created one case with number: '.$c->case_number);
2829
			$createCaseTemplateId = $this->get_stored_options('create_case_email_template', "");
2830
			if(!empty($this->stored_options)) {
2831
				$storedOptions = unserialize(base64_decode($this->stored_options));
2832
			}
2833
			if(!empty($createCaseTemplateId)) {
2834
				$fromName = "";
2835
				$fromAddress = "";
2836
				if (!empty($this->stored_options)) {
2837
					$fromAddress = $storedOptions['from_addr'];
2838
					$fromName = from_html($storedOptions['from_name']);
2839
					$replyToName = (!empty($storedOptions['reply_to_name']))? from_html($storedOptions['reply_to_name']) :$fromName ;
2840
					$replyToAddr = (!empty($storedOptions['reply_to_addr'])) ? $storedOptions['reply_to_addr'] : $fromAddress;
2841
				} // if
2842
				$defaults = $current_user->getPreferredEmail();
2843
				$fromAddress = (!empty($fromAddress)) ? $fromAddress : $defaults['email'];
2844
				$fromName = (!empty($fromName)) ? $fromName : $defaults['name'];
2845
				$to[0]['email'] = $contactAddr;
2846
2847
				// handle to name: address, prefer reply-to
2848
				if(!empty($email->reply_to_name)) {
2849
					$to[0]['display'] = $email->reply_to_name;
2850
				} elseif(!empty($email->from_name)) {
2851
					$to[0]['display'] = $email->from_name;
2852
				}
2853
2854
				$et = new EmailTemplate();
2855
				$et->retrieve($createCaseTemplateId);
2856
				if(empty($et->subject))		{ $et->subject = ''; }
2857
				if(empty($et->body))		{ $et->body = ''; }
2858
				if(empty($et->body_html))	{ $et->body_html = ''; }
2859
2860
				$et->subject = "Re:" . " " . str_replace('%1', $c->case_number, $c->getEmailSubjectMacro() . " ". $c->name);
2861
2862
				$html = trim($email->description_html);
2863
				$plain = trim($email->description);
2864
2865
				$email->email2init();
2866
	            $email->from_addr = $email->from_addr_name;
2867
	            $email->to_addrs = $email->to_addrs_names;
2868
	            $email->cc_addrs = $email->cc_addrs_names;
2869
	            $email->bcc_addrs = $email->bcc_addrs_names;
2870
	            $email->from_name = $email->from_addr;
2871
2872
            	$email = $email->et->handleReplyType($email, "reply");
2873
            	$ret = $email->et->displayComposeEmail($email);
2874
            	$ret['description'] = empty($email->description_html) ?  str_replace("\n", "\n<BR/>", $email->description) : $email->description_html;
2875
2876
				$reply = new Email();
2877
				$reply->type				= 'out';
2878
				$reply->to_addrs			= $to[0]['email'];
2879
				$reply->to_addrs_arr		= $to;
2880
				$reply->cc_addrs_arr		= array();
2881
				$reply->bcc_addrs_arr		= array();
2882
				$reply->from_name			= $fromName;
2883
				$reply->from_addr			= $fromAddress;
2884
				$reply->reply_to_name		= $replyToName;
2885
				$reply->reply_to_addr		= $replyToAddr;
2886
				$reply->name				= $et->subject;
2887
				$reply->description			= $et->body . "<div><hr /></div>" .  $email->description;
2888
				if (!$et->text_only) {
2889
					$reply->description_html	= $et->body_html .  "<div><hr /></div>" . $email->description;
2890
				}
2891
				$GLOBALS['log']->debug('saving and sending auto-reply email');
2892
				//$reply->save(); // don't save the actual email.
2893
				$reply->send();
2894
			} // if
2895
2896
		} else {
2897 1
			if(!empty($email->reply_to_email)) {
2898
				$contactAddr = $email->reply_to_email;
2899
			} else {
2900 1
				$contactAddr = $email->from_addr;
2901
			}
2902 1
			$this->handleAutoresponse($email, $contactAddr);
2903
		}
2904
2905 1
	} // fn
2906
2907
	/**
2908
	 * handles linking contacts, accounts, etc. to an email
2909
	 *
2910
	 * @param object Email bean to be linked against
2911
	 * @return string contactAddr is the email address of the sender
2912
	 */
2913 1
	function handleLinking(&$email) {
2914
		// link email to an User if emails match TO addr
2915 1
		if($userIds = $this->getRelatedId($email->to_addrs, 'users')) {
2916
			$GLOBALS['log']->debug('I-E linking email to User');
2917
			// link the user to the email
2918
			$email->load_relationship('users');
2919
			$email->users->add($userIds);
2920
		}
2921
2922
		// link email to a Contact, Lead, or Account if the emails match
2923
		// give precedence to REPLY-TO above FROM
2924 1
		if(!empty($email->reply_to_email)) {
2925
			$contactAddr = $email->reply_to_email;
2926
		} else {
2927 1
			$contactAddr = $email->from_addr;
2928
		}
2929
2930
		// Samir Gandhi : 12/06/07
2931
		// This changes has been done because the linking was done only with the from address and
2932
		// not with to address
2933 1
		$relationShipAddress = $contactAddr;
2934 1
		if (empty($relationShipAddress)) {
2935
			$relationShipAddress .= $email->to_addrs;
2936
		} else {
2937 1
			$relationShipAddress = $relationShipAddress . "," . $email->to_addrs;
2938
		}
2939 1
		if($leadIds = $this->getRelatedId($relationShipAddress, 'leads')) {
2940
			$GLOBALS['log']->debug('I-E linking email to Lead');
2941
			$email->load_relationship('leads');
2942
			$email->leads->add($leadIds);
2943
2944
			foreach($leadIds as $leadId) {
2945
				$lead = new Lead();
2946
				$lead->retrieve($leadId);
2947
				$lead->load_relationship('emails');
2948
				$lead->emails->add($email->id);
2949
			}
2950
		}
2951
2952 1
		if($contactIds = $this->getRelatedId($relationShipAddress, 'contacts')) {
2953
			$GLOBALS['log']->debug('I-E linking email to Contact');
2954
			// link the contact to the email
2955
			$email->load_relationship('contacts');
2956
			$email->contacts->add($contactIds);
2957
		}
2958
2959 1
		if($accountIds = $this->getRelatedId($relationShipAddress, 'accounts')) {
2960
			$GLOBALS['log']->debug('I-E linking email to Account');
2961
			// link the account to the email
2962
			$email->load_relationship('accounts');
2963
			$email->accounts->add($accountIds);
2964
2965
			/* cn: bug 9171 another cause of dying I-E - bad linking
2966
			foreach($accountIds as $accountId) {
2967
				$GLOBALS['log']->debug('I-E reverse-linking Accounts to Emails');
2968
				$acct = new Account();
2969
				$acct->retrieve($accountId);
2970
				$acct->load_relationship('emails');
2971
				$acct->account_emails->add($email->id);
2972
			}
2973
			*/
2974
		}
2975 1
		return $contactAddr;
2976
	}
2977
2978
	/**
2979
	 * Gets part by following breadcrumb path
2980
	 * @param string $bc the breadcrumb string in format (1.1.1)
2981
	 * @param array parts the root level parts array
2982
	 */
2983 2
	protected function getPartByPath($bc, $parts)
2984
	{
2985 2
		if(strstr($bc,'.')) {
2986 1
			$exBc = explode('.', $bc);
2987
		} else {
2988 1
			$exBc = array($bc);
2989
		}
2990
2991 2
		foreach($exBc as $step) {
2992 2
		    if(empty($parts)) return false;
2993 1
		    $res = $parts[$step-1]; // MIME starts with 1, array starts with 0
2994 1
		    if(!empty($res->parts)) {
2995 1
		        $parts = $res->parts;
2996
		    } else {
2997 1
		        $parts = false;
2998
		    }
2999
		}
3000
		return $res;
3001
 	}
3002
3003
	/**
3004
	 * takes a breadcrumb and returns the encoding at that level
3005
	 * @param	string bc the breadcrumb string in format (1.1.1)
3006
	 * @param	array parts the root level parts array
3007
	 * @return	int retInt Int key to transfer encoding (see handleTranserEncoding())
3008
	 */
3009 1
	function getEncodingFromBreadCrumb($bc, $parts) {
3010 1
		if(strstr($bc,'.')) {
3011
			$exBc = explode('.', $bc);
3012
		} else {
3013 1
			$exBc[0] = $bc;
3014
		}
3015
3016 1
		$depth = count($exBc);
3017
3018 1
		for($i=0; $i<$depth; $i++) {
3019 1
			$tempObj[$i] = $parts[($exBc[$i]-1)];
3020 1
			$retInt = imap_utf8($tempObj[$i]->encoding);
3021 1
			if(!empty($tempObj[$i]->parts)) {
3022
				$parts = $tempObj[$i]->parts;
3023
			}
3024
		}
3025 1
		return $retInt;
3026
	}
3027
3028
	/**
3029
	 * retrieves the charset for a given part of an email body
3030
	 *
3031
	 * @param string bc target part of the message in format (1.1.1)
3032
	 * @param array parts 1 level above ROOT array of Objects representing a multipart body
3033
	 * @return string charset name
3034
	 */
3035 2
	function getCharsetFromBreadCrumb($bc, $parts)
3036
	{
3037 2
		$tempObj = $this->getPartByPath($bc, $parts);
3038
		// now we have the tempObj at the end of the breadCrumb trail
3039
3040 2
		if(!empty($tempObj->ifparameters)) {
3041
			foreach($tempObj->parameters as $param) {
3042
				if(strtolower($param->attribute) == 'charset') {
3043
					return $param->value;
3044
				}
3045
			}
3046
		}
3047
3048 2
		return 'default';
3049
	}
3050
3051
	/**
3052
	 * Get the message text from a single mime section, html or plain.
3053
	 *
3054
	 * @param string $msgNo
3055
	 * @param string $section
3056
	 * @param stdObject $structure
3057
	 * @return string
3058
	 */
3059 1
	function getMessageTextFromSingleMimePart($msgNo,$section,$structure)
3060
	{
3061 1
	    $msgPartTmp = imap_fetchbody($this->conn, $msgNo, $section);
3062 1
	    $enc = $this->getEncodingFromBreadCrumb($section, $structure->parts);
3063 1
	    $charset = $this->getCharsetFromBreadCrumb($section, $structure->parts);
3064 1
	    $msgPartTmp = $this->handleTranserEncoding($msgPartTmp, $enc);
3065 1
	    return $this->handleCharsetTranslation($msgPartTmp, $charset);
3066
	}
3067
3068
	/**
3069
	 * Givin an existing breadcrumb add a cooresponding offset
3070
	 *
3071
	 * @param string $bc
3072
	 * @param string $offset
3073
	 * @return string
3074
	 */
3075 1
	function addBreadCrumbOffset($bc, $offset)
3076
	{
3077 1
	    if( (empty($bc) || is_null($bc)) && !empty($offset) )
3078 1
	       return $offset;
3079
3080 1
	    $a_bc = explode(".", $bc);
3081 1
	    $a_offset = explode(".",$offset);
3082 1
	    if(count($a_bc) < count($a_offset))
3083 1
	       $a_bc = array_merge($a_bc,array_fill( count($a_bc), count($a_offset) - count($a_bc), 0));
3084
3085 1
	    $results = array();
3086 1
	    for($i=0;$i < count($a_bc); $i++)
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
3087
	    {
3088 1
	        if(isset($a_offset[$i]))
3089 1
	           $results[] = $a_bc[$i] + $a_offset[$i];
3090
	        else
3091 1
	           $results[] = $a_bc[$i];
3092
	    }
3093 1
	    return implode(".", $results);
3094
	}
3095
3096
	/**
3097
	 * returns the HTML text part of a multi-part message
3098
	 *
3099
	 * @param int msgNo the relative message number for the monitored mailbox
3100
	 * @param string $type the type of text processed, either 'PLAIN' or 'HTML'
3101
	 * @return string UTF-8 encoded version of the requested message text
3102
	 */
3103 1
	function getMessageText($msgNo, $type, $structure, $fullHeader,$clean_email=true, $bcOffset = "") {
3104 1
		global $sugar_config;
3105
3106 1
		$msgPart = '';
3107 1
		$bc = $this->buildBreadCrumbs($structure->parts, $type);
3108
		//Add an offset if specified
3109 1
		if(!empty($bcOffset))
3110
            $bc = $this->addBreadCrumbOffset($bc, $bcOffset);
3111
3112 1
		if(!empty($bc)) { // multi-part
3113
			// HUGE difference between PLAIN and HTML
3114
			if($type == 'PLAIN') {
3115
				$msgPart = $this->getMessageTextFromSingleMimePart($msgNo,$bc,$structure);
3116
			} else {
3117
				// get part of structure that will
3118
				$msgPartRaw = '';
3119
				$bcArray = $this->buildBreadCrumbsHTML($structure->parts,$bcOffset);
3120
				// construct inline HTML/Rich msg
3121
				foreach($bcArray as $bcArryKey => $bcArr) {
3122
					foreach($bcArr as $type => $bcTrail) {
3123
						if($type == 'html')
3124
						    $msgPartRaw .= $this->getMessageTextFromSingleMimePart($msgNo,$bcTrail,$structure);
3125
						 else {
3126
							// deal with inline image
3127
							$part = $this->getPartByPath($bcTrail, $structure->parts);
3128
							if(empty($part) || empty($part->id)) continue;
3129
							$partid = substr($part->id, 1, -1); // strip <> around
3130
							if(isset($this->inlineImages[$partid])) {
3131
								$imageName = $this->inlineImages[$partid];
3132
								$newImagePath = "class=\"image\" src=\"{$this->imagePrefix}{$imageName}\"";
3133
								$preImagePath = "src=\"cid:$partid\"";
3134
								$msgPartRaw = str_replace($preImagePath, $newImagePath, $msgPartRaw);
3135
							}
3136
						}
3137
					}
3138
				}
3139
				$msgPart = $msgPartRaw;
3140
			}
3141
		} else { // either PLAIN message type (flowed) or b0rk3d RFC
3142
			// make sure we're working on valid data here.
3143 1
			if($structure->subtype != $type) {
3144 1
				return '';
3145
			}
3146
3147
			$decodedHeader = $this->decodeHeader($fullHeader);
3148
3149
			// now get actual body contents
3150
			$text = imap_body($this->conn, $msgNo);
3151
3152
			$upperCaseKeyDecodeHeader = array();
3153
			if (is_array($decodedHeader)) {
3154
				$upperCaseKeyDecodeHeader = array_change_key_case($decodedHeader, CASE_UPPER);
3155
			} // if
3156
			if(isset($upperCaseKeyDecodeHeader[strtoupper('Content-Transfer-Encoding')])) {
3157
				$flip = array_flip($this->transferEncoding);
3158
				$text = $this->handleTranserEncoding($text, $flip[strtoupper($upperCaseKeyDecodeHeader[strtoupper('Content-Transfer-Encoding')])]);
3159
			}
3160
3161
			if(is_array($upperCaseKeyDecodeHeader['CONTENT-TYPE']) && isset($upperCaseKeyDecodeHeader['CONTENT-TYPE']['charset']) && !empty($upperCaseKeyDecodeHeader['CONTENT-TYPE']['charset'])) {
3162
				// we have an explicit content type, use it
3163
                $msgPart = $this->handleCharsetTranslation($text, $upperCaseKeyDecodeHeader['CONTENT-TYPE']['charset']);
3164
			} else {
3165
                // make a best guess as to what our content type is
3166
                $msgPart = $this->convertToUtf8($text);
3167
            }
3168
		} // end else clause
3169
3170
		$msgPart = $this->customGetMessageText($msgPart);
3171
		/* cn: bug 9176 - htmlEntitites hide XSS attacks. */
3172
		if($type == 'PLAIN') {
3173
		    return SugarCleaner::cleanHtml(to_html($msgPart), false);
3174
		}
3175
        // Bug 50241: can't process <?xml:namespace .../> properly. Strip <?xml ...> tag first.
3176
		$msgPart = preg_replace("/<\?xml[^>]*>/","",$msgPart);
3177
3178
        return SugarCleaner::cleanHtml($msgPart, false);
3179
	}
3180
3181
	/**
3182
	 * decodes raw header information and passes back an associative array with
3183
	 * the important elements key'd by name
3184
	 * @param header string the raw header
3185
	 * @return decodedHeader array the associative array
3186
	 */
3187 1
	function decodeHeader($fullHeader) {
3188 1
		$decodedHeader = array();
3189 1
		$exHeaders = explode("\r", $fullHeader);
3190 1
		if (!is_array($exHeaders)) {
3191
			$exHeaders = explode("\r\n", $fullHeader);
3192
		}
3193 1
		$quotes = array('"', "'");
3194
3195 1
		foreach($exHeaders as $lineNum => $head) {
3196 1
			$key 	= '';
3197 1
			$key	= trim(substr($head, 0, strpos($head, ':')));
3198 1
			$value	= '';
3199 1
			$value	= trim(substr($head, (strpos($head, ':') + 1), strlen($head)));
3200
3201
			// handle content-type section in headers
3202 1
			if(strtolower($key) == 'content-type' && strpos($value, ';')) { // ";" means something follows related to (such as Charset)
3203
				$semiColPos = mb_strpos($value, ';');
3204
				$strLenVal = mb_strlen($value);
3205
				if(($semiColPos + 4) >= $strLenVal) {
3206
					// the charset="[something]" is on the next line
3207
					$value .= str_replace($quotes, "", trim($exHeaders[$lineNum+1]));
3208
				}
3209
3210
				$newValue = array();
3211
				$exValue = explode(';', $value);
3212
				$newValue['type'] = $exValue[0];
3213
3214
				for($i=1; $i<count($exValue); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
3215
					$exContent = explode('=', $exValue[$i]);
3216
					$newValue[trim($exContent[0])] = trim($exContent[1], "\t \"");
3217
				}
3218
				$value = $newValue;
3219
			}
3220
3221 1
			if(!empty($key) && !empty($value)) {
3222 1
				$decodedHeader[$key] = $value;
3223
			}
3224
		}
3225
3226 1
		return $decodedHeader;
3227
	}
3228
3229
	/**
3230
	 * handles translating message text from orignal encoding into UTF-8
3231
	 *
3232
	 * @param string text test to be re-encoded
3233
	 * @param string charset original character set
3234
	 * @return string utf8 re-encoded text
3235
	 */
3236 4
	function handleCharsetTranslation($text, $charset) {
3237 4
		global $locale;
3238
3239 4
		if(empty($charset)) {
3240
			$GLOBALS['log']->debug("***ERROR: InboundEmail::handleCharsetTranslation() called without a \$charset!");
3241
			$GLOBALS['log']->debug("***STACKTRACE: ".print_r(debug_backtrace(), true));
3242
			return $text;
3243
		}
3244
3245
		// typical headers have no charset - let destination pick (since it's all ASCII anyways)
3246 4
		if(strtolower($charset) == 'default' || strtolower($charset) == 'utf-8') {
3247 2
			return $text;
3248
		}
3249
3250 3
		return $locale->translateCharset($text, $charset);
3251
	}
3252
3253
3254
3255
	/**
3256
	 * Builds up the "breadcrumb" trail that imap_fetchbody() uses to return
3257
	 * parts of an email message, including attachments and inline images
3258
	 * @param	$parts	array of objects
3259
	 * @param	$subtype	what type of trail to return? HTML? Plain? binaries?
3260
	 * @param	$breadcrumb	text trail to build up
3261
	 */
3262 2
	function buildBreadCrumbs($parts, $subtype, $breadcrumb = '0') {
3263
		//_pp('buildBreadCrumbs building for '.$subtype.' with BC at '.$breadcrumb);
3264
		// loop through available parts in the array
3265 2
		foreach($parts as $k => $part) {
3266
			// mark passage through level
3267
			$thisBc = ($k+1);
3268
			// if this is not the first time through, start building the map
3269
			if($breadcrumb != 0) {
3270
				$thisBc = $breadcrumb.'.'.$thisBc;
3271
			}
3272
3273
			// found a multi-part/mixed 'part' - keep digging
3274
			if($part->type == 1 && (strtoupper($part->subtype) == 'RELATED' || strtoupper($part->subtype) == 'ALTERNATIVE' || strtoupper($part->subtype) == 'MIXED')) {
3275
				//_pp('in loop: going deeper with subtype: '.$part->subtype.' $k is: '.$k);
3276
				$thisBc = $this->buildBreadCrumbs($part->parts, $subtype, $thisBc);
3277
				return $thisBc;
3278
3279
			} elseif(strtolower($part->subtype) == strtolower($subtype)) { // found the subtype we want, return the breadcrumb value
3280
				//_pp('found '.$subtype.' bc! returning: '.$thisBc);
3281
				return $thisBc;
3282
			} else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
3283
				//_pp('found '.$part->subtype.' instead');
3284
			}
3285
		}
3286 2
	}
3287
3288
	/**
3289
	 * Similar to buildBreadCrumbs() but returns an ordered array containing all parts of the message that would be
3290
	 * considered "HTML" or Richtext (embedded images, formatting, etc.).
3291
	 * @param array parts Array of parts of a message
3292
	 * @param int breadcrumb Passed integer value to start breadcrumb trail
3293
	 * @param array stackedBreadcrumbs Persistent trail of breadcrumbs
3294
	 * @return array Ordered array of parts to retrieve via imap_fetchbody()
3295
	 */
3296 1
	function buildBreadCrumbsHTML($parts, $breadcrumb = '0', $stackedBreadcrumbs = array()) {
3297 1
		$subtype = 'HTML';
3298 1
		$disposition = 'inline';
3299
3300 1
		foreach($parts as $k => $part) {
3301
			// mark passage through level
3302
			$thisBc = ($k+1);
3303
3304
			if($breadcrumb != 0) {
3305
				$thisBc = $breadcrumb.'.'.$thisBc;
3306
			}
3307
			// found a multi-part/mixed 'part' - keep digging
3308
			if($part->type == 1 && (strtoupper($part->subtype) == 'RELATED' || strtoupper($part->subtype) == 'ALTERNATIVE' || strtoupper($part->subtype) == 'MIXED')) {
3309
				$stackedBreadcrumbs = $this->buildBreadCrumbsHTML($part->parts, $thisBc, $stackedBreadcrumbs);
3310
			} elseif(
3311
				(strtolower($part->subtype) == strtolower($subtype)) ||
3312
					(
3313
						isset($part->disposition) && strtolower($part->disposition) == 'inline' &&
3314
						in_array(strtoupper($part->subtype), $this->imageTypes)
3315
					)
3316
			) {
3317
				// found the subtype we want, return the breadcrumb value
3318
				$stackedBreadcrumbs[] = array(strtolower($part->subtype) => $thisBc);
3319
			} elseif($part->type == 5) {
3320
				$stackedBreadcrumbs[] = array(strtolower($part->subtype) => $thisBc);
3321
			}
3322
		}
3323
3324 1
		return $stackedBreadcrumbs;
3325
	}
3326
3327
	/**
3328
	 * Takes a PHP imap_* object's to/from/cc/bcc address field and converts it
3329
	 * to a standard string that SugarCRM expects
3330
	 * @param	$arr	an array of email address objects
3331
	 */
3332 1
	function convertImapToSugarEmailAddress($arr) {
3333 1
		if(is_array($arr)) {
3334 1
			$addr = '';
3335 1
			foreach($arr as $key => $obj) {
3336 1
				$addr .= $obj->mailbox.'@'.$obj->host.', ';
3337
			}
3338
			// strip last comma
3339 1
			$ret = substr_replace($addr,'',-2,-1);
3340 1
			return trim($ret);
3341
		}
3342
	}
3343
3344
	/**
3345
	 * tries to figure out what character set a given filename is using and
3346
	 * decode based on that
3347
	 *
3348
	 * @param string name Name of attachment
3349
	 * @return string decoded name
3350
	 */
3351 1
	function handleEncodedFilename($name) {
3352 1
		$imapDecode = imap_mime_header_decode($name);
3353
		/******************************
3354
		$imapDecode => stdClass Object
3355
			(
3356
				[charset] => utf-8
3357
				[text] => w�hlen.php
3358
			)
3359
3360
					OR
3361
3362
		$imapDecode => stdClass Object
3363
			(
3364
				[charset] => default
3365
				[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
3366
			)
3367
		*******************************/
3368 1
		if($imapDecode[0]->charset != 'default') { // mime-header encoded charset
3369
			$encoding = $imapDecode[0]->charset;
3370
			$name = $imapDecode[0]->text; // encoded in that charset
3371
		} else {
3372
			/* encoded filenames are formatted as [encoding]''[filename] */
3373 1
			if(strpos($name, "''") !== false) {
3374
3375
				$encoding = substr($name, 0, strpos($name, "'"));
3376
3377
				while(strpos($name, "'") !== false) {
3378
					$name = trim(substr($name, (strpos($name, "'")+1), strlen($name)));
3379
				}
3380
			}
3381 1
			$name = urldecode($name);
3382
		}
3383 1
		return (strtolower($encoding) == 'utf-8') ? $name : $GLOBALS['locale']->translateCharset($name, $encoding, 'UTF-8');
3384
	}
3385
3386
	/*
3387
		Primary body types for a part of a mail structure (imap_fetchstructure returned object)
3388
		0 => text
3389
		1 => multipart
3390
		2 => message
3391
		3 => application
3392
		4 => audio
3393
		5 => image
3394
		6 => video
3395
		7 => other
3396
	*/
3397
3398
	/**
3399
	Primary body types for a part of a mail structure (imap_fetchstructure returned object)
3400
	@var array $imap_types
3401
	*/
3402
	public $imap_types = array(
3403
			0 => 'text',
3404
			1 => 'multipart',
3405
			2 => 'message',
3406
			3 => 'application',
3407
			4 => 'audio',
3408
			5 => 'image',
3409
			6 => 'video',
3410
	);
3411
3412 1
	public function getMimeType($type, $subtype)
3413
	{
3414 1
		if(isset($this->imap_types[$type])) {
3415 1
			return $this->imap_types[$type]."/$subtype";
3416
		} else {
3417 1
			return "other/$subtype";
3418
		}
3419
	}
3420
3421
	/**
3422
	 * Takes the "parts" attribute of the object that imap_fetchbody() method
3423
	 * returns, and recursively goes through looking for objects that have a
3424
	 * disposition of "attachement" or "inline"
3425
	 * @param int $msgNo The relative message number for the monitored mailbox
3426
	 * @param object $parts Array of objects to examine
3427
	 * @param string $emailId The GUID of the email saved prior to calling this method
3428
	 * @param array $breadcrumb Default 0, build up of the parts mapping
3429
	 * @param bool $forDisplay Default false
3430
	 */
3431 1
	function saveAttachments($msgNo, $parts, $emailId, $breadcrumb='0', $forDisplay) {
3432 1
		global $sugar_config;
3433
		/*
3434
			Primary body types for a part of a mail structure (imap_fetchstructure returned object)
3435
			0 => text
3436
			1 => multipart
3437
			2 => message
3438
			3 => application
3439
			4 => audio
3440
			5 => image
3441
			6 => video
3442
			7 => other
3443
		*/
3444
3445 1
		foreach($parts as $k => $part) {
3446
			$thisBc = $k+1;
3447
			if($breadcrumb != '0') {
3448
				$thisBc = $breadcrumb.'.'.$thisBc;
3449
			}
3450
			$attach = null;
3451
			// check if we need to recurse into the object
3452
			//if($part->type == 1 && !empty($part->parts)) {
3453
			if(isset($part->parts) && !empty($part->parts) && !( isset($part->subtype) && strtolower($part->subtype) == 'rfc822')  ) {
3454
				$this->saveAttachments($msgNo, $part->parts, $emailId, $thisBc, $forDisplay);
3455
                continue;
3456
			} elseif($part->ifdisposition) {
3457
				// we will take either 'attachments' or 'inline'
3458
				if(strtolower($part->disposition) == 'attachment' || ((strtolower($part->disposition) == 'inline') && $part->type != 0)) {
3459
					$attach = $this->getNoteBeanForAttachment($emailId);
3460
					$fname = $this->handleEncodedFilename($this->retrieveAttachmentNameFromStructure($part));
3461
3462
					if(!empty($fname)) {//assign name to attachment
3463
						$attach->name = $fname;
3464
					} else {//if name is empty, default to filename
3465
						$attach->name = urlencode($this->retrieveAttachmentNameFromStructure($part));
3466
					}
3467
					$attach->filename = $attach->name;
3468
					if (empty($attach->filename)) {
3469
						continue;
3470
					}
3471
3472
					// deal with the MIME types email has
3473
					$attach->file_mime_type = $this->getMimeType($part->type, $part->subtype);
3474
					$attach->safeAttachmentName();
3475
					if($forDisplay) {
3476
						$attach->id = $this->getTempFilename();
3477
					} else {
3478
						// only save if doing a full import, else we want only the binaries
3479
						$attach->save();
3480
					}
3481
				} // end if disposition type 'attachment'
3482
			}// end ifdisposition
3483
			//Retrieve contents of subtype rfc8822
3484
			elseif ($part->type == 2 && isset($part->subtype) && strtolower($part->subtype) == 'rfc822' )
3485
			{
3486
			    $tmp_eml =  imap_fetchbody($this->conn, $msgNo, $thisBc);
3487
			    $attach = $this->getNoteBeanForAttachment($emailId);
3488
			    $attach->file_mime_type = 'messsage/rfc822';
3489
			    $attach->description = $tmp_eml;
3490
			    $attach->filename = 'bounce.eml';
3491
			    $attach->safeAttachmentName();
3492
			    if($forDisplay) {
3493
			        $attach->id = $this->getTempFilename();
3494
			    } else {
3495
			        // only save if doing a full import, else we want only the binaries
3496
			        $attach->save();
3497
			    }
3498
			} elseif(!$part->ifdisposition && $part->type != 1 && $part->type != 2 && $thisBc != '1') {
3499
        		// No disposition here, but some IMAP servers lie about disposition headers, try to find the truth
3500
				// Also Outlook puts inline attachments as type 5 (image) without a disposition
3501
				if($part->ifparameters) {
3502
                    foreach($part->parameters as $param) {
3503
                        if(strtolower($param->attribute) == "name" || strtolower($param->attribute) == "filename") {
3504
                            $fname = $this->handleEncodedFilename($param->value);
3505
                            break;
3506
                        }
3507
                    }
3508
                    if(empty($fname)) continue;
3509
3510
					// we assume that named parts are attachments too
3511
                    $attach = $this->getNoteBeanForAttachment($emailId);
3512
3513
					$attach->filename = $attach->name = $fname;
3514
					$attach->file_mime_type = $this->getMimeType($part->type, $part->subtype);
3515
3516
					$attach->safeAttachmentName();
3517
					if($forDisplay) {
3518
						$attach->id = $this->getTempFilename();
3519
					} else {
3520
						// only save if doing a full import, else we want only the binaries
3521
						$attach->save();
3522
					}
3523
				}
3524
			}
3525
			$this->saveAttachmentBinaries($attach, $msgNo, $thisBc, $part, $forDisplay);
3526
		} // end foreach
3527 1
	}
3528
3529
	/**
3530
	 * Return a new note object for attachments.
3531
	 *
3532
	 * @param string $emailId
3533
	 * @return Note
3534
	 */
3535 1
	function getNoteBeanForAttachment($emailId)
3536
	{
3537 1
	    $attach = new Note();
3538 1
	    $attach->parent_id = $emailId;
3539 1
	    $attach->parent_type = 'Emails';
3540
3541 1
	    return $attach;
3542
	}
3543
3544
	/**
3545
	 * Return the filename of the attachment by examining the dparameters or parameters returned from imap_fetch_structure
3546
     *
3547
	 * @param object $part
3548
	 * @return string
3549
	 */
3550 1
	function retrieveAttachmentNameFromStructure($part)
3551
	{
3552 1
	   $result = "";
3553
3554 1
	   foreach ($part->dparameters as $k => $v)
3555
	   {
3556 1
	       if( strtolower($v->attribute) == 'filename')
3557
	       {
3558 1
	           $result = $v->value;
3559 1
	           break;
3560
	       }
3561
	   }
3562
3563 1
		if (empty($result)) {
3564 1
			foreach ($part->parameters as $k => $v) {
3565 1
				if (strtolower($v->attribute) == 'name') {
3566 1
					$result = $v->value;
3567 1
					break;
3568
				}
3569
			}
3570
		}
3571
3572 1
	   return $result;
3573
3574
    }
3575
	/**
3576
	 * saves the actual binary file of a given attachment
3577
	 * @param object attach Note object that is attached to the binary file
3578
	 * @param string msgNo Message Number on IMAP/POP3 server
3579
	 * @param string thisBc Breadcrumb to navigate email structure to find the content
3580
	 * @param object part IMAP standard object that contains the "parts" of this section of email
3581
	 * @param bool $forDisplay
3582
	 */
3583 1
	function saveAttachmentBinaries($attach, $msgNo, $thisBc, $part, $forDisplay) {
3584
		// decide where to place the file temporarily
3585
3586 1
		if(isset($attach->id) && strpos($attach->id, "..") !== false && isset($this->id) && strpos($this->id, "..") !== false){
3587
			die("Directory navigation attack denied.");
3588
		}
3589
3590 1
		$uploadDir = ($forDisplay) ? "{$this->EmailCachePath}/{$this->id}/attachments/" : "upload://";
3591
3592
		// decide what name to save file as
3593 1
		$fileName = htmlspecialchars($attach->id);
3594
3595
		// download the attachment if we didn't do it yet
3596 1
		if(!file_exists($uploadDir.$fileName)) {
3597 1
			$msgPartRaw = imap_fetchbody($this->conn, $msgNo, $thisBc);
3598
    		// deal with attachment encoding and decode the text string
3599 1
			$msgPart = $this->handleTranserEncoding($msgPartRaw, $part->encoding);
3600
3601 1
			if(file_put_contents($uploadDir.$fileName, $msgPart)) {
3602
				$GLOBALS['log']->debug('InboundEmail saved attachment file: '.$attach->filename);
3603
			} else {
3604 1
                $GLOBALS['log']->debug('InboundEmail could not create attachment file: '.$attach->filename ." - temp file target: [ {$uploadDir}{$fileName} ]");
3605 1
                return;
3606
			}
3607
		}
3608
3609
		$this->tempAttachment[$fileName] = urldecode($attach->filename);
3610
		// if all was successful, feel for inline and cache Note ID for display:
3611
		if((strtolower($part->disposition) == 'inline' && in_array($part->subtype, $this->imageTypes))
3612
		    || ($part->type == 5)) {
3613
		    if(copy($uploadDir.$fileName, sugar_cached("images/{$fileName}.").strtolower($part->subtype))) {
3614
			    $id = substr($part->id, 1, -1); //strip <> around
3615
			    $this->inlineImages[$id] = $attach->id.".".strtolower($part->subtype);
3616
			} else {
3617
				$GLOBALS['log']->debug('InboundEmail could not copy '.$uploadDir.$fileName.' to cache');
3618
			}
3619
		}
3620
	}
3621
3622
	/**
3623
	 * decodes a string based on its associated encoding
3624
	 * if nothing is passed, we default to no-encoding type
3625
	 * @param	$str	encoded string
3626
	 * @param	$enc	detected encoding
3627
	 */
3628 3
	function handleTranserEncoding($str, $enc=0) {
3629
		switch($enc) {
3630 3
			case 2:// BINARY
3631
				$ret = $str;
3632
				break;
3633 3
			case 3:// BASE64
3634 1
				$ret = base64_decode($str);
3635 1
				break;
3636 3
			case 4:// QUOTED-PRINTABLE
3637 1
				$ret = quoted_printable_decode($str);
3638 1
				break;
3639 3
			case 0:// 7BIT or 8BIT
3640
			case 1:// already in a string-useable format - do nothing
3641
			case 5:// OTHER
3642
			default:// catch all
3643 3
				$ret = $str;
3644 3
				break;
3645
		}
3646
3647 3
		return $ret;
3648
	}
3649
3650
3651
	/**
3652
	 * Some emails do not get assigned a message_id, specifically from
3653
	 * Outlook/Exchange.
3654
	 *
3655
	 * We need to derive a reliable one for duplicate import checking.
3656
	 */
3657 1
	function getMessageId($header) {
3658 1
		$message_id = md5(print_r($header, true));
3659 1
		return $message_id;
3660
	}
3661
3662
	/**
3663
	 * checks for duplicate emails on polling.  The uniqueness of a given email message is determined by a concatenation
3664
	 * of 2 values, the messageID and the delivered-to field.  This allows multiple To: and B/CC: destination addresses
3665
	 * to be imported by Sugar without violating the true duplicate-email issues.
3666
	 *
3667
	 * @param string message_id message ID generated by sending server
3668
	 * @param int message number (mailserver's key) of email
3669
	 * @param object header object generated by imap_headerinfo()
3670
	 * @param string textHeader Headers in normal text format
3671
	 * @return bool
3672
	 */
3673 1
	function importDupeCheck($message_id, $header, $textHeader) {
3674 1
		$GLOBALS['log']->debug('*********** InboundEmail doing dupe check.');
3675
3676
		// generate "delivered-to" seed for email duplicate check
3677 1
		$deliveredTo = $this->id; // cn: bug 12236 - cc's failing dupe check
3678 1
		$exHeader = explode("\n", $textHeader);
3679
3680 1
		foreach($exHeader as $headerLine) {
3681 1
			if(strpos(strtolower($headerLine), 'delivered-to:') !== false) {
3682
				$deliveredTo = substr($headerLine, strpos($headerLine, " "), strlen($headerLine));
3683
				$GLOBALS['log']->debug('********* InboundEmail found [ '.$deliveredTo.' ] as the destination address for email [ '.$message_id.' ]');
3684 1
			} elseif(strpos(strtolower($headerLine), 'x-real-to:') !== false) {
3685
				$deliveredTo = substr($headerLine, strpos($headerLine, " "), strlen($headerLine));
3686 1
				$GLOBALS['log']->debug('********* InboundEmail found [ '.$deliveredTo.' ] for non-standards compliant email x-header [ '.$message_id.' ]');
3687
			}
3688
		}
3689
3690
		//if(empty($message_id) && !isset($message_id)) {
3691 1
		if(empty($message_id) || !isset($message_id)) {
3692
			$GLOBALS['log']->debug('*********** NO MESSAGE_ID.');
3693
			$message_id = $this->getMessageId($header);
3694
		}
3695
3696
		// generate compound messageId
3697 1
		$this->compoundMessageId = trim($message_id).trim($deliveredTo);
3698 1
		if (empty($this->compoundMessageId)) {
3699
			$GLOBALS['log']->error('Inbound Email found a message without a header and message_id');
3700
			return false;
3701
		} // if
3702 1
		$this->compoundMessageId = md5($this->compoundMessageId);
3703
3704 1
		$query = 'SELECT count(emails.id) AS c FROM emails WHERE emails.message_id = \''.$this->compoundMessageId.'\' and emails.deleted = 0';
3705 1
		$r = $this->db->query($query, true);
3706 1
		$a = $this->db->fetchByAssoc($r);
3707
3708 1
		if($a['c'] > 0) {
3709
			$GLOBALS['log']->debug('InboundEmail found a duplicate email with ID ('.$this->compoundMessageId.')');
3710
			return false; // we have a dupe and don't want to import the email'
3711
		} else {
3712 1
			return true;
3713
		}
3714
	}
3715
3716
	/**
3717
	 * takes the output from imap_mime_hader_decode() and handles multiple types of encoding
3718
	 * @param string subject Raw subject string from email
3719
	 * @return string ret properly formatted UTF-8 string
3720
	 */
3721 4
	function handleMimeHeaderDecode($subject) {
3722 4
		$subjectDecoded = imap_mime_header_decode($subject);
3723
3724 4
		$ret = '';
3725 4
		foreach($subjectDecoded as $object) {
3726 4
			if($object->charset != 'default') {
3727
				$ret .= $this->handleCharsetTranslation($object->text, $object->charset);
3728
			} else {
3729 4
				$ret .= $object->text;
3730
			}
3731
		}
3732 4
		return $ret;
3733
	}
3734
3735
	/**
3736
	 * Calculates the appropriate display date/time sent for an email.
3737
	 * @param string headerDate The date sent of email in MIME header format
3738
	 * @return string GMT-0 Unix timestamp
3739
	 */
3740
	function getUnixHeaderDate($headerDate) {
3741
		global $timedate;
3742
3743
		if (empty($headerDate)) {
3744
			return "";
3745
		}
3746
		///////////////////////////////////////////////////////////////////
3747
		////	CALCULATE CORRECT SENT DATE/TIME FOR EMAIL
3748
		if(!empty($headerDate)) {
3749
		    // Bug 25254 - Strip trailing space that come in some header dates (maybe ones with 1-digit day number)
3750
		    $headerDate = trim($headerDate);
3751
			// need to hack PHP/windows' bad handling of strings when using POP3
3752
			if(strstr($headerDate,'+0000 GMT')) {
3753
				$headerDate = str_replace('GMT','', $headerDate);
3754
			} elseif(!strtotime($headerDate)) {
3755
				$headerDate = 'now'; // catch non-standard format times.
3756
			} else {
3757
				// cn: bug 9196 parse the GMT offset
3758
				if(strpos($headerDate, '-') || strpos($headerDate, '+')) {
3759
					// cn: bug make sure last 5 chars are [+|-]nnnn
3760
					if(strpos($headerDate, "(")) {
3761
						$headerDate = preg_replace('/\([\w]+\)/i', "", $headerDate);
3762
						$headerDate = trim($headerDate);
3763
					}
3764
3765
					// parse mailserver time
3766
					$gmtEmail = trim(substr($headerDate, -5, 5));
3767
					$posNeg = substr($gmtEmail, 0, 1);
3768
					$gmtHours = substr($gmtEmail, 1, 2);
3769
					$gmtMins = substr($gmtEmail, -2, 2);
3770
3771
					// get seconds
3772
					$secsHours = $gmtHours * 60 * 60;
3773
					$secsTotal = $secsHours + ($gmtMins * 60);
3774
					$secsTotal = ($posNeg == '-') ? $secsTotal : -1 * $secsTotal;
3775
3776
					$headerDate = trim(substr_replace($headerDate, '', -5)); // mfh: bug 10961/12855 - date time values with GMT offsets not properly formatted
3777
				}
3778
			}
3779
		} else {
3780
			$headerDate = 'now';
3781
		}
3782
3783
		$unixHeaderDate = strtotime($headerDate);
3784
3785
		if(isset($secsTotal)) {
3786
			// this gets the timestamp to true GMT-0
3787
			$unixHeaderDate += $secsTotal;
3788
		}
3789
3790
		if(strtotime('Jan 1, 2001') > $unixHeaderDate) {
3791
			$unixHeaderDate = strtotime('now');
3792
		}
3793
3794
		return $unixHeaderDate;
3795
		////	END CALCULATE CORRECT SENT DATE/TIME FOR EMAIL
3796
		///////////////////////////////////////////////////////////////////
3797
	}
3798
3799
	/**
3800
	 * This method returns the correct messageno for the pop3 protocol
3801
	 * @param String UIDL
3802
	 * @return returnMsgNo
3803
	 */
3804 3
	function getCorrectMessageNoForPop3($messageId) {
3805 3
		$returnMsgNo = -1;
3806 3
		if ($this->protocol == 'pop3') {
3807 2
			if($this->pop3_open()) {
3808
				// get the UIDL from database;
3809
				$query = "SELECT msgno FROM email_cache WHERE ie_id = '{$this->id}' AND message_id = '{$messageId}'";
3810
				$r = $this->db->query($query);
3811
				$a = $this->db->fetchByAssoc($r);
3812
				$msgNo = $a['msgno'];
3813
				$returnMsgNo = $msgNo;
3814
3815
				// authenticate
3816
				$this->pop3_sendCommand("USER", $this->email_user);
3817
				$this->pop3_sendCommand("PASS", $this->email_password);
3818
3819
				// get UIDL for this msgNo
3820
				$this->pop3_sendCommand("UIDL {$msgNo}", '', false); // leave socket buffer alone until the while()
3821
				$buf = fgets($this->pop3socket, 1024); // handle "OK+ msgNo UIDL(UIDL for this messageno)";
3822
3823
				// if it returns OK then we have found the message else get all the UIDL
3824
				// and search for the correct msgNo;
3825
				$foundMessageNo = false;
3826
				if (preg_match("/OK/", $buf) > 0) {
3827
					$mailserverResponse = explode(" ", $buf);
3828
					// if the cachedUIDL and the UIDL from mail server matches then its the correct messageno
3829
					if (trim($mailserverResponse[sizeof($mailserverResponse) - 1]) == $messageId) {
3830
						$foundMessageNo = true;
3831
					}
3832
				} //if
3833
3834
				//get all the UIDL and then find the correct messageno
3835
				if (!$foundMessageNo) {
3836
					// get UIDLs
3837
					$this->pop3_sendCommand("UIDL", '', false); // leave socket buffer alone until the while()
3838
					fgets($this->pop3socket, 1024); // handle "OK+";
3839
					$UIDLs = array();
3840
					$buf = '!';
3841
					if(is_resource($this->pop3socket)) {
3842
						while(!feof($this->pop3socket)) {
3843
							$buf = fgets($this->pop3socket, 1024); // 8kb max buffer - shouldn't be more than 80 chars via pop3...
3844
							if(trim($buf) == '.') {
3845
								$GLOBALS['log']->debug("*** GOT '.'");
3846
								break;
3847
							} // if
3848
							// format is [msgNo] [UIDL]
3849
							$exUidl = explode(" ", $buf);
3850
							$UIDLs[trim($exUidl[1])] = trim($exUidl[0]);
3851
						} // while
3852
						if (array_key_exists($messageId, $UIDLs)) {
3853
							$returnMsgNo = $UIDLs[$messageId];
3854
						} else {
3855
							// message could not be found on server
3856
							$returnMsgNo = -1;
3857
						} // else
3858
					} // if
3859
3860
				} // if
3861
				$this->pop3_cleanUp();
3862
			} //if
3863
		} //if
3864 3
		return $returnMsgNo;
3865
	}
3866
3867
	/**
3868
	 * If the importOneEmail returns false, then findout if the duplicate email
3869
	 */
3870 1
	function getDuplicateEmailId($msgNo, $uid) {
3871 1
		global $timedate;
3872 1
		global $app_strings;
3873 1
		global $app_list_strings;
3874 1
		global $sugar_config;
3875 1
		global $current_user;
3876
3877 1
		$header = imap_headerinfo($this->conn, $msgNo);
3878 1
		$fullHeader = imap_fetchheader($this->conn, $msgNo); // raw headers
3879
3880
		// reset inline images cache
3881 1
		$this->inlineImages = array();
3882
3883
		// handle messages deleted on server
3884 1
		if(empty($header)) {
3885 1
			if(!isset($this->email) || empty($this->email)) {
3886 1
				$this->email = new Email();
3887
			} // if
3888 1
			return "";
3889
		} else {
3890
			$dupeCheckResult = $this->importDupeCheck($header->message_id, $header, $fullHeader);
3891
			if (!$dupeCheckResult && !empty($this->compoundMessageId)) {
3892
				// we have a duplicate email
3893
				$query = 'SELECT id FROM emails WHERE emails.message_id = \''.$this->compoundMessageId.'\' and emails.deleted = 0';
3894
				$r = $this->db->query($query, true);
3895
				$a = $this->db->fetchByAssoc($r);
3896
3897
				$this->email = new Email();
3898
				$this->email->id = $a['id'];
3899
				return $a['id'];
3900
			} // if
3901
			return "";
3902
		} // else
3903
	} // fn
3904
3905
3906
	/**
3907
	 * shiny new importOneEmail() method
3908
	 * @param int msgNo
3909
	 * @param bool forDisplay
3910
	 * @param clean_email boolean, default true,
3911
	 */
3912 1
	function importOneEmail($msgNo, $uid, $forDisplay=false, $clean_email=true) {
3913 1
		$GLOBALS['log']->debug("InboundEmail processing 1 email {$msgNo}-----------------------------------------------------------------------------------------");
3914 1
		global $timedate;
3915 1
		global $app_strings;
3916 1
		global $app_list_strings;
3917 1
		global $sugar_config;
3918 1
		global $current_user;
3919
3920
        // Bug # 45477
3921
        // So, on older versions of PHP (PHP VERSION < 5.3),
3922
        // calling imap_headerinfo and imap_fetchheader can cause a buffer overflow for exteremly large headers,
3923
        // This leads to the remaining messages not being read because Sugar crashes everytime it tries to read the headers.
3924
        // The workaround is to mark a message as read before making trying to read the header of the msg in question
3925
        // This forces this message not be read again, and we can continue processing remaining msgs.
3926
3927
        // UNCOMMENT THIS IF YOU HAVE THIS PROBLEM!  See notes on Bug # 45477
3928
        // $this->markEmails($uid, "read");
3929
3930 1
		$header = imap_headerinfo($this->conn, $msgNo);
3931 1
		$fullHeader = imap_fetchheader($this->conn, $msgNo); // raw headers
3932
3933
		// reset inline images cache
3934 1
		$this->inlineImages = array();
3935
3936
		// handle messages deleted on server
3937 1
		if(empty($header)) {
3938 1
			if(!isset($this->email) || empty($this->email)) {
3939 1
				$this->email = new Email();
3940
			}
3941
3942 1
			$q = "";
3943 1
			if ($this->isPop3Protocol()) {
3944
				$this->email->name = $app_strings['LBL_EMAIL_ERROR_MESSAGE_DELETED'];
3945
				$q = "DELETE FROM email_cache WHERE message_id = '{$uid}' AND ie_id = '{$this->id}' AND mbox = '{$this->mailbox}'";
3946
			} else {
3947 1
				$this->email->name = $app_strings['LBL_EMAIL_ERROR_IMAP_MESSAGE_DELETED'];
3948 1
				$q = "DELETE FROM email_cache WHERE imap_uid = {$uid} AND ie_id = '{$this->id}' AND mbox = '{$this->mailbox}'";
3949
			} // else
3950
			// delete local cache
3951 1
			$r = $this->db->query($q);
3952
3953 1
			$this->email->date_sent = $timedate->nowDb();
3954 1
			return false;
3955
			//return "Message deleted from server.";
3956
		}
3957
3958
		///////////////////////////////////////////////////////////////////////
3959
		////	DUPLICATE CHECK
3960
		$dupeCheckResult = $this->importDupeCheck($header->message_id, $header, $fullHeader);
3961
		if($forDisplay || $dupeCheckResult) {
3962
			$GLOBALS['log']->debug('*********** NO duplicate found, continuing with processing.');
3963
3964
			$structure = imap_fetchstructure($this->conn, $msgNo); // map of email
3965
3966
			///////////////////////////////////////////////////////////////////
3967
			////	CREATE SEED EMAIL OBJECT
3968
			$email = new Email();
3969
			$email->isDuplicate = ($dupeCheckResult) ? false : true;
3970
			$email->mailbox_id = $this->id;
3971
			$message = array();
3972
			$email->id = create_guid();
3973
			$email->new_with_id = true; //forcing a GUID here to prevent double saves.
3974
			////	END CREATE SEED EMAIL
3975
			///////////////////////////////////////////////////////////////////
3976
3977
			///////////////////////////////////////////////////////////////////
3978
			////	PREP SYSTEM USER
3979
			if(empty($current_user)) {
3980
				// I-E runs as admin, get admin prefs
3981
3982
				$current_user = new User();
3983
				$current_user->getSystemUser();
3984
			}
3985
			$tPref = $current_user->getUserDateTimePreferences();
3986
			////	END USER PREP
3987
			///////////////////////////////////////////////////////////////////
3988
            if(!empty($header->date)) {
3989
			    $unixHeaderDate = $timedate->fromString($header->date);
3990
            }
3991
			///////////////////////////////////////////////////////////////////
3992
			////	HANDLE EMAIL ATTACHEMENTS OR HTML TEXT
3993
			////	Inline images require that I-E handle attachments before body text
3994
			// parts defines attachments - be mindful of .html being interpreted as an attachment
3995
			if($structure->type == 1 && !empty($structure->parts)) {
3996
				$GLOBALS['log']->debug('InboundEmail found multipart email - saving attachments if found.');
3997
				$this->saveAttachments($msgNo, $structure->parts, $email->id, 0, $forDisplay);
3998
			} elseif($structure->type == 0) {
3999
				$uuemail = ($this->isUuencode($email->description)) ? true : false;
4000
				/*
4001
				 * UUEncoded attachments - legacy, but still have to deal with it
4002
				 * format:
4003
				 * begin 777 filename.txt
4004
				 * UUENCODE
4005
				 *
4006
				 * end
4007
				 */
4008
				// set body to the filtered one
4009
				if($uuemail) {
4010
					$email->description = $this->handleUUEncodedEmailBody($email->description, $email->id);
4011
					$email->retrieve($email->id);
4012
			   		$email->save();
4013
		   		}
4014
			} else {
4015
				if($this->port != 110) {
4016
					$GLOBALS['log']->debug('InboundEmail found a multi-part email (id:'.$msgNo.') with no child parts to parse.');
4017
				}
4018
			}
4019
			////	END HANDLE EMAIL ATTACHEMENTS OR HTML TEXT
4020
			///////////////////////////////////////////////////////////////////
4021
4022
			///////////////////////////////////////////////////////////////////
4023
			////	ASSIGN APPROPRIATE ATTRIBUTES TO NEW EMAIL OBJECT
4024
			// handle UTF-8/charset encoding in the ***headers***
4025
			global $db;
4026
			$email->name			= $this->handleMimeHeaderDecode($header->subject);
4027
			$email->type = 'inbound';
4028
			if(!empty($unixHeaderDate)) {
4029
			    $email->date_sent = $timedate->asUser($unixHeaderDate);
4030
			    list($email->date_start, $email->time_start) = $timedate->split_date_time($email->date_sent);
4031
			} else {
4032
			    $email->date_start = $email->time_start = $email->date_sent = "";
4033
			}
4034
			$email->status = 'unread'; // this is used in Contacts' Emails SubPanel
4035
			if(!empty($header->toaddress)) {
4036
				$email->to_name	 = $this->handleMimeHeaderDecode($header->toaddress);
4037
				$email->to_addrs_names = $email->to_name;
4038
			}
4039
			if(!empty($header->to)) {
4040
				$email->to_addrs	= $this->convertImapToSugarEmailAddress($header->to);
4041
			}
4042
			$email->from_name		= $this->handleMimeHeaderDecode($header->fromaddress);
4043
			$email->from_addr_name = $email->from_name;
4044
			$email->from_addr		= $this->convertImapToSugarEmailAddress($header->from);
4045
			if(!empty($header->cc)) {
4046
				$email->cc_addrs	= $this->convertImapToSugarEmailAddress($header->cc);
4047
			}
4048
			if(!empty($header->ccaddress)) {
4049
				$email->cc_addrs_names	 = $this->handleMimeHeaderDecode($header->ccaddress);
4050
			} // if
4051
			$email->reply_to_name   = $this->handleMimeHeaderDecode($header->reply_toaddress);
4052
			$email->reply_to_email  = $this->convertImapToSugarEmailAddress($header->reply_to);
4053
			if (!empty($email->reply_to_email)) {
4054
				$email->reply_to_addr   = $email->reply_to_name;
4055
			}
4056
			$email->intent			= $this->mailbox_type;
4057
4058
			$email->message_id		= $this->compoundMessageId; // filled by importDupeCheck();
4059
4060
			$oldPrefix = $this->imagePrefix;
4061
			if(!$forDisplay) {
4062
				// Store CIDs in imported messages, convert on display
4063
				$this->imagePrefix = "cid:";
4064
			}
4065
			// handle multi-part email bodies
4066
			$email->description_html= $this->getMessageText($msgNo, 'HTML', $structure, $fullHeader,$clean_email); // runs through handleTranserEncoding() already
4067
			$email->description	= $this->getMessageText($msgNo, 'PLAIN', $structure, $fullHeader,$clean_email); // runs through handleTranserEncoding() already
4068
			$this->imagePrefix = $oldPrefix;
4069
4070
			// empty() check for body content
4071
			if(empty($email->description)) {
4072
				$GLOBALS['log']->debug('InboundEmail Message (id:'.$email->message_id.') has no body');
4073
			}
4074
4075
			// assign_to group
4076
			if (!empty($_REQUEST['user_id'])) {
4077
				$email->assigned_user_id = $_REQUEST['user_id'];
4078
			} else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
4079
				// Samir Gandhi : Commented out this code as its not needed
4080
				//$email->assigned_user_id = $this->group_id;
4081
			}
4082
4083
	        //Assign Parent Values if set
4084
	        if (!empty($_REQUEST['parent_id']) && !empty($_REQUEST['parent_type'])) {
4085
                $email->parent_id = $_REQUEST['parent_id'];
4086
                $email->parent_type = $_REQUEST['parent_type'];
4087
4088
                $mod = strtolower($email->parent_type);
4089
                $rel = array_key_exists($mod, $email->field_defs) ? $mod : $mod . "_activities_emails"; //Custom modules rel name
4090
4091
                if(! $email->load_relationship($rel) )
4092
                    return FALSE;
4093
                $email->$rel->add($email->parent_id);
4094
	        }
4095
4096
			// override $forDisplay w/user pref
4097
			if($forDisplay) {
4098
				if($this->isAutoImport()) {
4099
					$forDisplay = false; // triggers save of imported email
4100
				}
4101
			}
4102
4103
			if(!$forDisplay) {
4104
				$email->save();
4105
4106
				$email->new_with_id = false; // to allow future saves by UPDATE, instead of INSERT
4107
				////	ASSIGN APPROPRIATE ATTRIBUTES TO NEW EMAIL OBJECT
4108
				///////////////////////////////////////////////////////////////////
4109
4110
				///////////////////////////////////////////////////////////////////
4111
				////	LINK APPROPRIATE BEANS TO NEWLY SAVED EMAIL
4112
				//$contactAddr = $this->handleLinking($email);
4113
				////	END LINK APPROPRIATE BEANS TO NEWLY SAVED EMAIL
4114
				///////////////////////////////////////////////////////////////////
4115
4116
				///////////////////////////////////////////////////////////////////
4117
				////	MAILBOX TYPE HANDLING
4118
				$this->handleMailboxType($email, $header);
4119
				////	END MAILBOX TYPE HANDLING
4120
				///////////////////////////////////////////////////////////////////
4121
4122
				///////////////////////////////////////////////////////////////////
4123
				////	SEND AUTORESPONSE
4124
				if(!empty($email->reply_to_email)) {
4125
					$contactAddr = $email->reply_to_email;
4126
				} else {
4127
					$contactAddr = $email->from_addr;
4128
				}
4129
				if (!$this->isMailBoxTypeCreateCase()) {
4130
					$this->handleAutoresponse($email, $contactAddr);
4131
				}
4132
				////	END SEND AUTORESPONSE
4133
				///////////////////////////////////////////////////////////////////
4134
				////	END IMPORT ONE EMAIL
4135
				///////////////////////////////////////////////////////////////////
4136
			}
4137
		} else {
4138
			// only log if not POP3; pop3 iterates through ALL mail
4139
			if($this->protocol != 'pop3') {
4140
				$GLOBALS['log']->info("InboundEmail found a duplicate email: ".$header->message_id);
4141
				//echo "This email has already been imported";
4142
			}
4143
			return false;
4144
		}
4145
		////	END DUPLICATE CHECK
4146
		///////////////////////////////////////////////////////////////////////
4147
4148
		///////////////////////////////////////////////////////////////////////
4149
		////	DEAL WITH THE MAILBOX
4150
		if(!$forDisplay) {
4151
			$r = imap_setflag_full($this->conn, $msgNo, '\\SEEN');
4152
4153
			// if delete_seen, mark msg as deleted
4154
			if($this->delete_seen == 1  && !$forDisplay) {
4155
				$GLOBALS['log']->info("INBOUNDEMAIL: delete_seen == 1 - deleting email");
4156
				imap_setflag_full($this->conn, $msgNo, '\\DELETED');
4157
			}
4158
		} else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
4159
			// for display - don't touch server files?
4160
			//imap_setflag_full($this->conn, $msgNo, '\\UNSEEN');
4161
		}
4162
4163
		$GLOBALS['log']->debug('********************************* InboundEmail finished import of 1 email: '.$email->name);
4164
		////	END DEAL WITH THE MAILBOX
4165
		///////////////////////////////////////////////////////////////////////
4166
4167
		///////////////////////////////////////////////////////////////////////
4168
		////	TO SUPPORT EMAIL 2.0
4169
		$this->email = $email;
4170
4171
		if(empty($this->email->et)) {
4172
			$this->email->email2init();
4173
		}
4174
4175
		return true;
4176
	}
4177
4178
	/**
4179
	 * figures out if a plain text email body has UUEncoded attachments
4180
	 * @param string string The email body
4181
	 * @return bool True if UUEncode is detected.
4182
	 */
4183 1
	function isUuencode($string) {
4184 1
		$rx = "begin [0-9]{3} .*";
4185
4186 1
		$exBody = explode("\r", $string);
4187 1
		foreach($exBody as $line) {
4188 1
			if(preg_match("/begin [0-9]{3} .*/i", $line)) {
4189 1
				return true;
4190
			}
4191
		}
4192
4193 1
		return false;
4194
	}
4195
4196
	/**
4197
	 * handles UU Encoded emails - a legacy from pre-RFC 822 which must still be supported (?)
4198
	 * @param string raw The raw email body
4199
	 * @param string id Parent email ID
4200
	 * @return string The filtered email body, stripped of attachments
4201
	 */
4202 1
	function handleUUEncodedEmailBody($raw, $id) {
4203 1
		global $locale;
4204
4205 1
		$emailBody = '';
4206 1
		$attachmentBody = '';
4207 1
		$inAttachment = false;
4208
4209 1
		$exRaw = explode("\n", $raw);
4210
4211 1
		foreach($exRaw as $k => $line) {
4212 1
			$line = trim($line);
4213
4214 1
			if(preg_match("/begin [0-9]{3} .*/i", $line, $m)) {
4215
				$inAttachment = true;
4216
				$fileName = $this->handleEncodedFilename(substr($m[0], 10, strlen($m[0])));
4217
4218
				$attachmentBody = ''; // reset for next part of loop;
4219
				continue;
4220
			}
4221
4222
			// handle "end"
4223 1
			if(strpos($line, "end") === 0) {
4224
				if(!empty($fileName) && !empty($attachmentBody)) {
4225
					$this->handleUUDecode($id, $fileName, trim($attachmentBody));
4226
					$attachmentBody = ''; // reset for next part of loop;
4227
				}
4228
			}
4229
4230 1
			if($inAttachment === false) {
4231 1
				$emailBody .= "\n".$line;
4232
			} else {
4233 1
				$attachmentBody .= "\n".$line;
4234
			}
4235
		}
4236
4237
		/* 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 */
4238 1
		$emailBody = $locale->translateCharset($emailBody, $locale->getExportCharset(), 'UTF-8');
4239 1
		return $emailBody;
4240
	}
4241
4242
	/**
4243
	 * wrapper for UUDecode
4244
	 * @param string id Id of the email
4245
	 * @param string UUEncode Encode US-ASCII
4246
	 */
4247
	function handleUUDecode($id, $fileName, $UUEncode) {
4248
		global $sugar_config;
4249
		/* include PHP_Compat library; it auto-feels for PHP5's compiled convert_uuencode() function */
4250
		require_once('include/PHP_Compat/convert_uudecode.php');
4251
4252
		$attach = new Note();
4253
		$attach->parent_id = $id;
4254
		$attach->parent_type = 'Emails';
4255
4256
		$fname = $this->handleEncodedFilename($fileName);
4257
4258
		if(!empty($fname)) {//assign name to attachment
4259
			$attach->name = $fname;
4260
		} else {//if name is empty, default to filename
4261
			$attach->name = urlencode($fileName);
4262
		}
4263
4264
		$attach->filename = urlencode($attach->name);
4265
4266
		//get position of last "." in file name
4267
		$file_ext_beg = strrpos($attach->filename,".");
4268
		$file_ext = "";
4269
		//get file extension
4270
		if($file_ext_beg >0) {
4271
			$file_ext = substr($attach->filename, $file_ext_beg+1);
4272
		}
4273
		//check to see if this is a file with extension located in "badext"
4274
		foreach($sugar_config['upload_badext'] as $badExt) {
4275
			if(strtolower($file_ext) == strtolower($badExt)) {
4276
				//if found, then append with .txt and break out of lookup
4277
				$attach->name = $attach->name . ".txt";
4278
				$attach->file_mime_type = 'text/';
4279
				$attach->filename = $attach->filename . ".txt";
4280
				break; // no need to look for more
4281
			}
4282
		}
4283
		$attach->save();
4284
4285
		$bin = convert_uudecode($UUEncode);
4286
		$filename = "upload://{$attach->id}";
4287
		if(file_put_contents($filename, $bin)) {
4288
    		$GLOBALS['log']->debug('InboundEmail saved attachment file: '.$filename);
4289
		} else {
4290
    		$GLOBALS['log']->debug('InboundEmail could not create attachment file: '.$filename);
4291
		}
4292
	}
4293
4294
	/**
4295
	 * returns true if the email's domain is NOT in the filter domain string
4296
	 *
4297
	 * @param object email Email object in question
4298
	 * @return bool true if not filtered, false if filtered
4299
	 */
4300 2
	function checkFilterDomain($email) {
4301 2
		$filterDomain = $this->get_stored_options('filter_domain');
4302 2
		if(!isset($filterDomain) || empty($filterDomain)) {
4303 2
			return true; // nothing set for this
4304
		} else {
4305
			$replyTo = strtolower($email->reply_to_email);
4306
			$from = strtolower($email->from_addr);
4307
			$filterDomain = '@'.strtolower($filterDomain);
4308
			if(strpos($replyTo, $filterDomain) !== false) {
4309
				$GLOBALS['log']->debug('Autoreply cancelled - [reply to] address domain matches filter domain.');
4310
				return false;
4311
			} elseif(strpos($from, $filterDomain) !== false) {
4312
				$GLOBALS['log']->debug('Autoreply cancelled - [from] address domain matches filter domain.');
4313
				return false;
4314
			} else {
4315
				return true; // no match
4316
			}
4317
		}
4318
	}
4319
4320
	/**
4321
	 * returns true if subject is NOT "out of the office" type
4322
	 *
4323
	 * @param string subject Subject line of email in question
4324
	 * @return bool returns false if OOTO found
4325
	 */
4326 2
	function checkOutOfOffice($subject) {
4327 2
		$ooto = array("Out of the Office", "Out of Office");
4328
4329 2
		foreach($ooto as $str) {
4330 2
			if(preg_match('/'.$str.'/i', $subject)) {
4331 1
				$GLOBALS['log']->debug('Autoreply cancelled - found "Out of Office" type of subject.');
4332 2
				return false;
4333
			}
4334
		}
4335 2
		return true; // no matches to ooto strings
4336
	}
4337
4338
4339
	/**
4340
	 * sets a timestamp for an autoreply to a single email addy
4341
	 *
4342
	 * @param string addr Address of auto-replied target
4343
	 */
4344 2
	function setAutoreplyStatus($addr) {
4345 2
	    $timedate = TimeDate::getInstance();
4346 2
		$this->db->query(	'INSERT INTO inbound_email_autoreply (id, deleted, date_entered, date_modified, autoreplied_to, ie_id) VALUES (
4347 2
							\''.create_guid().'\',
4348
							0,
4349 2
							\''.$timedate->nowDb().'\',
4350 2
							\''.$timedate->nowDb().'\',
4351 2
							\''.$addr.'\',
4352 2
		                    \''.$this->id.'\') ', true);
4353 2
	}
4354
4355
4356
	/**
4357
	 * returns true if recipient has NOT received 10 auto-replies in 24 hours
4358
	 *
4359
	 * @param string from target address for auto-reply
4360
	 * @return bool true if target is valid/under limit
4361
	 */
4362 2
	function getAutoreplyStatus($from) {
4363 2
		global $sugar_config;
4364 2
        $timedate = TimeDate::getInstance();
4365
4366 2
		$q_clean = 'UPDATE inbound_email_autoreply SET deleted = 1 WHERE date_entered < \''.$timedate->getNow()->modify("-24 hours")->asDb().'\'';
4367 2
		$r_clean = $this->db->query($q_clean, true);
4368
4369 2
		$q = 'SELECT count(*) AS c FROM inbound_email_autoreply WHERE deleted = 0 AND autoreplied_to = \''.$from.'\' AND ie_id = \''.$this->id.'\'';
4370 2
		$r = $this->db->query($q, true);
4371 2
		$a = $this->db->fetchByAssoc($r);
4372
4373 2
		$email_num_autoreplies_24_hours = $this->get_stored_options('email_num_autoreplies_24_hours');
4374 2
		$maxReplies = (isset($email_num_autoreplies_24_hours)) ? $email_num_autoreplies_24_hours : $this->maxEmailNumAutoreplies24Hours;
4375
4376 2
		if($a['c'] >= $maxReplies) {
4377
			$GLOBALS['log']->debug('Autoreply cancelled - more than ' . $maxReplies . ' replies sent in 24 hours.');
4378
			return false;
4379
		} else {
4380 2
			return true;
4381
		}
4382
	}
4383
4384
	/**
4385
	 * returns exactly 1 id match. if more than one, than returns false
4386
	 * @param	$emailName		the subject of the email to match
4387
	 * @param	$tableName		the table of the matching bean type
4388
	 */
4389
	function getSingularRelatedId($emailName, $tableName) {
4390
		$repStrings = array('RE:','Re:','re:');
4391
		$preppedName = str_replace($repStrings,'',trim($emailName));
4392
4393
		//TODO add team security to this query
4394
		$q = 'SELECT count(id) AS c FROM '.$tableName.' WHERE deleted = 0 AND name LIKE \'%'.$preppedName.'%\'';
4395
		$r = $this->db->query($q, true);
4396
		$a = $this->db->fetchByAssoc($r);
4397
4398
		if($a['c'] == 0) {
4399
			$q = 'SELECT id FROM '.$tableName.' WHERE deleted = 0 AND name LIKE \'%'.$preppedName.'%\'';
4400
			$r = $this->db->query($q, true);
4401
			$a = $this->db->fetchByAssoc($r);
4402
			return $a['id'];
4403
		} else {
4404
			return false;
4405
		}
4406
	}
4407
4408
	/**
4409
	 * saves InboundEmail parse macros to config.php
4410
	 * @param string type Bean to link
4411
	 * @param string macro The new macro
4412
	 */
4413 1
	function saveInboundEmailSystemSettings($type, $macro) {
4414 1
		global $sugar_config;
4415
4416
		// inbound_email_case_subject_macro
4417 1
		$var = "inbound_email_".strtolower($type)."_subject_macro";
4418 1
		$sugar_config[$var] = $macro;
4419
4420 1
		ksort($sugar_config);
4421
4422
		$sugar_config_string = "<?php\n" .
4423 1
			'// created: ' . date('Y-m-d H:i:s') . "\n" .
4424 1
			'$sugar_config = ' .
4425 1
			var_export($sugar_config, true) .
4426 1
			";\n?>\n";
4427
4428 1
		write_array_to_file("sugar_config", $sugar_config, "config.php");
4429 1
	}
4430
4431
	/**
4432
	 * returns the HTML for InboundEmail system settings
4433
	 * @return string HTML
4434
	 */
4435 1
	function getSystemSettingsForm() {
4436 1
		global $sugar_config;
4437 1
		global $mod_strings;
4438 1
		global $app_strings;
4439 1
		global $app_list_strings;
4440
4441
		////	Case Macro
4442 1
		$c = new aCase();
4443
4444 1
		$macro = $c->getEmailSubjectMacro();
4445
4446
		$ret =<<<eoq
4447
			<form action="index.php" method="post" name="Macro" id="form">
4448
						<input type="hidden" name="module" value="InboundEmail">
4449
						<input type="hidden" name="action" value="ListView">
4450
						<input type="hidden" name="save" value="true">
4451
4452
			<table width="100%" cellpadding="0" cellspacing="0" border="0">
4453
				<tr>
4454
					<td>
4455 1
						<input 	title="{$app_strings['LBL_SAVE_BUTTON_TITLE']}"
4456 1
								accessKey="{$app_strings['LBL_SAVE_BUTTON_KEY']}"
4457
								class="button"
4458
								onclick="this.form.return_module.value='InboundEmail'; this.form.return_action.value='ListView';"
4459 1
								type="submit" name="Edit" value="  {$app_strings['LBL_SAVE_BUTTON_LABEL']}  ">
4460
					</td>
4461
				</tr>
4462
			</table>
4463
4464
			<table width="100%" border="0" cellspacing="0" cellpadding="0" class="detail view">
4465
				<tr>
4466
					<td valign="top" width='10%' NOWRAP scope="row">
4467
						<slot>
4468 1
							<b>{$mod_strings['LBL_CASE_MACRO']}:</b>
4469
						</slot>
4470
					</td>
4471
					<td valign="top" width='20%'>
4472
						<slot>
4473 1
							<input name="inbound_email_case_macro" type="text" value="{$macro}">
4474
						</slot>
4475
					</td>
4476
					<td valign="top" width='70%'>
4477
						<slot>
4478 1
							{$mod_strings['LBL_CASE_MACRO_DESC']}
4479
							<br />
4480 1
							<i>{$mod_strings['LBL_CASE_MACRO_DESC2']}</i>
4481
						</slot>
4482
					</td>
4483
				</tr>
4484
			</table>
4485 1
			</form>
4486
eoq;
4487 1
		return $ret;
4488
	}
4489
4490
    /**
4491
     * For mailboxes of type "Support" parse for '[CASE:%1]'
4492
     *
4493
     * @param string $emailName The subject line of the email
4494
     * @param aCase  $aCase     A Case object
4495
     *
4496
     * @return string|boolean   Case ID or FALSE if not found
4497
     */
4498 4
	function getCaseIdFromCaseNumber($emailName, $aCase) {
4499
		//$emailSubjectMacro
4500 4
		$exMacro = explode('%1', $aCase->getEmailSubjectMacro());
4501 4
		$open = $exMacro[0];
4502 4
		$close = $exMacro[1];
4503
4504 4
		if($sub = stristr($emailName, $open)) { // eliminate everything up to the beginning of the macro and return the rest
4505
			// $sub is [CASE:XX] xxxxxxxxxxxxxxxxxxxxxx
4506
			$sub2 = str_replace($open, '', $sub);
4507
			// $sub2 is XX] xxxxxxxxxxxxxx
4508
			$sub3 = substr($sub2, 0, strpos($sub2, $close));
4509
4510
            // case number is supposed to be numeric
4511
            if (ctype_digit($sub3)) {
4512
                // filter out deleted records in order to create a new case
4513
                // if email is related to deleted one (bug #49840)
4514
                $query = 'SELECT id FROM cases WHERE case_number = '
4515
                    . $this->db->quoted($sub3)
4516
                    . ' and deleted = 0';
4517
                $r = $this->db->query($query, true);
4518
                $a = $this->db->fetchByAssoc($r);
4519
                if (!empty($a['id'])) {
4520
                    return $a['id'];
4521
                }
4522
            }
4523
        }
4524
4525 4
        return false;
4526
    }
4527
4528 13
	function get_stored_options($option_name,$default_value=null,$stored_options=null) {
4529 13
		if (empty($stored_options)) {
4530 13
			$stored_options=$this->stored_options;
4531
		}
4532 13
		return self::get_stored_options_static($option_name, $default_value, $stored_options);
4533
	}
4534
4535 13
	public static function get_stored_options_static($option_name,$default_value=null,$stored_options=null) {
4536 13
		if(!empty($stored_options)) {
4537
			$storedOptions = unserialize(base64_decode($stored_options));
4538
			if (isset($storedOptions[$option_name])) {
4539
				$default_value=$storedOptions[$option_name];
4540
			}
4541
		}
4542 13
		return $default_value;
4543
	}
4544
4545
4546
	/**
4547
	 * This function returns a contact or user ID if a matching email is found
4548
	 * @param	$email		the email address to match
4549
	 * @param	$table		which table to query
4550
	 */
4551 2
	function getRelatedId($email, $module) {
4552 2
		$email = trim(strtoupper($email));
4553 2
		if(strpos($email, ',') !== false) {
4554 1
			$emailsArray = explode(',', $email);
4555 1
			$emailAddressString = "";
4556 1
			foreach($emailsArray as $emailAddress) {
4557 1
				if (!empty($emailAddressString)) {
4558 1
					$emailAddressString .= ",";
4559
				}
4560 1
				$emailAddressString .= $this->db->quoted(trim($emailAddress));
4561
			} // foreach
4562 1
			$email = $emailAddressString;
4563
		} else {
4564 2
			$email = $this->db->quoted($email);
4565
		} // else
4566 2
		$module = $this->db->quoted(ucfirst($module));
4567
4568
		$q = "SELECT bean_id FROM email_addr_bean_rel eabr
4569
				JOIN email_addresses ea ON (eabr.email_address_id = ea.id)
4570 2
				WHERE bean_module = $module AND ea.email_address_caps in ( {$email} ) AND eabr.deleted=0";
4571
4572 2
		$r = $this->db->query($q, true);
4573
4574 2
		$retArr = array();
4575 2
		while($a = $this->db->fetchByAssoc($r)) {
4576
			$retArr[] = $a['bean_id'];
4577
		}
4578 2
		if(count($retArr) > 0) {
4579
			return $retArr;
4580
		} else {
4581 2
			return false;
4582
		}
4583
	}
4584
4585
	/**
4586
	 * finds emails tagged "//UNSEEN" on mailserver and "SINCE: [date]" if that
4587
	 * option is set
4588
	 *
4589
	 * @return array Array of messageNumbers (mail server's internal keys)
4590
	 */
4591 1
	function getNewMessageIds() {
4592 1
		$storedOptions = unserialize(base64_decode($this->stored_options));
4593
4594
		//TODO figure out if the since date is UDT
4595 1
		if($storedOptions['only_since']) {// POP3 does not support Unseen flags
4596
			if(!isset($storedOptions['only_since_last']) && !empty($storedOptions['only_since_last'])) {
4597
				$q = 'SELECT last_run FROM schedulers WHERE job = \'function::pollMonitoredInboxes\'';
4598
				$r = $this->db->query($q, true);
4599
				$a = $this->db->fetchByAssoc($r);
4600
4601
				$date = date('r', strtotime($a['last_run']));
4602
			} else {
4603
				$date = $storedOptions['only_since_last'];
4604
			}
4605
			$ret = imap_search($this->conn, 'SINCE "'.$date.'" UNDELETED UNSEEN');
4606
			$check = imap_check($this->conn);
4607
			$storedOptions['only_since_last'] = $check->Date;
4608
			$this->stored_options = base64_encode(serialize($storedOptions));
4609
			$this->save();
4610
		} else {
4611 1
            $ret = imap_search($this->conn, 'UNDELETED UNSEEN');
4612
		}
4613
4614 1
		$GLOBALS['log']->debug('-----> getNewMessageIds() got '.count($ret).' new Messages');
4615 1
		return $ret;
4616
	}
4617
4618
	/**
4619
	 * Constructs the resource connection string that IMAP needs
4620
	 * @param string $service Service string, will generate if not passed
4621
	 * @return string
4622
	 */
4623 17
	function getConnectString($service='', $mbox='', $includeMbox=true) {
4624 17
		$service = empty($service) ? $this->getServiceString() : $service;
4625 17
		$mbox = empty($mbox) ? $this->mailbox : $mbox;
4626
4627 17
		$connectString = '{'.$this->server_url.':'.$this->port.'/service='.$this->protocol.$service.'}';
4628 17
		$connectString .= ($includeMbox) ? $mbox : "";
4629
4630 17
		return $connectString;
4631
	}
4632
4633 1
	function disconnectMailserver() {
4634 1
		if(is_resource($this->conn)) {
4635
			imap_close($this->conn);
4636
		}
4637 1
	}
4638
4639
	/**
4640
	 * Connects to mailserver.  If an existing IMAP resource is available, it
4641
	 * will attempt to reuse the connection, updating the mailbox path.
4642
	 *
4643
	 * @param bool test Flag to test connection
4644
	 * @param bool force Force reconnect
4645
	 * @return string "true" on success, "false" or $errorMessage on failure
4646
	 */
4647 14
	function connectMailserver($test=false, $force=false) {
4648 14
		global $mod_strings;
4649 14
		if(!function_exists("imap_open")) {
4650
			$GLOBALS['log']->debug('------------------------- IMAP libraries NOT available!!!! die()ing thread.----');
4651
			return $mod_strings['LBL_WARN_NO_IMAP'];
4652
		}
4653
4654 14
		imap_errors(); // clearing error stack
4655 14
		error_reporting(0); // turn off notices from IMAP
4656
4657
		// tls::ca::ssl::protocol::novalidate-cert::notls
4658 14
		$useSsl = ($_REQUEST['ssl'] == 'true') ? true : false;
4659 14
		if($test) {
4660 2
			imap_timeout(1, 15); // 60 secs is the default
4661 2
			imap_timeout(2, 15);
4662 2
			imap_timeout(3, 15);
4663
4664 2
			$opts = $this->findOptimumSettings($useSsl);
4665 2
			if(isset($opts['good']) && empty($opts['good'])) {
4666
				return array_pop($opts['err']);
4667
			} else {
4668 2
				$service = $opts['service'];
4669 2
				$service = str_replace('foo','', $service); // foo there to support no-item explodes
4670
			}
4671
		} else {
4672 13
			$service = $this->getServiceString();
4673
		}
4674
4675 14
		$connectString = $this->getConnectString($service, $this->mailbox);
4676
4677
		/*
4678
		 * Try to recycle the current connection to reduce response times
4679
		 */
4680 14
		if(is_resource($this->conn)) {
4681
			if($force) {
4682
				// force disconnect
4683
				imap_close($this->conn);
4684
			}
4685
4686
			if(imap_ping($this->conn)) {
4687
				// we have a live connection
4688
				imap_reopen($this->conn, $connectString, CL_EXPUNGE);
4689
			}
4690
		}
4691
4692
		// final test
4693 14
		if(!is_resource($this->conn) && !$test) {
4694 13
            $this->conn = $this->getImapConnection($connectString, $this->email_user, $this->email_password, CL_EXPUNGE);
4695
		}
4696
4697 14
		if($test) {
4698 2
			if ($opts == false && !is_resource($this->conn)) {
4699 2
                $this->conn = $this->getImapConnection($connectString, $this->email_user, $this->email_password, CL_EXPUNGE);
4700
			}
4701 2
			$errors = '';
4702 2
			$alerts = '';
4703 2
			$successful = false;
4704 2
			if(($errors = imap_last_error()) || ($alerts = imap_alerts())) {
4705 2
				if($errors == 'Mailbox is empty') { // false positive
4706
					$successful = true;
4707
				} else {
4708 2
					$msg .= $errors;
4709 2
					$msg .= '<p>'.$alerts.'<p>';
4710 2
					$msg .= '<p>'.$mod_strings['ERR_TEST_MAILBOX'];
4711
				}
4712
			} else {
4713
				$successful = true;
4714
			}
4715
4716 2
			if($successful) {
4717
				if($this->protocol == 'imap') {
4718
					$msg .= $mod_strings['LBL_TEST_SUCCESSFUL'];
4719
					/*
4720
					$testConnectString = '{'.$this->server_url.':'.$this->port.'/service='.$this->protocol.$service.'}';
4721
					if (!is_resource($this->conn)) {
4722
						$this->conn = imap_open($connectString, $this->email_user, $this->email_password, CL_EXPUNGE);
4723
					}
4724
					$list = imap_getmailboxes($this->conn, $testConnectString, "*");
4725
					if(isset($_REQUEST['personal']) && $_REQUEST['personal'] == 'true') {
4726
						$msg .= $mod_strings['LBL_TEST_SUCCESSFUL'];
4727
					} elseif (is_array($list)) {
4728
						sort($list);
4729
						_ppd($boxes);
4730
4731
						$msg .= '<b>'.$mod_strings['LBL_FOUND_MAILBOXES'].'</b><p>';
4732
						foreach ($list as $key => $val) {
4733
							$mb = imap_utf7_decode(str_replace($testConnectString,'',$val->name));
4734
							$msg .= '<a onClick=\'setMailbox(\"'.$mb.'\"); window.close();\'>';
4735
							$msg .= $mb;
4736
							$msg .= '</a><br>';
4737
						}
4738
					} else {
4739
						$msg .= $errors;
4740
						$msg .= '<p>'.$mod_strings['ERR_MAILBOX_FAIL'].imap_last_error().'</p>';
4741
						$msg .= '<p>'.$mod_strings['ERR_TEST_MAILBOX'].'</p>';
4742
					}
4743
					*/
4744
				} else {
4745
					$msg .= $mod_strings['LBL_POP3_SUCCESS'];
4746
				}
4747
			}
4748
4749 2
			imap_errors(); // collapse error stack
4750 2
			imap_close($this->conn);
4751 2
			return $msg;
4752 13
		} elseif(!is_resource($this->conn)) {
4753 13
            $GLOBALS['log']->info('Couldn\'t connect to mail server id: ' . $this->id);
4754 13
			return "false";
4755
		} else {
4756
            $GLOBALS['log']->info('Connected to mail server id: ' . $this->id);
4757
			return "true";
4758
		}
4759
	}
4760
4761
4762
4763 1
	function checkImap() {
4764 1
		global $mod_strings;
4765
4766 1
		if(!function_exists('imap_open')) {
4767
			echo '
4768
			<table cellpadding="0" cellspacing="0" width="100%" border="0" class="list view">
4769
				<tr height="20">
4770
					<td scope="col" width="25%"  colspan="2"><slot>
4771
						'.$mod_strings['LBL_WARN_IMAP_TITLE'].'
4772
					</slot></td>
4773
				</tr>
4774
				<tr>
4775
					<td scope="row" valign=TOP bgcolor="#fdfdfd" width="20%"><slot>
4776
						'.$mod_strings['LBL_WARN_IMAP'].'
4777
					<td scope="row" valign=TOP class="oddListRowS1" bgcolor="#fdfdfd" width="80%"><slot>
4778
						<span class=error>'.$mod_strings['LBL_WARN_NO_IMAP'].'</span>
4779
					</slot></td>
4780
				</tr>
4781
			</table>
4782
			<br>';
4783
		}
4784 1
	}
4785
4786
    /**
4787
     * Attempt to create an IMAP connection using passed in parameters
4788
     * return either the connection resource or false if unable to connect
4789
     *
4790
     * @param  string  $mailbox  Mailbox to be used to create imap connection
4791
     * @param  string  $username The user name
4792
     * @param  string  $password The password associated with the username
4793
     * @param  integer $options  Bitmask for options parameter to the imap_open function
4794
     *
4795
     * @return resource|boolean  Connection resource on success, FALSE on failure
4796
     */
4797 16
    protected function getImapConnection($mailbox, $username, $password, $options = 0)
4798
    {
4799
        // if php is prior to 5.3.2, then return call without disable parameters as they are not supported yet
4800 16
        if (version_compare(phpversion(), '5.3.2', '<')) {
4801
            return imap_open($mailbox, $username, $password, $options);
4802
        }
4803
4804 16
        $connection = null;
4805 16
        $authenticators = array('', 'GSSAPI', 'NTLM');
4806
4807 16
        while (!$connection && ($authenticator = array_shift($authenticators)) !== null) {
4808 16
            if ($authenticator) {
4809
                $params = array(
4810 16
                    'DISABLE_AUTHENTICATOR' => $authenticator,
4811
                );
4812
            } else {
4813 16
                $params = array();
4814
            }
4815
4816 16
            $connection = imap_open($mailbox, $username, $password, $options, 0, $params);
4817
        }
4818
4819 16
        return $connection;
4820
    }
4821
4822
	/**
4823
	 * retrieves an array of I-E beans based on the group_id
4824
	 * @param	string	$groupId	GUID of the group user or Individual
4825
	 * @return	array	$beans		array of beans
4826
	 * @return 	boolean false if none returned
4827
	 */
4828
	function retrieveByGroupId($groupId) {
4829
		$q = 'SELECT id FROM inbound_email WHERE group_id = \''.$groupId.'\' AND deleted = 0 AND status = \'Active\'';
4830
		$r = $this->db->query($q, true);
4831
4832
		$beans = array();
4833
		while($a = $this->db->fetchByAssoc($r)) {
4834
			$ie = new InboundEmail();
4835
			$ie->retrieve($a['id']);
4836
			$beans[$a['id']] = $ie;
4837
		}
4838
		return $beans;
4839
	}
4840
4841
	/**
4842
	 * Retrieves the current count of personal accounts for the user specified.
4843
	 *
4844
	 * @param unknown_type $user
4845
	 */
4846 1
	function getUserPersonalAccountCount($user = null)
4847
	{
4848 1
	    if($user == null)
4849
	       $user = $GLOBALS['current_user'];
4850
4851 1
	    $query = "SELECT count(*) as c FROM inbound_email WHERE deleted=0 AND is_personal='1' AND group_id='{$user->id}' AND status='Active'";
4852
4853 1
	    $rs = $this->db->query($query);
4854 1
		$row = $this->db->fetchByAssoc($rs);
4855 1
        return $row['c'];
4856
	}
4857
4858
	/**
4859
	 * retrieves an array of I-E beans based on the group folder id
4860
	 * @param	string	$groupFolderId	GUID of the group folder
4861
	 * @return	array	$beans		array of beans
4862
	 * @return 	boolean false if none returned
4863
	 */
4864 1
	function retrieveByGroupFolderId($groupFolderId) {
4865 1
		$q = 'SELECT id FROM inbound_email WHERE groupfolder_id = \''.$groupFolderId.'\' AND deleted = 0 ';
4866 1
		$r = $this->db->query($q, true);
4867
4868 1
		$beans = array();
4869 1
		while($a = $this->db->fetchByAssoc($r)) {
4870
			$ie = new InboundEmail();
4871
			$ie->retrieve($a['id']);
4872
			$beans[] = $ie;
4873
		}
4874 1
		return $beans;
4875
	}
4876
4877
	/**
4878
	 * Retrieves an array of I-E beans that the user has team access to
4879
	 */
4880 1
	function retrieveAllByGroupId($id, $includePersonal=true) {
4881 1
		global $current_user;
4882
4883 1
		$beans = ($includePersonal) ? $this->retrieveByGroupId($id) : array();
4884
4885 1
		$teamJoin = '';
4886
4887
4888
4889
        // bug 50536: groupfolder_id cannot be updated to NULL from sugarbean's nullable check ('type' set to ID in the vardef)
4890
        // hence the awkward or check -- rbacon
4891 1
		$q = "SELECT inbound_email.id FROM inbound_email {$teamJoin} WHERE is_personal = 0 AND (groupfolder_id is null OR groupfolder_id = '') AND mailbox_type not like 'bounce' AND inbound_email.deleted = 0 AND status = 'Active' ";
4892
4893
4894
4895 1
		$r = $this->db->query($q, true);
4896
4897 1
		while($a = $this->db->fetchByAssoc($r)) {
4898
			$found = false;
4899
			foreach($beans as $bean) {
4900
				if($bean->id == $a['id']) {
4901
					$found = true;
4902
				}
4903
			}
4904
4905
			if(!$found) {
4906
				$ie = new InboundEmail();
4907
				$ie->retrieve($a['id']);
4908
				$beans[$a['id']] = $ie;
4909
			}
4910
		}
4911
4912 1
		return $beans;
4913
	}
4914
4915
	/**
4916
	 * Retrieves an array of I-E beans that the user has team access to including group
4917
	 */
4918
	function retrieveAllByGroupIdWithGroupAccounts($id, $includePersonal=true) {
4919
		global $current_user;
4920
4921
		$beans = ($includePersonal) ? $this->retrieveByGroupId($id) : array();
4922
4923
		$teamJoin = '';
4924
4925
4926
4927
4928
4929
4930
		$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 ";
4931
4932
		$r = $this->db->query($q, true);
4933
4934
		while($a = $this->db->fetchByAssoc($r)) {
4935
			$found = false;
4936
			foreach($beans as $bean) {
4937
				if($bean->id == $a['id']) {
4938
					$found = true;
4939
				}
4940
			}
4941
4942
			if(!$found) {
4943
				$ie = new InboundEmail();
4944
				$ie->retrieve($a['id']);
4945
				$beans[$a['id']] = $ie;
4946
			}
4947
		}
4948
4949
		return $beans;
4950
	}
4951
4952
4953
	/**
4954
	 * returns the bean name - overrides SugarBean's
4955
	 */
4956 2
	function get_summary_text() {
4957 2
		return $this->name;
4958
	}
4959
4960
	/**
4961
	 * Override's SugarBean's
4962
	 */
4963 1
	function create_export_query($order_by, $where, $show_deleted = 0) {
4964 1
		return $this->create_new_list_query($order_by, $where,array(), array(), $show_deleted);
4965
	}
4966
4967
	/**
4968
	 * Override's SugarBean's
4969
	 */
4970
4971
	/**
4972
	 * Override's SugarBean's
4973
	 */
4974 1
	function get_list_view_data(){
4975 1
		global $mod_strings;
4976 1
		global $app_list_strings;
4977 1
		$temp_array = $this->get_list_view_array();
4978 1
		$temp_array['MAILBOX_TYPE_NAME']= $app_list_strings['dom_mailbox_type'][$this->mailbox_type];
4979
		//cma, fix bug 21670.
4980 1
        $temp_array['GLOBAL_PERSONAL_STRING']= ($this->is_personal ? $mod_strings['LBL_IS_PERSONAL'] : $mod_strings['LBL_IS_GROUP']);
4981 1
        $temp_array['STATUS'] = ($this->status == 'Active') ? $mod_strings['LBL_STATUS_ACTIVE'] : $mod_strings['LBL_STATUS_INACTIVE'];
4982 1
		return $temp_array;
4983
	}
4984
4985
	/**
4986
	 * Override's SugarBean's
4987
	 */
4988 1
	function fill_in_additional_list_fields() {
4989 1
		$this->fill_in_additional_detail_fields();
4990 1
	}
4991
4992
	/**
4993
	 * Override's SugarBean's
4994
	 */
4995 3
	function fill_in_additional_detail_fields() {
4996 3
		if(!empty($this->service)) {
4997 3
			$exServ = explode('::', $this->service);
4998 3
			$this->tls		= $exServ[0];
4999 3
			if ( isset($exServ[1]) )
5000 3
			    $this->ca		= $exServ[1];
5001 3
			if ( isset($exServ[2]) )
5002 3
			    $this->ssl		= $exServ[2];
5003 3
			if ( isset($exServ[3]) )
5004 3
			    $this->protocol	= $exServ[3];
5005
		}
5006 3
	}
5007
5008
5009
5010
5011
5012
5013
5014
5015
5016
5017
5018
5019
5020
5021
	///////////////////////////////////////////////////////////////////////////
5022
	////	IN SUPPORT OF EMAIL 2.0
5023
	/**
5024
	 * Checks for $user's autoImport setting and returns the current value
5025
	 * @param object $user User in focus, defaults to $current_user
5026
	 * @return bool
5027
	 */
5028 1
	function isAutoImport($user=null) {
5029 1
		if(!empty($this->autoImport)) {
5030
			return $this->autoImport;
5031
		}
5032
5033 1
		global $current_user;
5034 1
		if(empty($user)) $user = $current_user;
5035
5036 1
		$emailSettings = $current_user->getPreference('emailSettings', 'Emails');
5037 1
		$emailSettings = is_string($emailSettings) ? unserialize($emailSettings) : $emailSettings;
5038
5039 1
		$this->autoImport = (isset($emailSettings['autoImport']) && !empty($emailSettings['autoImport'])) ? true : false;
5040 1
		return $this->autoImport;
5041
	}
5042
5043
	/**
5044
	 * Clears out cache files for a user
5045
	 */
5046 1
	function cleanOutCache() {
5047 1
		$GLOBALS['log']->debug("INBOUNDEMAIL: at cleanOutCache()");
5048 1
		$this->deleteCache();
5049 1
	}
5050
5051
	/**
5052
	 * moves emails from folder to folder
5053
	 * @param string $fromIe I-E id
5054
	 * @param string $fromFolder IMAP path to folder in which the email lives
5055
	 * @param string $toIe I-E id
5056
	 * @param string $toFolder
5057
	 * @param string $uids UIDs of emails to move, either Sugar GUIDS or IMAP
5058
	 * UIDs
5059
	 */
5060 1
	function copyEmails($fromIe, $fromFolder, $toIe, $toFolder, $uids) {
5061 1
		$this->moveEmails($fromIe, $fromFolder, $toIe, $toFolder, $uids, true);
5062 1
	}
5063
5064
	/**
5065
	 * moves emails from folder to folder
5066
	 * @param string $fromIe I-E id
5067
	 * @param string $fromFolder IMAP path to folder in which the email lives
5068
	 * @param string $toIe I-E id
5069
	 * @param string $toFolder
5070
	 * @param string $uids UIDs of emails to move, either Sugar GUIDS or IMAP
5071
	 * UIDs
5072
	 * @param bool $copy Default false
5073
	 * @return bool True on successful execution
5074
	 */
5075 2
	function moveEmails($fromIe, $fromFolder, $toIe, $toFolder, $uids, $copy=false) {
5076 2
		global $app_strings;
5077 2
		global $current_user;
5078
5079
5080
		// same I-E server
5081 2
		if($fromIe == $toIe) {
5082 2
			$GLOBALS['log']->debug("********* SUGARFOLDER - moveEmails() moving email from I-E to I-E");
5083
			//$exDestFolder = explode("::", $toFolder);
5084
			//preserve $this->mailbox
5085 2
	        if (isset($this->mailbox)) {
5086
	            $oldMailbox = $this->mailbox;
5087
	        }
5088
5089
5090 2
			$this->retrieve($fromIe);
5091 2
		    $this->mailbox = $fromFolder;
5092 2
			$this->connectMailserver();
5093 2
			$exUids = explode('::;::', $uids);
5094 2
			$uids = implode(",", $exUids);
5095
			// imap_mail_move accepts comma-delimited lists of UIDs
5096 2
			if($copy) {
5097 1
				if(imap_mail_copy($this->conn, $uids, $toFolder, CP_UID)) {
5098
					$this->mailbox = $toFolder;
5099
					$this->connectMailserver();
5100
					$newOverviews = imap_fetch_overview($this->conn, $uids, FT_UID);
5101
					$this->updateOverviewCacheFile($newOverviews, 'append');
5102
				    if (isset($oldMailbox)) {
5103
                        $this->mailbox = $oldMailbox;
5104
                    }
5105
					return true;
5106
				} else {
5107 1
					$GLOBALS['log']->debug("INBOUNDEMAIL: could not imap_mail_copy() [ {$uids} ] to folder [ {$toFolder} ] from folder [ {$fromFolder} ]");
5108
				}
5109
			} else {
5110 1
				if(imap_mail_move($this->conn, $uids, $toFolder, CP_UID)) {
5111
					$GLOBALS['log']->info("INBOUNDEMAIL: imap_mail_move() [ {$uids} ] to folder [ {$toFolder} ] from folder [ {$fromFolder} ]");
5112
					imap_expunge($this->conn); // hard deletes moved messages
5113
5114
					// update cache on fromFolder
5115
					$newOverviews = $this->getOverviewsFromCacheFile($uids, $fromFolder, true);
5116
					$this->deleteCachedMessages($uids, $fromFolder);
5117
5118
					// update cache on toFolder
5119
					$this->checkEmailOneMailbox($toFolder, true, true);
5120
				    if (isset($oldMailbox)) {
5121
                        $this->mailbox = $oldMailbox;
5122
                    }
5123
5124
					return true;
5125
				} else {
5126 2
					$GLOBALS['log']->debug("INBOUNDEMAIL: could not imap_mail_move() [ {$uids} ] to folder [ {$toFolder} ] from folder [ {$fromFolder} ]");
5127
				}
5128
			}
5129 1
		} elseif($toIe == 'folder' && $fromFolder == 'sugar::Emails') {
5130
			$GLOBALS['log']->debug("********* SUGARFOLDER - moveEmails() moving email from SugarFolder to SugarFolder");
5131
			// move from sugar folder to sugar folder
5132
			require_once("include/SugarFolders/SugarFolders.php");
5133
			$sugarFolder = new SugarFolder();
5134
			$exUids = explode($app_strings['LBL_EMAIL_DELIMITER'], $uids);
5135
			foreach($exUids as $id) {
5136
				if($copy) {
5137
					$sugarFolder->copyBean($fromIe, $toFolder, $id, "Emails");
5138
				} else {
5139
					$fromSugarFolder = new SugarFolder();
5140
					$fromSugarFolder->retrieve($fromIe);
5141
					$toSugarFolder = new SugarFolder();
5142
					$toSugarFolder->retrieve($toFolder);
5143
5144
					$email = new Email();
5145
					$email->retrieve($id);
5146
					$email->status = 'unread';
5147
5148
					// when you move from My Emails to Group Folder, Assign To field for the Email should become null
5149
					if ($fromSugarFolder->is_dynamic && $toSugarFolder->is_group) {
5150
                        // Bug 50972 - assigned_user_id set to empty string not true null
5151
                        // Modifying the field defs in just this one place to allow
5152
                        // a true null since this is what is expected when reading
5153
                        // inbox folders
5154
                        $email->setFieldNullable('assigned_user_id');
5155
						$email->assigned_user_id = "";
5156
						$email->save();
5157
                        $email->revertFieldNullable('assigned_user_id');
5158
                        // End fix 50972
5159
						if (!$toSugarFolder->checkEmailExistForFolder($id)) {
5160
							$fromSugarFolder->deleteEmailFromAllFolder($id);
5161
							$toSugarFolder->addBean($email);
5162
						}
5163
					} elseif ($fromSugarFolder->is_group && $toSugarFolder->is_dynamic) {
5164
						$fromSugarFolder->deleteEmailFromAllFolder($id);
5165
						$email->assigned_user_id = $current_user->id;
5166
						$email->save();
5167
					} else {
5168
						// If you are moving something from personal folder then delete an entry from all folder
5169
						if (!$fromSugarFolder->is_dynamic && !$fromSugarFolder->is_group) {
5170
							$fromSugarFolder->deleteEmailFromAllFolder($id);
5171
						} // if
5172
5173
						if ($fromSugarFolder->is_dynamic && !$toSugarFolder->is_dynamic && !$toSugarFolder->is_group) {
5174
							$email->assigned_user_id = "";
5175
							$toSugarFolder->addBean($email);
5176
						} // if
5177
						if (!$toSugarFolder->checkEmailExistForFolder($id)) {
5178
							if (!$toSugarFolder->is_dynamic) {
5179
								$fromSugarFolder->deleteEmailFromAllFolder($id);
5180
								$toSugarFolder->addBean($email);
5181
							} else {
5182
								$fromSugarFolder->deleteEmailFromAllFolder($id);
5183
								$email->assigned_user_id = $current_user->id;
5184
							}
5185
						} else {
5186
							$sugarFolder->move($fromIe, $toFolder, $id);
5187
						} // else
5188
						$email->save();
5189
					} // else
5190
				}
5191
			}
5192
5193
			return true;
5194 1
		} elseif($toIe == 'folder') {
5195
			$GLOBALS['log']->debug("********* SUGARFOLDER - moveEmails() moving email from I-E to SugarFolder");
5196
			// move to Sugar folder
5197
			require_once("include/SugarFolders/SugarFolders.php");
5198
			$sugarFolder = new SugarFolder();
5199
			$sugarFolder->retrieve($toFolder);
5200
			//Show the import form if we don't have the required info
5201
			if (!isset($_REQUEST['delete'])) {
5202
				$json = getJSONobj();
5203
				if ($sugarFolder->is_group) {
5204
					$_REQUEST['showTeam'] = false;
5205
					$_REQUEST['showAssignTo'] = false;
5206
				}
5207
	            $ret = $this->email->et->getImportForm($_REQUEST, $this->email);
5208
	            $ret['move'] = true;
5209
	            $ret['srcFolder'] = $fromFolder;
5210
	            $ret['srcIeId']   = $fromIe;
5211
	            $ret['dstFolder'] = $toFolder;
5212
	            $ret['dstIeId']   = $toIe;
5213
	            $out = trim($json->encode($ret, false));
5214
	            echo  $out;
5215
	            return true;
5216
			}
5217
5218
5219
			// import to Sugar
5220
			$this->retrieve($fromIe);
5221
			$this->mailbox = $fromFolder;
5222
			$this->connectMailserver();
5223
			// If its a group folder the team should be of the folder team
5224
			if ($sugarFolder->is_group) {
5225
				$_REQUEST['team_id'] = $sugarFolder->team_id;
5226
				$_REQUEST['team_set_id'] = $sugarFolder->team_set_id;
5227
			} else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
5228
				// TODO - set team_id, team_set for new UI
5229
			} // else
5230
5231
			$exUids = explode($app_strings['LBL_EMAIL_DELIMITER'], $uids);
5232
5233
			if(!empty($sugarFolder->id)) {
5234
				$count = 1;
5235
				$return = array();
5236
				$json = getJSONobj();
5237
				foreach($exUids as $k => $uid) {
5238
					$msgNo = $uid;
5239
					if ($this->isPop3Protocol()) {
5240
						$msgNo = $this->getCorrectMessageNoForPop3($uid);
5241
					} else {
5242
						$msgNo = imap_msgno($this->conn, $uid);
5243
					}
5244
5245
					if(!empty($msgNo)) {
5246
						$importStatus = $this->importOneEmail($msgNo, $uid);
5247
						// add to folder
5248
						if($importStatus) {
5249
							$sugarFolder->addBean($this->email);
5250
							if(!$copy && isset($_REQUEST['delete']) && ($_REQUEST['delete'] == "true") && $importStatus) {
5251
								$GLOBALS['log']->error("********* delete from mailserver [ {explode(",", $uids)} ]");
5252
								// delete from mailserver
5253
								$this->deleteMessageOnMailServer($uid);
5254
								$this->deleteMessageFromCache($uid);
5255
							} // if
5256
						}
5257
						$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']);
5258
						$count++;
5259
					} // if
5260
				} // foreach
5261
				echo $json->encode($return);
5262
				return true;
5263
			} else {
5264
				$GLOBALS['log']->error("********* SUGARFOLDER - failed to retrieve folder ID [ {$toFolder} ]");
5265
			}
5266
		} else {
5267 1
			$GLOBALS['log']->debug("********* SUGARFOLDER - moveEmails() called with no passing criteria");
5268
		}
5269
5270 2
		return false;
5271
	}
5272
5273
5274
	/**
5275
	 * Hard deletes an I-E account
5276
	 * @param string id GUID
5277
	 */
5278
	function hardDelete($id) {
5279
		$q = "DELETE FROM inbound_email WHERE id = '{$id}'";
5280
		$r = $this->db->query($q, true);
5281
	}
5282
5283
	/**
5284
	 * Generate a unique filename for attachments based on the message id.  There are no maximum
5285
	 * specifications for the length of the message id, the only requirement is that it be globally unique.
5286
	 *
5287
	 * @param bool $nameOnly Whether or not the attachment count should be appended to the filename.
5288
	 * @return string The temp file name
5289
	 */
5290 1
	function getTempFilename($nameOnly=false) {
5291
5292 1
        $str = $this->compoundMessageId;
5293
5294 1
		if(!$nameOnly) {
5295 1
			$str = $str.$this->attachmentCount;
5296 1
			$this->attachmentCount++;
5297
		}
5298
5299 1
		return $str;
5300
	}
5301
5302
	/**
5303
	 * deletes and expunges emails on server
5304
	 * @param string $uid UID(s), comma delimited, of email(s) on server
5305
	 * @return bool true on success
5306
	 */
5307 1
	function deleteMessageOnMailServer($uid) {
5308 1
		global $app_strings;
5309 1
		$this->connectMailserver();
5310
5311 1
		if(strpos($uid, $app_strings['LBL_EMAIL_DELIMITER']) !== false) {
5312
			$uids = explode($app_strings['LBL_EMAIL_DELIMITER'], $uid);
5313
		} else {
5314 1
			$uids[] = $uid;
5315
		}
5316
5317 1
		$return = true;
5318
5319 1
		if($this->protocol == 'imap') {
5320
			$trashFolder = $this->get_stored_options("trashFolder");
5321
			if (empty($trashFolder)) {
5322
				$trashFolder = "INBOX.Trash";
5323
			}
5324
			$uidsToMove = implode('::;::', $uids);
5325
			if($this->moveEmails($this->id, $this->mailbox, $this->id, $trashFolder, $uidsToMove))
5326
				$GLOBALS['log']->debug("INBOUNDEMAIL: MoveEmail to {$trashFolder} successful.");
5327
			else {
5328
				$GLOBALS['log']->debug("INBOUNDEMAIL: MoveEmail to {$trashFolder} FAILED - trying hard delete for message: $uid");
5329
				$uidsToDelete = implode(',', $uids);
5330
				imap_delete($this->conn, $uidsToDelete, FT_UID);
5331
				$return = true;
5332
			}
5333
		}
5334
        else {
5335 1
            $msgnos = array();
5336 1
        	foreach($uids as $uid) {
5337 1
            	$msgnos[] = $this->getCorrectMessageNoForPop3($uid);
5338
			}
5339 1
			$msgnos = implode(',', $msgnos);
5340 1
			imap_delete($this->conn, $msgnos);
5341 1
			$return = true;
5342
		}
5343
5344 1
		if(!imap_expunge($this->conn)) {
5345 1
            $GLOBALS['log']->debug("NOOP: could not expunge deleted email.");
5346 1
            $return = false;
5347
         }
5348
         else
5349
            $GLOBALS['log']->info("INBOUNDEMAIL: hard-deleted mail with MSgno's' [ {$msgnos} ]");
5350
5351 1
		return $return;
5352
	}
5353
5354
	/**
5355
	 * deletes and expunges emails on server
5356
	 * @param string $uid UID(s), comma delimited, of email(s) on server
5357
	 */
5358 1
	function deleteMessageOnMailServerForPop3($uid) {
5359 1
		if(imap_delete($this->conn, $uid)) {
5360
            if(!imap_expunge($this->conn)) {
5361
                $GLOBALS['log']->debug("NOOP: could not expunge deleted email.");
5362
                $return = false;
5363
            } else {
5364
                $GLOBALS['log']->info("INBOUNDEMAIL: hard-deleted mail with MSgno's' [ {$uid} ]");
5365
            }
5366
		}
5367 1
	}
5368
5369
	/**
5370
	 * Checks if this is a pop3 type of an account or not
5371
	 * @return boolean
5372
	 */
5373 12
	function isPop3Protocol() {
5374 12
		return ($this->protocol == 'pop3');
5375
	}
5376
5377
	/**
5378
	 * Gets the UIDL from database for the corresponding msgno
5379
	 * @param int messageNo of a message
5380
	 * @return UIDL for the message
5381
	 */
5382 1
	function getUIDLForMessage($msgNo) {
5383 1
		$query = "SELECT message_id FROM email_cache WHERE ie_id = '{$this->id}' AND msgno = '{$msgNo}'";
5384 1
		$r = $this->db->query($query);
5385 1
		$a = $this->db->fetchByAssoc($r);
5386 1
		return $a['message_id'];
5387
	}
5388
		/**
5389
	 * Get the users default IE account id
5390
	 *
5391
	 * @param User $user
5392
	 * @return string
5393
	 */
5394 1
	function getUsersDefaultOutboundServerId($user)
5395
	{
5396 1
		$id =  $user->getPreference($this->keyForUsersDefaultIEAccount,'Emails',$user);
5397
		//If no preference has been set, grab the default system id.
5398 1
		if(empty($id))
5399
		{
5400
			$oe = new OutboundEmail();
5401
			$system = $oe->getSystemMailerSettings();
5402
			$id=empty($system->id) ? '' : $system->id;
5403
		}
5404
5405 1
		return $id;
5406
	}
5407
5408
	/**
5409
	 * Get the users default IE account id
5410
	 *
5411
	 * @param User $user
5412
	 */
5413 1
	function setUsersDefaultOutboundServerId($user,$oe_id)
5414
	{
5415 1
		$user->setPreference($this->keyForUsersDefaultIEAccount, $oe_id, '', 'Emails');
5416 1
	}
5417
	/**
5418
	 * Gets the UIDL from database for the corresponding msgno
5419
	 * @param int messageNo of a message
5420
	 * @return UIDL for the message
5421
	 */
5422 1
	function getMsgnoForMessageID($messageid) {
5423 1
		$query = "SELECT msgno FROM email_cache WHERE ie_id = '{$this->id}' AND message_id = '{$messageid}'";
5424 1
		$r = $this->db->query($query);
5425 1
		$a = $this->db->fetchByAssoc($r);
5426 1
		return $a['message_id'];
5427
	}
5428
5429
	/**
5430
	 * fills InboundEmail->email with an email's details
5431
	 * @param int uid Unique ID of email
5432
	 * @param bool isMsgNo flag that passed ID is msgNo, default false
5433
	 * @param bool setRead Sets the 'seen' flag in cache
5434
	 * @param bool forceRefresh Skips cache file
5435
	 * @return string
5436
	 */
5437 1
	function setEmailForDisplay($uid, $isMsgNo=false, $setRead=false, $forceRefresh=false) {
5438
5439 1
		if(empty($uid)) {
5440 1
			$GLOBALS['log']->debug("*** ERROR: INBOUNDEMAIL trying to setEmailForDisplay() with no UID");
5441 1
			return 'NOOP';
5442
		}
5443
5444 1
		global $sugar_config;
5445 1
		global $app_strings;
5446
5447
		// if its a pop3 then get the UIDL and see if this file name exist or not
5448 1
		if ($this->isPop3Protocol()) {
5449
			// get the UIDL from database;
5450 1
			$cachedUIDL = md5($uid);
5451 1
			$cache = "{$this->EmailCachePath}/{$this->id}/messages/{$this->mailbox}{$cachedUIDL}.php";
5452
		} else {
5453
			$cache = "{$this->EmailCachePath}/{$this->id}/messages/{$this->mailbox}{$uid}.php";
5454
		}
5455
5456 1
		if(isset($cache) && strpos($cache, "..") !== false){
5457
			die("Directory navigation attack denied.");
5458
		}
5459
5460 1
		if(file_exists($cache) && !$forceRefresh) {
5461
			$GLOBALS['log']->info("INBOUNDEMAIL: Using cache file for setEmailForDisplay()");
5462
5463
			include($cache); // profides $cacheFile
5464
            /** @var $cacheFile array */
5465
5466
            $metaOut = unserialize($cacheFile['out']);
5467
			$meta = $metaOut['meta']['email'];
5468
			$email = new Email();
5469
5470
			foreach($meta as $k => $v) {
5471
				$email->$k = $v;
5472
			}
5473
5474
			$email->to_addrs = $meta['toaddrs'];
5475
			$email->date_sent = $meta['date_start'];
5476
			//_ppf($email,true);
5477
5478
			$this->email = $email;
5479
			$this->email->email2init();
5480
			$ret = 'cache';
5481
		} else {
5482 1
			$GLOBALS['log']->info("INBOUNDEMAIL: opening new connection for setEmailForDisplay()");
5483 1
            if($this->isPop3Protocol()) {
5484 1
            	$msgNo = $this->getCorrectMessageNoForPop3($uid);
5485
            } else {
5486
				if(empty($this->conn)) {
5487
					$this->connectMailserver();
5488
				}
5489
            	$msgNo = ($isMsgNo) ? $uid : imap_msgno($this->conn, $uid);
5490
            }
5491 1
			if(empty($this->conn)) {
5492 1
				$status = $this->connectMailserver();
5493 1
				if($status == "false") {
5494 1
					$this->email = new Email();
5495 1
					$this->email->name = $app_strings['LBL_EMAIL_ERROR_MAILSERVERCONNECTION'];
5496 1
					$ret = 'error';
5497 1
					return $ret;
5498
				}
5499
5500
			}
5501
5502
			$this->importOneEmail($msgNo, $uid, true);
5503
			$this->email->id = '';
5504
			$this->email->new_with_id = false;
5505
			$ret = 'import';
5506
		}
5507
5508
		if($setRead) {
5509
			$this->setStatuses($uid, 'seen', 1);
5510
		}
5511
5512
		return $ret;
5513
	}
5514
5515
5516
	/**
5517
	 * Sets status for a particular attribute on the mailserver and the local cache file
5518
	 */
5519
	function setStatuses($uid, $field, $value) {
5520
		global $sugar_config;
5521
		/** available status fields
5522
		    [subject] => aaa
5523
		    [from] => Some Name
5524
		    [to] => Some Name
5525
		    [date] => Mon, 22 Jan 2007 17:32:57 -0800
5526
		    [message_id] =>
5527
		    [size] => 718
5528
		    [uid] => 191
5529
		    [msgno] => 141
5530
		    [recent] => 0
5531
		    [flagged] => 0
5532
		    [answered] => 0
5533
		    [deleted] => 0
5534
		    [seen] => 1
5535
		    [draft] => 0
5536
		*/
5537
		// local cache
5538
		$file = "{$this->mailbox}.imapFetchOverview.php";
5539
		$overviews = $this->getCacheValueForUIDs($this->mailbox, array($uid));
5540
5541
		if(!empty($overviews)) {
5542
			$updates = array();
5543
5544
			foreach($overviews['retArr'] as $k => $obj) {
5545
				if($obj->imap_uid == $uid) {
5546
					$obj->$field = $value;
5547
					$updates[] = $obj;
5548
				}
5549
			}
5550
5551
			if(!empty($updates)) {
5552
				$this->setCacheValue($this->mailbox, array(), $updates);
5553
			}
5554
		}
5555
	}
5556
5557
	/**
5558
	 * Removes an email from the cache file, deletes the message from the cache too
5559
	 * @param string String of uids, comma delimited
5560
	 */
5561
	function deleteMessageFromCache($uids) {
5562
		global $sugar_config;
5563
		global $app_strings;
5564
5565
		// delete message cache file and email_cache file
5566
		$exUids = explode($app_strings['LBL_EMAIL_DELIMITER'], $uids);
5567
5568
		foreach($exUids as $uid) {
5569
			// local cache
5570
			if ($this->isPop3Protocol()) {
5571
				$q = "DELETE FROM email_cache WHERE message_id = '{$uid}' AND ie_id = '{$this->id}'";
5572
			} else {
5573
				$q = "DELETE FROM email_cache WHERE imap_uid = {$uid} AND ie_id = '{$this->id}'";
5574
			}
5575
			$r = $this->db->query($q);
5576
			if ($this->isPop3Protocol()) {
5577
				$uid = md5($uid);
5578
			} // if
5579
			$msgCacheFile = "{$this->EmailCachePath}/{$this->id}/messages/{$this->mailbox}{$uid}.php";
5580
			if(file_exists($msgCacheFile)) {
5581
				if(!unlink($msgCacheFile)) {
5582
					$GLOBALS['log']->error("***ERROR: InboundEmail could not delete the cache file [ {$msgCacheFile} ]");
5583
				}
5584
			}
5585
		}
5586
	}
5587
5588
5589
	/**
5590
	 * Shows one email.
5591
	 * @param int uid UID of email to display
5592
	 * @param string mbox Mailbox to look in for the message
5593
	 * @param bool isMsgNo Flag to assume $uid is a MessageNo, not UniqueID, default false
5594
	 */
5595 1
	function displayOneEmail($uid, $mbox, $isMsgNo=false) {
5596 1
		require_once("include/JSON.php");
5597
5598 1
		global $timedate;
5599 1
		global $app_strings;
5600 1
		global $app_list_strings;
5601 1
		global $sugar_smarty;
5602 1
		global $theme;
5603 1
		global $current_user;
5604 1
		global $sugar_config;
5605
5606
		$fetchedAttributes = array(
5607 1
			'name',
5608
			'from_name',
5609
			'from_addr',
5610
			'date_start',
5611
			'time_start',
5612
			'message_id',
5613
		);
5614
5615 1
		$souEmail = array();
5616 1
		foreach($fetchedAttributes as $k) {
5617 1
			if ($k == 'date_start') {
5618 1
				$this->email->$k . " " . $this->email->time_start;
5619 1
				$souEmail[$k] = $this->email->$k . " " . $this->email->time_start;
5620 1
			} elseif ($k == 'time_start') {
5621 1
				$souEmail[$k] = "";
5622
			} else {
5623 1
				$souEmail[$k] = trim($this->email->$k);
5624
			}
5625
		}
5626
5627
		// if a MsgNo is passed in, convert to UID
5628 1
		if($isMsgNo)
5629
			$uid = imap_uid($this->conn, $uid);
5630
5631
		// meta object to allow quick retrieval for replies
5632 1
		$meta = array();
5633 1
		$meta['type'] = $this->email->type;
5634 1
		$meta['uid'] = $uid;
5635 1
		$meta['ieId'] = $this->id;
5636 1
		$meta['email'] = $souEmail;
5637 1
		$meta['mbox'] = $this->mailbox;
5638 1
		$ccs = '';
5639
		// imap vs pop3
5640
5641
		// self mapping
5642 1
		$exMbox = explode("::", $mbox);
5643
5644
		// CC section
5645 1
		$cc = '';
5646 1
		if(!empty($this->email->cc_addrs)) {
5647
			//$ccs = $this->collapseLongMailingList($this->email->cc_addrs);
5648
			$ccs = to_html($this->email->cc_addrs_names);
5649
			$cc =<<<eoq
5650
				<tr>
5651
					<td NOWRAP valign="top" class="displayEmailLabel">
5652
						{$app_strings['LBL_EMAIL_CC']}:
5653
					</td>
5654
					<td class="displayEmailValue">
5655
						{$ccs}
5656
					</td>
5657
				</tr>
5658
eoq;
5659
		}
5660 1
		$meta['cc'] = $cc;
5661 1
		$meta['email']['cc_addrs'] = $ccs;
5662
		// attachments
5663 1
		$attachments = '';
5664 1
		if ($mbox == "sugar::Emails") {
5665
5666
			$q = "SELECT id, filename, file_mime_type FROM notes WHERE parent_id = '{$uid}' AND deleted = 0";
5667
			$r = $this->db->query($q);
5668
			$i = 0;
5669
			while($a = $this->db->fetchByAssoc($r)) {
5670
				$url = "index.php?entryPoint=download&type=notes&id={$a['id']}";
5671
				$lbl = ($i == 0) ? $app_strings['LBL_EMAIL_ATTACHMENTS'].":" : '';
5672
				$i++;
5673
				$attachments .=<<<EOQ
5674
				<tr>
5675
							<td NOWRAP valign="top" class="displayEmailLabel">
5676
								{$lbl}
5677
							</td>
5678
							<td NOWRAP valign="top" colspan="2" class="displayEmailValue">
5679
								<a href="{$url}">{$a['filename']}</a>
5680
							</td>
5681
						</tr>
5682
EOQ;
5683
				$this->email->cid2Link($a['id'], $a['file_mime_type']);
5684
		    } // while
5685
5686
5687
		} else {
5688
5689 1
			if($this->attachmentCount > 0) {
5690
				$theCount = $this->attachmentCount;
5691
5692
				for($i=0; $i<$theCount; $i++) {
5693
					$lbl = ($i == 0) ? $app_strings['LBL_EMAIL_ATTACHMENTS'].":" : '';
5694
					$name = $this->getTempFilename(true).$i;
5695
					$tempName = urlencode($this->tempAttachment[$name]);
5696
5697
					$url = "index.php?entryPoint=download&type=temp&isTempFile=true&ieId={$this->id}&tempName={$tempName}&id={$name}";
5698
5699
					$attachments .=<<<eoq
5700
						<tr>
5701
							<td NOWRAP valign="top" class="displayEmailLabel">
5702
								{$lbl}
5703
							</td>
5704
							<td NOWRAP valign="top" colspan="2" class="displayEmailValue">
5705
								<a href="{$url}">{$this->tempAttachment[$name]}</a>
5706
							</td>
5707
						</tr>
5708
eoq;
5709
				} // for
5710
			} // if
5711
		} // else
5712 1
		$meta['email']['attachments'] = $attachments;
5713
5714
		// toasddrs
5715 1
		$meta['email']['toaddrs'] = $this->collapseLongMailingList($this->email->to_addrs);
5716 1
		$meta['email']['cc_addrs'] = $ccs;
5717
5718
		// body
5719 1
		$description = (empty($this->email->description_html)) ? nl2br($this->email->description) : $this->email->description_html;
5720 1
		$meta['email']['description'] = $description;
5721
5722
		// meta-metadata
5723 1
		$meta['is_sugarEmail'] = ($exMbox[0] == 'sugar') ? true : false;
5724
5725 1
		if(!$meta['is_sugarEmail']) {
5726 1
			if($this->isAutoImport) {
5727 1
				$meta['is_sugarEmail'] = true;
5728
			}
5729
		} else {
5730
			if( $this->email->status != 'sent' ){
5731
				// mark SugarEmail read
5732
				$q = "UPDATE emails SET status = 'read' WHERE id = '{$uid}'";
5733
				$r = $this->db->query($q);
5734
			}
5735
		}
5736
5737 1
		$return = array();
5738 1
        $meta['email']['name'] = to_html($this->email->name);
5739 1
        $meta['email']['from_addr'] = ( !empty($this->email->from_addr_name) ) ? to_html($this->email->from_addr_name) : to_html($this->email->from_addr);
0 ignored issues
show
Bug introduced by
It seems like $this->email->from_addr can also be of type array<string,?,{"name":"?"}> or null; however, to_html() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
5740 1
        $meta['email']['toaddrs'] = ( !empty($this->email->to_addrs_names) ) ? to_html($this->email->to_addrs_names) : to_html($this->email->to_addrs);
0 ignored issues
show
Bug introduced by
It seems like $this->email->to_addrs can also be of type array<string,?,{"name":"?"}> or null; however, to_html() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
5741 1
        $meta['email']['cc_addrs'] = to_html($this->email->cc_addrs_names);
5742 1
        $meta['email']['reply_to_addr'] = to_html($this->email->reply_to_addr);
5743 1
		$return['meta'] = $meta;
5744
5745 1
		return $return;
5746
	}
5747
5748
	/**
5749
	 * Takes a long list of email addresses from a To or CC field and shows the first 3, the rest hidden
5750
	 * @param string emails
5751
	 * @return string
5752
	 */
5753 2
	function collapseLongMailingList($emails) {
5754 2
		global $app_strings;
5755
5756 2
		$ex = explode(",", $emails);
5757 2
		$i = 0;
5758 2
		$j = 0;
5759
5760 2
		if(count($ex) > 3) {
5761 1
			$emails = "";
5762 1
			$emailsHidden = "";
5763
5764 1
			foreach($ex as $email) {
5765 1
				if($i < 2) {
5766 1
					if(!empty($emails)) {
5767 1
						$emails .= ", ";
5768
					}
5769 1
					$emails .= trim($email);
5770
				} else {
5771 1
					if(!empty($emailsHidden)) {
5772 1
						$emailsHidden .= ", ";
5773
					}
5774 1
					$emailsHidden .= trim($email);
5775 1
					$j++;
5776
				}
5777 1
				$i++;
5778
			}
5779
5780 1
			if(!empty($emailsHidden)) {
5781 1
				$email2 = $emails;
5782 1
				$emails = "<span onclick='javascript:SUGAR.email2.detailView.showFullEmailList(this);' style='cursor:pointer;'>{$emails} [...{$j} {$app_strings['LBL_MORE']}]</span>";
5783 1
				$emailsHidden = "<span onclick='javascript:SUGAR.email2.detailView.showCroppedEmailList(this)' style='cursor:pointer; display:none;'>{$email2}, {$emailsHidden} [ {$app_strings['LBL_LESS']} ]</span>";
5784
			}
5785
5786 1
			$emails .= $emailsHidden;
5787
		}
5788
5789 2
		return $emails;
5790
	}
5791
5792
5793
	/**
5794
	 * Sorts IMAP's imap_fetch_overview() results
5795
	 * @param array $arr Array of standard objects
5796
	 * @param string $sort Column to sort by
5797
	 * @param string direction Direction to sort by (asc/desc)
5798
	 * @return array Sorted array of obj.
5799
	 */
5800 1
	function sortFetchedOverview($arr, $sort=4, $direction='DESC', $forceSeen=false) {
5801 1
		global $current_user;
5802
5803 1
		$sortPrefs = $current_user->getPreference('folderSortOrder', 'Emails');
5804 1
		if(!empty($sortPrefs))
5805
			$listPrefs = $sortPrefs;
5806
		else
5807 1
			$listPrefs = array();
5808
5809 1
		if(isset($listPrefs[$this->id][$this->mailbox])) {
5810
			$currentNode = $listPrefs[$this->id][$this->mailbox];
5811
		}
5812
5813 1
		if(isset($currentNode['current']) && !empty($currentNode['current'])) {
5814
			$sort = $currentNode['current']['sort'];
5815
			$direction = $currentNode['current']['direction'];
5816
		}
5817
5818
		// sort defaults
5819 1
		if(empty($sort)) {
5820
			$sort = $this->defaultSort;//4;
5821
			$direction = $this->defaultDirection; //'DESC';
5822 1
		} elseif(!is_numeric($sort)) {
5823
			// handle bad sort index
5824
			$sort = $this->defaultSort;
5825
		} else {
5826
			// translate numeric index to human readable
5827 1
            $sort = $this->hrSort[$sort];
5828
		}
5829 1
		if(empty($direction)) {
5830
			$direction = 'DESC';
5831
		}
5832
5833
5834
5835 1
		$retArr = array();
5836 1
		$sorts = array();
5837
5838 1
		foreach($arr as $k => $overview) {
5839 1
			$sorts['flagged'][$k] = $overview->flagged;
5840 1
			$sorts['status'][$k] = $overview->answered;
5841 1
			$sorts['from'][$k] = str_replace('"', "", $this->handleMimeHeaderDecode($overview->from));
5842 1
			$sorts['subj'][$k] = $this->handleMimeHeaderDecode(quoted_printable_decode($overview->subject));
5843 1
			$sorts['date'][$k] = $overview->date;
5844
		}
5845
5846
		// sort by column
5847 1
		natcasesort($sorts[$sort]);
5848
		//_ppd($sorts[$sort]);
5849
		// direction
5850 1
		if(strtolower($direction) == 'desc') {
5851 1
			$revSorts = array();
5852 1
			$keys = array_reverse(array_keys($sorts[$sort]));
5853
//			_pp("count keys in DESC: ".count($keys));
5854
//			_pp("count elements in sort[sort]: ".count($sorts[$sort]));
5855
5856 1
			for($i=0; $i<count($keys); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
5857 1
				$v = $keys[$i];
5858 1
				$revSorts[$v] = $sorts[$sort][$v];
5859
			}
5860
5861
			//_pp("count post-sort: ".count($revSorts));
5862 1
			$sorts[$sort] = $revSorts;
5863
		}
5864 1
        $timedate = TimeDate::getInstance();
5865 1
		foreach($sorts[$sort] as $k2 => $overview2) {
5866 1
		    $arr[$k2]->date = $timedate->fromString($arr[$k2]->date)->asDb();
5867 1
			$retArr[] = $arr[$k2];
5868
		}
5869
		//_pp("final count: ".count($retArr));
5870
5871 1
		$finalReturn = array();
5872 1
		$finalReturn['retArr'] = $retArr;
5873 1
		$finalReturn['sortBy'] = $sort;
5874 1
		$finalReturn['direction'] = $direction;
5875 1
		return $finalReturn;
5876
	}
5877
5878
5879 1
	function setReadFlagOnFolderCache($mbox, $uid) {
5880 1
		global $sugar_config;
5881
5882 1
		$this->mailbox = $mbox;
5883
5884
		// cache
5885 1
		if($this->validCacheExists($this->mailbox)) {
5886
			$ret = $this->getCacheValue($this->mailbox);
5887
5888
			$updates = array();
5889
5890
			foreach($ret as $k => $v) {
5891
				if($v->imap_uid == $uid) {
5892
					$v->seen = 1;
5893
					$updates[] = $v;
5894
					break;
5895
				}
5896
			}
5897
5898
			$this->setCacheValue($this->mailbox, array(), $updates);
5899
		}
5900 1
	}
5901
5902
	/**
5903
	 * Returns a list of emails in a mailbox.
5904
	 * @param string mbox Name of mailbox using dot notation paths to display
5905
	 * @param string $forceRefresh Flag to use cache or not
5906
	 */
5907 1
	function displayFolderContents($mbox, $forceRefresh='false', $page) {
5908 1
		global $current_user;
5909
5910 1
		$delimiter = $this->get_stored_options('folderDelimiter');
5911 1
		if ($delimiter) {
5912
			$mbox = str_replace('.', $delimiter, $mbox);
5913
		}
5914
5915 1
		$this->mailbox = $mbox;
5916
5917
		// jchi #9424, get sort and direction from user preference
5918 1
		$sort = 'date';
5919 1
		$direction = 'desc';
5920 1
		$sortSerial = $current_user->getPreference('folderSortOrder', 'Emails');
5921 1
		if(!empty($sortSerial) && !empty($_REQUEST['ieId']) && !empty($_REQUEST['mbox'])) {
5922
			$sortArray = unserialize($sortSerial);
5923
			$sort = $sortArray[$_REQUEST['ieId']][$_REQUEST['mbox']]['current']['sort'];
5924
			$direction = $sortArray[$_REQUEST['ieId']][$_REQUEST['mbox']]['current']['direction'];
5925
		}
5926
		//end
5927
5928
		// save sort order
5929 1
		if(!empty($_REQUEST['sort']) && !empty($_REQUEST['dir'])) {
5930
			$this->email->et->saveListViewSortOrder($_REQUEST['ieId'], $_REQUEST['mbox'], $_REQUEST['sort'], $_REQUEST['dir']);
5931
			$sort = $_REQUEST['sort'];
5932
			$direction = $_REQUEST['dir'];
5933
		} else {
5934 1
			$_REQUEST['sort'] = '';
5935 1
			$_REQUEST['dir'] = '';
5936
		}
5937
5938
		// cache
5939 1
		$ret = array();
5940 1
		$cacheUsed = false;
5941 1
		if($forceRefresh == 'false' && $this->validCacheExists($this->mailbox)) {
5942
			$emailSettings = $current_user->getPreference('emailSettings', 'Emails');
5943
5944
			// cn: default to a low number until user specifies otherwise
5945
			if(empty($emailSettings['showNumInList'])) {
5946
				$emailSettings['showNumInList'] = 20;
5947
			}
5948
5949
			$ret = $this->getCacheValue($this->mailbox, $emailSettings['showNumInList'], $page, $sort, $direction);
5950
			$cacheUsed = true;
5951
		}
5952
5953 1
		$out = $this->displayFetchedSortedListXML($ret, $mbox);
5954
5955 1
		$metadata = array();
5956 1
		$metadata['mbox'] = $mbox;
5957 1
		$metadata['ieId'] = $this->id;
5958 1
		$metadata['name'] = $this->name;
5959 1
		$metadata['fromCache'] = $cacheUsed ? 1 : 0;
5960 1
		$metadata['out'] = $out;
5961
5962 1
		return $metadata;
5963
	}
5964
5965
	/**
5966
	 * For a group email account, create subscriptions for all users associated with the
5967
	 * team assigned to the account.
5968
	 *
5969
	 */
5970
	function createUserSubscriptionsForGroupAccount()
5971
	{
5972
	    $team = new Team();
5973
	    $team->retrieve($this->team_id);
5974
	    $usersList = $team->get_team_members(true);
5975
	    foreach($usersList as $userObject)
5976
	    {
5977
	        $previousSubscriptions = unserialize(base64_decode($userObject->getPreference('showFolders', 'Emails',$userObject)));
5978
	        if($previousSubscriptions === FALSE)
5979
	            $previousSubscriptions = array();
5980
5981
	        $previousSubscriptions[] = $this->id;
5982
5983
	        $encodedSubs = base64_encode(serialize($previousSubscriptions));
5984
	        $userObject->setPreference('showFolders',$encodedSubs , '', 'Emails');
5985
	        $userObject->savePreferencesToDB();
5986
	    }
5987
    }
5988
	/**
5989
    * Create a sugar folder for this inbound email account
5990
    * if the Enable Auto Import option is selected
5991
    *
5992
    * @return String Id of the sugar folder created.
5993
    */
5994 1
	function createAutoImportSugarFolder()
5995
	{
5996 1
	    global $current_user;
5997 1
	    $guid = create_guid();
5998 1
	    $GLOBALS['log']->debug("Creating Sugar Folder for IE with id $guid");
5999 1
	    $folder = new SugarFolder();
6000 1
	    $folder->id = $guid;
6001 1
	    $folder->new_with_id = TRUE;
6002 1
	    $folder->name = $this->name;
6003 1
	    $folder->has_child = 0;
6004 1
	    $folder->is_group = 1;
6005 1
	    $folder->assign_to_id = $current_user->id;
6006 1
	    $folder->parent_folder = "";
6007
6008
6009
	    //If this inbound email is marked as inactive, don't add subscriptions.
6010 1
	    $addSubscriptions = ($this->status == 'Inactive' || $this->mailbox_type == 'bounce') ? FALSE : TRUE;
6011 1
	    $folder->save($addSubscriptions);
6012
6013 1
	    return $guid;
6014
	}
6015
6016 3
	function validCacheExists($mbox) {
6017 3
		$q = "SELECT count(*) c FROM email_cache WHERE ie_id = '{$this->id}'";
6018 3
		$r = $this->db->query($q);
6019 3
		$a = $this->db->fetchByAssoc($r);
6020 3
		$count = $a['c'];
6021
6022 3
		if($count > 0) {
6023 1
			return true;
6024
		}
6025
6026 3
		return false;
6027
	}
6028
6029
6030
6031
6032 2
	function displayFetchedSortedListXML($ret, $mbox) {
6033
6034 2
		global $timedate;
6035 2
		global $current_user;
6036 2
		global $sugar_config;
6037
6038 2
		if(empty($ret['retArr'])) {
6039 1
		    return array();
6040
		}
6041
6042 1
		$tPref = $current_user->getUserDateTimePreferences();
6043
6044 1
		$return = array();
6045
6046 1
		foreach($ret['retArr'] as $msg) {
6047
6048 1
			$flagged	= ($msg->flagged == 0) ? "" : $this->iconFlagged;
6049 1
			$status		= ($msg->deleted) ? $this->iconDeleted : "";
6050 1
			$status		= ($msg->draft == 0) ? $status : $this->iconDraft;
6051 1
			$status		= ($msg->answered == 0) ? $status : $this->iconAnswered;
6052 1
			$from		= $this->handleMimeHeaderDecode($msg->from);
6053 1
			$subject	= $this->handleMimeHeaderDecode($msg->subject);
6054
			//$date		= date($tPref['date']." ".$tPref['time'], $msg->date);
6055 1
			$date		= $timedate->to_display_date_time($this->db->fromConvert($msg->date, 'datetime'));
6056
			//$date		= date($tPref['date'], $this->getUnixHeaderDate($msg->date));
6057
6058 1
			$temp = array();
6059 1
			$temp['flagged'] = $flagged;
6060 1
			$temp['status'] = $status;
6061 1
			$temp['from']	= to_html($from);
6062 1
			$temp['subject'] = $subject;
6063 1
			$temp['date']	= $date;
6064 1
			$temp['uid'] = $msg->uid; // either from an imap_search() or massaged cache value
6065 1
			$temp['mbox'] = $this->mailbox;
6066 1
			$temp['ieId'] = $this->id;
6067 1
			$temp['site_url'] = $sugar_config['site_url'];
6068 1
			$temp['seen'] = $msg->seen;
6069 1
			$temp['type'] = (isset($msg->type)) ? $msg->type: 'remote';
6070 1
			$temp['to_addrs'] = to_html($msg->to);
6071 1
			$temp['hasAttach'] = '0';
6072
6073 1
			$return[] = $temp;
6074
		}
6075
6076 1
		return $return;
6077
	}
6078
6079
6080
6081
	/**
6082
	 * retrieves the mailboxes for a given account in the following format
6083
	 * Array(
6084
	    [INBOX] => Array
6085
	        (
6086
	            [Bugs] => Bugs
6087
	            [Builder] => Builder
6088
	            [DEBUG] => Array
6089
	                (
6090
	                    [out] => out
6091
	                    [test] => test
6092
	                )
6093
	        )
6094
	 * @param bool $justRaw Default false
6095
	 * @return array
6096
	 */
6097 3
	function getMailboxes($justRaw=false) {
6098 3
		if($justRaw == true) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
6099 3
			return $this->mailboxarray;
6100
		} // if
6101
6102 1
		return $this->generateMultiDimArrayFromFlatArray($this->mailboxarray, $this->retrieveDelimiter());
6103
		/*
6104
		$serviceString = $this->getConnectString('', '', false);
6105
6106
		if(strpos($serviceString, 'pop3')) {
6107
			$obj = new temp();
6108
			$obj->name = $serviceString."INBOX";
6109
			$boxes = array("INBOX" => $obj);
6110
		} else {
6111
			$boxes = imap_getmailboxes($this->conn, $serviceString, "*");
6112
		}
6113
		$raw = array();
6114
		//_ppd($boxes);
6115
		$delimiter = '.';
6116
		// clean MBOX path names
6117
		foreach($boxes as $k => $mbox) {
6118
			$raw[] = str_replace($serviceString, "", $mbox->name);
6119
			if ($mbox->delimiter) {
6120
				$delimiter = $mbox->delimiter;
6121
			}
6122
		}
6123
		$storedOptions = unserialize(base64_decode($this->stored_options));
6124
		$storedOptions['folderDelimiter'] = $delimiter;
6125
		$this->stored_options = base64_encode(serialize($storedOptions));
6126
        $this->save();
6127
6128
		sort($raw);
6129
		//_ppd($raw);
6130
6131
		// used by $this->search()
6132
		if($justRaw == true) {
6133
			return $raw;
6134
		}
6135
6136
6137
		// generate a multi-dimensional array to iterate through
6138
		$ret = array();
6139
		foreach($raw as $mbox) {
6140
			$ret = $this->sortMailboxes($mbox, $ret, $delimiter);
6141
		}
6142
		//_ppd($ret);
6143
		return $ret;
6144
		*/
6145
	}
6146
6147 1
	function getMailBoxesForGroupAccount() {
6148 1
		$mailboxes = $this->generateMultiDimArrayFromFlatArray(explode(",", $this->mailbox), $this->retrieveDelimiter());
6149 1
		$mailboxesArray = $this->generateFlatArrayFromMultiDimArray($mailboxes, $this->retrieveDelimiter());
6150 1
		$mailboxesArray = $this->filterMailBoxFromRaw(explode(",", $this->mailbox), $mailboxesArray);
6151 1
		$this->saveMailBoxFolders($mailboxesArray);
6152
		/*
6153
		if ($this->mailbox != $this->$email_user) {
6154
			$mailboxes = $this->sortMailboxes($this->mailbox, $this->retrieveDelimiter());
6155
			$mailboxesArray = $this->generateFlatArrayFromMultiDimArray($mailboxes, $this->retrieveDelimiter());
6156
			$this->saveMailBoxFolders($mailboxesArray);
6157
			// save mailbox value of an inbound email account to email user
6158
			$this->saveMailBoxValueOfInboundEmail();
6159
		} else {
6160
			$mailboxes = $this->getMailboxes();
6161
		}
6162
		*/
6163 1
		return $mailboxes;
6164
	} // fn
6165
6166 1
	function saveMailBoxFolders($value) {
6167 1
		if (is_array($value)) {
6168 1
			$value = implode(",", $value);
6169
		}
6170 1
		$this->mailboxarray = explode(",", $value);
6171 1
		$value = $this->db->quoted($value);
6172 1
		$query = "update inbound_email set mailbox = $value where id ='{$this->id}'";
6173 1
		$this->db->query($query);
6174 1
	}
6175
6176 1
	function insertMailBoxFolders($value) {
6177 1
		$query = "select value from config where category='InboundEmail' and name='{$this->id}'";
6178 1
		$r = $this->db->query($query);
6179 1
		$a = $this->db->fetchByAssoc($r);
6180 1
		if (empty($a['value'])) {
6181 1
			if (is_array($value)) {
6182 1
				$value = implode(",", $value);
6183
			}
6184 1
			$this->mailboxarray = explode(",", $value);
6185 1
			$value = $this->db->quoted($value);
6186
6187 1
			$query = "INSERT INTO config VALUES('InboundEmail', '{$this->id}', $value)";
6188 1
			$this->db->query($query);
6189
		} // if
6190 1
	}
6191
6192
	function saveMailBoxValueOfInboundEmail() {
6193
		$query = "update Inbound_email set mailbox = '{$this->email_user}'";
6194
		$this->db->query($query);
6195
	}
6196
6197 2
	function retrieveMailBoxFolders() {
6198 2
		$this->mailboxarray = explode(",", $this->mailbox);
6199
		/*
6200
		$query = "select value from config where category='InboundEmail' and name='{$this->id}'";
6201
		$r = $this->db->query($query);
6202
		$a = $this->db->fetchByAssoc($r);
6203
		$this->mailboxarray = explode(",", $a['value']);
6204
		*/
6205 2
	} // fn
6206
6207
6208 4
	function retrieveDelimiter() {
6209 4
		$delimiter = $this->get_stored_options('folderDelimiter');
6210 4
        if (!$delimiter) {
6211 4
        	$delimiter = '.';
6212
        }
6213 4
		return $delimiter;
6214
	} // fn
6215
6216 3
	function generateFlatArrayFromMultiDimArray($arraymbox, $delimiter) {
6217 3
		$ret = array();
6218 3
		foreach($arraymbox as $key => $value) {
6219 3
			$this->generateArrayData($key, $value, $ret, $delimiter);
6220
		} // foreach
6221 3
		return $ret;
6222
6223
	} // fn
6224
6225 4
	function generateMultiDimArrayFromFlatArray($raw, $delimiter) {
6226
		// generate a multi-dimensional array to iterate through
6227 4
		$ret = array();
6228 4
		foreach($raw as $mbox) {
6229 4
			$ret = $this->sortMailboxes($mbox, $ret, $delimiter);
6230
		}
6231 4
		return $ret;
6232
6233
	} // fn
6234
6235 4
	function generateArrayData($key, $arraymbox, &$ret, $delimiter) {
6236 4
		$ret [] = $key;
6237 4
		if (is_array($arraymbox)) {
6238 3
			foreach($arraymbox as $mboxKey => $value) {
6239 3
				$newKey = $key . $delimiter . $mboxKey;
6240 3
				$this->generateArrayData($newKey, $value, $ret, $delimiter);
6241
			} // foreach
6242
		} // if
6243 4
	}
6244
6245
	/**
6246
	 * sorts the folders in a mailbox in a multi-dimensional array
6247
	 * @param string $MBOX
0 ignored issues
show
Bug introduced by
There is no parameter named $MBOX. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
6248
	 * @param array $ret
6249
	 * @return array
6250
	 */
6251 5
	function sortMailboxes($mbox, $ret, $delimeter = ".") {
6252 5
		if(strpos($mbox, $delimeter)) {
6253 4
			$node = substr($mbox, 0, strpos($mbox, $delimeter));
6254 4
			$nodeAfter = substr($mbox, strpos($mbox, $node) + strlen($node) + 1, strlen($mbox));
6255
6256 4
			if(!isset($ret[$node])) {
6257 4
				$ret[$node] = array();
6258
			} elseif(isset($ret[$node]) && !is_array($ret[$node])) {
6259
				$ret[$node] = array();
6260
			}
6261 4
			$ret[$node] = $this->sortMailboxes($nodeAfter, $ret[$node], $delimeter);
6262
		} else {
6263 5
			$ret[$mbox] = $mbox;
6264
		}
6265
6266 5
		return $ret;
6267
	}
6268
6269
	/**
6270
	 * parses Sugar's storage method for imap server service strings
6271
	 * @return string
6272
	 */
6273 18
	function getServiceString() {
6274 18
		$service = '';
6275 18
		$exServ = explode('::', $this->service);
6276
6277 18
		foreach($exServ as $v) {
6278 18
			if(!empty($v) && ($v != 'imap' && $v !='pop3')) {
6279 18
				$service .= '/'.$v;
6280
			}
6281
		}
6282 18
		return $service;
6283
	}
6284
6285
6286
    /**
6287
     * Get Email messages IDs from server which aren't in database
6288
     * @return array Ids of messages, which aren't still in database
6289
     */
6290 1
    public function getNewEmailsForSyncedMailbox()
6291
    {
6292
        // ids's count limit for batch processing
6293 1
        $limit = 20;
6294 1
        $msgIds = imap_search($this->conn, 'ALL UNDELETED');
6295 1
        $result = array();
6296
        try{
6297 1
            if(count($msgIds) > 0)
6298
            {
6299
                /*
6300
                 * @var collect results of queries and message headers
6301
                 */
6302
                $tmpMsgs = array();
6303
                $repeats = 0;
6304
                $counter = 0;
6305
6306
                // sort IDs to get lastest on top
6307
                arsort($msgIds);
6308
                $GLOBALS['log']->debug('-----> getNewEmailsForSyncedMailbox() got '.count($msgIds).' Messages');
6309
                foreach($msgIds as $k => &$msgNo)
6310
                {
6311
                    $uid = imap_uid($this->conn, $msgNo);
6312
                    $header = imap_headerinfo($this->conn, $msgNo);
6313
                    $fullHeader = imap_fetchheader($this->conn, $msgNo);
6314
                    $message_id = $header->message_id;
6315
                    $deliveredTo = $this->id;
6316
                    $matches = array();
6317
                    preg_match('/(delivered-to:|x-real-to:){1}\s*(\S+)\s*\n{1}/im', $fullHeader, $matches);
6318
                    if(count($matches))
6319
                    {
6320
                        $deliveredTo = $matches[2];
6321
                    }
6322
                    if(empty($message_id) || !isset($message_id))
6323
                    {
6324
                        $GLOBALS['log']->debug('*********** NO MESSAGE_ID.');
6325
                        $message_id = $this->getMessageId($header);
6326
                    }
6327
6328
                    // generate compound messageId
6329
                    $this->compoundMessageId = trim($message_id) . trim($deliveredTo);
6330
                    // if the length > 255 then md5 it so that the data will be of smaller length
6331
                    if (strlen($this->compoundMessageId) > 255)
6332
                    {
6333
                        $this->compoundMessageId = md5($this->compoundMessageId);
6334
                    } // if
6335
6336
                    if (empty($this->compoundMessageId))
6337
                    {
6338
                        break;
6339
                    } // if
6340
                    $counter++;
6341
                    $potentials = clean_xss($this->compoundMessageId, false);
0 ignored issues
show
Deprecated Code introduced by
The function clean_xss() has been deprecated.

This function has been deprecated.

Loading history...
6342
6343
                    if(is_array($potentials) && !empty($potentials))
6344
                    {
6345
                        foreach($potentials as $bad)
6346
                        {
6347
                            $this->compoundMessageId = str_replace($bad, "", $this->compoundMessageId);
6348
                        }
6349
                    }
6350
                    array_push($tmpMsgs, array('msgNo' => $msgNo, 'msgId' => $this->compoundMessageId, 'exists' => 0));
6351
                    if($counter == $limit)
6352
                    {
6353
                        $counter = 0;
6354
                        $query = array();
6355
                        foreach(array_slice($tmpMsgs, -$limit, $limit) as $k1 => $v1)
6356
                        {
6357
                            $query[] = $v1['msgId'];
6358
                        }
6359
                        $query = 'SELECT count(emails.message_id) as cnt, emails.message_id AS mid FROM emails WHERE emails.message_id IN ("' . implode('","', $query) . '") and emails.deleted = 0 group by emails.message_id';
6360
                        $r = $this->db->query($query);
6361
                        $tmp = array();
6362
                        while($a = $this->db->fetchByAssoc($r))
6363
                        {
6364
                            $tmp[html_entity_decode($a['mid'])] = $a['cnt'];
6365
                        }
6366
                        foreach($tmpMsgs as $k1 => $v1)
6367
                        {
6368
                            if(isset($tmp[$v1['msgId']]) && $tmp[$v1['msgId']] > 0)
6369
                            {
6370
                                $tmpMsgs[$k1]['exists'] = 1;
6371
                            }
6372
                        }
6373
                        foreach($tmpMsgs as $k1 => $v1)
6374
                        {
6375
                            if($v1['exists'] == 0)
6376
                            {
6377
                                $repeats = 0;
6378
                                array_push($result, $v1['msgNo']);
6379
                            }else{
6380
                                $repeats++;
6381
                            }
6382
                        }
6383
                        if($repeats > 0)
6384
                        {
6385
                            if($repeats >= $limit)
6386
                            {
6387
                                break;
6388
                            }
6389
                            else
6390
                            {
6391
                                $tmpMsgs = array_splice($tmpMsgs, -$repeats, $repeats);
6392
                            }
6393
                        }
6394
                        else
6395
                        {
6396
                            $tmpMsgs = array();
6397
                        }
6398
                    }
6399
                }
6400 1
                unset($msgNo);
6401
            }
6402
        }catch(Exception $ex)
6403
        {
6404
            $GLOBALS['log']->fatal($ex->getMessage());
6405
        }
6406 1
        $GLOBALS['log']->debug('-----> getNewEmailsForSyncedMailbox() got '.count($result).' unsynced messages');
6407 1
        return $result;
6408
    }
6409
6410
    /**
6411
     * Import new messages from given account.
6412
     */
6413 1
    public function importMessages()
6414
    {
6415 1
        $protocol = $this->isPop3Protocol() ? 'pop3' : 'imap';
6416
        switch ($protocol) {
6417 1
            case 'pop3':
6418 1
                $this->importMailboxMessages($protocol);
6419 1
                break;
6420
            case 'imap':
6421
                $mailboxes = $this->getMailboxes(true);
6422
                foreach ($mailboxes as $mailbox) {
6423
                    $this->importMailboxMessages($protocol, $mailbox);
6424
                }
6425
                imap_expunge($this->conn);
6426
                imap_close($this->conn);
6427
                break;
6428
        }
6429 1
    }
6430
6431
    /**
6432
     * Import messages from specified mailbox
6433
     *
6434
     * @param string      $protocol Mailing protocol
6435
     * @param string|null $mailbox  Mailbox (if applied to protocol)
6436
     */
6437 1
    protected function importMailboxMessages($protocol, $mailbox = null)
6438
    {
6439
        switch ($protocol) {
6440 1
            case 'pop3':
6441 1
                $msgNumbers = $this->getPop3NewMessagesToDownload();
6442 1
                break;
6443
            case 'imap':
6444
                $this->mailbox = $mailbox;
6445
                $this->connectMailserver();
6446
                $msgNumbers = $this->getNewMessageIds();
6447
                if (!$msgNumbers) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $msgNumbers of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
6448
                    $msgNumbers = array();
6449
                }
6450
                break;
6451
            default:
6452
                $msgNumbers = array();
6453
                break;
6454
        }
6455
6456 1
        foreach ($msgNumbers as $msgNumber) {
6457
            $uid = $this->getMessageUID($msgNumber, $protocol);
6458
            $GLOBALS['log']->info('Importing message no: ' . $msgNumber);
6459
            $this->importOneEmail($msgNumber, $uid, false, false);
6460
        }
6461 1
    }
6462
6463
    /**
6464
     * Retrieves message UID by it's number
6465
     *
6466
     * @param int     $msgNumber Number of the message in current sequence
6467
     * @param string  $protocol  Mailing protocol
6468
     * @return string
6469
     */
6470
    protected function getMessageUID($msgNumber, $protocol)
6471
    {
6472
        switch ($protocol) {
6473
            case 'pop3':
6474
                $uid = $this->getUIDLForMessage($msgNumber);
6475
                break;
6476
            case 'imap':
6477
                $uid = imap_uid($this->conn, $msgNumber);
6478
                break;
6479
            default:
6480
                $uid = null;
6481
                break;
6482
        }
6483
6484
        return $uid;
6485
    }
6486
} // end class definition
6487
6488
6489
/**
6490
 * Simple class to mirror the passed object from an imap_fetch_overview() call
6491
 */
6492
class Overview {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
6493
	var $subject;
6494
	var $from;
6495
	var $fromaddr;
6496
	var $to;
6497
	var $toaddr;
6498
	var $date;
6499
	var $message_id;
6500
	var $size;
6501
	var $uid;
6502
	var $msgno;
6503
	var $recent;
6504
	var $flagged;
6505
	var $answered;
6506
	var $deleted;
6507
	var $seen;
6508
	var $draft;
6509
	var $indices; /* = array(
6510
6511
			array(
6512
				'name'			=> 'mail_date',
6513
				'type'			=> 'index',
6514
				'fields'		=> array(
6515
					'mbox',
6516
					'senddate',
6517
				)
6518
			),
6519
			array(
6520
				'name'			=> 'mail_from',
6521
				'type'			=> 'index',
6522
				'fields'		=> array(
6523
					'mbox',
6524
					'fromaddr',
6525
				)
6526
			),
6527
			array(
6528
				'name'			=> 'mail_subj',
6529
				'type'			=> 'index',
6530
				'fields'		=> array(
6531
					'mbox',
6532
					'subject',
6533
				)
6534
			),
6535
		);
6536
	*/
6537
	var $fieldDefs;/* = array(
6538
			'mbox' => array(
6539
				'name'		=> 'mbox',
6540
				'type'		=> 'varchar',
6541
				'len'		=> 60,
6542
				'required'	=> true,
6543
			),
6544
			'subject' => array(
6545
				'name'		=> 'subject',
6546
				'type'		=> 'varchar',
6547
				'len'		=> 100,
6548
				'required'	=> false,
6549
			),
6550
			'fromaddr' => array(
6551
				'name'		=> 'fromaddr',
6552
				'type'		=> 'varchar',
6553
				'len'		=> 100,
6554
				'required'	=> true,
6555
			),
6556
			'toaddr' => array(
6557
				'name'		=> 'toaddr',
6558
				'type'		=> 'varchar',
6559
				'len'		=> 100,
6560
				'required'	=> true,
6561
			),
6562
			'senddate' => array(
6563
				'name'		=> 'senddate',
6564
				'type'		=> 'datetime',
6565
				'required'	=> true,
6566
			),
6567
			'message_id' => array(
6568
				'name'		=> 'message_id',
6569
				'type'		=> 'varchar',
6570
				'len'		=> 255,
6571
				'required'	=> false,
6572
			),
6573
			'mailsize' => array(
6574
				'name'		=> 'mailsize',
6575
				'type'		=> 'uint',
6576
				'len'		=> 16,
6577
				'required'	=> true,
6578
			),
6579
			'uid' => array(
6580
				'name'		=> 'uid',
6581
				'type'		=> 'uint',
6582
				'len'		=> 32,
6583
				'required'	=> true,
6584
			),
6585
			'msgno' => array(
6586
				'name'		=> 'msgno',
6587
				'type'		=> 'uint',
6588
				'len'		=> 32,
6589
				'required'	=> false,
6590
			),
6591
			'recent' => array(
6592
				'name'		=> 'recent',
6593
				'type'		=> 'tinyint',
6594
				'len'		=> 1,
6595
				'required'	=> true,
6596
			),
6597
			'flagged' => array(
6598
				'name'		=> 'flagged',
6599
				'type'		=> 'tinyint',
6600
				'len'		=> 1,
6601
				'required'	=> true,
6602
			),
6603
			'answered' => array(
6604
				'name'		=> 'answered',
6605
				'type'		=> 'tinyint',
6606
				'len'		=> 1,
6607
				'required'	=> true,
6608
			),
6609
			'deleted' => array(
6610
				'name'		=> 'deleted',
6611
				'type'		=> 'tinyint',
6612
				'len'		=> 1,
6613
				'required'	=> true,
6614
			),
6615
			'seen' => array(
6616
				'name'		=> 'seen',
6617
				'type'		=> 'tinyint',
6618
				'len'		=> 1,
6619
				'required'	=> true,
6620
			),
6621
			'draft' => array(
6622
				'name'		=> 'draft',
6623
				'type'		=> 'tinyint',
6624
				'len'		=> 1,
6625
				'required'	=> true,
6626
			),
6627
		);
6628
	*/
6629 143
	function __construct() {
6630 143
		global $dictionary;
6631
6632 143
		if(!isset($dictionary['email_cache']) || empty($dictionary['email_cache'])) {
6633
			if(file_exists('custom/metadata/email_cacheMetaData.php')) {
6634
			   include('custom/metadata/email_cacheMetaData.php');
6635
			} else {
6636
			   include('metadata/email_cacheMetaData.php');
6637
			}
6638
		}
6639
6640 143
		$this->fieldDefs = $dictionary['email_cache']['fields'];
6641 143
		$this->indices = $dictionary['email_cache']['indices'];
6642 143
	}
6643
}
6644