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

Email   F

Complexity

Total Complexity 516

Size/Duplication

Total Lines 3118
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 14
Metric Value
wmc 516
lcom 1
cbo 14
dl 0
loc 3118
rs 1.913

66 Methods

Rating   Name   Duplication   Size   Complexity  
A email2init() 0 4 1
A bean_implements() 0 7 2
B email2saveAttachment() 0 27 4
B safeAttachmentName() 0 24 4
B email2ParseAddresses() 0 40 6
B email2ParseAddressesForAddressesOnly() 0 34 6
A email2GetMime() 0 13 4
C sendEmailTest() 0 53 9
A decodeDuringSend() 0 5 1
A isDraftEmail() 0 4 3
F email2Send() 0 519 133
B getNamePlusEmailAddressesForCompose() 0 56 6
A _arrayToDelimitedString() 0 11 3
C save() 0 55 17
A saveTempNoteAttachments() 0 16 2
C saveEmailAddresses() 0 49 11
A linkEmailToAddress() 0 16 3
B cleanEmails() 0 19 5
A saveEmailText() 0 13 3
B retrieve() 0 26 5
C retrieveEmailAddresses() 0 31 8
A retrieveEmailText() 0 14 1
A delete() 0 15 2
A getForwardHeader() 0 18 1
C getNotes() 0 40 7
A getReplyHeader() 0 9 1
A quotePlainTextEmail() 0 13 2
A quoteHtmlEmail() 0 10 2
A quoteHtmlEmailForNewEmailUI() 0 11 2
C check_email_settings() 0 30 11
B js_set_archived() 0 38 1
A u_get_clear_form_js() 0 21 4
A pickOneButton() 0 11 1
A getUserEditorPreference() 0 21 3
B parse_addrs() 0 57 9
A remove_empty_fields() 0 12 3
F handleAttachments() 0 196 42
B hasSignatureInBody() 0 16 5
A removeAllNewlines() 0 6 1
A getStartPage() 0 21 4
B setMailer() 0 34 6
D handleBody() 0 41 9
A handleBodyInHTMLformat() 0 22 1
F send() 0 141 22
C listviewACLHelper() 0 66 12
A getSystemDefaultEmail() 0 13 1
D create_new_list_query() 0 41 10
C fill_in_additional_list_fields() 0 31 7
C fill_in_additional_detail_fields() 0 73 14
B create_export_query() 0 32 4
D get_list_view_data() 0 69 15
A quickCreateForm() 0 8 2
F searchImportedEmails() 0 93 18
A doesImportedEmailHaveAttachment() 0 11 2
C _genereateSearchImportedEmailsQuery() 0 44 8
C _generateSearchImportWhereClause() 0 63 11
A trimLongTo() 0 11 3
A get_summary_text() 0 3 1
B distributionForm() 0 137 1
B userSelectTable() 0 93 2
A checkInbox() 0 11 1
B fillPrimaryParentFields() 0 22 6
B cid2Link() 0 16 5
A cids2Links() 0 9 3
C setFieldNullable() 0 23 9
A revertFieldNullable() 0 12 4

How to fix   Complexity   

Complex Class

