Test Failed
Push — CI ( 0f01dd...c95a04 )
by Adam
55:13
created

InboundEmail::findOptimumSettings()   F

Complexity

Conditions 37
Paths > 20000

Size

Total Lines 213
Code Lines 156

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 213
rs 2
cc 37
eloc 156
nc 33793
nop 7

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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