Complex classes like Email often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Email, and based on these observations, apply Extract Interface, too.

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/SugarPHPMailer.php');
43
require_once 'include/upload_file.php';
44
45
class Email extends SugarBean {
46
	/* SugarBean schema */
47
	var $id;
48
	var $date_entered;
49
	var $date_modified;
50
	var $assigned_user_id;
51
	var $assigned_user_name;
52
	var $modified_user_id;
53
	var $created_by;
54
	var $deleted;
55
	var $from_addr;
56
	var $reply_to_addr;
57
	var $to_addrs;
58
    var $cc_addrs;
59
    var $bcc_addrs;
60
	var $message_id;
61
62
	/* Bean Attributes */
63
	var $name;
64
    var $type = 'archived';
65
    var $date_sent;
66
	var $status;
67
	var $intent;
68
	var $mailbox_id;
69
	var $from_name;
70
71
	var $reply_to_status;
72
	var $reply_to_name;
73
	var $reply_to_email;
74
	var $description;
75
	var $description_html;
76
	var $raw_source;
77
	var $parent_id;
78
	var $parent_type;
79
80
	/* link attributes */
81
	var $parent_name;
82
83
84
	/* legacy */
85
	var $date_start; // legacy
86
	var $time_start; // legacy
87
	var $from_addr_name;
88
	var $to_addrs_arr;
89
    var $cc_addrs_arr;
90
    var $bcc_addrs_arr;
91
	var $to_addrs_ids;
92
	var $to_addrs_names;
93
	var $to_addrs_emails;
94
	var $cc_addrs_ids;
95
	var $cc_addrs_names;
96
	var $cc_addrs_emails;
97
	var $bcc_addrs_ids;
98
	var $bcc_addrs_names;
99
	var $bcc_addrs_emails;
100
	var $contact_id;
101
	var $contact_name;
102
103
	/* Archive Email attrs */
104
	var $duration_hours;
105
106
107
108
	var $new_schema = true;
109
	var $table_name = 'emails';
110
	var $module_dir = 'Emails';
111
    var $module_name = 'Emails';
112
	var $object_name = 'Email';
113
	var $db;
114
115
	/* private attributes */
116
	var $rolloverStyle		= "<style>div#rollover {position: relative;float: left;margin: none;text-decoration: none;}div#rollover a:hover {padding: 0;text-decoration: none;}div#rollover a span {display: none;}div#rollover a:hover span {text-decoration: none;display: block;width: 250px;margin-top: 5px;margin-left: 5px;position: absolute;padding: 10px;color: #333;	border: 1px solid #ccc;	background-color: #fff;	font-size: 12px;z-index: 1000;}</style>\n";
117
	var $cachePath;
118
	var $cacheFile			= 'robin.cache.php';
119
	var $replyDelimiter	= "> ";
120
	var $emailDescription;
121
	var $emailDescriptionHTML;
122
	var $emailRawSource;
123
	var $link_action;
124
	var $emailAddress;
125
	var $attachments = array();
126
127
	/* to support Email 2.0 */
128
	var $isDuplicate;
129
	var $uid;
130
	var $to;
131
	var $flagged;
132
	var $answered;
133
	var $seen;
134
	var $draft;
135
	var $relationshipMap = array(
136
		'Contacts'	=> 'emails_contacts_rel',
137
		'Accounts'	=> 'emails_accounts_rel',
138
		'Leads'		=> 'emails_leads_rel',
139
		'Users'		=> 'emails_users_rel',
140
		'Prospects'	=> 'emails_prospects_rel',
141
	);
142
143
	/* public */
144
	var $et;		// EmailUI object
145
	// prefix to use when importing inlinge images in emails
146
	public $imagePrefix;
147
148
    /**
149
     * Used for keeping track of field defs that have been modified
150
     *
151
     * @var array
152
     */
153
    public $modifiedFieldDefs = array();
154
155
    public $attachment_image;
156
157
	/**
158
	 * sole constructor
159
	 */
160
	function Email()
161
	{
162
	    global $current_user;
163
	    $this->cachePath = sugar_cached('modules/Emails');
164
		parent::SugarBean();
165
166
		$this->emailAddress = new SugarEmailAddress();
167
168
		$this->imagePrefix = rtrim($GLOBALS['sugar_config']['site_url'], "/")."/cache/images/";
169
	}
170
171
	function email2init() {
172
		require_once('modules/Emails/EmailUI.php');
173
		$this->et = new EmailUI();
174
	}
175
176
	function bean_implements($interface){
177
		switch($interface){
178
			case 'ACL': return true;
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...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

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

Loading history...
179
			default: return false;
0 ignored issues
show
Coding Style introduced by
The default body in a switch statement must start on the line following the statement.

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

switch ($expr) {
    default:
        doSomething(); //right
        break;
}


switch ($expr) {
    default:

        doSomething(); //wrong
        break;
}

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

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

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

Loading history...
180
		}
181
182
	}
183
184
	/**
185
	 * Presaves one attachment for new email 2.0 spec
186
	 * DOES NOT CREATE A NOTE
187
	 * @return string ID of note associated with the attachment
188
	 */
189
	public function email2saveAttachment()
190
	{
191
        $email_uploads = "modules/Emails/{$GLOBALS['current_user']->id}";
192
	    $upload = new UploadFile('email_attachment');
193
		if(!$upload->confirm_upload()) {
194
		    $err = $upload->get_upload_error();
195
   		    if($err) {
196
   		        $GLOBALS['log']->error("Email Attachment could not be attached due to error: $err");
197
   		    }
198
   		    return array();
199
		}
200
201
		$guid = create_guid();
202
		$fileName = $upload->create_stored_filename();
203
        $GLOBALS['log']->debug("Email Attachment [$fileName]");
204
        if($upload->final_move($guid)) {
205
        	copy("upload://$guid", sugar_cached("$email_uploads/$guid"));
206
			return array(
207
					'guid' => $guid,
208
					'name' => $GLOBALS['db']->quote($fileName),
209
					'nameForDisplay' => $fileName
210
				);
211
        } else {
212
			$GLOBALS['log']->debug("Email Attachment [$fileName] could not be moved to upload dir");
213
			return array();
214
        }
215
	}
216
217
	function safeAttachmentName($filename) {
218
		global $sugar_config;
219
		$badExtension = false;
220
		//get position of last "." in file name
221
		$file_ext_beg = strrpos($filename, ".");
222
		$file_ext = "";
223
224
		//get file extension
225
		if($file_ext_beg !== false) {
226
			$file_ext = substr($filename, $file_ext_beg + 1);
227
		}
228
229
		//check to see if this is a file with extension located in "badext"
230
		foreach($sugar_config['upload_badext'] as $badExt) {
231
			if(strtolower($file_ext) == strtolower($badExt)) {
232
				//if found, then append with .txt and break out of lookup
233
				$filename = $filename . ".txt";
234
				$badExtension = true;
235
				break; // no need to look for more
236
			} // if
237
		} // foreach
238
239
		return $badExtension;
240
	} // fn
241
242
	/**
243
	 * takes output from email 2.0 to/cc/bcc fields and returns appropriate arrays for usage by PHPMailer
244
	 * @param string addresses
245
	 * @return array
246
	 */
247
	function email2ParseAddresses($addresses) {
248
		$addresses = from_html($addresses);
249
        $addresses = $this->et->unifyEmailString($addresses);
250
251
		$pattern = '/@.*,/U';
252
		preg_match_all($pattern, $addresses, $matchs);
253
		if (!empty($matchs[0])){
254
			$total = $matchs[0];
255
			foreach ($total as $match) {
256
				$convertedPattern = str_replace(',', '::;::', $match);
257
				$addresses = str_replace($match, $convertedPattern, $addresses);
258
			} //foreach
259
		}
260
261
		$exAddr = explode("::;::", $addresses);
262
263
		$ret = array();
264
		$clean = array("<", ">");
265
		$dirty = array("&lt;", "&gt;");
266
267
		foreach($exAddr as $addr) {
268
			$name = '';
269
270
			$addr = str_replace($dirty, $clean, $addr);
271
272
			if((strpos($addr, "<") === false) && (strpos($addr, ">") === false)) {
273
				$address = $addr;
274
			} else {
275
				$address = substr($addr, strpos($addr, "<") + 1, strpos($addr, ">") - 1 - strpos($addr, "<"));
276
				$name = substr($addr, 0, strpos($addr, "<"));
277
			}
278
279
			$addrTemp = array();
280
			$addrTemp['email'] = trim($address);
281
			$addrTemp['display'] = trim($name);
282
			$ret[] = $addrTemp;
283
		}
284
285
		return $ret;
286
	}
287
288
	/**
289
	 * takes output from email 2.0 to/cc/bcc fields and returns appropriate arrays for usage by PHPMailer
290
	 * @param string addresses
291
	 * @return array
292
	 */
293
	function email2ParseAddressesForAddressesOnly($addresses) {
294
		$addresses = from_html($addresses);
295
		$pattern = '/@.*,/U';
296
		preg_match_all($pattern, $addresses, $matchs);
297
		if (!empty($matchs[0])){
298
			$total = $matchs[0];
299
			foreach ($total as $match) {
300
				$convertedPattern = str_replace(',', '::;::', $match);
301
				$addresses = str_replace($match, $convertedPattern, $addresses);
302
			} //foreach
303
		}
304
305
		$exAddr = explode("::;::", $addresses);
306
307
		$ret = array();
308
		$clean = array("<", ">");
309
		$dirty = array("&lt;", "&gt;");
310
311
		foreach($exAddr as $addr) {
312
			$name = '';
313
314
			$addr = str_replace($dirty, $clean, $addr);
315
316
			if(strpos($addr, "<") && strpos($addr, ">")) {
317
				$address = substr($addr, strpos($addr, "<") + 1, strpos($addr, ">") - 1 - strpos($addr, "<"));
318
			} else {
319
				$address = $addr;
320
			}
321
322
			$ret[] = trim($address);
323
		}
324
325
		return $ret;
326
	}
327
328
	/**
329
	 * Determines MIME-type encoding as possible.
330
	 * @param string $fileLocation relative path to file
331
	 * @return string MIME-type
332
	 */
333
	function email2GetMime($fileLocation) {
334
	    if(!is_readable($fileLocation)) {
335
	        return 'application/octet-stream';
336
	    }
337
		if(function_exists('mime_content_type')) {
338
			$mime = mime_content_type($fileLocation);
339
		} elseif(function_exists('ext2mime')) {
340
			$mime = ext2mime($fileLocation);
341
		} else {
342
			$mime = 'application/octet-stream';
343
		}
344
		return $mime;
345
	}
346
347
348
	function sendEmailTest($mailserver_url, $port, $ssltls, $smtp_auth_req, $smtp_username, $smtppassword, $fromaddress, $toaddress, $mail_sendtype = 'smtp', $fromname = '') {
349
		global $current_user,$app_strings;
350
		$mod_strings = return_module_language($GLOBALS['current_language'], 'Emails'); //Called from EmailMan as well.
351
	    $mail = new SugarPHPMailer();
352
		$mail->Mailer = strtolower($mail_sendtype);
353
		if($mail->Mailer == 'smtp')
354
		{
355
    		$mail->Host = $mailserver_url;
356
    		$mail->Port = $port;
357
    		if (isset($ssltls) && !empty($ssltls)) {
358
    			$mail->protocol = "ssl://";
359
    	        if ($ssltls == 1) {
360
    	            $mail->SMTPSecure = 'ssl';
361
    	        } // if
362
    	        if ($ssltls == 2) {
363
    	            $mail->SMTPSecure = 'tls';
364
    	        } // if
365
    		} else {
366
    			$mail->protocol = "tcp://";
367
    		}
368
    		if ($smtp_auth_req) {
369
    			$mail->SMTPAuth = TRUE;
370
    			$mail->Username = $smtp_username;
371
    			$mail->Password = $smtppassword;
372
    		}
373
		}
374
		else
375
		    $mail->Mailer = 'sendmail';
376
377
		$mail->Subject = from_html($mod_strings['LBL_TEST_EMAIL_SUBJECT']);
378
		$mail->From = $fromaddress;
379
380
        if ($fromname != '') {
381
            $mail->FromName = html_entity_decode($fromname,ENT_QUOTES);
382
        } else {
383
            $mail->FromName = $current_user->name;
384
        }
385
386
        $mail->Sender = $mail->From;
387
		$mail->AddAddress($toaddress);
388
		$mail->Body = $mod_strings['LBL_TEST_EMAIL_BODY'];
389
390
		$return = array();
391
392
		if(!$mail->Send()) {
393
	        ob_clean();
394
	        $return['status'] = false;
395
	        $return['errorMessage'] = $app_strings['LBL_EMAIL_ERROR_PREPEND']. $mail->ErrorInfo;
396
	        return $return;
397
		} // if
398
		$return['status'] = true;
399
        return $return;
400
	} // fn
401
402
	function decodeDuringSend($htmlData) {
403
	    $htmlData = str_replace("sugarLessThan", "&lt;", $htmlData);
404
	    $htmlData = str_replace("sugarGreaterThan", "&gt;", $htmlData);
405
		return $htmlData;
406
	}
407
408
	/**
409
	 * Returns true or false if this email is a draft.
410
	 *
411
	 * @param array $request
412
	 * @return bool True indicates this email is a draft.
413
	 */
414
	function isDraftEmail($request)
415
	{
416
	    return ( isset($request['saveDraft']) || ($this->type == 'draft' && $this->status == 'draft') );
417
	}
418
419
	/**
420
	 * Sends Email for Email 2.0
421
	 */
422
	function email2Send($request) {
423
		global $mod_strings;
424
		global $app_strings;
425
		global $current_user;
426
		global $sugar_config;
427
		global $locale;
428
		global $timedate;
429
		global $beanList;
430
		global $beanFiles;
431
        $OBCharset = $locale->getPrecedentPreference('default_email_charset');
432
433
		/**********************************************************************
434
		 * Sugar Email PREP
435
		 */
436
		/* preset GUID */
437
438
		$orignialId = "";
439
		if(!empty($this->id)) {
440
			$orignialId = 	$this->id;
441
		} // if
442
443
		if(empty($this->id)) {
444
			$this->id = create_guid();
445
			$this->new_with_id = true;
446
		}
447
448
		/* satisfy basic HTML email requirements */
449
		$this->name = $request['sendSubject'];
450
		$this->description_html = '&lt;html&gt;&lt;body&gt;'.$request['sendDescription'].'&lt;/body&gt;&lt;/html&gt;';
451
452
		/**********************************************************************
453
		 * PHPMAILER PREP
454
		 */
455
		$mail = new SugarPHPMailer();
456
		$mail = $this->setMailer($mail, '', $_REQUEST['fromAccount']);
457
		if (empty($mail->Host) && !$this->isDraftEmail($request))
458
		{
459
            $this->status = 'send_error';
460
461
            if ($mail->oe->type == 'system')
462
            	echo($app_strings['LBL_EMAIL_ERROR_PREPEND']. $app_strings['LBL_EMAIL_INVALID_SYSTEM_OUTBOUND']);
463
             else
464
            	echo($app_strings['LBL_EMAIL_ERROR_PREPEND']. $app_strings['LBL_EMAIL_INVALID_PERSONAL_OUTBOUND']);
465
466
            return false;
467
		}
468
469
		$subject = $this->name;
470
		$mail->Subject = from_html($this->name);
471
472
		// work-around legacy code in SugarPHPMailer
473
		if($_REQUEST['setEditor'] == 1) {
474
			$_REQUEST['description_html'] = $_REQUEST['sendDescription'];
475
			$this->description_html = $_REQUEST['description_html'];
476
		} else {
477
			$this->description_html = '';
478
			$this->description = $_REQUEST['sendDescription'];
479
		}
480
		// end work-around
481
482
		if ( $this->isDraftEmail($request) )
483
		{
484
			if($this->type != 'draft' && $this->status != 'draft') {
485
	        	$this->id = create_guid();
486
	        	$this->new_with_id = true;
487
	        	$this->date_entered = "";
488
			} // if
489
			$q1 = "update emails_email_addr_rel set deleted = 1 WHERE email_id = '{$this->id}'";
490
			$r1 = $this->db->query($q1);
491
		} // if
492
493
		if (isset($request['saveDraft'])) {
494
			$this->type = 'draft';
495
			$this->status = 'draft';
496
			$forceSave = true;
497
		} else {
498
			/* Apply Email Templates */
499
			// do not parse email templates if the email is being saved as draft....
500
		    $toAddresses = $this->email2ParseAddresses($_REQUEST['sendTo']);
501
	        $sea = new SugarEmailAddress();
502
	        $object_arr = array();
503
504
			if( isset($_REQUEST['parent_type']) && !empty($_REQUEST['parent_type']) &&
505
				isset($_REQUEST['parent_id']) && !empty($_REQUEST['parent_id']) &&
506
				($_REQUEST['parent_type'] == 'Accounts' ||
507
				$_REQUEST['parent_type'] == 'Contacts' ||
508
				$_REQUEST['parent_type'] == 'Leads' ||
509
				$_REQUEST['parent_type'] == 'Users' ||
510
				$_REQUEST['parent_type'] == 'Prospects')) {
511
					if(isset($beanList[$_REQUEST['parent_type']]) && !empty($beanList[$_REQUEST['parent_type']])) {
512
						$className = $beanList[$_REQUEST['parent_type']];
513
						if(isset($beanFiles[$className]) && !empty($beanFiles[$className])) {
514
							if(!class_exists($className)) {
515
								require_once($beanFiles[$className]);
516
							}
517
							$bean = new $className();
518
							$bean->retrieve($_REQUEST['parent_id']);
519
	                		$object_arr[$bean->module_dir] = $bean->id;
520
						} // if
521
					} // if
522
			}
523
			foreach($toAddresses as $addrMeta) {
524
				$addr = $addrMeta['email'];
525
				$beans = $sea->getBeansByEmailAddress($addr);
526
				foreach($beans as $bean) {
527
					if (!isset($object_arr[$bean->module_dir])) {
528
						$object_arr[$bean->module_dir] = $bean->id;
529
					}
530
				}
531
			}
532
533
	        /* template parsing */
534
	        if (empty($object_arr)) {
535
	          $object_arr= array('Contacts' => '123');
536
	        }
537
	        $object_arr['Users'] = $current_user->id;
538
	        $this->description_html = EmailTemplate::parse_template($this->description_html, $object_arr);
539
	        $this->name = EmailTemplate::parse_template($this->name, $object_arr);
540
	        $this->description = EmailTemplate::parse_template($this->description, $object_arr);
541
	        $this->description = html_entity_decode($this->description,ENT_COMPAT,'UTF-8');
0 ignored issues
show
Unused Code introduced by
The call to html_entity_decode() has too many arguments starting with 'UTF-8'.

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...
542
			if($this->type != 'draft' && $this->status != 'draft') {
543
	        	$this->id = create_guid();
544
	        	$this->date_entered = "";
545
	        	$this->new_with_id = true;
546
		        $this->type = 'out';
547
		        $this->status = 'sent';
548
			}
549
        }
550
551
        if(isset($_REQUEST['parent_type']) && empty($_REQUEST['parent_type']) &&
552
			isset($_REQUEST['parent_id']) && empty($_REQUEST['parent_id']) ) {
553
				$this->parent_id = "";
554
				$this->parent_type = "";
555
		} // if
556
557
558
        $mail->Subject = $this->name;
559
        $mail = $this->handleBody($mail);
560
        $mail->Subject = $this->name;
561
        $this->description_html = from_html($this->description_html);
562
        $this->description_html = $this->decodeDuringSend($this->description_html);
563
		$this->description = $this->decodeDuringSend($this->description);
564
565
		/* from account */
566
		$replyToAddress = $current_user->emailAddress->getReplyToAddress($current_user, true);
567
		$replyToName = "";
568
		if(empty($request['fromAccount'])) {
569
			$defaults = $current_user->getPreferredEmail();
570
			$mail->From = $defaults['email'];
571
			$mail->FromName = $defaults['name'];
572
			$replyToName = $mail->FromName;
573
			//$replyToAddress = $current_user->emailAddress->getReplyToAddress($current_user);
574
		} else {
575
			// passed -> user -> system default
576
			$ie = new InboundEmail();
577
			$ie->retrieve($request['fromAccount']);
578
			$storedOptions = unserialize(base64_decode($ie->stored_options));
579
			$fromName = "";
580
			$fromAddress = "";
581
			$replyToName = "";
582
			//$replyToAddress = "";
583
			if (!empty($storedOptions)) {
584
				$fromAddress = $storedOptions['from_addr'];
585
				$fromName = from_html($storedOptions['from_name']);
586
				$replyToAddress = (isset($storedOptions['reply_to_addr']) ? $storedOptions['reply_to_addr'] : "");
587
				$replyToName = (isset($storedOptions['reply_to_name']) ? from_html($storedOptions['reply_to_name']) : "");
588
			} // if
589
			$defaults = $current_user->getPreferredEmail();
590
			// Personal Account doesn't have reply To Name and Reply To Address. So add those columns on UI
591
			// After adding remove below code
592
593
			// code to remove
594
			if ($ie->is_personal)
595
			{
596
				if (empty($replyToAddress))
597
				{
598
					$replyToAddress = $current_user->emailAddress->getReplyToAddress($current_user, true);
599
				} // if
600
				if (empty($replyToName))
601
				{
602
					$replyToName = $defaults['name'];
603
				} // if
604
				//Personal accounts can have a reply_address, which should
605
				//overwrite the users set default.
606
				if( !empty($storedOptions['reply_to_addr']) )
607
					$replyToAddress = $storedOptions['reply_to_addr'];
608
609
			}
610
			// end of code to remove
611
			$mail->From = (!empty($fromAddress)) ? $fromAddress : $defaults['email'];
612
			$mail->FromName = (!empty($fromName)) ? $fromName : $defaults['name'];
613
			$replyToName = (!empty($replyToName)) ? $replyToName : $mail->FromName;
614
		}
615
616
		$mail->Sender = $mail->From; /* set Return-Path field in header to reduce spam score in emails sent via Sugar's Email module */
617
618
		if (!empty($replyToAddress)) {
619
			$mail->AddReplyTo($replyToAddress,$locale->translateCharsetMIME(trim( $replyToName), 'UTF-8', $OBCharset));
620
		} else {
621
			$mail->AddReplyTo($mail->From,$locale->translateCharsetMIME(trim( $mail->FromName), 'UTF-8', $OBCharset));
622
		} // else
623
        $emailAddressCollection = array(); // used in linking to beans below
624
		// handle to/cc/bcc
625
		foreach($this->email2ParseAddresses($request['sendTo']) as $addr_arr) {
626
			if(empty($addr_arr['email'])) continue;
627
628
			if(empty($addr_arr['display'])) {
629
				$mail->AddAddress($addr_arr['email'], "");
630
			} else {
631
				$mail->AddAddress($addr_arr['email'],$locale->translateCharsetMIME(trim( $addr_arr['display']), 'UTF-8', $OBCharset));
632
			}
633
			$emailAddressCollection[] = $addr_arr['email'];
634
		}
635
		foreach($this->email2ParseAddresses($request['sendCc']) as $addr_arr) {
636
			if(empty($addr_arr['email'])) continue;
637
638
			if(empty($addr_arr['display'])) {
639
				$mail->AddCC($addr_arr['email'], "");
640
			} else {
641
				$mail->AddCC($addr_arr['email'],$locale->translateCharsetMIME(trim( $addr_arr['display']), 'UTF-8', $OBCharset));
642
			}
643
			$emailAddressCollection[] = $addr_arr['email'];
644
		}
645
646
		foreach($this->email2ParseAddresses($request['sendBcc']) as $addr_arr) {
647
			if(empty($addr_arr['email'])) continue;
648
649
			if(empty($addr_arr['display'])) {
650
				$mail->AddBCC($addr_arr['email'], "");
651
			} else {
652
				$mail->AddBCC($addr_arr['email'],$locale->translateCharsetMIME(trim( $addr_arr['display']), 'UTF-8', $OBCharset));
653
			}
654
			$emailAddressCollection[] = $addr_arr['email'];
655
		}
656
657
658
		/* parse remove attachments array */
659
		$removeAttachments = array();
660
		if(!empty($request['templateAttachmentsRemove'])) {
661
			$exRemove = explode("::", $request['templateAttachmentsRemove']);
662
663
			foreach($exRemove as $file) {
664
				$removeAttachments = substr($file, 0, 36);
665
			}
666
		}
667
668
		/* handle attachments */
669
		if(!empty($request['attachments'])) {
670
			$exAttachments = explode("::", $request['attachments']);
671
672
			foreach($exAttachments as $file) {
673
				$file = trim(from_html($file));
674
				$file = str_replace("\\", "", $file);
675
				if(!empty($file)) {
676
					//$fileLocation = $this->et->userCacheDir."/{$file}";
677
					$fileGUID = preg_replace('/[^a-z0-9\-]/', "", substr($file, 0, 36));
678
					$fileLocation = $this->et->userCacheDir."/{$fileGUID}";
679
					$filename = substr($file, 36, strlen($file)); // strip GUID	for PHPMailer class to name outbound file
680
681
					$mail->AddAttachment($fileLocation,$filename, 'base64', $this->email2GetMime($fileLocation));
682
					//$mail->AddAttachment($fileLocation, $filename, 'base64');
683
684
					// only save attachments if we're archiving or drafting
685
					if((($this->type == 'draft') && !empty($this->id)) || (isset($request['saveToSugar']) && $request['saveToSugar'] == 1)) {
686
						$note = new Note();
687
						$note->id = create_guid();
688
						$note->new_with_id = true; // duplicating the note with files
689
						$note->parent_id = $this->id;
690
						$note->parent_type = $this->module_dir;
691
						$note->name = $filename;
692
						$note->filename = $filename;
693
						$note->file_mime_type = $this->email2GetMime($fileLocation);
694
                        $dest = "upload://{$note->id}";
695
						if(!copy($fileLocation, $dest)) {
696
							$GLOBALS['log']->debug("EMAIL 2.0: could not copy attachment file to $fileLocation => $dest");
697
						}
698
699
						$note->save();
700
					}
701
				}
702
			}
703
		}
704
705
		/* handle sugar documents */
706
		if(!empty($request['documents'])) {
707
			$exDocs = explode("::", $request['documents']);
708
709
			foreach($exDocs as $docId) {
710
				$docId = trim($docId);
711
				if(!empty($docId)) {
712
					$doc = new Document();
713
					$docRev = new DocumentRevision();
714
					$doc->retrieve($docId);
715
					$docRev->retrieve($doc->document_revision_id);
716
717
					$filename = $docRev->filename;
718
					$docGUID = preg_replace('/[^a-z0-9\-]/', "", $docRev->id);
719
					$fileLocation = "upload://{$docGUID}";
720
					$mime_type = $docRev->file_mime_type;
721
					$mail->AddAttachment($fileLocation,$locale->translateCharsetMIME(trim($filename), 'UTF-8', $OBCharset), 'base64', $mime_type);
722
723
					// only save attachments if we're archiving or drafting
724
					if((($this->type == 'draft') && !empty($this->id)) || (isset($request['saveToSugar']) && $request['saveToSugar'] == 1)) {
725
						$note = new Note();
726
						$note->id = create_guid();
727
						$note->new_with_id = true; // duplicating the note with files
728
						$note->parent_id = $this->id;
729
						$note->parent_type = $this->module_dir;
730
						$note->name = $filename;
731
						$note->filename = $filename;
732
						$note->file_mime_type = $mime_type;
733
                        $dest = "upload://{$note->id}";
734
						if(!copy($fileLocation, $dest)) {
735
							$GLOBALS['log']->debug("EMAIL 2.0: could not copy SugarDocument revision file $fileLocation => $dest");
736
						}
737
738
						$note->save();
739
					}
740
				}
741
			}
742
		}
743
744
		/* handle template attachments */
745
		if(!empty($request['templateAttachments'])) {
746
747
			$exNotes = explode("::", $request['templateAttachments']);
748
			foreach($exNotes as $noteId) {
749
				$noteId = trim($noteId);
750
				if(!empty($noteId)) {
751
					$note = new Note();
752
					$note->retrieve($noteId);
753
					if (!empty($note->id)) {
754
						$filename = $note->filename;
755
						$noteGUID = preg_replace('/[^a-z0-9\-]/', "", $note->id);
756
						$fileLocation = "upload://{$noteGUID}";
757
						$mime_type = $note->file_mime_type;
758
						if (!$note->embed_flag) {
759
							$mail->AddAttachment($fileLocation,$filename, 'base64', $mime_type);
760
							// only save attachments if we're archiving or drafting
761
							if((($this->type == 'draft') && !empty($this->id)) || (isset($request['saveToSugar']) && $request['saveToSugar'] == 1)) {
762
763
								if ($note->parent_id != $this->id)
764
								    $this->saveTempNoteAttachments($filename,$fileLocation, $mime_type);
765
							} // if
766
767
						} // if
768
					} else {
769
						//$fileLocation = $this->et->userCacheDir."/{$file}";
770
						$fileGUID = preg_replace('/[^a-z0-9\-]/', "", substr($noteId, 0, 36));
771
						$fileLocation = $this->et->userCacheDir."/{$fileGUID}";
772
						//$fileLocation = $this->et->userCacheDir."/{$noteId}";
773
						$filename = substr($noteId, 36, strlen($noteId)); // strip GUID	for PHPMailer class to name outbound file
774
775
						$mail->AddAttachment($fileLocation,$locale->translateCharsetMIME(trim($filename), 'UTF-8', $OBCharset), 'base64', $this->email2GetMime($fileLocation));
776
777
						//If we are saving an email we were going to forward we need to save the attachments as well.
778
						if( (($this->type == 'draft') && !empty($this->id))
779
						      || (isset($request['saveToSugar']) && $request['saveToSugar'] == 1))
780
						  {
781
						      $mimeType = $this->email2GetMime($fileLocation);
782
						      $this->saveTempNoteAttachments($filename,$fileLocation, $mimeType);
783
						 } // if
784
					}
785
				}
786
			}
787
		}
788
789
790
791
		/**********************************************************************
792
		 * Final Touches
793
		 */
794
		/* save email to sugar? */
795
		$forceSave = false;
796
797
		if($this->type == 'draft' && !isset($request['saveDraft'])) {
798
			// sending a draft email
799
			$this->type = 'out';
800
			$this->status = 'sent';
801
			$forceSave = true;
802
		} elseif(isset($request['saveDraft'])) {
803
			$this->type = 'draft';
804
			$this->status = 'draft';
805
			$forceSave = true;
806
		}
807
808
		      /**********************************************************************
809
         * SEND EMAIL (finally!)
810
         */
811
        $mailSent = false;
812
        if ($this->type != 'draft') {
813
            $mail->prepForOutbound();
814
            $mail->Body = $this->decodeDuringSend($mail->Body);
815
            $mail->AltBody = $this->decodeDuringSend($mail->AltBody);
816
            if (!$mail->Send()) {
817
                $this->status = 'send_error';
818
                ob_clean();
819
                echo($app_strings['LBL_EMAIL_ERROR_PREPEND']. $mail->ErrorInfo);
820
                return false;
821
            }
822
        }
823
824
		if ((!(empty($orignialId) || isset($request['saveDraft']) || ($this->type == 'draft' && $this->status == 'draft'))) &&
825
			(($_REQUEST['composeType'] == 'reply') || ($_REQUEST['composeType'] == 'replyAll') || ($_REQUEST['composeType'] == 'replyCase')) && ($orignialId != $this->id)) {
826
			$originalEmail = new Email();
827
			$originalEmail->retrieve($orignialId);
828
			$originalEmail->reply_to_status = 1;
829
			$originalEmail->save();
830
			$this->reply_to_status = 0;
831
		} // if
832
833
		if ($_REQUEST['composeType'] == 'reply' || $_REQUEST['composeType'] == 'replyCase') {
834
			if (isset($_REQUEST['ieId']) && isset($_REQUEST['mbox'])) {
835
				$emailFromIe = new InboundEmail();
836
				$emailFromIe->retrieve($_REQUEST['ieId']);
837
				$emailFromIe->mailbox = $_REQUEST['mbox'];
838
				if (isset($emailFromIe->id) && $emailFromIe->is_personal) {
839
					if ($emailFromIe->isPop3Protocol()) {
840
						$emailFromIe->mark_answered($this->uid, 'pop3');
841
					}
842
					elseif ($emailFromIe->connectMailserver() == 'true') {
843
						$emailFromIe->markEmails($this->uid, 'answered');
844
						$emailFromIe->mark_answered($this->uid);
845
					}
846
				}
847
			}
848
		}
849
850
851
		if(	$forceSave ||
852
			$this->type == 'draft' ||
853
			(isset($request['saveToSugar']) && $request['saveToSugar'] == 1)) {
854
855
			// saving a draft OR saving a sent email
856
			$decodedFromName = mb_decode_mimeheader($mail->FromName);
857
			$this->from_addr = "{$decodedFromName} <{$mail->From}>";
858
			$this->from_addr_name = $this->from_addr;
859
			$this->to_addrs = $_REQUEST['sendTo'];
860
			$this->to_addrs_names = $_REQUEST['sendTo'];
861
			$this->cc_addrs = $_REQUEST['sendCc'];
862
			$this->cc_addrs_names = $_REQUEST['sendCc'];
863
			$this->bcc_addrs = $_REQUEST['sendBcc'];
864
			$this->bcc_addrs_names = $_REQUEST['sendBcc'];
865
			$this->assigned_user_id = $current_user->id;
866
867
			$this->date_sent = $timedate->now();
868
			///////////////////////////////////////////////////////////////////
869
			////	LINK EMAIL TO SUGARBEANS BASED ON EMAIL ADDY
870
871
			if( isset($_REQUEST['parent_type']) && !empty($_REQUEST['parent_type']) &&
872
				isset($_REQUEST['parent_id']) && !empty($_REQUEST['parent_id']) ) {
873
	                $this->parent_id = $_REQUEST['parent_id'];
874
	                $this->parent_type = $_REQUEST['parent_type'];
875
					$q = "SELECT count(*) c FROM emails_beans WHERE  email_id = '{$this->id}' AND bean_id = '{$_REQUEST['parent_id']}' AND bean_module = '{$_REQUEST['parent_type']}'";
876
					$r = $this->db->query($q);
877
					$a = $this->db->fetchByAssoc($r);
878
					if($a['c'] <= 0) {
879
						if(isset($beanList[$_REQUEST['parent_type']]) && !empty($beanList[$_REQUEST['parent_type']])) {
880
							$className = $beanList[$_REQUEST['parent_type']];
881
							if(isset($beanFiles[$className]) && !empty($beanFiles[$className])) {
882
								if(!class_exists($className)) {
883
									require_once($beanFiles[$className]);
884
								}
885
								$bean = new $className();
886
								$bean->retrieve($_REQUEST['parent_id']);
887
								if($bean->load_relationship('emails')) {
888
									$bean->emails->add($this->id);
889
								} // if
890
891
							} // if
892
893
						} // if
894
895
					} // if
896
897
				} else {
898
					if(!class_exists('aCase')) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies 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 if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

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

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
899
900
					}
901
					else{
902
						$c = new aCase();
903
						if($caseId = InboundEmail::getCaseIdFromCaseNumber($mail->Subject, $c)) {
904
							$c->retrieve($caseId);
905
							$c->load_relationship('emails');
906
							$c->emails->add($this->id);
907
							$this->parent_type = "Cases";
908
							$this->parent_id = $caseId;
909
						} // if
910
					}
911
912
				} // else
913
914
			////	LINK EMAIL TO SUGARBEANS BASED ON EMAIL ADDY
915
			///////////////////////////////////////////////////////////////////
916
			$this->save();
917
		}
918
919
		if(!empty($request['fromAccount'])) {
920
			if (isset($ie->id) && !$ie->isPop3Protocol() && $mail->oe->mail_smtptype != 'gmail') {
921
				$sentFolder = $ie->get_stored_options("sentFolder");
922
				if (!empty($sentFolder)) {
923
					$data = $mail->CreateHeader() . "\r\n" . $mail->CreateBody() . "\r\n";
924
					$ie->mailbox = $sentFolder;
925
					if ($ie->connectMailserver() == 'true') {
926
						$connectString = $ie->getConnectString($ie->getServiceString(), $ie->mailbox);
927
						$returnData = imap_append($ie->conn,$connectString, $data, "\\Seen");
928
						if (!$returnData) {
929
							$GLOBALS['log']->debug("could not copy email to {$ie->mailbox} for {$ie->name}");
930
						} // if
931
					} else {
932
						$GLOBALS['log']->debug("could not connect to mail serve for folder {$ie->mailbox} for {$ie->name}");
933
					} // else
934
				} else {
935
					$GLOBALS['log']->debug("could not copy email to {$ie->mailbox} sent folder as its empty");
936
				} // else
937
			} // if
938
		} // if
939
		return true;
940
	} // end email2send
941
942
    /**
943
     * Generates a config-specified separated name and addresses to be used in compose email screen for
944
     * contacts or leads from listview
945
     * By default, use comma, but allow for non-standard delimiters as specified in email_address_separator
946
     *
947
     * @param $module string module name
948
     * @param $idsArray array of record ids to get the email address for
949
     * @return string (config-specified) delimited list of email addresses
950
     */
951
    public function getNamePlusEmailAddressesForCompose($module, $idsArray)
952
    {
953
        global $locale;
954
        $result = array();
955
956
        foreach ($idsArray as $id)
957
        {
958
            // Load bean
959
            $bean = BeanFactory::getBean($module, $id);
960
961
            // Got a bean
962
            if (!empty($bean))
963
            {
964
                // For CE, just get primary e-mail address
965
                $emailAddress = $bean->email1;
966
967
968
                // If we have an e-mail address loaded
969
                if (!empty($emailAddress))
970
                {
971
                    // Use bean name by default
972
                    $fullName = $bean->name;
973
974
                    // Depending on module, format the name
975
                    if (in_array($module, array('Users', 'Employees')))
976
                    {
977
                        $fullName = from_html(
978
                            $locale->getLocaleFormattedName(
979
                                $bean->first_name,
980
                                $bean->last_name,
981
                                '',
982
                                $bean->title
983
                            )
984
                        );
985
                    }
986
                    else if (SugarModule::get($module)->moduleImplements('Person'))
987
                    {
988
                        $fullName = from_html(
989
                            $locale->getLocaleFormattedName(
990
                                $bean->first_name,
991
                                $bean->last_name,
992
                                $bean->salutation,
993
                                $bean->title
994
                            )
995
                        );
996
                    }
997
998
                    // Make e-mail address in format "Name <@email>"
999
                    $result[$bean->id] = $fullName . " <" . from_html($emailAddress) . ">";
1000
                }
1001
            }
1002
        }
1003
1004
        // Broken out of method to facilitate unit testing
1005
        return $this->_arrayToDelimitedString($result);
1006
    }
1007
1008
    /**
1009
     * @param Array $arr - list of strings
1010
     * @return string the list of strings delimited by email_address_separator
1011
     */
1012
    function _arrayToDelimitedString($arr)
1013
    {
1014
        // bug 51804: outlook does not respect the correct email address separator (',') , so let
1015
        // clients override the default.
1016
        $separator = (isset($GLOBALS['sugar_config']['email_address_separator']) &&
1017
                        !empty($GLOBALS['sugar_config']['email_address_separator'])) ?
1018
                     $GLOBALS['sugar_config']['email_address_separator'] :
1019
                     ',';
1020
1021
		return join($separator, array_values($arr));
1022
    }
1023
1024
	/**
1025
	 * Overrides
1026
	 */
1027
	///////////////////////////////////////////////////////////////////////////
1028
	////	SAVERS
1029
	function save($check_notify = false) {
1030
        global $current_user;
1031
1032
		if($this->isDuplicate) {
1033
			$GLOBALS['log']->debug("EMAIL - tried to save a duplicate Email record");
1034
		} else {
1035
1036
			if(empty($this->id)) {
1037
				$this->id = create_guid();
1038
				$this->new_with_id = true;
1039
			}
1040
			$this->from_addr_name = $this->cleanEmails($this->from_addr_name);
1041
			$this->to_addrs_names = $this->cleanEmails($this->to_addrs_names);
1042
			$this->cc_addrs_names = $this->cleanEmails($this->cc_addrs_names);
1043
			$this->bcc_addrs_names = $this->cleanEmails($this->bcc_addrs_names);
1044
			$this->reply_to_addr = $this->cleanEmails($this->reply_to_addr);
1045
			$this->description = SugarCleaner::cleanHtml($this->description);
1046
            $this->description_html = SugarCleaner::cleanHtml($this->description_html, true);
1047
            $this->raw_source = SugarCleaner::cleanHtml($this->raw_source, true);
1048
			$this->saveEmailText();
1049
			$this->saveEmailAddresses();
1050
1051
			$GLOBALS['log']->debug('-------------------------------> Email called save()');
1052
1053
			// handle legacy concatenation of date and time fields
1054
			//Bug 39503 - SugarBean is not setting date_sent when seconds missing
1055
 			if(empty($this->date_sent)) {
1056
				global $timedate;
1057
				$date_sent_obj = $timedate->fromUser($timedate->merge_date_time($this->date_start, $this->time_start), $current_user);
1058
                 if (!empty($date_sent_obj) && ($date_sent_obj instanceof SugarDateTime)) {
1059
 				    $this->date_sent = $date_sent_obj->asDb();
1060
                 }
1061
			}
1062
1063
			parent::save($check_notify);
1064
1065
			if(!empty($this->parent_type) && !empty($this->parent_id)) {
1066
                if(!empty($this->fetched_row) && !empty($this->fetched_row['parent_id']) && !empty($this->fetched_row['parent_type'])) {
1067
                    if($this->fetched_row['parent_id'] != $this->parent_id || $this->fetched_row['parent_type'] != $this->parent_type) {
1068
                        $mod = strtolower($this->fetched_row['parent_type']);
1069
                        $rel = array_key_exists($mod, $this->field_defs) ? $mod : $mod . "_activities_emails"; //Custom modules rel name
1070
                        if($this->load_relationship($rel) ) {
1071
                            $this->$rel->delete($this->id, $this->fetched_row['parent_id']);
1072
                        }
1073
                    }
1074
                }
1075
                $mod = strtolower($this->parent_type);
1076
                $rel = array_key_exists($mod, $this->field_defs) ? $mod : $mod . "_activities_emails"; //Custom modules rel name
1077
                if($this->load_relationship($rel) ) {
1078
                    $this->$rel->add($this->parent_id);
1079
                }
1080
			}
1081
		}
1082
		$GLOBALS['log']->debug('-------------------------------> Email save() done');
1083
	}
1084
1085
	/**
1086
	 * Helper function to save temporary attachments assocaited to an email as note.
1087
	 *
1088
	 * @param string $filename
1089
	 * @param string $fileLocation
1090
	 * @param string $mimeType
1091
	 */
1092
	function saveTempNoteAttachments($filename,$fileLocation, $mimeType)
1093
	{
1094
	    $tmpNote = new Note();
1095
	    $tmpNote->id = create_guid();
1096
	    $tmpNote->new_with_id = true;
1097
	    $tmpNote->parent_id = $this->id;
1098
	    $tmpNote->parent_type = $this->module_dir;
1099
	    $tmpNote->name = $filename;
1100
	    $tmpNote->filename = $filename;
1101
	    $tmpNote->file_mime_type = $mimeType;
1102
	    $noteFile = "upload://{$tmpNote->id}";
1103
	    if(!copy($fileLocation, $noteFile)) {
1104
    	    $GLOBALS['log']->fatal("EMAIL 2.0: could not copy SugarDocument revision file $fileLocation => $noteFile");
1105
	    }
1106
	    $tmpNote->save();
1107
	}
1108
	/**
1109
	 * Handles normalization of Email Addressess
1110
	 */
1111
	function saveEmailAddresses() {
1112
		// from, single address
1113
		$fromId = $this->emailAddress->getEmailGUID(from_html($this->from_addr));
0 ignored issues
show
Bug introduced by
It seems like $this->from_addr can also be of type array<string,?,{"name":"?"}> or null; however, from_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...
1114
        if(!empty($fromId)){
1115
		  $this->linkEmailToAddress($fromId, 'from');
1116
        }
1117
1118
		// to, multiple
1119
		$replace = array(",",";");
1120
		$toaddrs = str_replace($replace, "::", from_html($this->to_addrs));
0 ignored issues
show
Bug introduced by
It seems like $this->to_addrs can also be of type array<string,?,{"name":"?"}> or null; however, from_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...
1121
		$exToAddrs = explode("::", $toaddrs);
1122
1123
		if(!empty($exToAddrs)) {
1124
			foreach($exToAddrs as $toaddr) {
1125
				$toaddr = trim($toaddr);
1126
				if(!empty($toaddr)) {
1127
					$toId = $this->emailAddress->getEmailGUID($toaddr);
1128
					$this->linkEmailToAddress($toId, 'to');
1129
				}
1130
			}
1131
		}
1132
1133
		// cc, multiple
1134
		$ccAddrs = str_replace($replace, "::", from_html($this->cc_addrs));
1135
		$exccAddrs = explode("::", $ccAddrs);
1136
1137
		if(!empty($exccAddrs)) {
1138
			foreach($exccAddrs as $ccAddr) {
1139
				$ccAddr = trim($ccAddr);
1140
				if(!empty($ccAddr)) {
1141
					$ccId = $this->emailAddress->getEmailGUID($ccAddr);
1142
					$this->linkEmailToAddress($ccId, 'cc');
1143
				}
1144
			}
1145
		}
1146
1147
		// bcc, multiple
1148
		$bccAddrs = str_replace($replace, "::", from_html($this->bcc_addrs));
1149
		$exbccAddrs = explode("::", $bccAddrs);
1150
		if(!empty($exbccAddrs)) {
1151
			foreach($exbccAddrs as $bccAddr) {
1152
				$bccAddr = trim($bccAddr);
1153
				if(!empty($bccAddr)) {
1154
					$bccId = $this->emailAddress->getEmailGUID($bccAddr);
1155
					$this->linkEmailToAddress($bccId, 'bcc');
1156
				}
1157
			}
1158
		}
1159
	}
1160
1161
	function linkEmailToAddress($id, $type) {
1162
		// TODO: make this update?
1163
		$q1 = "SELECT * FROM emails_email_addr_rel WHERE email_id = '{$this->id}' AND email_address_id = '{$id}' AND address_type = '{$type}' AND deleted = 0";
1164
		$r1 = $this->db->query($q1);
1165
		$a1 = $this->db->fetchByAssoc($r1);
1166
1167
		if(!empty($a1) && !empty($a1['id'])) {
1168
			return $a1['id'];
1169
		} else {
1170
			$guid = create_guid();
1171
			$q2 = "INSERT INTO emails_email_addr_rel VALUES('{$guid}', '{$this->id}', '{$type}', '{$id}', 0)";
1172
			$r2 = $this->db->query($q2);
1173
		}
1174
1175
		return $guid;
1176
	}
1177
1178
    protected $email_to_text = array(
1179
        "email_id" => "id",
1180
        "description" => "description",
1181
        "description_html" => "description_html",
1182
        "raw_source" => "raw_source",
1183
        "from_addr" => "from_addr_name",
1184
        "reply_to_addr" => "reply_to_addr",
1185
    	"to_addrs" => "to_addrs_names",
1186
        "cc_addrs" => "cc_addrs_names",
1187
        "bcc_addrs" => "bcc_addrs_names",
1188
    );
1189
1190
	function cleanEmails($emails)
1191
	{
1192
	    if(empty($emails)) return '';
1193
		$emails = str_replace(array(",",";"), "::", from_html($emails));
1194
		$addrs = explode("::", $emails);
1195
		$res = array();
1196
		foreach($addrs as $addr) {
1197
            $parts = $this->emailAddress->splitEmailAddress($addr);
1198
            if(empty($parts["email"])) {
1199
                continue;
1200
            }
1201
            if(!empty($parts["name"])) {
1202
                $res[] = "{$parts['name']} <{$parts['email']}>";
1203
            } else {
1204
                $res[] .= $parts["email"];
1205
            }
1206
		}
1207
		return join(", ", $res);
1208
	}
1209
1210
	protected function saveEmailText()
1211
	{
1212
        $text = SugarModule::get("EmailText")->loadBean();
1213
        foreach($this->email_to_text as $textfield=>$mailfield) {
1214
            $text->$textfield = $this->$mailfield;
1215
        }
1216
        $text->email_id = $this->id;
1217
		if(!$this->new_with_id) {
1218
            $this->db->update($text);
1219
		} else {
1220
		    $this->db->insert($text);
1221
		}
1222
	}
1223
1224
	///////////////////////////////////////////////////////////////////////////
1225
	////	RETRIEVERS
1226
	function retrieve($id = -1, $encoded=true, $deleted=true) {
1227
		// cn: bug 11915, return SugarBean's retrieve() call bean instead of $this
1228
		$ret = parent::retrieve($id, $encoded, $deleted);
1229
1230
		if($ret) {
1231
			$ret->retrieveEmailText();
1232
            //$ret->raw_source = SugarCleaner::cleanHtml($ret->raw_source);
1233
			$ret->description = to_html($ret->description);
1234
            //$ret->description_html = SugarCleaner::cleanHtml($ret->description_html);
1235
			$ret->retrieveEmailAddresses();
1236
1237
			$ret->date_start = '';
1238
			$ret->time_start = '';
1239
			$dateSent = explode(' ', $ret->date_sent);
1240
			if (!empty($dateSent)) {
1241
			    $ret->date_start = $dateSent[0];
1242
			    if ( isset($dateSent[1]) )
1243
			        $ret->time_start = $dateSent[1];
1244
			}
1245
			// for Email 2.0
1246
			foreach($ret as $k => $v) {
0 ignored issues
show
Bug introduced by
The expression $ret of type this<Email> is not traversable.
Loading history...
1247
				$this->$k = $v;
1248
			}
1249
		}
1250
		return $ret;
1251
	}
1252
1253
1254
	/**
1255
	 * Retrieves email addresses from GUIDs
1256
	 */
1257
	function retrieveEmailAddresses() {
1258
		$return = array();
1259
1260
		$q = "SELECT email_address, address_type
1261
				FROM emails_email_addr_rel eam
1262
				JOIN email_addresses ea ON ea.id = eam.email_address_id
1263
				WHERE eam.email_id = '{$this->id}' AND eam.deleted=0";
1264
		$r = $this->db->query($q);
1265
1266
		while($a = $this->db->fetchByAssoc($r)) {
1267
			if(!isset($return[$a['address_type']])) {
1268
				$return[$a['address_type']] = array();
1269
			}
1270
			$return[$a['address_type']][] = $a['email_address'];
1271
		}
1272
1273
		if(count($return) > 0) {
1274
			if(isset($return['from'])) {
1275
				$this->from_addr = implode(", ", $return['from']);
1276
			}
1277
			if(isset($return['to'])) {
1278
				$this->to_addrs = implode(", ", $return['to']);
1279
			}
1280
			if(isset($return['cc'])) {
1281
				$this->cc_addrs = implode(", ", $return['cc']);
1282
			}
1283
			if(isset($return['bcc'])) {
1284
				$this->bcc_addrs = implode(", ", $return['bcc']);
1285
			}
1286
		}
1287
	}
1288
1289
	/**
1290
	 * Handles longtext fields
1291
	 */
1292
	function retrieveEmailText() {
1293
		$q = "SELECT from_addr, reply_to_addr, to_addrs, cc_addrs, bcc_addrs, description, description_html, raw_source FROM emails_text WHERE email_id = '{$this->id}'";
1294
		$r = $this->db->query($q);
1295
		$a = $this->db->fetchByAssoc($r, false);
1296
1297
		$this->description = $a['description'];
1298
		$this->description_html = $a['description_html'];
1299
		$this->raw_source = $a['raw_source'];
1300
		$this->from_addr_name = $a['from_addr'];
1301
		$this->reply_to_addr  = $a['reply_to_addr'];
1302
		$this->to_addrs_names = $a['to_addrs'];
1303
		$this->cc_addrs_names = $a['cc_addrs'];
1304
		$this->bcc_addrs_names = $a['bcc_addrs'];
1305
	}
1306
1307
	function delete($id='') {
1308
		if(empty($id))
1309
			$id = $this->id;
1310
1311
        $id = $this->db->quote($id);
1312
1313
		$q  = "UPDATE emails SET deleted = 1 WHERE id = '{$id}'";
1314
		$qt = "UPDATE emails_text SET deleted = 1 WHERE email_id = '{$id}'";
1315
		$qf = "UPDATE folders_rel SET deleted = 1 WHERE polymorphic_id = '{$id}' AND polymorphic_module = 'Emails'";
1316
        $qn = "UPDATE notes SET deleted = 1 WHERE parent_id = '{$id}' AND parent_type = 'Emails'";
1317
        $this->db->query($q);
1318
        $this->db->query($qt);
1319
        $this->db->query($qf);
1320
        $this->db->query($qn);
1321
	}
1322
1323
	/**
1324
	 * creates the standard "Forward" info at the top of the forwarded message
1325
	 * @return string
1326
	 */
1327
	function getForwardHeader() {
1328
		global $mod_strings;
1329
		global $current_user;
1330
1331
		//$from = str_replace(array("&gt;","&lt;"), array(")","("), $this->from_name);
1332
		$from = to_html($this->from_name);
1333
		$subject = to_html($this->name);
1334
		$ret  = "<br /><br />";
1335
		$ret .= $this->replyDelimiter."{$mod_strings['LBL_FROM']} {$from}<br />";
1336
		$ret .= $this->replyDelimiter."{$mod_strings['LBL_DATE_SENT']} {$this->date_sent}<br />";
1337
		$ret .= $this->replyDelimiter."{$mod_strings['LBL_TO']} {$this->to_addrs}<br />";
1338
		$ret .= $this->replyDelimiter."{$mod_strings['LBL_CC']} {$this->cc_addrs}<br />";
1339
		$ret .= $this->replyDelimiter."{$mod_strings['LBL_SUBJECT']} {$subject}<br />";
1340
		$ret .= $this->replyDelimiter."<br />";
1341
1342
		return $ret;
1343
		//return from_html($ret);
1344
	}
1345
1346
    /**
1347
     * retrieves Notes that belong to this Email and stuffs them into the "attachments" attribute
1348
     */
1349
    function getNotes($id, $duplicate=false) {
1350
        if(!class_exists('Note')) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies 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 if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

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

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
1351
1352
        }
1353
1354
        $exRemoved = array();
1355
		if(isset($_REQUEST['removeAttachment'])) {
1356
			$exRemoved = explode('::', $_REQUEST['removeAttachment']);
1357
		}
1358
1359
        $noteArray = array();
1360
        $q = "SELECT id FROM notes WHERE parent_id = '".$id."'";
1361
        $r = $this->db->query($q);
1362
1363
        while($a = $this->db->fetchByAssoc($r)) {
1364
        	if(!in_array($a['id'], $exRemoved)) {
1365
	            $note = new Note();
1366
	            $note->retrieve($a['id']);
1367
1368
	            // duplicate actual file when creating forwards
1369
		        if($duplicate) {
1370
		        	if(!class_exists('UploadFile')) {
1371
		        		require_once('include/upload_file.php');
1372
		        	}
1373
		        	// save a brand new Note
1374
		        	$noteDupe->id = create_guid();
1375
		        	$noteDupe->new_with_id = true;
1376
					$noteDupe->parent_id = $this->id;
1377
					$noteDupe->parent_type = $this->module_dir;
1378
1379
					$noteFile = new UploadFile();
1380
					$noteFile->duplicate_file($a['id'], $note->id, $note->filename);
1381
1382
					$note->save();
1383
		        }
1384
		        // add Note to attachments array
1385
	            $this->attachments[] = $note;
1386
        	}
1387
        }
1388
    }
1389
1390
	/**
1391
	 * creates the standard "Reply" info at the top of the forwarded message
1392
	 * @return string
1393
	 */
1394
	function getReplyHeader() {
1395
		global $mod_strings;
1396
		global $current_user;
1397
1398
		$from = str_replace(array("&gt;","&lt;", ">","<"), array(")","(",")","("), $this->from_name);
1399
		$ret  = "<br>{$mod_strings['LBL_REPLY_HEADER_1']} {$this->date_start}, {$this->time_start}, {$from} {$mod_strings['LBL_REPLY_HEADER_2']}";
1400
1401
		return from_html($ret);
1402
	}
1403
1404
	/**
1405
	 * Quotes plain-text email text
1406
	 * @param string $text
1407
	 * @return string
1408
	 */
1409
	function quotePlainTextEmail($text) {
1410
		$quoted = "\n";
1411
1412
		// plain-text
1413
		$desc = nl2br(trim($text));
1414
		$exDesc = explode('<br />', $desc);
1415
1416
		foreach($exDesc as $k => $line) {
1417
			$quoted .= '> '.trim($line)."\r";
1418
		}
1419
1420
		return $quoted;
1421
	}
1422
1423
	/**
1424
	 * "quotes" (i.e., "> my text yadda" the HTML part of an email
1425
	 * @param string $text HTML text to quote
1426
	 * @return string
1427
	 */
1428
	function quoteHtmlEmail($text) {
1429
		$text = trim(from_html($text));
1430
1431
		if(empty($text)) {
1432
			return '';
1433
		}
1434
		$out = "<div style='border-left:1px solid #00c; padding:5px; margin-left:10px;'>{$text}</div>";
1435
1436
		return $out;
1437
	}
1438
1439
	/**
1440
	 * "quotes" (i.e., "> my text yadda" the HTML part of an email
1441
	 * @param string $text HTML text to quote
1442
	 * @return string
1443
	 */
1444
	function quoteHtmlEmailForNewEmailUI($text) {
1445
		$text = trim($text);
1446
1447
		if(empty($text)) {
1448
			return '';
1449
		}
1450
		$text = str_replace("\n", "<BR/>", $text);
1451
		$out = "<div style='border-left:1px solid #00c; padding:5px; margin-left:10px;'>{$text}</div>";
1452
1453
		return $out;
1454
	}
1455
1456
	/**
1457
	 * Ensures that the user is able to send outbound emails
1458
	 */
1459
	function check_email_settings() {
1460
		global $current_user;
1461
1462
		$mail_fromaddress = $current_user->emailAddress->getPrimaryAddress($current_user);
1463
		$replyToName = $current_user->getPreference('mail_fromname');
1464
		$mail_fromname = (!empty($replyToName)) ? $current_user->getPreference('mail_fromname') : $current_user->full_name;
1465
1466
		if(empty($mail_fromaddress)) {
1467
			return false;
1468
		}
1469
		if(empty($mail_fromname)) {
1470
	  		return false;
1471
		}
1472
1473
    	$send_type = $current_user->getPreference('mail_sendtype') ;
1474
		if (!empty($send_type) && $send_type == "SMTP") {
1475
			$mail_smtpserver = $current_user->getPreference('mail_smtpserver');
1476
			$mail_smtpport = $current_user->getPreference('mail_smtpport');
1477
			$mail_smtpauth_req = $current_user->getPreference('mail_smtpauth_req');
1478
			$mail_smtpuser = $current_user->getPreference('mail_smtpuser');
1479
			$mail_smtppass = $current_user->getPreference('mail_smtppass');
1480
			if (empty($mail_smtpserver) ||
1481
				empty($mail_smtpport) ||
1482
                (!empty($mail_smtpauth_req) && ( empty($mail_smtpuser) || empty($mail_smtppass)))
1483
			) {
1484
				return false;
1485
			}
1486
		}
1487
		return true;
1488
	}
1489
1490
	/**
1491
	 * outputs JS to set fields in the MassUpdate form in the "My Inbox" view
1492
	 */
1493
	function js_set_archived() {
1494
		global $mod_strings;
1495
		$script = '
1496
		<script type="text/javascript" language="JavaScript"><!-- Begin
1497
			function setArchived() {
1498
				var form = document.getElementById("MassUpdate");
1499
				var status = document.getElementById("mass_status");
1500
				var ok = false;
1501
1502
				for(var i=0; i < form.elements.length; i++) {
1503
					if(form.elements[i].name == "mass[]") {
1504
						if(form.elements[i].checked == true) {
1505
							ok = true;
1506
						}
1507
					}
1508
				}
1509
1510
				if(ok == true) {
1511
					var user = document.getElementById("mass_assigned_user_name");
1512
					var team = document.getElementById("team");
1513
1514
					user.value = "";
1515
					for(var j=0; j<status.length; j++) {
1516
						if(status.options[j].value == "archived") {
1517
							status.options[j].selected = true;
1518
							status.selectedIndex = j; // for IE
1519
						}
1520
					}
1521
1522
					form.submit();
1523
				} else {
1524
					alert("'.$mod_strings['ERR_ARCHIVE_EMAIL'].'");
1525
				}
1526
1527
			}
1528
		//  End --></script>';
1529
		return $script;
1530
	}
1531
1532
	/**
1533
	 * replaces the javascript in utils.php - more specialized
1534
	 */
1535
	function u_get_clear_form_js($type='', $group='', $assigned_user_id='') {
1536
		$uType				= '';
1537
		$uGroup				= '';
1538
		$uAssigned_user_id	= '';
1539
1540
		if(!empty($type)) { $uType = '&type='.$type; }
1541
		if(!empty($group)) { $uGroup = '&group='.$group; }
1542
		if(!empty($assigned_user_id)) { $uAssigned_user_id = '&assigned_user_id='.$assigned_user_id; }
1543
1544
		$the_script = '
1545
		<script type="text/javascript" language="JavaScript"><!-- Begin
1546
			function clear_form(form) {
1547
				var newLoc = "index.php?action=" + form.action.value + "&module=" + form.module.value + "&query=true&clear_query=true'.$uType.$uGroup.$uAssigned_user_id.'";
1548
				if(typeof(form.advanced) != "undefined"){
1549
					newLoc += "&advanced=" + form.advanced.value;
1550
				}
1551
				document.location.href= newLoc;
1552
			}
1553
		//  End --></script>';
1554
		return $the_script;
1555
	}
1556
1557
	function pickOneButton() {
1558
		global $theme;
1559
		global $mod_strings;
1560
		$out = '<div><input	title="'.$mod_strings['LBL_BUTTON_GRAB_TITLE'].'"
1561
						class="button"
1562
						type="button" name="button"
1563
						onClick="window.location=\'index.php?module=Emails&action=Grab\';"
1564
						style="margin-bottom:2px"
1565
						value="  '.$mod_strings['LBL_BUTTON_GRAB'].'  "></div>';
1566
		return $out;
1567
	}
1568
1569
	/**
1570
	 * Determines what Editor (HTML or Plain-text) the current_user uses;
1571
	 * @return string Editor type
1572
	 */
1573
	function getUserEditorPreference() {
1574
		global $sugar_config;
1575
		global $current_user;
1576
1577
		$editor = '';
1578
1579
		if(!isset($sugar_config['email_default_editor'])) {
1580
			$sugar_config = $current_user->setDefaultsInConfig();
1581
		}
1582
1583
		$userEditor = $current_user->getPreference('email_editor_option');
1584
		$systemEditor = $sugar_config['email_default_editor'];
1585
1586
		if($userEditor != '') {
1587
			$editor = $userEditor;
1588
		} else {
1589
			$editor = $systemEditor;
1590
		}
1591
1592
		return $editor;
1593
	}
1594
1595
	/**
1596
	 * takes the mess we pass from EditView and tries to create some kind of order
1597
	 * @param array addrs
1598
	 * @param array addrs_ids (from contacts)
1599
	 * @param array addrs_names (from contacts);
1600
	 * @param array addrs_emails (from contacts);
1601
	 * @return array Parsed assoc array to feed to PHPMailer
1602
	 */
1603
	function parse_addrs($addrs, $addrs_ids, $addrs_names, $addrs_emails) {
1604
		// cn: bug 9406 - enable commas to separate email addresses
1605
		$addrs = str_replace(",", ";", $addrs);
1606
1607
		$ltgt = array('&lt;','&gt;');
1608
		$gtlt = array('<','>');
1609
1610
		$return				= array();
1611
		$addrs				= str_replace($ltgt, '', $addrs);
1612
		$addrs_arr			= explode(";",$addrs);
1613
		$addrs_arr			= $this->remove_empty_fields($addrs_arr);
1614
		$addrs_ids_arr		= explode(";",$addrs_ids);
1615
		$addrs_ids_arr		= $this->remove_empty_fields($addrs_ids_arr);
1616
		$addrs_emails_arr	= explode(";",$addrs_emails);
1617
		$addrs_emails_arr	= $this->remove_empty_fields($addrs_emails_arr);
1618
		$addrs_names_arr	= explode(";",$addrs_names);
1619
		$addrs_names_arr	= $this->remove_empty_fields($addrs_names_arr);
1620
1621
		///////////////////////////////////////////////////////////////////////
1622
		////	HANDLE EMAILS HAND-WRITTEN
1623
		$contactRecipients = array();
1624
		$knownEmails = array();
1625
1626
		foreach($addrs_arr as $i => $v) {
1627
			if(trim($v) == "")
1628
				continue; // skip any "blanks" - will always have 1
1629
1630
			$recipient = array();
1631
1632
			//// get the email to see if we're dealing with a dupe
1633
			//// what crappy coding
1634
			preg_match("/[A-Z0-9._%-\']+@[A-Z0-9.-]+\.[A-Z]{2,}/i",$v, $match);
1635
1636
1637
			if(!empty($match[0]) && !in_array(trim($match[0]), $knownEmails)) {
1638
				$knownEmails[] = $match[0];
1639
				$recipient['email'] = $match[0];
1640
1641
				//// handle the Display name
1642
				$display = trim(str_replace($match[0], '', $v));
1643
1644
				//// only trigger a "displayName" <email@address> when necessary
1645
				if(isset($addrs_names_arr[$i])){
1646
						$recipient['display'] = $addrs_names_arr[$i];
1647
				}
1648
				else if(!empty($display)) {
1649
					$recipient['display'] = $display;
1650
				}
1651
				if(isset($addrs_ids_arr[$i]) && $addrs_emails_arr[$i] == $match[0]){
1652
					$recipient['contact_id'] = $addrs_ids_arr[$i];
1653
				}
1654
				$return[] = $recipient;
1655
			}
1656
		}
1657
1658
		return $return;
1659
	}
1660
1661
	function remove_empty_fields(&$arr) {
1662
		$newarr = array();
1663
1664
		foreach($arr as $field) {
1665
			$field = trim($field);
1666
			if(empty($field)) {
1667
				continue;
1668
			}
1669
			array_push($newarr,$field);
1670
		}
1671
		return $newarr;
1672
	}
1673
1674
	/**
1675
	 * handles attachments of various kinds when sending email
1676
	 */
1677
	function handleAttachments() {
1678
1679
1680
1681
1682
		global $mod_strings;
1683
1684
        ///////////////////////////////////////////////////////////////////////////
1685
        ////    ATTACHMENTS FROM DRAFTS
1686
        if(($this->type == 'out' || $this->type == 'draft') && $this->status == 'draft' && isset($_REQUEST['record'])) {
1687
            $this->getNotes($_REQUEST['record']); // cn: get notes from OLD email for use in new email
1688
        }
1689
        ////    END ATTACHMENTS FROM DRAFTS
1690
        ///////////////////////////////////////////////////////////////////////////
1691
1692
        ///////////////////////////////////////////////////////////////////////////
1693
        ////    ATTACHMENTS FROM FORWARDS
1694
        // Bug 8034 Jenny - Need the check for type 'draft' here to handle cases where we want to save
1695
        // forwarded messages as drafts.  We still need to save the original message's attachments.
1696
        if(($this->type == 'out' || $this->type == 'draft') &&
1697
        	isset($_REQUEST['origType']) && $_REQUEST['origType'] == 'forward' &&
1698
        	isset($_REQUEST['return_id']) && !empty($_REQUEST['return_id'])
1699
        ) {
1700
            $this->getNotes($_REQUEST['return_id'], true);
1701
        }
1702
1703
        // cn: bug 8034 - attachments from forward/replies lost when saving in draft
1704
        if(isset($_REQUEST['prior_attachments']) && !empty($_REQUEST['prior_attachments']) && $this->new_with_id == 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...
1705
        	$exIds = explode(",", $_REQUEST['prior_attachments']);
1706
        	if(!isset($_REQUEST['template_attachment'])) {
1707
        		$_REQUEST['template_attachment'] = array();
1708
        	}
1709
        	$_REQUEST['template_attachment'] = array_merge($_REQUEST['template_attachment'], $exIds);
1710
        }
1711
        ////    END ATTACHMENTS FROM FORWARDS
1712
        ///////////////////////////////////////////////////////////////////////////
1713
1714
		///////////////////////////////////////////////////////////////////////////
1715
		////	ATTACHMENTS FROM TEMPLATES
1716
		// to preserve individual email integrity, we must dupe Notes and associated files
1717
		// for each outbound email - good for integrity, bad for filespace
1718
		if(isset($_REQUEST['template_attachment']) && !empty($_REQUEST['template_attachment'])) {
1719
			$removeArr = array();
1720
			$noteArray = array();
1721
1722
			if(isset($_REQUEST['temp_remove_attachment']) && !empty($_REQUEST['temp_remove_attachment'])) {
1723
				$removeArr = $_REQUEST['temp_remove_attachment'];
1724
			}
1725
1726
1727
			foreach($_REQUEST['template_attachment'] as $noteId) {
1728
				if(in_array($noteId, $removeArr)) {
1729
					continue;
1730
				}
1731
				$noteTemplate = new Note();
1732
				$noteTemplate->retrieve($noteId);
1733
				$noteTemplate->id = create_guid();
1734
				$noteTemplate->new_with_id = true; // duplicating the note with files
1735
				$noteTemplate->parent_id = $this->id;
1736
				$noteTemplate->parent_type = $this->module_dir;
1737
				$noteTemplate->date_entered = '';
1738
				$noteTemplate->save();
1739
1740
				$noteFile = new UploadFile();
1741
				$noteFile->duplicate_file($noteId, $noteTemplate->id, $noteTemplate->filename);
1742
				$noteArray[] = $noteTemplate;
1743
			}
1744
			$this->attachments = array_merge($this->attachments, $noteArray);
1745
		}
1746
		////	END ATTACHMENTS FROM TEMPLATES
1747
		///////////////////////////////////////////////////////////////////////////
1748
1749
		///////////////////////////////////////////////////////////////////////////
1750
		////	ADDING NEW ATTACHMENTS
1751
		$max_files_upload = 10;
1752
        // Jenny - Bug 8211 Since attachments for drafts have already been processed,
1753
        // we don't need to re-process them.
1754
        if($this->status != "draft") {
1755
    		$notes_list = array();
1756
    		if(!empty($this->id) && !$this->new_with_id) {
1757
    			$note = new Note();
1758
    			$where = "notes.parent_id='{$this->id}'";
1759
    			$notes_list = $note->get_full_list("", $where, true);
1760
    		}
1761
    		$this->attachments = array_merge($this->attachments, $notes_list);
1762
        }
1763
		// cn: Bug 5995 - rudimentary error checking
1764
		$filesError = array(
1765
			0 => 'UPLOAD_ERR_OK - There is no error, the file uploaded with success.',
1766
			1 => 'UPLOAD_ERR_INI_SIZE - The uploaded file exceeds the upload_max_filesize directive in php.ini.',
1767
			2 => 'UPLOAD_ERR_FORM_SIZE - The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.',
1768
			3 => 'UPLOAD_ERR_PARTIAL - The uploaded file was only partially uploaded.',
1769
			4 => 'UPLOAD_ERR_NO_FILE - No file was uploaded.',
1770
			5 => 'UNKNOWN ERROR',
1771
			6 => 'UPLOAD_ERR_NO_TMP_DIR - Missing a temporary folder. Introduced in PHP 4.3.10 and PHP 5.0.3.',
1772
			7 => 'UPLOAD_ERR_CANT_WRITE - Failed to write file to disk. Introduced in PHP 5.1.0.',
1773
		);
1774
1775
		for($i = 0; $i < $max_files_upload; $i++) {
1776
			// cn: Bug 5995 - rudimentary error checking
1777
			if (!isset($_FILES["email_attachment{$i}"])) {
1778
				$GLOBALS['log']->debug("Email Attachment {$i} does not exist.");
1779
				continue;
1780
			}
1781
			if($_FILES['email_attachment'.$i]['error'] != 0 && $_FILES['email_attachment'.$i]['error'] != 4) {
1782
				$GLOBALS['log']->debug('Email Attachment could not be attach due to error: '.$filesError[$_FILES['email_attachment'.$i]['error']]);
1783
				continue;
1784
			}
1785
1786
			$note = new Note();
1787
			$note->parent_id = $this->id;
1788
			$note->parent_type = $this->module_dir;
1789
			$upload_file = new UploadFile('email_attachment'.$i);
1790
1791
			if(empty($upload_file)) {
1792
				continue;
1793
			}
1794
1795
			if(isset($_FILES['email_attachment'.$i]) && $upload_file->confirm_upload()) {
1796
				$note->filename = $upload_file->get_stored_file_name();
1797
				$note->file = $upload_file;
1798
				$note->name = $mod_strings['LBL_EMAIL_ATTACHMENT'].': '.$note->file->original_file_name;
1799
1800
				$this->attachments[] = $note;
1801
			}
1802
		}
1803
1804
		$this->saved_attachments = array();
1805
		foreach($this->attachments as $note) {
1806
			if(!empty($note->id)) {
1807
				array_push($this->saved_attachments, $note);
1808
				continue;
1809
			}
1810
			$note->parent_id = $this->id;
1811
			$note->parent_type = 'Emails';
1812
			$note->file_mime_type = $note->file->mime_type;
1813
			$note_id = $note->save();
1814
1815
			$this->saved_attachments[] = $note;
1816
1817
			$note->id = $note_id;
1818
			$note->file->final_move($note->id);
1819
		}
1820
		////	END NEW ATTACHMENTS
1821
		///////////////////////////////////////////////////////////////////////////
1822
1823
		///////////////////////////////////////////////////////////////////////////
1824
		////	ATTACHMENTS FROM DOCUMENTS
1825
		for($i=0; $i<10; $i++) {
1826
			if(isset($_REQUEST['documentId'.$i]) && !empty($_REQUEST['documentId'.$i])) {
1827
				$doc = new Document();
1828
				$docRev = new DocumentRevision();
1829
				$docNote = new Note();
1830
				$noteFile = new UploadFile();
1831
1832
				$doc->retrieve($_REQUEST['documentId'.$i]);
1833
				$docRev->retrieve($doc->document_revision_id);
1834
1835
				$this->saved_attachments[] = $docRev;
1836
1837
				// cn: bug 9723 - Emails with documents send GUID instead of Doc name
1838
				$docNote->name = $docRev->getDocumentRevisionNameForDisplay();
1839
				$docNote->filename = $docRev->filename;
1840
				$docNote->description = $doc->description;
1841
				$docNote->parent_id = $this->id;
1842
				$docNote->parent_type = 'Emails';
1843
				$docNote->file_mime_type = $docRev->file_mime_type;
1844
				$docId = $docNote = $docNote->save();
1845
1846
				$noteFile->duplicate_file($docRev->id, $docId, $docRev->filename);
1847
			}
1848
		}
1849
1850
		////	END ATTACHMENTS FROM DOCUMENTS
1851
		///////////////////////////////////////////////////////////////////////////
1852
1853
		///////////////////////////////////////////////////////////////////////////
1854
		////	REMOVE ATTACHMENTS
1855
        if(isset($_REQUEST['remove_attachment']) && !empty($_REQUEST['remove_attachment'])) {
1856
            foreach($_REQUEST['remove_attachment'] as $noteId) {
1857
                $q = 'UPDATE notes SET deleted = 1 WHERE id = \''.$noteId.'\'';
1858
                $this->db->query($q);
1859
            }
1860
        }
1861
1862
        //this will remove attachments that have been selected to be removed from drafts.
1863
        if(isset($_REQUEST['removeAttachment']) && !empty($_REQUEST['removeAttachment'])) {
1864
            $exRemoved = explode('::', $_REQUEST['removeAttachment']);
1865
            foreach($exRemoved as $noteId) {
1866
                $q = 'UPDATE notes SET deleted = 1 WHERE id = \''.$noteId.'\'';
1867
                $this->db->query($q);
1868
            }
1869
        }
1870
		////	END REMOVE ATTACHMENTS
1871
		///////////////////////////////////////////////////////////////////////////
1872
	}
1873
1874
1875
	/**
1876
	 * Determines if an email body (HTML or Plain) has a User signature already in the content
1877
	 * @param array Array of signatures
1878
	 * @return bool
1879
	 */
1880
	function hasSignatureInBody($sig) {
1881
		// strpos can't handle line breaks - normalize
1882
		$html = $this->removeAllNewlines($this->description_html);
1883
		$htmlSig = $this->removeAllNewlines($sig['signature_html']);
1884
		$plain = $this->removeAllNewlines($this->description);
1885
		$plainSig = $this->removeAllNewlines($sig['signature']);
1886
1887
		// cn: bug 11621 - empty sig triggers notice error
1888
		if(!empty($htmlSig) && false !== strpos($html, $htmlSig)) {
1889
			return true;
1890
		} elseif(!empty($plainSig) && false !== strpos($plain, $plainSig)) {
1891
			return true;
1892
		} else {
1893
			return false;
1894
		}
1895
	}
1896
1897
	/**
1898
	 * internal helper
1899
	 * @param string String to be normalized
1900
	 * @return string
1901
	 */
1902
	function removeAllNewlines($str) {
1903
		$bad = array("\r\n", "\n\r", "\n", "\r");
1904
		$good = array('', '', '', '');
1905
1906
		return str_replace($bad, $good, strip_tags(br2nl(from_html($str))));
1907
	}
1908
1909
1910
1911
	/**
1912
	 * Set navigation anchors to aid DetailView record navigation (VCR buttons)
1913
	 * @param string uri The URI from the referring page (always ListView)
1914
	 * @return array start Array of the URI broken down with a special "current_view" for My Inbox Navs
1915
	 */
1916
	function getStartPage($uri) {
1917
		if(strpos($uri, '&')) { // "&" to ensure that we can explode the GET vars - else we're gonna trigger a Notice error
1918
			$serial = substr($uri, (strpos($uri, '?')+1), strlen($uri));
1919
			$exUri = explode('&', $serial);
1920
			$start = array('module' => '', 'action' => '', 'group' => '', 'record' => '', 'type' => '');
1921
1922
			foreach($exUri as $k => $pair) {
1923
				$exPair = explode('=', $pair);
1924
				$start[$exPair[0]] = $exPair[1];
1925
			}
1926
1927
			// specific views for current_user
1928
			if(isset($start['assigned_user_id'])) {
1929
				$start['current_view'] = "{$start['action']}&module={$start['module']}&assigned_user_id={$start['assigned_user_id']}&type={$start['type']}";
1930
			}
1931
1932
			return $start;
1933
		} else {
1934
			return array();
1935
		}
1936
	}
1937
1938
	/**
1939
	 * preps SMTP info for email transmission
1940
	 * @param object mail SugarPHPMailer object
1941
	 * @param string mailer_id
1942
	 * @param string ieId
1943
	 * @return object mail SugarPHPMailer object
1944
	 */
1945
	function setMailer($mail, $mailer_id='', $ieId='') {
1946
		global $current_user;
1947
1948
		require_once("include/OutboundEmail/OutboundEmail.php");
1949
		$oe = new OutboundEmail();
1950
		$oe = $oe->getInboundMailerSettings($current_user, $mailer_id, $ieId);
1951
1952
		// ssl or tcp - keeping outside isSMTP b/c a default may inadvertantly set ssl://
1953
		$mail->protocol = ($oe->mail_smtpssl) ? "ssl://" : "tcp://";
1954
        if($oe->mail_sendtype == "SMTP")
1955
        {
1956
    		//Set mail send type information
1957
    		$mail->Mailer = "smtp";
1958
    		$mail->Host = $oe->mail_smtpserver;
1959
    		$mail->Port = $oe->mail_smtpport;
1960
            if ($oe->mail_smtpssl == 1) {
1961
                $mail->SMTPSecure = 'ssl';
1962
            } // if
1963
            if ($oe->mail_smtpssl == 2) {
1964
                $mail->SMTPSecure = 'tls';
1965
            } // if
1966
1967
    		if($oe->mail_smtpauth_req) {
1968
    			$mail->SMTPAuth = TRUE;
1969
    			$mail->Username = $oe->mail_smtpuser;
1970
    			$mail->Password = $oe->mail_smtppass;
1971
    		}
1972
        }
1973
        else
1974
			$mail->Mailer = "sendmail";
1975
1976
		$mail->oe = $oe;
1977
		return $mail;
1978
	}
1979
1980
	/**
1981
	 * preps SugarPHPMailer object for HTML or Plain text sends
1982
	 * @param SugarPHPMailer $mail SugarPHPMailer instance
1983
	 */
1984
	function handleBody($mail) {
1985
		global $current_user;
1986
		global $sugar_config;
1987
		///////////////////////////////////////////////////////////////////////
1988
		////	HANDLE EMAIL FORMAT PREFERENCE
1989
		// the if() below is HIGHLY dependent on the Javascript unchecking the Send HTML Email box
1990
		// HTML email
1991
		if( (isset($_REQUEST['setEditor']) /* from Email EditView navigation */
1992
			&& $_REQUEST['setEditor'] == 1
1993
			&& trim($_REQUEST['description_html']) != '')
1994
			|| trim($this->description_html) != '' /* from email templates */
1995
            && $current_user->getPreference('email_editor_option', 'global') !== 'plain' //user preference is not set to plain text
1996
		) {
1997
		    $this->handleBodyInHTMLformat($mail);
1998
		} else {
1999
			// plain text only
2000
			$this->description_html = '';
2001
			$mail->IsHTML(false);
2002
			$plainText = from_html($this->description);
2003
			$plainText = str_replace("&nbsp;", " ", $plainText);
2004
			$plainText = str_replace("</p>", "</p><br />", $plainText);
2005
			$plainText = strip_tags(br2nl($plainText));
2006
			$plainText = str_replace("&amp;", "&", $plainText);
2007
            $plainText = str_replace("&#39;", "'", $plainText);
2008
			$mail->Body = wordwrap($plainText, 996);
2009
			$mail->Body = $this->decodeDuringSend($mail->Body);
2010
			$this->description = $mail->Body;
2011
		}
2012
2013
		// wp: if plain text version has lines greater than 998, use base64 encoding
2014
		foreach(explode("\n", ($mail->ContentType == "text/html") ? $mail->AltBody : $mail->Body) as $line) {
2015
			if(strlen($line) > 998) {
2016
				$mail->Encoding = 'base64';
2017
				break;
2018
			}
2019
		}
2020
		////	HANDLE EMAIL FORMAT PREFERENCE
2021
		///////////////////////////////////////////////////////////////////////
2022
2023
		return $mail;
2024
	}
2025
2026
	/**
2027
	 * Retrieve function from handlebody() to unit test easily
2028
	 * @param SugarPHPMailer $mail SugarPHPMailer instance
2029
	 * @return formatted $mail body
2030
	 */
2031
	function handleBodyInHTMLformat($mail) {
2032
		global $sugar_config;
2033
		// wp: if body is html, then insert new lines at 996 characters. no effect on client side
2034
		// due to RFC 2822 which limits email lines to 998
2035
		$mail->IsHTML(true);
2036
		$body = from_html(wordwrap($this->description_html, 996));
2037
		$mail->Body = $body;
2038
2039
		// cn: bug 9725
2040
		// new plan is to use the selected type (html or plain) to fill the other
2041
		$plainText = from_html($this->description_html);
2042
		$plainText = strip_tags(br2nl($plainText));
2043
		$mail->AltBody = $plainText;
2044
		$this->description = $plainText;
2045
2046
		$mail->replaceImageByRegex("(?:{$sugar_config['site_url']})?/?cache/images/", sugar_cached("images/"));
2047
2048
		//Replace any embeded images using the secure entryPoint for src url.
2049
		$mail->replaceImageByRegex("(?:{$sugar_config['site_url']})?/?index.php[?]entryPoint=download&(?:amp;)?[^\"]+?id=", "upload://", true);
2050
2051
		$mail->Body = from_html($mail->Body);
2052
	}
2053
2054
	/**
2055
	 * Sends Email
2056
	 * @return bool True on success
2057
	 */
2058
	function send() {
2059
		global $mod_strings,$app_strings;
2060
		global $current_user;
2061
		global $sugar_config;
2062
		global $locale;
2063
        $OBCharset = $locale->getPrecedentPreference('default_email_charset');
2064
		$mail = new SugarPHPMailer();
2065
2066
		foreach ($this->to_addrs_arr as $addr_arr) {
2067
			if ( empty($addr_arr['display'])) {
2068
				$mail->AddAddress($addr_arr['email'], "");
2069
			} else {
2070
				$mail->AddAddress($addr_arr['email'],$locale->translateCharsetMIME(trim( $addr_arr['display']), 'UTF-8', $OBCharset));
2071
			}
2072
		}
2073
		foreach ($this->cc_addrs_arr as $addr_arr) {
2074
			if ( empty($addr_arr['display'])) {
2075
				$mail->AddCC($addr_arr['email'], "");
2076
			} else {
2077
				$mail->AddCC($addr_arr['email'],$locale->translateCharsetMIME(trim($addr_arr['display']), 'UTF-8', $OBCharset));
2078
			}
2079
		}
2080
2081
		foreach ($this->bcc_addrs_arr as $addr_arr) {
2082
			if ( empty($addr_arr['display'])) {
2083
				$mail->AddBCC($addr_arr['email'], "");
2084
			} else {
2085
				$mail->AddBCC($addr_arr['email'],$locale->translateCharsetMIME(trim($addr_arr['display']), 'UTF-8', $OBCharset));
2086
			}
2087
		}
2088
2089
		$mail = $this->setMailer($mail);
2090
2091
		// FROM ADDRESS
2092
		if(!empty($this->from_addr)) {
2093
			$mail->From = $this->from_addr;
2094
		} else {
2095
			$mail->From = $current_user->getPreference('mail_fromaddress');
2096
			$this->from_addr = $mail->From;
2097
		}
2098
		// FROM NAME
2099
		if(!empty($this->from_name)) {
2100
			$mail->FromName = $this->from_name;
2101
		} else {
2102
			$mail->FromName =  $current_user->getPreference('mail_fromname');
2103
			$this->from_name = $mail->FromName;
2104
		}
2105
2106
		//Reply to information for case create and autoreply.
2107
		if(!empty($this->reply_to_name)) {
2108
			$ReplyToName = $this->reply_to_name;
2109
		} else {
2110
			$ReplyToName = $mail->FromName;
2111
		}
2112
		if(!empty($this->reply_to_addr)) {
2113
			$ReplyToAddr = $this->reply_to_addr;
2114
		} else {
2115
			$ReplyToAddr = $mail->From;
2116
		}
2117
		$mail->Sender = $mail->From; /* set Return-Path field in header to reduce spam score in emails sent via Sugar's Email module */
2118
		$mail->AddReplyTo($ReplyToAddr,$locale->translateCharsetMIME(trim($ReplyToName), 'UTF-8', $OBCharset));
2119
2120
		//$mail->Subject = html_entity_decode($this->name, ENT_QUOTES, 'UTF-8');
2121
		$mail->Subject = $this->name;
2122
2123
		///////////////////////////////////////////////////////////////////////
2124
		////	ATTACHMENTS
2125
		foreach($this->saved_attachments as $note) {
2126
			$mime_type = 'text/plain';
2127
			if($note->object_name == 'Note') {
2128
				if(!empty($note->file->temp_file_location) && is_file($note->file->temp_file_location)) { // brandy-new file upload/attachment
2129
					$file_location = "upload://$note->id";
2130
					$filename = $note->file->original_file_name;
2131
					$mime_type = $note->file->mime_type;
2132
				} else { // attachment coming from template/forward
2133
					$file_location = "upload://{$note->id}";
2134
					// cn: bug 9723 - documents from EmailTemplates sent with Doc Name, not file name.
2135
					$filename = !empty($note->filename) ? $note->filename : $note->name;
2136
					$mime_type = $note->file_mime_type;
2137
				}
2138
			} elseif($note->object_name == 'DocumentRevision') { // from Documents
2139
				$filePathName = $note->id;
2140
				// cn: bug 9723 - Emails with documents send GUID instead of Doc name
2141
				$filename = $note->getDocumentRevisionNameForDisplay();
2142
				$file_location = "upload://$note->id";
2143
				$mime_type = $note->file_mime_type;
2144
			}
2145
2146
			// strip out the "Email attachment label if exists
2147
			$filename = str_replace($mod_strings['LBL_EMAIL_ATTACHMENT'].': ', '', $filename);
2148
            $file_ext = pathinfo($filename, PATHINFO_EXTENSION);
2149
			//is attachment in our list of bad files extensions?  If so, append .txt to file location
2150
			//check to see if this is a file with extension located in "badext"
2151
			foreach($sugar_config['upload_badext'] as $badExt) {
2152
		       	if(strtolower($file_ext) == strtolower($badExt)) {
2153
			       	//if found, then append with .txt to filename and break out of lookup
2154
			       	//this will make sure that the file goes out with right extension, but is stored
2155
			       	//as a text in db.
2156
			        $file_location = $file_location . ".txt";
2157
			        break; // no need to look for more
2158
		       	}
2159
	        }
2160
			$mail->AddAttachment($file_location,$locale->translateCharsetMIME(trim($filename), 'UTF-8', $OBCharset), 'base64', $mime_type);
2161
2162
			// embedded Images
2163
			if($note->embed_flag == true) {
2164
				$cid = $filename;
2165
				$mail->AddEmbeddedImage($file_location, $cid, $filename, 'base64',$mime_type);
2166
			}
2167
		}
2168
		////	END ATTACHMENTS
2169
		///////////////////////////////////////////////////////////////////////
2170
2171
		$mail = $this->handleBody($mail);
2172
2173
		$GLOBALS['log']->debug('Email sending --------------------- ');
2174
2175
		///////////////////////////////////////////////////////////////////////
2176
		////	I18N TRANSLATION
2177
		$mail->prepForOutbound();
2178
		////	END I18N TRANSLATION
2179
		///////////////////////////////////////////////////////////////////////
2180
2181
		if($mail->Send()) {
2182
			///////////////////////////////////////////////////////////////////
2183
			////	INBOUND EMAIL HANDLING
2184
			// mark replied
2185
			if(!empty($_REQUEST['inbound_email_id'])) {
2186
				$ieMail = new Email();
2187
				$ieMail->retrieve($_REQUEST['inbound_email_id']);
2188
				$ieMail->status = 'replied';
2189
				$ieMail->save();
2190
			}
2191
			$GLOBALS['log']->debug(' --------------------- buh bye -- sent successful');
2192
			////	END INBOUND EMAIL HANDLING
2193
			///////////////////////////////////////////////////////////////////
2194
  			return true;
2195
		}
2196
	    $GLOBALS['log']->debug($app_strings['LBL_EMAIL_ERROR_PREPEND'].$mail->ErrorInfo);
2197
		return false;
2198
	}
2199
2200
2201
	function listviewACLHelper(){
2202
		$array_assign = parent::listviewACLHelper();
2203
		$is_owner = false;
2204
		$in_group = false; //SECURITY GROUPS
2205
		if(!empty($this->parent_name)){
2206
2207
			if(!empty($this->parent_name_owner)){
2208
				global $current_user;
2209
				$is_owner = $current_user->id == $this->parent_name_owner;
2210
			}
2211
			/* BEGIN - SECURITY GROUPS */
2212
			//parent_name_owner not being set for whatever reason so we need to figure this out
2213
			else if(!empty($this->parent_type) && !empty($this->parent_id)) {
2214
				global $current_user;
2215
                $parent_bean = BeanFactory::getBean($this->parent_type,$this->parent_id);
2216
                if($parent_bean !== false) {
2217
                	$is_owner = $current_user->id == $parent_bean->assigned_user_id;
2218
                }
2219
			}
2220
			require_once("modules/SecurityGroups/SecurityGroup.php");
2221
			$in_group = SecurityGroup::groupHasAccess($this->parent_type, $this->parent_id, 'view'); 
2222
        	/* END - SECURITY GROUPS */
2223
		}
2224
		/* BEGIN - SECURITY GROUPS */
2225
		/**
2226
		if(!ACLController::moduleSupportsACL($this->parent_type) || ACLController::checkAccess($this->parent_type, 'view', $is_owner)){
2227
		*/
2228
		if(!ACLController::moduleSupportsACL($this->parent_type) || ACLController::checkAccess($this->parent_type, 'view', $is_owner, 'module', $in_group)){
2229
        /* END - SECURITY GROUPS */
2230
			$array_assign['PARENT'] = 'a';
2231
		} else {
2232
			$array_assign['PARENT'] = 'span';
2233
		}
2234
		$is_owner = false;
2235
		$in_group = false; //SECURITY GROUPS
2236
		if(!empty($this->contact_name)) {
2237
			if(!empty($this->contact_name_owner)) {
2238
				global $current_user;
2239
				$is_owner = $current_user->id == $this->contact_name_owner;
2240
			}
2241
			/* BEGIN - SECURITY GROUPS */
2242
			//contact_name_owner not being set for whatever reason so we need to figure this out
2243
			else {
2244
				global $current_user;
2245
                $parent_bean = BeanFactory::getBean('Contacts',$this->contact_id);
2246
                if($parent_bean !== false) {
2247
                	$is_owner = $current_user->id == $parent_bean->assigned_user_id;
2248
                }
2249
			}
2250
			require_once("modules/SecurityGroups/SecurityGroup.php");
2251
			$in_group = SecurityGroup::groupHasAccess('Contacts', $this->contact_id, 'view'); 
2252
        	/* END - SECURITY GROUPS */
2253
		}
2254
		/* BEGIN - SECURITY GROUPS */
2255
		/**
2256
		if(ACLController::checkAccess('Contacts', 'view', $is_owner)) {
2257
		*/
2258
		if(ACLController::checkAccess('Contacts', 'view', $is_owner, 'module', $in_group)) {
2259
        /* END - SECURITY GROUPS */
2260
			$array_assign['CONTACT'] = 'a';
2261
		} else {
2262
			$array_assign['CONTACT'] = 'span';
2263
		}
2264
2265
		return $array_assign;
2266
	}
2267
2268
	function getSystemDefaultEmail() {
2269
		$email = array();
2270
2271
		$r1 = $this->db->query('SELECT config.value FROM config WHERE name=\'fromaddress\'');
2272
		$r2 = $this->db->query('SELECT config.value FROM config WHERE name=\'fromname\'');
2273
		$a1 = $this->db->fetchByAssoc($r1);
2274
		$a2 = $this->db->fetchByAssoc($r2);
2275
2276
		$email['email'] = $a1['value'];
2277
		$email['name']  = $a2['value'];
2278
2279
		return $email;
2280
	}
2281
2282
2283
    function create_new_list_query($order_by, $where,$filter=array(),$params=array(), $show_deleted = 0,$join_type='', $return_array = false,$parentbean=null, $singleSelect = false, $ifListForExport = false) {
2284
2285
		if ($return_array) {
2286
			return parent::create_new_list_query($order_by, $where,$filter,$params, $show_deleted,$join_type, $return_array,$parentbean, $singleSelect);
2287
		}
2288
        $custom_join = $this->getCustomJoin();
2289
2290
		$query = "SELECT ".$this->table_name.".*, users.user_name as assigned_user_name\n";
2291
2292
        $query .= $custom_join['select'];
2293
    	$query .= " FROM emails\n";
2294
    	if ($where != "" && (strpos($where, "contacts.first_name") > 0))  {
2295
			$query .= " LEFT JOIN emails_beans ON emails.id = emails_beans.email_id\n";
2296
    	}
2297
2298
    	$query .= " LEFT JOIN users ON emails.assigned_user_id=users.id \n";
2299
    	if ($where != "" && (strpos($where, "contacts.first_name") > 0))  {
2300
2301
        $query .= " JOIN contacts ON contacts.id= emails_beans.bean_id AND emails_beans.bean_module='Contacts' and contacts.deleted=0 \n";
2302
    	}
2303
2304
        $query .= $custom_join['join'];
2305
2306
		if($show_deleted == 0) {
2307
			$where_auto = " emails.deleted=0 \n";
2308
		}else if($show_deleted == 1){
2309
			$where_auto = " emails.deleted=1 \n";
2310
		}
2311
2312
        if($where != "")
2313
			$query .= "WHERE $where AND ".$where_auto;
2314
		else
2315
			$query .= "WHERE ".$where_auto;
2316
2317
		if($order_by != "")
2318
			$query .= " ORDER BY $order_by";
2319
		else
2320
			$query .= " ORDER BY date_sent DESC";
2321
2322
		return $query;
2323
    } // fn
2324
2325
2326
	function fill_in_additional_list_fields() {
2327
		global $timedate, $mod_strings;
2328
		$this->fill_in_additional_detail_fields();
2329
2330
		$this->link_action = 'DetailView';
2331
		///////////////////////////////////////////////////////////////////////
2332
		//populate attachment_image, used to display attachment icon.
2333
		$query =  "select 1 from notes where notes.parent_id = '$this->id' and notes.deleted = 0";
2334
		$result =$this->db->query($query,true," Error filling in additional list fields: ");
2335
2336
		$row = $this->db->fetchByAssoc($result);
2337
2338
        if ($row) {
2339
            $this->attachment_image = SugarThemeRegistry::current()->getImage(
2340
                'attachment',
2341
                '',
2342
                null,
2343
                null,
2344
                '.gif',
2345
                translate('LBL_ATTACHMENT', 'Emails')
2346
            );
2347
        } else {
2348
            $this->attachment_image = '';
2349
        }
2350
2351
		///////////////////////////////////////////////////////////////////////
2352
		if(empty($this->contact_id) && !empty($this->parent_id) && !empty($this->parent_type) && $this->parent_type === 'Contacts' && !empty($this->parent_name) ){
2353
			$this->contact_id = $this->parent_id;
2354
			$this->contact_name = $this->parent_name;
2355
		}
2356
	}
2357
2358
	function fill_in_additional_detail_fields() {
2359
		global $app_list_strings,$mod_strings;
2360
		// Fill in the assigned_user_name
2361
		$this->assigned_user_name = get_assigned_user_name($this->assigned_user_id, '');
2362
		//if ($this->parent_type == 'Contacts') {
2363
			$query  = "SELECT contacts.first_name, contacts.last_name, contacts.phone_work, contacts.id, contacts.assigned_user_id contact_name_owner, 'Contacts' contact_name_mod FROM contacts, emails_beans ";
2364
			$query .= "WHERE emails_beans.email_id='$this->id' AND emails_beans.bean_id=contacts.id AND emails_beans.bean_module = 'Contacts' AND emails_beans.deleted=0 AND contacts.deleted=0";
2365
			if(!empty($this->parent_id)){
2366
				$query .= " AND contacts.id= '".$this->parent_id."' ";
2367
			}else if(!empty($_REQUEST['record'])){
2368
				$query .= " AND contacts.id= '".$_REQUEST['record']."' ";
2369
			}
2370
			$result =$this->db->query($query,true," Error filling in additional detail fields: ");
2371
2372
			// Get the id and the name.
2373
			$row = $this->db->fetchByAssoc($result);
2374
			if($row != null)
2375
			{
2376
2377
				$contact = new Contact();
2378
				$contact->retrieve($row['id']);
2379
				$this->contact_name = $contact->full_name;
2380
				$this->contact_phone = $row['phone_work'];
2381
				$this->contact_id = $row['id'];
2382
				$this->contact_email = $contact->emailAddress->getPrimaryAddress($contact);
2383
				$this->contact_name_owner = $row['contact_name_owner'];
2384
				$this->contact_name_mod = $row['contact_name_mod'];
2385
				$GLOBALS['log']->debug("Call($this->id): contact_name = $this->contact_name");
2386
				$GLOBALS['log']->debug("Call($this->id): contact_phone = $this->contact_phone");
2387
				$GLOBALS['log']->debug("Call($this->id): contact_id = $this->contact_id");
2388
				$GLOBALS['log']->debug("Call($this->id): contact_email1 = $this->contact_email");
2389
			}
2390
			else {
2391
				$this->contact_name = '';
2392
				$this->contact_phone = '';
2393
				$this->contact_id = '';
2394
				$this->contact_email = '';
2395
				$this->contact_name_owner = '';
2396
				$this->contact_name_mod = '';
2397
				$GLOBALS['log']->debug("Call($this->id): contact_name = $this->contact_name");
2398
				$GLOBALS['log']->debug("Call($this->id): contact_phone = $this->contact_phone");
2399
				$GLOBALS['log']->debug("Call($this->id): contact_id = $this->contact_id");
2400
				$GLOBALS['log']->debug("Call($this->id): contact_email1 = $this->contact_email");
2401
			}
2402
		//}
2403
		$this->created_by_name = get_assigned_user_name($this->created_by);
2404
		$this->modified_by_name = get_assigned_user_name($this->modified_user_id);
2405
2406
		$this->link_action = 'DetailView';
2407
2408
		if(!empty($this->type)) {
2409
			if($this->type == 'out' && $this->status == 'send_error') {
2410
				$this->type_name = $mod_strings['LBL_NOT_SENT'];
2411
			} else {
2412
				$this->type_name = $app_list_strings['dom_email_types'][$this->type];
2413
			}
2414
2415
			if(($this->type == 'out' && $this->status == 'send_error') || $this->type == 'draft') {
2416
				$this->link_action = 'EditView';
2417
			}
2418
		}
2419
2420
		//todo this  isset( $app_list_strings['dom_email_status'][$this->status]) is hack for 3261.
2421
		if(!empty($this->status) && isset( $app_list_strings['dom_email_status'][$this->status])) {
2422
			$this->status_name = $app_list_strings['dom_email_status'][$this->status];
2423
		}
2424
2425
		if ( empty($this->name ) &&  empty($_REQUEST['record'])) {
2426
			$this->name = $mod_strings['LBL_NO_SUBJECT'];
2427
		}
2428
2429
		$this->fill_in_additional_parent_fields();
2430
	}
2431
2432
2433
2434
	function create_export_query($order_by, $where)
2435
    {
2436
		$contact_required = stristr($where, "contacts");
2437
		$custom_join = $this->getCustomJoin(true, true, $where);
2438
2439
		if($contact_required) {
2440
			$query = "SELECT emails.*, contacts.first_name, contacts.last_name";
2441
            $query .= $custom_join['select'];
2442
2443
			$query .= " FROM contacts, emails, emails_contacts ";
2444
			$where_auto = "emails_contacts.contact_id = contacts.id AND emails_contacts.email_id = emails.id AND emails.deleted=0 AND contacts.deleted=0";
2445
		} else {
2446
			$query = 'SELECT emails.*';
2447
            $query .= $custom_join['select'];
2448
2449
            $query .= ' FROM emails ';
2450
            $where_auto = "emails.deleted=0";
2451
		}
2452
2453
        $query .= $custom_join['join'];
2454
2455
		if($where != "")
2456
			$query .= "where $where AND ".$where_auto;
2457
        else
2458
			$query .= "where ".$where_auto;
2459
2460
        if($order_by != "")
2461
			$query .= " ORDER BY $order_by";
2462
        else
2463
			$query .= " ORDER BY emails.name";
2464
        return $query;
2465
    }
2466
2467
	function get_list_view_data() {
2468
		global $app_list_strings;
2469
		global $theme;
2470
		global $current_user;
2471
		global $timedate;
2472
		global $mod_strings;
2473
2474
		$email_fields = $this->get_list_view_array();
2475
		$this->retrieveEmailText();
2476
		$email_fields['FROM_ADDR'] = $this->from_addr_name;
2477
		$mod_strings = return_module_language($GLOBALS['current_language'], 'Emails'); // hard-coding for Home screen ListView
2478
2479
		if($this->status != 'replied') {
2480
			$email_fields['QUICK_REPLY'] = '<a  href="index.php?module=Emails&action=Compose&replyForward=true&reply=reply&record='.$this->id.'&inbound_email_id='.$this->id.'">'.$mod_strings['LNK_QUICK_REPLY'].'</a>';
2481
			$email_fields['STATUS'] = ($email_fields['REPLY_TO_STATUS'] == 1 ? $mod_strings['LBL_REPLIED'] : $email_fields['STATUS']);
2482
		} else {
2483
			$email_fields['QUICK_REPLY'] = $mod_strings['LBL_REPLIED'];
2484
		}
2485
		if(!empty($this->parent_type)) {
2486
			$email_fields['PARENT_MODULE'] = $this->parent_type;
2487
		} else {
2488
			switch($this->intent) {
2489
				case 'support':
2490
					$email_fields['CREATE_RELATED'] = '<a href="index.php?module=Cases&action=EditView&inbound_email_id='.$this->id.'" >' . SugarThemeRegistry::current()->getImage('CreateCases', 'border="0"', null, null, ".gif", $mod_strings['LBL_CREATE_CASES']).$mod_strings['LBL_CREATE_CASE'].'</a>';
2491
				break;
2492
2493
				case 'sales':
2494
					$email_fields['CREATE_RELATED'] = '<a href="index.php?module=Leads&action=EditView&inbound_email_id='.$this->id.'" >'.SugarThemeRegistry::current()->getImage('CreateLeads', 'border="0"', null, null, ".gif", $mod_strings['LBL_CREATE_LEADS']).$mod_strings['LBL_CREATE_LEAD'].'</a>';
2495
				break;
2496
2497
				case 'contact':
2498
					$email_fields['CREATE_RELATED'] = '<a href="index.php?module=Contacts&action=EditView&inbound_email_id='.$this->id.'" >'.SugarThemeRegistry::current()->getImage('CreateContacts', 'border="0"', null, null, ".gif", $mod_strings['LBL_CREATE_CONTACTS']).$mod_strings['LBL_CREATE_CONTACT'].'</a>';
2499
				break;
2500
2501
				case 'bug':
2502
					$email_fields['CREATE_RELATED'] = '<a href="index.php?module=Bugs&action=EditView&inbound_email_id='.$this->id.'" >'.SugarThemeRegistry::current()->getImage('CreateBugs', 'border="0"', null, null, ".gif", $mod_strings['LBL_CREATE_BUGS']).$mod_strings['LBL_CREATE_BUG'].'</a>';
2503
				break;
2504
2505
				case 'task':
2506
					$email_fields['CREATE_RELATED'] = '<a href="index.php?module=Tasks&action=EditView&inbound_email_id='.$this->id.'" >'.SugarThemeRegistry::current()->getImage('CreateTasks', 'border="0"', null, null, ".gif", $mod_strings['LBL_CREATE_TASKS']).$mod_strings['LBL_CREATE_TASK'].'</a>';
2507
				break;
2508
2509
				case 'bounce':
2510
				break;
2511
2512
				case 'pick':
2513
				// break;
2514
2515
				case 'info':
2516
				//break;
2517
2518
				default:
2519
					$email_fields['CREATE_RELATED'] = $this->quickCreateForm();
2520
				break;
2521
			}
2522
2523
		}
2524
2525
		//BUG 17098 - MFH changed $this->from_addr to $this->to_addrs
2526
		$email_fields['CONTACT_NAME']		= empty($this->contact_name) ? '</a>'.$this->trimLongTo($this->to_addrs).'<a>' : $this->contact_name;
2527
		$email_fields['CONTACT_ID']		= empty($this->contact_id) ? '' : $this->contact_id;
2528
		$email_fields['ATTACHMENT_IMAGE']	= $this->attachment_image;
2529
		$email_fields['LINK_ACTION']		= $this->link_action;
2530
2531
    	if(isset($this->type_name))
2532
	      	$email_fields['TYPE_NAME'] = $this->type_name;
2533
2534
		return $email_fields;
2535
	}
2536
2537
    function quickCreateForm() {
2538
        global $mod_strings, $app_strings, $currentModule, $current_language;
2539
2540
        // Coming from the home page via Dashlets
2541
        if($currentModule != 'Email')
2542
        	$mod_strings = return_module_language($current_language, 'Emails');
2543
        return $mod_strings['LBL_QUICK_CREATE']."&nbsp;<a id='$this->id' onclick='return quick_create_overlib(\"{$this->id}\", \"".SugarThemeRegistry::current()->__toString()."\", this);' href=\"#\" >".SugarThemeRegistry::current()->getImage("advanced_search","border='0' align='absmiddle'", null,null,'.gif',$mod_strings['LBL_QUICK_CREATE'])."</a>";
2544
    }
2545
2546
    /**
2547
     * Searches all imported emails and returns the result set as an array.
2548
     *
2549
     */
2550
    function searchImportedEmails($sort = '', $direction='')
2551
    {
2552
       	require_once('include/TimeDate.php');
2553
		global $timedate;
2554
		global $current_user;
2555
		global $beanList;
2556
		global $sugar_config;
2557
		global $app_strings;
2558
2559
		$emailSettings = $current_user->getPreference('emailSettings', 'Emails');
2560
		// cn: default to a low number until user specifies otherwise
2561
		if(empty($emailSettings['showNumInList']))
2562
			$pageSize = 20;
2563
        else
2564
            $pageSize = $emailSettings['showNumInList'];
2565
2566
        if( isset($_REQUEST['start']) && isset($_REQUEST['limit']) )
2567
	       $page = ceil($_REQUEST['start'] / $_REQUEST['limit']) + 1;
2568
	    else
2569
	       $page = 1;
2570
2571
	     //Determine sort ordering
2572
2573
	     //Sort ordering parameters in the request do not coincide with actual column names
2574
	     //so we need to remap them.
2575
	     $hrSortLocal = array(
2576
            'flagged' => 'type',
2577
            'status'  => 'reply_to_status',
2578
            'from'    => 'emails_text.from_addr',
2579
            'subject' => 'name',
2580
            'date'    => 'date_sent',
2581
            'AssignedTo' => 'assigned_user_id',
2582
            'flagged' => 'flagged'
2583
        );
2584
2585
	     $sort = !empty($_REQUEST['sort']) ? $this->db->getValidDBName($_REQUEST['sort']) : "";
2586
         $direction = !empty($_REQUEST['dir'])  && in_array(strtolower($_REQUEST['dir']), array("asc", "desc")) ? $_REQUEST['dir'] : "";
2587
2588
         $order = ( !empty($sort) && !empty($direction) ) ? " ORDER BY {$hrSortLocal[$sort]} {$direction}" : "";
2589
2590
         //Get our main query.
2591
		$fullQuery = $this->_genereateSearchImportedEmailsQuery();
2592
2593
		//Perform a count query needed for pagination.
2594
		$countQuery = $this->create_list_count_query($fullQuery);
2595
		
2596
		$count_rs = $this->db->query($countQuery, false, 'Error executing count query for imported emails search');
2597
		$count_row = $this->db->fetchByAssoc($count_rs);
2598
		$total_count = ($count_row != null) ? $count_row['c'] : 0;
2599
2600
        $start = ($page - 1) * $pageSize;
2601
2602
        //Execute the query
2603
		$rs = $this->db->limitQuery($fullQuery . $order, $start, $pageSize);
2604
2605
		$return = array();
2606
2607
		while($a = $this->db->fetchByAssoc($rs)) {
2608
			$temp = array();
2609
			$temp['flagged'] = (is_null($a['flagged']) || $a['flagged'] == '0') ? '' : 1;
2610
			$temp['status'] = (is_null($a['reply_to_status']) || $a['reply_to_status'] == '0') ? '' : 1;
2611
			$temp['subject'] = $a['name'];
2612
			$temp['date']	= $timedate->to_display_date_time($a['date_sent']);
2613
			$temp['uid'] = $a['id'];
2614
			$temp['ieId'] = $a['mailbox_id'];
2615
			$temp['site_url'] = $sugar_config['site_url'];
2616
			$temp['seen'] = ($a['status'] == 'unread') ? 0 : 1;
2617
			$temp['type'] = $a['type'];
2618
			$temp['mbox'] = 'sugar::Emails';
2619
			$temp['hasAttach'] =  $this->doesImportedEmailHaveAttachment($a['id']);
2620
			//To and from addresses may be stored in emails_text, if nothing is found, revert to
2621
			//regular email addresses.
2622
			$temp['to_addrs'] = preg_replace('/[\x00-\x08\x0B-\x1F]/', '', $a['to_addrs']);
2623
			$temp['from']	= preg_replace('/[\x00-\x08\x0B-\x1F]/', '', $a['from_addr']);
2624
			if( empty($temp['from']) || empty($temp['to_addrs']) )
2625
			{
2626
    			//Retrieve email addresses seperatly.
2627
    			$tmpEmail = new Email();
2628
    			$tmpEmail->id = $a['id'];
2629
    			$tmpEmail->retrieveEmailAddresses();
2630
    			$temp['from'] = $tmpEmail->from_addr;
2631
    			$temp['to_addrs'] = $tmpEmail->to_addrs;
2632
			}
2633
2634
			$return[] = $temp;
2635
		}
2636
2637
		$metadata = array();
2638
		$metadata['totalCount'] = $total_count;
2639
		$metadata['out'] = $return;
2640
2641
		return $metadata;
2642
    }
2643
2644
    /**
2645
     * Determine if an imported email has an attachment by examining the relationship to notes.
2646
     *
2647
     * @param string $id
2648
     * @return boolean
2649
     */
2650
    function doesImportedEmailHaveAttachment($id)
2651
	{
2652
	   $hasAttachment = FALSE;
2653
	   $query = "SELECT id FROM notes where parent_id='$id' AND parent_type='Emails' AND file_mime_type is not null AND deleted=0";
2654
	   $rs = $this->db->limitQuery($query, 0, 1);
2655
	   $row = $this->db->fetchByAssoc($rs);
2656
	   if( !empty($row['id']) )
2657
	       $hasAttachment = TRUE;
2658
2659
	   return (int) $hasAttachment;
2660
	}
2661
2662
    /**
2663
     * Generate the query used for searching imported emails.
2664
     *
2665
     * @return String Query to be executed.
2666
     */
2667
    function _genereateSearchImportedEmailsQuery()
2668
    {
2669
		global $timedate;
2670
2671
        $additionalWhereClause = $this->_generateSearchImportWhereClause();
2672
2673
        $query = array();
2674
        $fullQuery = "";
2675
        $query['select'] = "emails.id , emails.mailbox_id, emails.name, emails.date_sent, emails.status, emails.type, emails.flagged, emails.reply_to_status,
2676
		                      emails_text.from_addr, emails_text.to_addrs  FROM emails ";
2677
2678
        $query['joins'] = " JOIN emails_text on emails.id = emails_text.email_id ";
2679
2680
        //Handle from and to addr joins
2681
        if( !empty($_REQUEST['from_addr']) )
2682
        {
2683
            $from_addr = $this->db->quote(strtolower($_REQUEST['from_addr']));
2684
            $query['joins'] .= "INNER JOIN emails_email_addr_rel er_from ON er_from.email_id = emails.id AND er_from.deleted = 0 INNER JOIN email_addresses ea_from ON ea_from.id = er_from.email_address_id
2685
                                AND er_from.address_type='from' AND emails_text.from_addr LIKE '%" . $from_addr . "%'";
2686
        }
2687
2688
        if( !empty($_REQUEST['to_addrs'])  )
2689
        {
2690
            $to_addrs = $this->db->quote(strtolower($_REQUEST['to_addrs']));
2691
            $query['joins'] .= "INNER JOIN emails_email_addr_rel er_to ON er_to.email_id = emails.id AND er_to.deleted = 0 INNER JOIN email_addresses ea_to ON ea_to.id = er_to.email_address_id
2692
                                    AND er_to.address_type='to' AND ea_to.email_address LIKE '%" . $to_addrs . "%'";
2693
        }
2694
2695
        $query['where'] = " WHERE (emails.type= 'inbound' OR emails.type='archived' OR emails.type='out') AND emails.deleted = 0 ";
2696
		if( !empty($additionalWhereClause) )
2697
    	    $query['where'] .= "AND $additionalWhereClause";
2698
2699
    	//If we are explicitly looking for attachments.  Do not use a distinct query as the to_addr is defined
2700
    	//as a text which equals clob in oracle and the distinct query can not be executed correctly.
2701
    	$addDistinctKeyword = "";
2702
        if( !empty($_REQUEST['attachmentsSearch']) &&  $_REQUEST['attachmentsSearch'] == 1) //1 indicates yes
2703
            $query['where'] .= " AND EXISTS ( SELECT id FROM notes n WHERE n.parent_id = emails.id AND n.deleted = 0 AND n.filename is not null )";
2704
        else if( !empty($_REQUEST['attachmentsSearch']) &&  $_REQUEST['attachmentsSearch'] == 2 )
2705
             $query['where'] .= " AND NOT EXISTS ( SELECT id FROM notes n WHERE n.parent_id = emails.id AND n.deleted = 0 AND n.filename is not null )";
2706
2707
        $fullQuery = "SELECT " . $query['select'] . " " . $query['joins'] . " " . $query['where'];
2708
        
2709
        return $fullQuery;
2710
    }
2711
        /**
2712
     * Generate the where clause for searching imported emails.
2713
     *
2714
     */
2715
    function _generateSearchImportWhereClause()
2716
    {
2717
        global $timedate;
2718
2719
        //The clear button was removed so if a user removes the asisgned user name, do not process the id.
2720
        if( empty($_REQUEST['assigned_user_name']) && !empty($_REQUEST['assigned_user_id'])  )
2721
            unset($_REQUEST['assigned_user_id']);
2722
2723
        $availableSearchParam = array('name' => array('table_name' =>'emails'),
2724
                                      'data_parent_id_search' => array('table_name' =>'emails','db_key' => 'parent_id','opp' => '='),
2725
                                      'assigned_user_id' => array('table_name' => 'emails', 'opp' => '=') );
2726
2727
		$additionalWhereClause = array();
2728
		foreach ($availableSearchParam as $key => $properties)
2729
		{
2730
		      if( !empty($_REQUEST[$key]) )
2731
		      {
2732
		          $db_key =  isset($properties['db_key']) ? $properties['db_key'] : $key;
2733
                  $searchValue = $this->db->quote($_REQUEST[$key]);
2734
2735
		          $opp = isset($properties['opp']) ? $properties['opp'] : 'like';
2736
		          if($opp == 'like')
2737
		              $searchValue = "%" . $searchValue . "%";
2738
2739
		          $additionalWhereClause[] = "{$properties['table_name']}.$db_key $opp '$searchValue' ";
2740
		      }
2741
        }
2742
        
2743
        
2744
2745
        $isDateFromSearchSet = !empty($_REQUEST['searchDateFrom']);
2746
        $isdateToSearchSet = !empty($_REQUEST['searchDateTo']);
2747
        $bothDateRangesSet = $isDateFromSearchSet & $isdateToSearchSet;
2748
2749
        //Hanlde date from and to separately
2750
        if($bothDateRangesSet)
2751
        {
2752
            $dbFormatDateFrom = $timedate->to_db_date($_REQUEST['searchDateFrom'], false);
2753
            $dbFormatDateFrom = db_convert("'" . $dbFormatDateFrom . "'",'datetime');
0 ignored issues
show
Deprecated Code introduced by
The function db_convert() has been deprecated with message: use DBManager::convert() instead.

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
2754
2755
            $dbFormatDateTo = $timedate->to_db_date($_REQUEST['searchDateTo'], false);
2756
            $dbFormatDateTo = db_convert("'" . $dbFormatDateTo . "'",'datetime');
0 ignored issues
show
Deprecated Code introduced by
The function db_convert() has been deprecated with message: use DBManager::convert() instead.

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
2757
2758
            $additionalWhereClause[] = "( emails.date_sent >= $dbFormatDateFrom AND
2759
                                          emails.date_sent <= $dbFormatDateTo )";
2760
        }
2761
        elseif ($isdateToSearchSet)
2762
        {
2763
            $dbFormatDateTo = $timedate->to_db_date($_REQUEST['searchDateTo'], false);
2764
            $dbFormatDateTo = db_convert("'" . $dbFormatDateTo . "'",'datetime');
0 ignored issues
show
Deprecated Code introduced by
The function db_convert() has been deprecated with message: use DBManager::convert() instead.

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
2765
            $additionalWhereClause[] = "emails.date_sent <= $dbFormatDateTo ";
2766
        }
2767
        elseif ($isDateFromSearchSet)
2768
        {
2769
            $dbFormatDateFrom = $timedate->to_db_date($_REQUEST['searchDateFrom'], false);
2770
            $dbFormatDateFrom = db_convert("'" . $dbFormatDateFrom . "'",'datetime');
0 ignored issues
show
Deprecated Code introduced by
The function db_convert() has been deprecated with message: use DBManager::convert() instead.

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
2771
            $additionalWhereClause[] = "emails.date_sent >= $dbFormatDateFrom ";
2772
        }
2773
2774
        $additionalWhereClause = implode(" AND ", $additionalWhereClause);
2775
2776
        return $additionalWhereClause;
2777
    }
2778
2779
2780
2781
	/**
2782
	 * takes a long TO: string of emails and returns the first appended by an
2783
	 * elipse
2784
	 */
2785
	function trimLongTo($str) {
2786
		if(strpos($str, ',')) {
2787
			$exStr = explode(',', $str);
2788
			return $exStr[0].'...';
2789
		} elseif(strpos($str, ';')) {
2790
			$exStr = explode(';', $str);
2791
			return $exStr[0].'...';
2792
		} else {
2793
			return $str;
2794
		}
2795
	}
2796
2797
	function get_summary_text() {
2798
		return $this->name;
2799
	}
2800
2801
2802
2803
	function distributionForm($where) {
2804
		global $app_list_strings;
2805
		global $app_strings;
2806
		global $mod_strings;
2807
		global $theme;
2808
		global $current_user;
2809
2810
		$distribution	= get_select_options_with_id($app_list_strings['dom_email_distribution'], '');
2811
		$_SESSION['distribute_where'] = $where;
2812
2813
2814
		$out = '<form name="Distribute" id="Distribute">';
2815
		$out .= get_form_header($mod_strings['LBL_DIST_TITLE'], '', false);
2816
		$out .=<<<eoq
2817
		<script>
2818
			enableQS(true);
2819
		</script>
2820
eoq;
2821
		$out .= '
2822
		<table cellpadding="0" cellspacing="0" width="100%" border="0">
2823
			<tr>
2824
				<td>
2825
					<script type="text/javascript">
2826
2827
2828
						function checkDeps(form) {
2829
							return;
2830
						}
2831
2832
						function mySubmit() {
2833
							var assform = document.getElementById("Distribute");
2834
							var select = document.getElementById("userSelect");
2835
							var assign1 = assform.r1.checked;
2836
							var assign2 = assform.r2.checked;
2837
							var dist = assform.dm.value;
2838
							var assign = false;
2839
							var users = false;
2840
							var rules = false;
2841
							var warn1 = "'.$mod_strings['LBL_WARN_NO_USERS'].'";
2842
							var warn2 = "";
2843
2844
							if(assign1 || assign2) {
2845
								assign = true;
2846
2847
							}
2848
2849
							for(i=0; i<select.options.length; i++) {
2850
								if(select.options[i].selected == true) {
2851
									users = true;
2852
									warn1 = "";
2853
								}
2854
							}
2855
2856
							if(dist != "") {
2857
								rules = true;
2858
							} else {
2859
								warn2 = "'.$mod_strings['LBL_WARN_NO_DIST'].'";
2860
							}
2861
2862
							if(assign && users && rules) {
2863
2864
								if(document.getElementById("r1").checked) {
2865
									var mu = document.getElementById("MassUpdate");
2866
									var grabbed = "";
2867
2868
									for(i=0; i<mu.elements.length; i++) {
2869
										if(mu.elements[i].type == "checkbox" && mu.elements[i].checked && mu.elements[i].name.value != "massall") {
2870
											if(grabbed != "") { grabbed += "::"; }
2871
											grabbed += mu.elements[i].value;
2872
										}
2873
									}
2874
									var formgrab = document.getElementById("grabbed");
2875
									formgrab.value = grabbed;
2876
								}
2877
								assform.submit();
2878
							} else {
2879
								alert("'.$mod_strings['LBL_ASSIGN_WARN'].'" + "\n" + warn1 + "\n" + warn2);
2880
							}
2881
						}
2882
2883
						function submitDelete() {
2884
							if(document.getElementById("r1").checked) {
2885
								var mu = document.getElementById("MassUpdate");
2886
								var grabbed = "";
2887
2888
								for(i=0; i<mu.elements.length; i++) {
2889
									if(mu.elements[i].type == "checkbox" && mu.elements[i].checked && mu.elements[i].name != "massall") {
2890
										if(grabbed != "") { grabbed += "::"; }
2891
										grabbed += mu.elements[i].value;
2892
									}
2893
								}
2894
								var formgrab = document.getElementById("grabbed");
2895
								formgrab.value = grabbed;
2896
							}
2897
							if(grabbed == "") {
2898
								alert("'.$mod_strings['LBL_MASS_DELETE_ERROR'].'");
2899
							} else {
2900
								document.getElementById("Distribute").submit();
2901
							}
2902
						}
2903
2904
					</script>
2905
						<input type="hidden" name="module" value="Emails">
2906
						<input type="hidden" name="action" id="action">
2907
						<input type="hidden" name="grabbed" id="grabbed">
2908
2909
					<table cellpadding="1" cellspacing="0" width="100%" border="0" class="edit view">
2910
						<tr height="20">
2911
							<td scope="col" scope="row" NOWRAP align="center">
2912
								&nbsp;'.$mod_strings['LBL_ASSIGN_SELECTED_RESULTS_TO'].'&nbsp;';
2913
					$out .= $this->userSelectTable();
2914
					$out .=	'</td>
2915
							<td scope="col" scope="row" NOWRAP align="left">
2916
								&nbsp;'.$mod_strings['LBL_USING_RULES'].'&nbsp;
2917
								<select name="distribute_method" id="dm" onChange="checkDeps(this.form);">'.$distribution.'</select>
2918
							</td>';
2919
2920
2921
					$out .= '</td>
2922
							</tr>';
2923
2924
2925
					$out .= '<tr>
2926
								<td scope="col" width="50%" scope="row" NOWRAP align="right" colspan="2">
2927
								<input title="'.$mod_strings['LBL_BUTTON_DISTRIBUTE_TITLE'].'"
2928
									id="dist_button"
2929
									class="button" onClick="AjaxObject.detailView.handleAssignmentDialogAssignAction();"
2930
									type="button" name="button"
2931
									value="  '.$mod_strings['LBL_BUTTON_DISTRIBUTE'].'  ">';
2932
					$out .= '</tr>
2933
					</table>
2934
				</td>
2935
			</tr>
2936
		</table>
2937
		</form>';
2938
	return $out;
2939
	}
2940
2941
	function userSelectTable() {
2942
		global $theme;
2943
		global $mod_strings;
2944
2945
		$colspan = 1;
2946
		$setTeamUserFunction = '';
2947
2948
2949
		// get users
2950
		$r = $this->db->query("SELECT users.id, users.user_name, users.first_name, users.last_name FROM users WHERE deleted=0 AND status = 'Active' AND is_group=0 ORDER BY users.last_name, users.first_name");
2951
2952
		$userTable = '<table cellpadding="0" cellspacing="0" border="0">';
2953
		$userTable .= '<tr><td colspan="2"><b>'.$mod_strings['LBL_USER_SELECT'].'</b></td></tr>';
2954
		$userTable .= '<tr><td><input type="checkbox" style="border:0px solid #000000" onClick="toggleAll(this); setCheckMark(); checkDeps(this.form);"></td> <td>'.$mod_strings['LBL_TOGGLE_ALL'].'</td></tr>';
2955
		$userTable .= '<tr><td colspan="2"><select style="visibility:hidden;" name="users[]" id="userSelect" multiple size="12">';
2956
2957
		while($a = $this->db->fetchByAssoc($r)) {
2958
			$userTable .= '<option value="'.$a['id'].'" id="'.$a['id'].'">'.$a['first_name'].' '.$a['last_name'].'</option>';
2959
		}
2960
		$userTable .= '</select></td></tr>';
2961
		$userTable .= '</table>';
2962
2963
		$out  = '<script type="text/javascript">';
2964
		$out .= $setTeamUserFunction;
2965
		$out .= '
2966
					function setCheckMark() {
2967
						var select = document.getElementById("userSelect");
2968
2969
						for(i=0 ; i<select.options.length; i++) {
2970
							if(select.options[i].selected == true) {
2971
								document.getElementById("checkMark").style.display="";
2972
								return;
2973
							}
2974
						}
2975
2976
						document.getElementById("checkMark").style.display="none";
2977
						return;
2978
					}
2979
2980
					function showUserSelect() {
2981
						var targetTable = document.getElementById("user_select");
2982
						targetTable.style.visibility="visible";
2983
						var userSelectTable = document.getElementById("userSelect");
2984
						userSelectTable.style.visibility="visible";
2985
						return;
2986
					}
2987
					function hideUserSelect() {
2988
						var targetTable = document.getElementById("user_select");
2989
						targetTable.style.visibility="hidden";
2990
						var userSelectTable = document.getElementById("userSelect");
2991
						userSelectTable.style.visibility="hidden";
2992
						return;
2993
					}
2994
					function toggleAll(toggle) {
2995
						if(toggle.checked) {
2996
							var stat = true;
2997
						} else {
2998
							var stat = false;
2999
						}
3000
						var form = document.getElementById("userSelect");
3001
						for(i=0; i<form.options.length; i++) {
3002
							form.options[i].selected = stat;
3003
						}
3004
					}
3005
3006
3007
				</script>
3008
			<span id="showUsersDiv" style="position:relative;">
3009
				<a href="#" id="showUsers" onClick="javascript:showUserSelect();">
3010
					'.SugarThemeRegistry::current()->getImage('Users', '', null, null, ".gif", $mod_strings['LBL_USERS']).'</a>&nbsp;
3011
				<a href="#" id="showUsers" onClick="javascript:showUserSelect();">
3012
					<span style="display:none;" id="checkMark">'.SugarThemeRegistry::current()->getImage('check_inline', 'border="0"', null, null, ".gif", $mod_strings['LBL_CHECK_INLINE']).'</span>
3013
				</a>
3014
3015
3016
				<div id="user_select" style="width:200px;position:absolute;left:2;top:2;visibility:hidden;z-index:1000;">
3017
				<table cellpadding="0" cellspacing="0" border="0" class="list view">
3018
					<tr height="20">
3019
						<td  colspan="'.$colspan.'" id="hiddenhead" onClick="hideUserSelect();" onMouseOver="this.style.border = \'outset red 1px\';" onMouseOut="this.style.border = \'inset white 0px\';this.style.borderBottom = \'inset red 1px\';">
3020
							<a href="#" onClick="javascript:hideUserSelect();">'.SugarThemeRegistry::current()->getImage('close', 'border="0"', null, null, ".gif", $mod_strings['LBL_CLOSE']).'</a>
3021
							'.$mod_strings['LBL_USER_SELECT'].'
3022
						</td>
3023
					</tr>
3024
					<tr>';
3025
//<td valign="middle" height="30"  colspan="'.$colspan.'" id="hiddenhead" onClick="hideUserSelect();" onMouseOver="this.style.border = \'outset red 1px\';" onMouseOut="this.style.border = \'inset white 0px\';this.style.borderBottom = \'inset red 1px\';">
3026
		$out .=	'		<td style="padding:5px" class="oddListRowS1" bgcolor="#fdfdfd" valign="top" align="left" style="left:0;top:0;">
3027
							'.$userTable.'
3028
						</td>
3029
					</tr>
3030
				</table></div>
3031
			</span>';
3032
		return $out;
3033
	}
3034
3035
	function checkInbox($type) {
3036
		global $theme;
3037
		global $mod_strings;
3038
		$out = '<div><input	title="'.$mod_strings['LBL_BUTTON_CHECK_TITLE'].'"
3039
						class="button"
3040
						type="button" name="button"
3041
						onClick="window.location=\'index.php?module=Emails&action=Check&type='.$type.'\';"
3042
						style="margin-bottom:2px"
3043
						value="  '.$mod_strings['LBL_BUTTON_CHECK'].'  "></div>';
3044
		return $out;
3045
	}
3046
3047
        /**
3048
         * Guesses Primary Parent id from From: email address.  Cascades guesses from Accounts to Contacts to Leads to
3049
         * Users.  This will not affect the many-to-many relationships already constructed as this is, at best,
3050
         * informational linking.
3051
         */
3052
        function fillPrimaryParentFields() {
3053
                if(empty($this->from_addr))
3054
                        return;
3055
3056
                $GLOBALS['log']->debug("*** Email trying to guess Primary Parent from address [ {$this->from_addr} ]");
3057
3058
                $tables = array('accounts');
3059
                $ret = array();
3060
                // loop through types to get hits
3061
                foreach($tables as $table) {
3062
                        $q = "SELECT name, id FROM {$table} WHERE email1 = '{$this->from_addr}' OR email2 = '{$this->from_addr}' AND deleted = 0";
3063
                        $r = $this->db->query($q);
3064
                        while($a = $this->db->fetchByAssoc($r)) {
3065
                                if(!empty($a['name']) && !empty($a['id'])) {
3066
                                        $this->parent_type      = ucwords($table);
3067
                                        $this->parent_id        = $a['id'];
3068
                                        $this->parent_name      = $a['name'];
3069
                                        return;
3070
                                }
3071
                        }
3072
                }
3073
        }
3074
3075
        /**
3076
         * Convert reference to inline image (stored as Note) to URL link
3077
         * Enter description here ...
3078
         * @param string $note ID of the note
0 ignored issues
show
Bug introduced by
There is no parameter named $note. 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...
3079
         * @param string $ext type of the note
0 ignored issues
show
Bug introduced by
There is no parameter named $ext. 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...
3080
         */
3081
        public function cid2Link($noteId, $noteType)
3082
        {
3083
            if(empty($this->description_html)) return;
3084
			list($type, $subtype) = explode('/', $noteType);
3085
			if(strtolower($type) != 'image') {
3086
			    return;
3087
			}
3088
            $upload = new UploadFile();
3089
			$this->description_html = preg_replace("#class=\"image\" src=\"cid:$noteId\.(.+?)\"#", "class=\"image\" src=\"{$this->imagePrefix}{$noteId}.\\1\"", $this->description_html);
3090
	        // ensure the image is in the cache
3091
			$imgfilename = sugar_cached("images/")."$noteId.".strtolower($subtype);
3092
			$src = "upload://$noteId";
3093
			if(!file_exists($imgfilename) && file_exists($src)) {
3094
				copy($src, $imgfilename);
3095
			}
3096
        }
3097
3098
        /**
3099
         * Convert all cid: links in this email into URLs
3100
         */
3101
    	function cids2Links()
3102
    	{
3103
            if(empty($this->description_html)) return;
3104
    	    $q = "SELECT id, file_mime_type FROM notes WHERE parent_id = '{$this->id}' AND deleted = 0";
3105
    		$r = $this->db->query($q);
3106
            while($a = $this->db->fetchByAssoc($r)) {
3107
                $this->cid2Link($a['id'], $a['file_mime_type']);
3108
            }
3109
    	}
3110
3111
    /**
3112
     * Bugs 50972, 50973
3113
     * Sets the field def for a field to allow null values
3114
     *
3115
     * @todo Consider moving to SugarBean to allow other models to set fields to NULL
3116
     * @param string $field The field name to modify
3117
     * @return void
3118
     */
3119
    public function setFieldNullable($field)
3120
    {
3121
        if (isset($this->field_defs[$field]) && is_array($this->field_defs[$field]))
3122
        {
3123
            if (empty($this->modifiedFieldDefs[$field]))
3124
            {
3125
                if (
3126
                    isset($this->field_defs[$field]['isnull']) &&
3127
                    (strtolower($this->field_defs[$field]['isnull']) == 'false' || $this->field_defs[$field]['isnull'] === false)
3128
                )
3129
                {
3130
                    $this->modifiedFieldDefs[$field]['isnull'] = $this->field_defs[$field]['isnull'];
3131
                    unset($this->field_defs[$field]['isnull']);
3132
                }
3133
3134
                if (isset($this->field_defs[$field]['dbType']) && $this->field_defs[$field]['dbType'] == 'id')
3135
                {
3136
                    $this->modifiedFieldDefs[$field]['dbType'] = $this->field_defs[$field]['dbType'];
3137
                    unset($this->field_defs[$field]['dbType']);
3138
                }
3139
            }
3140
        }
3141
    }
3142
3143
    /**
3144
     * Bugs 50972, 50973
3145
     * Set the field def back to the way it was prior to modification
3146
     *
3147
     * @param $field
3148
     * @return void
3149
     */
3150
    public function revertFieldNullable($field)
3151
    {
3152
        if (!empty($this->modifiedFieldDefs[$field]) && is_array($this->modifiedFieldDefs[$field]))
3153
        {
3154
            foreach ($this->modifiedFieldDefs[$field] as $k => $v)
3155
            {
3156
                $this->field_defs[$field][$k] = $v;
3157
            }
3158
3159
            unset($this->modifiedFieldDefs[$field]);
3160
            }
3161
    	}
3162
} // end class def
3163