]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - modules/Emails/Email.php
Release 6.5.16
[Github/sugarcrm.git] / modules / Emails / Email.php
1 <?php
2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 /*********************************************************************************
4  * SugarCRM Community Edition is a customer relationship management program developed by
5  * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
6  * 
7  * This program is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU Affero General Public License version 3 as published by the
9  * Free Software Foundation with the addition of the following permission added
10  * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
11  * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
12  * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
13  * 
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
17  * details.
18  * 
19  * You should have received a copy of the GNU Affero General Public License along with
20  * this program; if not, see http://www.gnu.org/licenses or write to the Free
21  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22  * 02110-1301 USA.
23  * 
24  * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
25  * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
26  * 
27  * The interactive user interfaces in modified source and object code versions
28  * of this program must display Appropriate Legal Notices, as required under
29  * Section 5 of the GNU Affero General Public License version 3.
30  * 
31  * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
32  * these Appropriate Legal Notices must retain the display of the "Powered by
33  * SugarCRM" logo. If the display of the logo is not reasonably feasible for
34  * technical reasons, the Appropriate Legal Notices must display the words
35  * "Powered by SugarCRM".
36  ********************************************************************************/
37
38
39 require_once('include/SugarPHPMailer.php');
40 require_once 'include/upload_file.php';
41
42 class Email extends SugarBean {
43         /* SugarBean schema */
44         var $id;
45         var $date_entered;
46         var $date_modified;
47         var $assigned_user_id;
48         var $assigned_user_name;
49         var $modified_user_id;
50         var $created_by;
51         var $deleted;
52         var $from_addr;
53         var $reply_to_addr;
54         var $to_addrs;
55     var $cc_addrs;
56     var $bcc_addrs;
57         var $message_id;
58
59         /* Bean Attributes */
60         var $name;
61     var $type = 'archived';
62     var $date_sent;
63         var $status;
64         var $intent;
65         var $mailbox_id;
66         var $from_name;
67
68         var $reply_to_status;
69         var $reply_to_name;
70         var $reply_to_email;
71         var $description;
72         var $description_html;
73         var $raw_source;
74         var $parent_id;
75         var $parent_type;
76
77         /* link attributes */
78         var $parent_name;
79
80
81         /* legacy */
82         var $date_start; // legacy
83         var $time_start; // legacy
84         var $from_addr_name;
85         var $to_addrs_arr;
86     var $cc_addrs_arr;
87     var $bcc_addrs_arr;
88         var $to_addrs_ids;
89         var $to_addrs_names;
90         var $to_addrs_emails;
91         var $cc_addrs_ids;
92         var $cc_addrs_names;
93         var $cc_addrs_emails;
94         var $bcc_addrs_ids;
95         var $bcc_addrs_names;
96         var $bcc_addrs_emails;
97         var $contact_id;
98         var $contact_name;
99
100         /* Archive Email attrs */
101         var $duration_hours;
102
103
104
105         var $new_schema = true;
106         var $table_name = 'emails';
107         var $module_dir = 'Emails';
108     var $module_name = 'Emails';
109         var $object_name = 'Email';
110         var $db;
111
112         /* private attributes */
113         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";
114         var $cachePath;
115         var $cacheFile                  = 'robin.cache.php';
116         var $replyDelimiter     = "> ";
117         var $emailDescription;
118         var $emailDescriptionHTML;
119         var $emailRawSource;
120         var $link_action;
121         var $emailAddress;
122         var $attachments = array();
123
124         /* to support Email 2.0 */
125         var $isDuplicate;
126         var $uid;
127         var $to;
128         var $flagged;
129         var $answered;
130         var $seen;
131         var $draft;
132         var $relationshipMap = array(
133                 'Contacts'      => 'emails_contacts_rel',
134                 'Accounts'      => 'emails_accounts_rel',
135                 'Leads'         => 'emails_leads_rel',
136                 'Users'         => 'emails_users_rel',
137                 'Prospects'     => 'emails_prospects_rel',
138         );
139
140         /* public */
141         var $et;                // EmailUI object
142         // prefix to use when importing inlinge images in emails
143         public $imagePrefix;
144
145     /**
146      * Used for keeping track of field defs that have been modified
147      *
148      * @var array
149      */
150     public $modifiedFieldDefs = array();
151
152     public $attachment_image;
153
154         /**
155          * sole constructor
156          */
157         function Email()
158         {
159             global $current_user;
160             $this->cachePath = sugar_cached('modules/Emails');
161                 parent::SugarBean();
162
163                 $this->emailAddress = new SugarEmailAddress();
164
165                 $this->imagePrefix = rtrim($GLOBALS['sugar_config']['site_url'], "/")."/cache/images/";
166         }
167
168         function email2init() {
169                 require_once('modules/Emails/EmailUI.php');
170                 $this->et = new EmailUI();
171         }
172
173         function bean_implements($interface){
174                 switch($interface){
175                         case 'ACL': return true;
176                         default: return false;
177                 }
178
179         }
180
181         /**
182          * Presaves one attachment for new email 2.0 spec
183          * DOES NOT CREATE A NOTE
184          * @return string ID of note associated with the attachment
185          */
186         public function email2saveAttachment()
187         {
188         $email_uploads = "modules/Emails/{$GLOBALS['current_user']->id}";
189             $upload = new UploadFile('email_attachment');
190                 if(!$upload->confirm_upload()) {
191                     $err = $upload->get_upload_error();
192                     if($err) {
193                         $GLOBALS['log']->error("Email Attachment could not be attached due to error: $err");
194                     }
195                     return array();
196                 }
197
198                 $guid = create_guid();
199                 $fileName = $upload->create_stored_filename();
200         $GLOBALS['log']->debug("Email Attachment [$fileName]");
201         if($upload->final_move($guid)) {
202                 copy("upload://$guid", sugar_cached("$email_uploads/$guid"));
203                         return array(
204                                         'guid' => $guid,
205                                         'name' => $GLOBALS['db']->quote($fileName),
206                                         'nameForDisplay' => $fileName
207                                 );
208         } else {
209                         $GLOBALS['log']->debug("Email Attachment [$fileName] could not be moved to upload dir");
210                         return array();
211         }
212         }
213
214         function safeAttachmentName($filename) {
215                 global $sugar_config;
216                 $badExtension = false;
217                 //get position of last "." in file name
218                 $file_ext_beg = strrpos($filename, ".");
219                 $file_ext = "";
220
221                 //get file extension
222                 if($file_ext_beg !== false) {
223                         $file_ext = substr($filename, $file_ext_beg + 1);
224                 }
225
226                 //check to see if this is a file with extension located in "badext"
227                 foreach($sugar_config['upload_badext'] as $badExt) {
228                         if(strtolower($file_ext) == strtolower($badExt)) {
229                                 //if found, then append with .txt and break out of lookup
230                                 $filename = $filename . ".txt";
231                                 $badExtension = true;
232                                 break; // no need to look for more
233                         } // if
234                 } // foreach
235
236                 return $badExtension;
237         } // fn
238
239         /**
240          * takes output from email 2.0 to/cc/bcc fields and returns appropriate arrays for usage by PHPMailer
241          * @param string addresses
242          * @return array
243          */
244         function email2ParseAddresses($addresses) {
245                 $addresses = from_html($addresses);
246         $addresses = $this->et->unifyEmailString($addresses);
247
248                 $pattern = '/@.*,/U';
249                 preg_match_all($pattern, $addresses, $matchs);
250                 if (!empty($matchs[0])){
251                         $total = $matchs[0];
252                         foreach ($total as $match) {
253                                 $convertedPattern = str_replace(',', '::;::', $match);
254                                 $addresses = str_replace($match, $convertedPattern, $addresses);
255                         } //foreach
256                 }
257
258                 $exAddr = explode("::;::", $addresses);
259
260                 $ret = array();
261                 $clean = array("<", ">");
262                 $dirty = array("&lt;", "&gt;");
263
264                 foreach($exAddr as $addr) {
265                         $name = '';
266
267                         $addr = str_replace($dirty, $clean, $addr);
268
269                         if((strpos($addr, "<") === false) && (strpos($addr, ">") === false)) {
270                                 $address = $addr;
271                         } else {
272                                 $address = substr($addr, strpos($addr, "<") + 1, strpos($addr, ">") - 1 - strpos($addr, "<"));
273                                 $name = substr($addr, 0, strpos($addr, "<"));
274                         }
275
276                         $addrTemp = array();
277                         $addrTemp['email'] = trim($address);
278                         $addrTemp['display'] = trim($name);
279                         $ret[] = $addrTemp;
280                 }
281
282                 return $ret;
283         }
284
285         /**
286          * takes output from email 2.0 to/cc/bcc fields and returns appropriate arrays for usage by PHPMailer
287          * @param string addresses
288          * @return array
289          */
290         function email2ParseAddressesForAddressesOnly($addresses) {
291                 $addresses = from_html($addresses);
292                 $pattern = '/@.*,/U';
293                 preg_match_all($pattern, $addresses, $matchs);
294                 if (!empty($matchs[0])){
295                         $total = $matchs[0];
296                         foreach ($total as $match) {
297                                 $convertedPattern = str_replace(',', '::;::', $match);
298                                 $addresses = str_replace($match, $convertedPattern, $addresses);
299                         } //foreach
300                 }
301
302                 $exAddr = explode("::;::", $addresses);
303
304                 $ret = array();
305                 $clean = array("<", ">");
306                 $dirty = array("&lt;", "&gt;");
307
308                 foreach($exAddr as $addr) {
309                         $name = '';
310
311                         $addr = str_replace($dirty, $clean, $addr);
312
313                         if(strpos($addr, "<") && strpos($addr, ">")) {
314                                 $address = substr($addr, strpos($addr, "<") + 1, strpos($addr, ">") - 1 - strpos($addr, "<"));
315                         } else {
316                                 $address = $addr;
317                         }
318
319                         $ret[] = trim($address);
320                 }
321
322                 return $ret;
323         }
324
325         /**
326          * Determines MIME-type encoding as possible.
327          * @param string $fileLocation relative path to file
328          * @return string MIME-type
329          */
330         function email2GetMime($fileLocation) {
331             if(!is_readable($fileLocation)) {
332                 return 'application/octet-stream';
333             }
334                 if(function_exists('mime_content_type')) {
335                         $mime = mime_content_type($fileLocation);
336                 } elseif(function_exists('ext2mime')) {
337                         $mime = ext2mime($fileLocation);
338                 } else {
339                         $mime = 'application/octet-stream';
340                 }
341                 return $mime;
342         }
343
344
345         function sendEmailTest($mailserver_url, $port, $ssltls, $smtp_auth_req, $smtp_username, $smtppassword, $fromaddress, $toaddress, $mail_sendtype = 'smtp', $fromname = '') {
346                 global $current_user,$app_strings;
347                 $mod_strings = return_module_language($GLOBALS['current_language'], 'Emails'); //Called from EmailMan as well.
348             $mail = new SugarPHPMailer();
349                 $mail->Mailer = strtolower($mail_sendtype);
350                 if($mail->Mailer == 'smtp')
351                 {
352                 $mail->Host = $mailserver_url;
353                 $mail->Port = $port;
354                 if (isset($ssltls) && !empty($ssltls)) {
355                         $mail->protocol = "ssl://";
356                 if ($ssltls == 1) {
357                     $mail->SMTPSecure = 'ssl';
358                 } // if
359                 if ($ssltls == 2) {
360                     $mail->SMTPSecure = 'tls';
361                 } // if
362                 } else {
363                         $mail->protocol = "tcp://";
364                 }
365                 if ($smtp_auth_req) {
366                         $mail->SMTPAuth = TRUE;
367                         $mail->Username = $smtp_username;
368                         $mail->Password = $smtppassword;
369                 }
370                 }
371                 else
372                     $mail->Mailer = 'sendmail';
373
374                 $mail->Subject = from_html($mod_strings['LBL_TEST_EMAIL_SUBJECT']);
375                 $mail->From = $fromaddress;
376
377         if ($fromname != '') {
378             $mail->FromName = html_entity_decode($fromname,ENT_QUOTES);
379         } else {
380             $mail->FromName = $current_user->name;
381         }
382
383         $mail->Sender = $mail->From;
384                 $mail->AddAddress($toaddress);
385                 $mail->Body = $mod_strings['LBL_TEST_EMAIL_BODY'];
386
387                 $return = array();
388
389                 if(!$mail->Send()) {
390                 ob_clean();
391                 $return['status'] = false;
392                 $return['errorMessage'] = $app_strings['LBL_EMAIL_ERROR_PREPEND']. $mail->ErrorInfo;
393                 return $return;
394                 } // if
395                 $return['status'] = true;
396         return $return;
397         } // fn
398
399         function decodeDuringSend($htmlData) {
400             $htmlData = str_replace("sugarLessThan", "&lt;", $htmlData);
401             $htmlData = str_replace("sugarGreaterThan", "&gt;", $htmlData);
402                 return $htmlData;
403         }
404
405         /**
406          * Returns true or false if this email is a draft.
407          *
408          * @param array $request
409          * @return bool True indicates this email is a draft.
410          */
411         function isDraftEmail($request)
412         {
413             return ( isset($request['saveDraft']) || ($this->type == 'draft' && $this->status == 'draft') );
414         }
415
416         /**
417          * Sends Email for Email 2.0
418          */
419         function email2Send($request) {
420                 global $mod_strings;
421                 global $app_strings;
422                 global $current_user;
423                 global $sugar_config;
424                 global $locale;
425                 global $timedate;
426                 global $beanList;
427                 global $beanFiles;
428         $OBCharset = $locale->getPrecedentPreference('default_email_charset');
429
430                 /**********************************************************************
431                  * Sugar Email PREP
432                  */
433                 /* preset GUID */
434
435                 $orignialId = "";
436                 if(!empty($this->id)) {
437                         $orignialId =   $this->id;
438                 } // if
439
440                 if(empty($this->id)) {
441                         $this->id = create_guid();
442                         $this->new_with_id = true;
443                 }
444
445                 /* satisfy basic HTML email requirements */
446                 $this->name = $request['sendSubject'];
447                 $this->description_html = '&lt;html&gt;&lt;body&gt;'.$request['sendDescription'].'&lt;/body&gt;&lt;/html&gt;';
448
449                 /**********************************************************************
450                  * PHPMAILER PREP
451                  */
452                 $mail = new SugarPHPMailer();
453                 $mail = $this->setMailer($mail, '', $_REQUEST['fromAccount']);
454                 if (empty($mail->Host) && !$this->isDraftEmail($request))
455                 {
456             $this->status = 'send_error';
457
458             if ($mail->oe->type == 'system')
459                 echo($app_strings['LBL_EMAIL_ERROR_PREPEND']. $app_strings['LBL_EMAIL_INVALID_SYSTEM_OUTBOUND']);
460              else
461                 echo($app_strings['LBL_EMAIL_ERROR_PREPEND']. $app_strings['LBL_EMAIL_INVALID_PERSONAL_OUTBOUND']);
462
463             return false;
464                 }
465
466                 $subject = $this->name;
467                 $mail->Subject = from_html($this->name);
468
469                 // work-around legacy code in SugarPHPMailer
470                 if($_REQUEST['setEditor'] == 1) {
471                         $_REQUEST['description_html'] = $_REQUEST['sendDescription'];
472                         $this->description_html = $_REQUEST['description_html'];
473                 } else {
474                         $this->description_html = '';
475                         $this->description = $_REQUEST['sendDescription'];
476                 }
477                 // end work-around
478
479                 if ( $this->isDraftEmail($request) )
480                 {
481                         if($this->type != 'draft' && $this->status != 'draft') {
482                         $this->id = create_guid();
483                         $this->new_with_id = true;
484                         $this->date_entered = "";
485                         } // if
486                         $q1 = "update emails_email_addr_rel set deleted = 1 WHERE email_id = '{$this->id}'";
487                         $r1 = $this->db->query($q1);
488                 } // if
489
490                 if (isset($request['saveDraft'])) {
491                         $this->type = 'draft';
492                         $this->status = 'draft';
493                         $forceSave = true;
494                 } else {
495                         /* Apply Email Templates */
496                         // do not parse email templates if the email is being saved as draft....
497                     $toAddresses = $this->email2ParseAddresses($_REQUEST['sendTo']);
498                 $sea = new SugarEmailAddress();
499                 $object_arr = array();
500
501                         if( isset($_REQUEST['parent_type']) && !empty($_REQUEST['parent_type']) &&
502                                 isset($_REQUEST['parent_id']) && !empty($_REQUEST['parent_id']) &&
503                                 ($_REQUEST['parent_type'] == 'Accounts' ||
504                                 $_REQUEST['parent_type'] == 'Contacts' ||
505                                 $_REQUEST['parent_type'] == 'Leads' ||
506                                 $_REQUEST['parent_type'] == 'Users' ||
507                                 $_REQUEST['parent_type'] == 'Prospects')) {
508                                         if(isset($beanList[$_REQUEST['parent_type']]) && !empty($beanList[$_REQUEST['parent_type']])) {
509                                                 $className = $beanList[$_REQUEST['parent_type']];
510                                                 if(isset($beanFiles[$className]) && !empty($beanFiles[$className])) {
511                                                         if(!class_exists($className)) {
512                                                                 require_once($beanFiles[$className]);
513                                                         }
514                                                         $bean = new $className();
515                                                         $bean->retrieve($_REQUEST['parent_id']);
516                                         $object_arr[$bean->module_dir] = $bean->id;
517                                                 } // if
518                                         } // if
519                         }
520                         foreach($toAddresses as $addrMeta) {
521                                 $addr = $addrMeta['email'];
522                                 $beans = $sea->getBeansByEmailAddress($addr);
523                                 foreach($beans as $bean) {
524                                         if (!isset($object_arr[$bean->module_dir])) {
525                                                 $object_arr[$bean->module_dir] = $bean->id;
526                                         }
527                                 }
528                         }
529
530                 /* template parsing */
531                 if (empty($object_arr)) {
532                   $object_arr= array('Contacts' => '123');
533                 }
534                 $object_arr['Users'] = $current_user->id;
535                 $this->description_html = EmailTemplate::parse_template($this->description_html, $object_arr);
536                 $this->name = EmailTemplate::parse_template($this->name, $object_arr);
537                 $this->description = EmailTemplate::parse_template($this->description, $object_arr);
538                 $this->description = html_entity_decode($this->description,ENT_COMPAT,'UTF-8');
539                         if($this->type != 'draft' && $this->status != 'draft') {
540                         $this->id = create_guid();
541                         $this->date_entered = "";
542                         $this->new_with_id = true;
543                         $this->type = 'out';
544                         $this->status = 'sent';
545                         }
546         }
547
548         if(isset($_REQUEST['parent_type']) && empty($_REQUEST['parent_type']) &&
549                         isset($_REQUEST['parent_id']) && empty($_REQUEST['parent_id']) ) {
550                                 $this->parent_id = "";
551                                 $this->parent_type = "";
552                 } // if
553
554
555         $mail->Subject = $this->name;
556         $mail = $this->handleBody($mail);
557         $mail->Subject = $this->name;
558         $this->description_html = from_html($this->description_html);
559         $this->description_html = $this->decodeDuringSend($this->description_html);
560                 $this->description = $this->decodeDuringSend($this->description);
561
562                 /* from account */
563                 $replyToAddress = $current_user->emailAddress->getReplyToAddress($current_user, true);
564                 $replyToName = "";
565                 if(empty($request['fromAccount'])) {
566                         $defaults = $current_user->getPreferredEmail();
567                         $mail->From = $defaults['email'];
568                         $mail->FromName = $defaults['name'];
569                         $replyToName = $mail->FromName;
570                         //$replyToAddress = $current_user->emailAddress->getReplyToAddress($current_user);
571                 } else {
572                         // passed -> user -> system default
573                         $ie = new InboundEmail();
574                         $ie->retrieve($request['fromAccount']);
575                         $storedOptions = unserialize(base64_decode($ie->stored_options));
576                         $fromName = "";
577                         $fromAddress = "";
578                         $replyToName = "";
579                         //$replyToAddress = "";
580                         if (!empty($storedOptions)) {
581                                 $fromAddress = $storedOptions['from_addr'];
582                                 $fromName = from_html($storedOptions['from_name']);
583                                 $replyToAddress = (isset($storedOptions['reply_to_addr']) ? $storedOptions['reply_to_addr'] : "");
584                                 $replyToName = (isset($storedOptions['reply_to_name']) ? from_html($storedOptions['reply_to_name']) : "");
585                         } // if
586                         $defaults = $current_user->getPreferredEmail();
587                         // Personal Account doesn't have reply To Name and Reply To Address. So add those columns on UI
588                         // After adding remove below code
589
590                         // code to remove
591                         if ($ie->is_personal)
592                         {
593                                 if (empty($replyToAddress))
594                                 {
595                                         $replyToAddress = $current_user->emailAddress->getReplyToAddress($current_user, true);
596                                 } // if
597                                 if (empty($replyToName))
598                                 {
599                                         $replyToName = $defaults['name'];
600                                 } // if
601                                 //Personal accounts can have a reply_address, which should
602                                 //overwrite the users set default.
603                                 if( !empty($storedOptions['reply_to_addr']) )
604                                         $replyToAddress = $storedOptions['reply_to_addr'];
605
606                         }
607                         // end of code to remove
608                         $mail->From = (!empty($fromAddress)) ? $fromAddress : $defaults['email'];
609                         $mail->FromName = (!empty($fromName)) ? $fromName : $defaults['name'];
610                         $replyToName = (!empty($replyToName)) ? $replyToName : $mail->FromName;
611                 }
612
613                 $mail->Sender = $mail->From; /* set Return-Path field in header to reduce spam score in emails sent via Sugar's Email module */
614
615                 if (!empty($replyToAddress)) {
616                         $mail->AddReplyTo($replyToAddress,$locale->translateCharsetMIME(trim( $replyToName), 'UTF-8', $OBCharset));
617                 } else {
618                         $mail->AddReplyTo($mail->From,$locale->translateCharsetMIME(trim( $mail->FromName), 'UTF-8', $OBCharset));
619                 } // else
620         $emailAddressCollection = array(); // used in linking to beans below
621                 // handle to/cc/bcc
622                 foreach($this->email2ParseAddresses($request['sendTo']) as $addr_arr) {
623                         if(empty($addr_arr['email'])) continue;
624
625                         if(empty($addr_arr['display'])) {
626                                 $mail->AddAddress($addr_arr['email'], "");
627                         } else {
628                                 $mail->AddAddress($addr_arr['email'],$locale->translateCharsetMIME(trim( $addr_arr['display']), 'UTF-8', $OBCharset));
629                         }
630                         $emailAddressCollection[] = $addr_arr['email'];
631                 }
632                 foreach($this->email2ParseAddresses($request['sendCc']) as $addr_arr) {
633                         if(empty($addr_arr['email'])) continue;
634
635                         if(empty($addr_arr['display'])) {
636                                 $mail->AddCC($addr_arr['email'], "");
637                         } else {
638                                 $mail->AddCC($addr_arr['email'],$locale->translateCharsetMIME(trim( $addr_arr['display']), 'UTF-8', $OBCharset));
639                         }
640                         $emailAddressCollection[] = $addr_arr['email'];
641                 }
642
643                 foreach($this->email2ParseAddresses($request['sendBcc']) as $addr_arr) {
644                         if(empty($addr_arr['email'])) continue;
645
646                         if(empty($addr_arr['display'])) {
647                                 $mail->AddBCC($addr_arr['email'], "");
648                         } else {
649                                 $mail->AddBCC($addr_arr['email'],$locale->translateCharsetMIME(trim( $addr_arr['display']), 'UTF-8', $OBCharset));
650                         }
651                         $emailAddressCollection[] = $addr_arr['email'];
652                 }
653
654
655                 /* parse remove attachments array */
656                 $removeAttachments = array();
657                 if(!empty($request['templateAttachmentsRemove'])) {
658                         $exRemove = explode("::", $request['templateAttachmentsRemove']);
659
660                         foreach($exRemove as $file) {
661                                 $removeAttachments = substr($file, 0, 36);
662                         }
663                 }
664
665                 /* handle attachments */
666                 if(!empty($request['attachments'])) {
667                         $exAttachments = explode("::", $request['attachments']);
668
669                         foreach($exAttachments as $file) {
670                                 $file = trim(from_html($file));
671                                 $file = str_replace("\\", "", $file);
672                                 if(!empty($file)) {
673                                         //$fileLocation = $this->et->userCacheDir."/{$file}";
674                                         $fileGUID = preg_replace('/[^a-z0-9\-]/', "", substr($file, 0, 36));
675                                         $fileLocation = $this->et->userCacheDir."/{$fileGUID}";
676                                         $filename = substr($file, 36, strlen($file)); // strip GUID     for PHPMailer class to name outbound file
677
678                                         $mail->AddAttachment($fileLocation,$filename, 'base64', $this->email2GetMime($fileLocation));
679                                         //$mail->AddAttachment($fileLocation, $filename, 'base64');
680
681                                         // only save attachments if we're archiving or drafting
682                                         if((($this->type == 'draft') && !empty($this->id)) || (isset($request['saveToSugar']) && $request['saveToSugar'] == 1)) {
683                                                 $note = new Note();
684                                                 $note->id = create_guid();
685                                                 $note->new_with_id = true; // duplicating the note with files
686                                                 $note->parent_id = $this->id;
687                                                 $note->parent_type = $this->module_dir;
688                                                 $note->name = $filename;
689                                                 $note->filename = $filename;
690                                                 $note->file_mime_type = $this->email2GetMime($fileLocation);
691                         $dest = "upload://{$note->id}";
692                                                 if(!copy($fileLocation, $dest)) {
693                                                         $GLOBALS['log']->debug("EMAIL 2.0: could not copy attachment file to $fileLocation => $dest");
694                                                 }
695
696                                                 $note->save();
697                                         }
698                                 }
699                         }
700                 }
701
702                 /* handle sugar documents */
703                 if(!empty($request['documents'])) {
704                         $exDocs = explode("::", $request['documents']);
705
706                         foreach($exDocs as $docId) {
707                                 $docId = trim($docId);
708                                 if(!empty($docId)) {
709                                         $doc = new Document();
710                                         $docRev = new DocumentRevision();
711                                         $doc->retrieve($docId);
712                                         $docRev->retrieve($doc->document_revision_id);
713
714                                         $filename = $docRev->filename;
715                                         $docGUID = preg_replace('/[^a-z0-9\-]/', "", $docRev->id);
716                                         $fileLocation = "upload://{$docGUID}";
717                                         $mime_type = $docRev->file_mime_type;
718                                         $mail->AddAttachment($fileLocation,$locale->translateCharsetMIME(trim($filename), 'UTF-8', $OBCharset), 'base64', $mime_type);
719
720                                         // only save attachments if we're archiving or drafting
721                                         if((($this->type == 'draft') && !empty($this->id)) || (isset($request['saveToSugar']) && $request['saveToSugar'] == 1)) {
722                                                 $note = new Note();
723                                                 $note->id = create_guid();
724                                                 $note->new_with_id = true; // duplicating the note with files
725                                                 $note->parent_id = $this->id;
726                                                 $note->parent_type = $this->module_dir;
727                                                 $note->name = $filename;
728                                                 $note->filename = $filename;
729                                                 $note->file_mime_type = $mime_type;
730                         $dest = "upload://{$note->id}";
731                                                 if(!copy($fileLocation, $dest)) {
732                                                         $GLOBALS['log']->debug("EMAIL 2.0: could not copy SugarDocument revision file $fileLocation => $dest");
733                                                 }
734
735                                                 $note->save();
736                                         }
737                                 }
738                         }
739                 }
740
741                 /* handle template attachments */
742                 if(!empty($request['templateAttachments'])) {
743
744                         $exNotes = explode("::", $request['templateAttachments']);
745                         foreach($exNotes as $noteId) {
746                                 $noteId = trim($noteId);
747                                 if(!empty($noteId)) {
748                                         $note = new Note();
749                                         $note->retrieve($noteId);
750                                         if (!empty($note->id)) {
751                                                 $filename = $note->filename;
752                                                 $noteGUID = preg_replace('/[^a-z0-9\-]/', "", $note->id);
753                                                 $fileLocation = "upload://{$noteGUID}";
754                                                 $mime_type = $note->file_mime_type;
755                                                 if (!$note->embed_flag) {
756                                                         $mail->AddAttachment($fileLocation,$filename, 'base64', $mime_type);
757                                                         // only save attachments if we're archiving or drafting
758                                                         if((($this->type == 'draft') && !empty($this->id)) || (isset($request['saveToSugar']) && $request['saveToSugar'] == 1)) {
759
760                                                                 if ($note->parent_id != $this->id)
761                                                                     $this->saveTempNoteAttachments($filename,$fileLocation, $mime_type);
762                                                         } // if
763
764                                                 } // if
765                                         } else {
766                                                 //$fileLocation = $this->et->userCacheDir."/{$file}";
767                                                 $fileGUID = preg_replace('/[^a-z0-9\-]/', "", substr($noteId, 0, 36));
768                                                 $fileLocation = $this->et->userCacheDir."/{$fileGUID}";
769                                                 //$fileLocation = $this->et->userCacheDir."/{$noteId}";
770                                                 $filename = substr($noteId, 36, strlen($noteId)); // strip GUID for PHPMailer class to name outbound file
771
772                                                 $mail->AddAttachment($fileLocation,$locale->translateCharsetMIME(trim($filename), 'UTF-8', $OBCharset), 'base64', $this->email2GetMime($fileLocation));
773
774                                                 //If we are saving an email we were going to forward we need to save the attachments as well.
775                                                 if( (($this->type == 'draft') && !empty($this->id))
776                                                       || (isset($request['saveToSugar']) && $request['saveToSugar'] == 1))
777                                                   {
778                                                       $mimeType = $this->email2GetMime($fileLocation);
779                                                       $this->saveTempNoteAttachments($filename,$fileLocation, $mimeType);
780                                                  } // if
781                                         }
782                                 }
783                         }
784                 }
785
786
787
788                 /**********************************************************************
789                  * Final Touches
790                  */
791                 /* save email to sugar? */
792                 $forceSave = false;
793
794                 if($this->type == 'draft' && !isset($request['saveDraft'])) {
795                         // sending a draft email
796                         $this->type = 'out';
797                         $this->status = 'sent';
798                         $forceSave = true;
799                 } elseif(isset($request['saveDraft'])) {
800                         $this->type = 'draft';
801                         $this->status = 'draft';
802                         $forceSave = true;
803                 }
804
805                       /**********************************************************************
806          * SEND EMAIL (finally!)
807          */
808         $mailSent = false;
809         if ($this->type != 'draft') {
810             $mail->prepForOutbound();
811             $mail->Body = $this->decodeDuringSend($mail->Body);
812             $mail->AltBody = $this->decodeDuringSend($mail->AltBody);
813             if (!$mail->Send()) {
814                 $this->status = 'send_error';
815                 ob_clean();
816                 echo($app_strings['LBL_EMAIL_ERROR_PREPEND']. $mail->ErrorInfo);
817                 return false;
818             }
819         }
820
821                 if ((!(empty($orignialId) || isset($request['saveDraft']) || ($this->type == 'draft' && $this->status == 'draft'))) &&
822                         (($_REQUEST['composeType'] == 'reply') || ($_REQUEST['composeType'] == 'replyAll') || ($_REQUEST['composeType'] == 'replyCase')) && ($orignialId != $this->id)) {
823                         $originalEmail = new Email();
824                         $originalEmail->retrieve($orignialId);
825                         $originalEmail->reply_to_status = 1;
826                         $originalEmail->save();
827                         $this->reply_to_status = 0;
828                 } // if
829
830                 if ($_REQUEST['composeType'] == 'reply' || $_REQUEST['composeType'] == 'replyCase') {
831                         if (isset($_REQUEST['ieId']) && isset($_REQUEST['mbox'])) {
832                                 $emailFromIe = new InboundEmail();
833                                 $emailFromIe->retrieve($_REQUEST['ieId']);
834                                 $emailFromIe->mailbox = $_REQUEST['mbox'];
835                                 if (isset($emailFromIe->id) && $emailFromIe->is_personal) {
836                                         if ($emailFromIe->isPop3Protocol()) {
837                                                 $emailFromIe->mark_answered($this->uid, 'pop3');
838                                         }
839                                         elseif ($emailFromIe->connectMailserver() == 'true') {
840                                                 $emailFromIe->markEmails($this->uid, 'answered');
841                                                 $emailFromIe->mark_answered($this->uid);
842                                         }
843                                 }
844                         }
845                 }
846
847
848                 if(     $forceSave ||
849                         $this->type == 'draft' ||
850                         (isset($request['saveToSugar']) && $request['saveToSugar'] == 1)) {
851
852                         // saving a draft OR saving a sent email
853                         $decodedFromName = mb_decode_mimeheader($mail->FromName);
854                         $this->from_addr = "{$decodedFromName} <{$mail->From}>";
855                         $this->from_addr_name = $this->from_addr;
856                         $this->to_addrs = $_REQUEST['sendTo'];
857                         $this->to_addrs_names = $_REQUEST['sendTo'];
858                         $this->cc_addrs = $_REQUEST['sendCc'];
859                         $this->cc_addrs_names = $_REQUEST['sendCc'];
860                         $this->bcc_addrs = $_REQUEST['sendBcc'];
861                         $this->bcc_addrs_names = $_REQUEST['sendBcc'];
862                         $this->assigned_user_id = $current_user->id;
863
864                         $this->date_sent = $timedate->now();
865                         ///////////////////////////////////////////////////////////////////
866                         ////    LINK EMAIL TO SUGARBEANS BASED ON EMAIL ADDY
867
868                         if( isset($_REQUEST['parent_type']) && !empty($_REQUEST['parent_type']) &&
869                                 isset($_REQUEST['parent_id']) && !empty($_REQUEST['parent_id']) ) {
870                         $this->parent_id = $_REQUEST['parent_id'];
871                         $this->parent_type = $_REQUEST['parent_type'];
872                                         $q = "SELECT count(*) c FROM emails_beans WHERE  email_id = '{$this->id}' AND bean_id = '{$_REQUEST['parent_id']}' AND bean_module = '{$_REQUEST['parent_type']}'";
873                                         $r = $this->db->query($q);
874                                         $a = $this->db->fetchByAssoc($r);
875                                         if($a['c'] <= 0) {
876                                                 if(isset($beanList[$_REQUEST['parent_type']]) && !empty($beanList[$_REQUEST['parent_type']])) {
877                                                         $className = $beanList[$_REQUEST['parent_type']];
878                                                         if(isset($beanFiles[$className]) && !empty($beanFiles[$className])) {
879                                                                 if(!class_exists($className)) {
880                                                                         require_once($beanFiles[$className]);
881                                                                 }
882                                                                 $bean = new $className();
883                                                                 $bean->retrieve($_REQUEST['parent_id']);
884                                                                 if($bean->load_relationship('emails')) {
885                                                                         $bean->emails->add($this->id);
886                                                                 } // if
887
888                                                         } // if
889
890                                                 } // if
891
892                                         } // if
893
894                                 } else {
895                                         if(!class_exists('aCase')) {
896
897                                         }
898                                         else{
899                                                 $c = new aCase();
900                                                 if($caseId = InboundEmail::getCaseIdFromCaseNumber($mail->Subject, $c)) {
901                                                         $c->retrieve($caseId);
902                                                         $c->load_relationship('emails');
903                                                         $c->emails->add($this->id);
904                                                         $this->parent_type = "Cases";
905                                                         $this->parent_id = $caseId;
906                                                 } // if
907                                         }
908
909                                 } // else
910
911                         ////    LINK EMAIL TO SUGARBEANS BASED ON EMAIL ADDY
912                         ///////////////////////////////////////////////////////////////////
913                         $this->save();
914                 }
915
916                 if(!empty($request['fromAccount'])) {
917                         if (isset($ie->id) && !$ie->isPop3Protocol() && $mail->oe->mail_smtptype != 'gmail') {
918                                 $sentFolder = $ie->get_stored_options("sentFolder");
919                                 if (!empty($sentFolder)) {
920                                         $data = $mail->CreateHeader() . "\r\n" . $mail->CreateBody() . "\r\n";
921                                         $ie->mailbox = $sentFolder;
922                                         if ($ie->connectMailserver() == 'true') {
923                                                 $connectString = $ie->getConnectString($ie->getServiceString(), $ie->mailbox);
924                                                 $returnData = imap_append($ie->conn,$connectString, $data, "\\Seen");
925                                                 if (!$returnData) {
926                                                         $GLOBALS['log']->debug("could not copy email to {$ie->mailbox} for {$ie->name}");
927                                                 } // if
928                                         } else {
929                                                 $GLOBALS['log']->debug("could not connect to mail serve for folder {$ie->mailbox} for {$ie->name}");
930                                         } // else
931                                 } else {
932                                         $GLOBALS['log']->debug("could not copy email to {$ie->mailbox} sent folder as its empty");
933                                 } // else
934                         } // if
935                 } // if
936                 return true;
937         } // end email2send
938
939     /**
940      * Generates a config-specified separated name and addresses to be used in compose email screen for
941      * contacts or leads from listview
942      * By default, use comma, but allow for non-standard delimiters as specified in email_address_separator
943      *
944      * @param $module string module name
945      * @param $idsArray array of record ids to get the email address for
946      * @return string (config-specified) delimited list of email addresses
947      */
948     public function getNamePlusEmailAddressesForCompose($module, $idsArray)
949     {
950         global $locale;
951         $result = array();
952
953         foreach ($idsArray as $id)
954         {
955             // Load bean
956             $bean = BeanFactory::getBean($module, $id);
957
958             // Got a bean
959             if (!empty($bean))
960             {
961                 // For CE, just get primary e-mail address
962                 $emailAddress = $bean->email1;
963
964
965                 // If we have an e-mail address loaded
966                 if (!empty($emailAddress))
967                 {
968                     // Use bean name by default
969                     $fullName = $bean->name;
970
971                     // Depending on module, format the name
972                     if (in_array($module, array('Users', 'Employees')))
973                     {
974                         $fullName = from_html(
975                             $locale->getLocaleFormattedName(
976                                 $bean->first_name,
977                                 $bean->last_name,
978                                 '',
979                                 $bean->title
980                             )
981                         );
982                     }
983                     else if (SugarModule::get($module)->moduleImplements('Person'))
984                     {
985                         $fullName = from_html(
986                             $locale->getLocaleFormattedName(
987                                 $bean->first_name,
988                                 $bean->last_name,
989                                 $bean->salutation,
990                                 $bean->title
991                             )
992                         );
993                     }
994
995                     // Make e-mail address in format "Name <@email>"
996                     $result[$bean->id] = $fullName . " <" . from_html($emailAddress) . ">";
997                 }
998             }
999         }
1000
1001         // Broken out of method to facilitate unit testing
1002         return $this->_arrayToDelimitedString($result);
1003     }
1004
1005     /**
1006      * @param Array $arr - list of strings
1007      * @return string the list of strings delimited by email_address_separator
1008      */
1009     function _arrayToDelimitedString($arr)
1010     {
1011         // bug 51804: outlook does not respect the correct email address separator (',') , so let
1012         // clients override the default.
1013         $separator = (isset($GLOBALS['sugar_config']['email_address_separator']) &&
1014                         !empty($GLOBALS['sugar_config']['email_address_separator'])) ?
1015                      $GLOBALS['sugar_config']['email_address_separator'] :
1016                      ',';
1017
1018                 return join($separator, array_values($arr));
1019     }
1020
1021         /**
1022          * Overrides
1023          */
1024         ///////////////////////////////////////////////////////////////////////////
1025         ////    SAVERS
1026         function save($check_notify = false) {
1027         global $current_user;
1028
1029                 if($this->isDuplicate) {
1030                         $GLOBALS['log']->debug("EMAIL - tried to save a duplicate Email record");
1031                 } else {
1032
1033                         if(empty($this->id)) {
1034                                 $this->id = create_guid();
1035                                 $this->new_with_id = true;
1036                         }
1037                         $this->from_addr_name = $this->cleanEmails($this->from_addr_name);
1038                         $this->to_addrs_names = $this->cleanEmails($this->to_addrs_names);
1039                         $this->cc_addrs_names = $this->cleanEmails($this->cc_addrs_names);
1040                         $this->bcc_addrs_names = $this->cleanEmails($this->bcc_addrs_names);
1041                         $this->reply_to_addr = $this->cleanEmails($this->reply_to_addr);
1042                         $this->description = SugarCleaner::cleanHtml($this->description);
1043             $this->description_html = SugarCleaner::cleanHtml($this->description_html, true);
1044             $this->raw_source = SugarCleaner::cleanHtml($this->raw_source, true);
1045                         $this->saveEmailText();
1046                         $this->saveEmailAddresses();
1047
1048                         $GLOBALS['log']->debug('-------------------------------> Email called save()');
1049
1050                         // handle legacy concatenation of date and time fields
1051                         //Bug 39503 - SugarBean is not setting date_sent when seconds missing
1052                         if(empty($this->date_sent)) {
1053                                 global $timedate;
1054                                 $date_sent_obj = $timedate->fromUser($timedate->merge_date_time($this->date_start, $this->time_start), $current_user);
1055                  if (!empty($date_sent_obj) && ($date_sent_obj instanceof SugarDateTime)) {
1056                                     $this->date_sent = $date_sent_obj->asDb();
1057                  }
1058                         }
1059
1060                         parent::save($check_notify);
1061
1062                         if(!empty($this->parent_type) && !empty($this->parent_id)) {
1063                 if(!empty($this->fetched_row) && !empty($this->fetched_row['parent_id']) && !empty($this->fetched_row['parent_type'])) {
1064                     if($this->fetched_row['parent_id'] != $this->parent_id || $this->fetched_row['parent_type'] != $this->parent_type) {
1065                         $mod = strtolower($this->fetched_row['parent_type']);
1066                         $rel = array_key_exists($mod, $this->field_defs) ? $mod : $mod . "_activities_emails"; //Custom modules rel name
1067                         if($this->load_relationship($rel) ) {
1068                             $this->$rel->delete($this->id, $this->fetched_row['parent_id']);
1069                         }
1070                     }
1071                 }
1072                 $mod = strtolower($this->parent_type);
1073                 $rel = array_key_exists($mod, $this->field_defs) ? $mod : $mod . "_activities_emails"; //Custom modules rel name
1074                 if($this->load_relationship($rel) ) {
1075                     $this->$rel->add($this->parent_id);
1076                 }
1077                         }
1078                 }
1079                 $GLOBALS['log']->debug('-------------------------------> Email save() done');
1080         }
1081
1082         /**
1083          * Helper function to save temporary attachments assocaited to an email as note.
1084          *
1085          * @param string $filename
1086          * @param string $fileLocation
1087          * @param string $mimeType
1088          */
1089         function saveTempNoteAttachments($filename,$fileLocation, $mimeType)
1090         {
1091             $tmpNote = new Note();
1092             $tmpNote->id = create_guid();
1093             $tmpNote->new_with_id = true;
1094             $tmpNote->parent_id = $this->id;
1095             $tmpNote->parent_type = $this->module_dir;
1096             $tmpNote->name = $filename;
1097             $tmpNote->filename = $filename;
1098             $tmpNote->file_mime_type = $mimeType;
1099             $noteFile = "upload://{$tmpNote->id}";
1100             if(!copy($fileLocation, $noteFile)) {
1101             $GLOBALS['log']->fatal("EMAIL 2.0: could not copy SugarDocument revision file $fileLocation => $noteFile");
1102             }
1103             $tmpNote->save();
1104         }
1105         /**
1106          * Handles normalization of Email Addressess
1107          */
1108         function saveEmailAddresses() {
1109                 // from, single address
1110                 $fromId = $this->emailAddress->getEmailGUID(from_html($this->from_addr));
1111         if(!empty($fromId)){
1112                   $this->linkEmailToAddress($fromId, 'from');
1113         }
1114
1115                 // to, multiple
1116                 $replace = array(",",";");
1117                 $toaddrs = str_replace($replace, "::", from_html($this->to_addrs));
1118                 $exToAddrs = explode("::", $toaddrs);
1119
1120                 if(!empty($exToAddrs)) {
1121                         foreach($exToAddrs as $toaddr) {
1122                                 $toaddr = trim($toaddr);
1123                                 if(!empty($toaddr)) {
1124                                         $toId = $this->emailAddress->getEmailGUID($toaddr);
1125                                         $this->linkEmailToAddress($toId, 'to');
1126                                 }
1127                         }
1128                 }
1129
1130                 // cc, multiple
1131                 $ccAddrs = str_replace($replace, "::", from_html($this->cc_addrs));
1132                 $exccAddrs = explode("::", $ccAddrs);
1133
1134                 if(!empty($exccAddrs)) {
1135                         foreach($exccAddrs as $ccAddr) {
1136                                 $ccAddr = trim($ccAddr);
1137                                 if(!empty($ccAddr)) {
1138                                         $ccId = $this->emailAddress->getEmailGUID($ccAddr);
1139                                         $this->linkEmailToAddress($ccId, 'cc');
1140                                 }
1141                         }
1142                 }
1143
1144                 // bcc, multiple
1145                 $bccAddrs = str_replace($replace, "::", from_html($this->bcc_addrs));
1146                 $exbccAddrs = explode("::", $bccAddrs);
1147                 if(!empty($exbccAddrs)) {
1148                         foreach($exbccAddrs as $bccAddr) {
1149                                 $bccAddr = trim($bccAddr);
1150                                 if(!empty($bccAddr)) {
1151                                         $bccId = $this->emailAddress->getEmailGUID($bccAddr);
1152                                         $this->linkEmailToAddress($bccId, 'bcc');
1153                                 }
1154                         }
1155                 }
1156         }
1157
1158         function linkEmailToAddress($id, $type) {
1159                 // TODO: make this update?
1160                 $q1 = "SELECT * FROM emails_email_addr_rel WHERE email_id = '{$this->id}' AND email_address_id = '{$id}' AND address_type = '{$type}' AND deleted = 0";
1161                 $r1 = $this->db->query($q1);
1162                 $a1 = $this->db->fetchByAssoc($r1);
1163
1164                 if(!empty($a1) && !empty($a1['id'])) {
1165                         return $a1['id'];
1166                 } else {
1167                         $guid = create_guid();
1168                         $q2 = "INSERT INTO emails_email_addr_rel VALUES('{$guid}', '{$this->id}', '{$type}', '{$id}', 0)";
1169                         $r2 = $this->db->query($q2);
1170                 }
1171
1172                 return $guid;
1173         }
1174
1175     protected $email_to_text = array(
1176         "email_id" => "id",
1177         "description" => "description",
1178         "description_html" => "description_html",
1179         "raw_source" => "raw_source",
1180         "from_addr" => "from_addr_name",
1181         "reply_to_addr" => "reply_to_addr",
1182         "to_addrs" => "to_addrs_names",
1183         "cc_addrs" => "cc_addrs_names",
1184         "bcc_addrs" => "bcc_addrs_names",
1185     );
1186
1187         function cleanEmails($emails)
1188         {
1189             if(empty($emails)) return '';
1190                 $emails = str_replace(array(",",";"), "::", from_html($emails));
1191                 $addrs = explode("::", $emails);
1192                 $res = array();
1193                 foreach($addrs as $addr) {
1194             $parts = $this->emailAddress->splitEmailAddress($addr);
1195             if(empty($parts["email"])) {
1196                 continue;
1197             }
1198             if(!empty($parts["name"])) {
1199                 $res[] = "{$parts['name']} <{$parts['email']}>";
1200             } else {
1201                 $res[] .= $parts["email"];
1202             }
1203                 }
1204                 return join(", ", $res);
1205         }
1206
1207         protected function saveEmailText()
1208         {
1209         $text = SugarModule::get("EmailText")->loadBean();
1210         foreach($this->email_to_text as $textfield=>$mailfield) {
1211             $text->$textfield = $this->$mailfield;
1212         }
1213         $text->email_id = $this->id;
1214                 if(!$this->new_with_id) {
1215             $this->db->update($text);
1216                 } else {
1217                     $this->db->insert($text);
1218                 }
1219         }
1220
1221         ///////////////////////////////////////////////////////////////////////////
1222         ////    RETRIEVERS
1223         function retrieve($id, $encoded=true, $deleted=true) {
1224                 // cn: bug 11915, return SugarBean's retrieve() call bean instead of $this
1225                 $ret = parent::retrieve($id, $encoded, $deleted);
1226
1227                 if($ret) {
1228                         $ret->retrieveEmailText();
1229             //$ret->raw_source = SugarCleaner::cleanHtml($ret->raw_source);
1230                         $ret->description = to_html($ret->description);
1231             //$ret->description_html = SugarCleaner::cleanHtml($ret->description_html);
1232                         $ret->retrieveEmailAddresses();
1233
1234                         $ret->date_start = '';
1235                         $ret->time_start = '';
1236                         $dateSent = explode(' ', $ret->date_sent);
1237                         if (!empty($dateSent)) {
1238                             $ret->date_start = $dateSent[0];
1239                             if ( isset($dateSent[1]) )
1240                                 $ret->time_start = $dateSent[1];
1241                         }
1242                         // for Email 2.0
1243                         foreach($ret as $k => $v) {
1244                                 $this->$k = $v;
1245                         }
1246                 }
1247                 return $ret;
1248         }
1249
1250
1251         /**
1252          * Retrieves email addresses from GUIDs
1253          */
1254         function retrieveEmailAddresses() {
1255                 $return = array();
1256
1257                 $q = "SELECT email_address, address_type
1258                                 FROM emails_email_addr_rel eam
1259                                 JOIN email_addresses ea ON ea.id = eam.email_address_id
1260                                 WHERE eam.email_id = '{$this->id}' AND eam.deleted=0";
1261                 $r = $this->db->query($q);
1262
1263                 while($a = $this->db->fetchByAssoc($r)) {
1264                         if(!isset($return[$a['address_type']])) {
1265                                 $return[$a['address_type']] = array();
1266                         }
1267                         $return[$a['address_type']][] = $a['email_address'];
1268                 }
1269
1270                 if(count($return) > 0) {
1271                         if(isset($return['from'])) {
1272                                 $this->from_addr = implode(", ", $return['from']);
1273                         }
1274                         if(isset($return['to'])) {
1275                                 $this->to_addrs = implode(", ", $return['to']);
1276                         }
1277                         if(isset($return['cc'])) {
1278                                 $this->cc_addrs = implode(", ", $return['cc']);
1279                         }
1280                         if(isset($return['bcc'])) {
1281                                 $this->bcc_addrs = implode(", ", $return['bcc']);
1282                         }
1283                 }
1284         }
1285
1286         /**
1287          * Handles longtext fields
1288          */
1289         function retrieveEmailText() {
1290                 $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}'";
1291                 $r = $this->db->query($q);
1292                 $a = $this->db->fetchByAssoc($r, false);
1293
1294                 $this->description = $a['description'];
1295                 $this->description_html = $a['description_html'];
1296                 $this->raw_source = $a['raw_source'];
1297                 $this->from_addr_name = $a['from_addr'];
1298                 $this->reply_to_addr  = $a['reply_to_addr'];
1299                 $this->to_addrs_names = $a['to_addrs'];
1300                 $this->cc_addrs_names = $a['cc_addrs'];
1301                 $this->bcc_addrs_names = $a['bcc_addrs'];
1302         }
1303
1304         function delete($id='') {
1305                 if(empty($id))
1306                         $id = $this->id;
1307
1308         $id = $this->db->quote($id);
1309
1310                 $q  = "UPDATE emails SET deleted = 1 WHERE id = '{$id}'";
1311                 $qt = "UPDATE emails_text SET deleted = 1 WHERE email_id = '{$id}'";
1312                 $qf = "UPDATE folders_rel SET deleted = 1 WHERE polymorphic_id = '{$id}' AND polymorphic_module = 'Emails'";
1313         $qn = "UPDATE notes SET deleted = 1 WHERE parent_id = '{$id}' AND parent_type = 'Emails'";
1314         $this->db->query($q);
1315         $this->db->query($qt);
1316         $this->db->query($qf);
1317         $this->db->query($qn);
1318         }
1319
1320         /**
1321          * creates the standard "Forward" info at the top of the forwarded message
1322          * @return string
1323          */
1324         function getForwardHeader() {
1325                 global $mod_strings;
1326                 global $current_user;
1327
1328                 //$from = str_replace(array("&gt;","&lt;"), array(")","("), $this->from_name);
1329                 $from = to_html($this->from_name);
1330                 $subject = to_html($this->name);
1331                 $ret  = "<br /><br />";
1332                 $ret .= $this->replyDelimiter."{$mod_strings['LBL_FROM']} {$from}<br />";
1333                 $ret .= $this->replyDelimiter."{$mod_strings['LBL_DATE_SENT']} {$this->date_sent}<br />";
1334                 $ret .= $this->replyDelimiter."{$mod_strings['LBL_TO']} {$this->to_addrs}<br />";
1335                 $ret .= $this->replyDelimiter."{$mod_strings['LBL_CC']} {$this->cc_addrs}<br />";
1336                 $ret .= $this->replyDelimiter."{$mod_strings['LBL_SUBJECT']} {$subject}<br />";
1337                 $ret .= $this->replyDelimiter."<br />";
1338
1339                 return $ret;
1340                 //return from_html($ret);
1341         }
1342
1343     /**
1344      * retrieves Notes that belong to this Email and stuffs them into the "attachments" attribute
1345      */
1346     function getNotes($id, $duplicate=false) {
1347         if(!class_exists('Note')) {
1348
1349         }
1350
1351         $exRemoved = array();
1352                 if(isset($_REQUEST['removeAttachment'])) {
1353                         $exRemoved = explode('::', $_REQUEST['removeAttachment']);
1354                 }
1355
1356         $noteArray = array();
1357         $q = "SELECT id FROM notes WHERE parent_id = '".$id."'";
1358         $r = $this->db->query($q);
1359
1360         while($a = $this->db->fetchByAssoc($r)) {
1361                 if(!in_array($a['id'], $exRemoved)) {
1362                     $note = new Note();
1363                     $note->retrieve($a['id']);
1364
1365                     // duplicate actual file when creating forwards
1366                         if($duplicate) {
1367                                 if(!class_exists('UploadFile')) {
1368                                         require_once('include/upload_file.php');
1369                                 }
1370                                 // save a brand new Note
1371                                 $noteDupe->id = create_guid();
1372                                 $noteDupe->new_with_id = true;
1373                                         $noteDupe->parent_id = $this->id;
1374                                         $noteDupe->parent_type = $this->module_dir;
1375
1376                                         $noteFile = new UploadFile();
1377                                         $noteFile->duplicate_file($a['id'], $note->id, $note->filename);
1378
1379                                         $note->save();
1380                         }
1381                         // add Note to attachments array
1382                     $this->attachments[] = $note;
1383                 }
1384         }
1385     }
1386
1387         /**
1388          * creates the standard "Reply" info at the top of the forwarded message
1389          * @return string
1390          */
1391         function getReplyHeader() {
1392                 global $mod_strings;
1393                 global $current_user;
1394
1395                 $from = str_replace(array("&gt;","&lt;", ">","<"), array(")","(",")","("), $this->from_name);
1396                 $ret  = "<br>{$mod_strings['LBL_REPLY_HEADER_1']} {$this->date_start}, {$this->time_start}, {$from} {$mod_strings['LBL_REPLY_HEADER_2']}";
1397
1398                 return from_html($ret);
1399         }
1400
1401         /**
1402          * Quotes plain-text email text
1403          * @param string $text
1404          * @return string
1405          */
1406         function quotePlainTextEmail($text) {
1407                 $quoted = "\n";
1408
1409                 // plain-text
1410                 $desc = nl2br(trim($text));
1411                 $exDesc = explode('<br />', $desc);
1412
1413                 foreach($exDesc as $k => $line) {
1414                         $quoted .= '> '.trim($line)."\r";
1415                 }
1416
1417                 return $quoted;
1418         }
1419
1420         /**
1421          * "quotes" (i.e., "> my text yadda" the HTML part of an email
1422          * @param string $text HTML text to quote
1423          * @return string
1424          */
1425         function quoteHtmlEmail($text) {
1426                 $text = trim(from_html($text));
1427
1428                 if(empty($text)) {
1429                         return '';
1430                 }
1431                 $out = "<div style='border-left:1px solid #00c; padding:5px; margin-left:10px;'>{$text}</div>";
1432
1433                 return $out;
1434         }
1435
1436         /**
1437          * "quotes" (i.e., "> my text yadda" the HTML part of an email
1438          * @param string $text HTML text to quote
1439          * @return string
1440          */
1441         function quoteHtmlEmailForNewEmailUI($text) {
1442                 $text = trim($text);
1443
1444                 if(empty($text)) {
1445                         return '';
1446                 }
1447                 $text = str_replace("\n", "<BR/>", $text);
1448                 $out = "<div style='border-left:1px solid #00c; padding:5px; margin-left:10px;'>{$text}</div>";
1449
1450                 return $out;
1451         }
1452
1453         /**
1454          * Ensures that the user is able to send outbound emails
1455          */
1456         function check_email_settings() {
1457                 global $current_user;
1458
1459                 $mail_fromaddress = $current_user->emailAddress->getPrimaryAddress($current_user);
1460                 $replyToName = $current_user->getPreference('mail_fromname');
1461                 $mail_fromname = (!empty($replyToName)) ? $current_user->getPreference('mail_fromname') : $current_user->full_name;
1462
1463                 if(empty($mail_fromaddress)) {
1464                         return false;
1465                 }
1466                 if(empty($mail_fromname)) {
1467                         return false;
1468                 }
1469
1470         $send_type = $current_user->getPreference('mail_sendtype') ;
1471                 if (!empty($send_type) && $send_type == "SMTP") {
1472                         $mail_smtpserver = $current_user->getPreference('mail_smtpserver');
1473                         $mail_smtpport = $current_user->getPreference('mail_smtpport');
1474                         $mail_smtpauth_req = $current_user->getPreference('mail_smtpauth_req');
1475                         $mail_smtpuser = $current_user->getPreference('mail_smtpuser');
1476                         $mail_smtppass = $current_user->getPreference('mail_smtppass');
1477                         if (empty($mail_smtpserver) ||
1478                                 empty($mail_smtpport) ||
1479                 (!empty($mail_smtpauth_req) && ( empty($mail_smtpuser) || empty($mail_smtppass)))
1480                         ) {
1481                                 return false;
1482                         }
1483                 }
1484                 return true;
1485         }
1486
1487         /**
1488          * outputs JS to set fields in the MassUpdate form in the "My Inbox" view
1489          */
1490         function js_set_archived() {
1491                 global $mod_strings;
1492                 $script = '
1493                 <script type="text/javascript" language="JavaScript"><!-- Begin
1494                         function setArchived() {
1495                                 var form = document.getElementById("MassUpdate");
1496                                 var status = document.getElementById("mass_status");
1497                                 var ok = false;
1498
1499                                 for(var i=0; i < form.elements.length; i++) {
1500                                         if(form.elements[i].name == "mass[]") {
1501                                                 if(form.elements[i].checked == true) {
1502                                                         ok = true;
1503                                                 }
1504                                         }
1505                                 }
1506
1507                                 if(ok == true) {
1508                                         var user = document.getElementById("mass_assigned_user_name");
1509                                         var team = document.getElementById("team");
1510
1511                                         user.value = "";
1512                                         for(var j=0; j<status.length; j++) {
1513                                                 if(status.options[j].value == "archived") {
1514                                                         status.options[j].selected = true;
1515                                                         status.selectedIndex = j; // for IE
1516                                                 }
1517                                         }
1518
1519                                         form.submit();
1520                                 } else {
1521                                         alert("'.$mod_strings['ERR_ARCHIVE_EMAIL'].'");
1522                                 }
1523
1524                         }
1525                 //  End --></script>';
1526                 return $script;
1527         }
1528
1529         /**
1530          * replaces the javascript in utils.php - more specialized
1531          */
1532         function u_get_clear_form_js($type='', $group='', $assigned_user_id='') {
1533                 $uType                          = '';
1534                 $uGroup                         = '';
1535                 $uAssigned_user_id      = '';
1536
1537                 if(!empty($type)) { $uType = '&type='.$type; }
1538                 if(!empty($group)) { $uGroup = '&group='.$group; }
1539                 if(!empty($assigned_user_id)) { $uAssigned_user_id = '&assigned_user_id='.$assigned_user_id; }
1540
1541                 $the_script = '
1542                 <script type="text/javascript" language="JavaScript"><!-- Begin
1543                         function clear_form(form) {
1544                                 var newLoc = "index.php?action=" + form.action.value + "&module=" + form.module.value + "&query=true&clear_query=true'.$uType.$uGroup.$uAssigned_user_id.'";
1545                                 if(typeof(form.advanced) != "undefined"){
1546                                         newLoc += "&advanced=" + form.advanced.value;
1547                                 }
1548                                 document.location.href= newLoc;
1549                         }
1550                 //  End --></script>';
1551                 return $the_script;
1552         }
1553
1554         function pickOneButton() {
1555                 global $theme;
1556                 global $mod_strings;
1557                 $out = '<div><input     title="'.$mod_strings['LBL_BUTTON_GRAB_TITLE'].'"
1558                                                 class="button"
1559                                                 type="button" name="button"
1560                                                 onClick="window.location=\'index.php?module=Emails&action=Grab\';"
1561                                                 style="margin-bottom:2px"
1562                                                 value="  '.$mod_strings['LBL_BUTTON_GRAB'].'  "></div>';
1563                 return $out;
1564         }
1565
1566         /**
1567          * Determines what Editor (HTML or Plain-text) the current_user uses;
1568          * @return string Editor type
1569          */
1570         function getUserEditorPreference() {
1571                 global $sugar_config;
1572                 global $current_user;
1573
1574                 $editor = '';
1575
1576                 if(!isset($sugar_config['email_default_editor'])) {
1577                         $sugar_config = $current_user->setDefaultsInConfig();
1578                 }
1579
1580                 $userEditor = $current_user->getPreference('email_editor_option');
1581                 $systemEditor = $sugar_config['email_default_editor'];
1582
1583                 if($userEditor != '') {
1584                         $editor = $userEditor;
1585                 } else {
1586                         $editor = $systemEditor;
1587                 }
1588
1589                 return $editor;
1590         }
1591
1592         /**
1593          * takes the mess we pass from EditView and tries to create some kind of order
1594          * @param array addrs
1595          * @param array addrs_ids (from contacts)
1596          * @param array addrs_names (from contacts);
1597          * @param array addrs_emails (from contacts);
1598          * @return array Parsed assoc array to feed to PHPMailer
1599          */
1600         function parse_addrs($addrs, $addrs_ids, $addrs_names, $addrs_emails) {
1601                 // cn: bug 9406 - enable commas to separate email addresses
1602                 $addrs = str_replace(",", ";", $addrs);
1603
1604                 $ltgt = array('&lt;','&gt;');
1605                 $gtlt = array('<','>');
1606
1607                 $return                         = array();
1608                 $addrs                          = str_replace($ltgt, '', $addrs);
1609                 $addrs_arr                      = explode(";",$addrs);
1610                 $addrs_arr                      = $this->remove_empty_fields($addrs_arr);
1611                 $addrs_ids_arr          = explode(";",$addrs_ids);
1612                 $addrs_ids_arr          = $this->remove_empty_fields($addrs_ids_arr);
1613                 $addrs_emails_arr       = explode(";",$addrs_emails);
1614                 $addrs_emails_arr       = $this->remove_empty_fields($addrs_emails_arr);
1615                 $addrs_names_arr        = explode(";",$addrs_names);
1616                 $addrs_names_arr        = $this->remove_empty_fields($addrs_names_arr);
1617
1618                 ///////////////////////////////////////////////////////////////////////
1619                 ////    HANDLE EMAILS HAND-WRITTEN
1620                 $contactRecipients = array();
1621                 $knownEmails = array();
1622
1623                 foreach($addrs_arr as $i => $v) {
1624                         if(trim($v) == "")
1625                                 continue; // skip any "blanks" - will always have 1
1626
1627                         $recipient = array();
1628
1629                         //// get the email to see if we're dealing with a dupe
1630                         //// what crappy coding
1631                         preg_match("/[A-Z0-9._%-\']+@[A-Z0-9.-]+\.[A-Z]{2,}/i",$v, $match);
1632
1633
1634                         if(!empty($match[0]) && !in_array(trim($match[0]), $knownEmails)) {
1635                                 $knownEmails[] = $match[0];
1636                                 $recipient['email'] = $match[0];
1637
1638                                 //// handle the Display name
1639                                 $display = trim(str_replace($match[0], '', $v));
1640
1641                                 //// only trigger a "displayName" <email@address> when necessary
1642                                 if(isset($addrs_names_arr[$i])){
1643                                                 $recipient['display'] = $addrs_names_arr[$i];
1644                                 }
1645                                 else if(!empty($display)) {
1646                                         $recipient['display'] = $display;
1647                                 }
1648                                 if(isset($addrs_ids_arr[$i]) && $addrs_emails_arr[$i] == $match[0]){
1649                                         $recipient['contact_id'] = $addrs_ids_arr[$i];
1650                                 }
1651                                 $return[] = $recipient;
1652                         }
1653                 }
1654
1655                 return $return;
1656         }
1657
1658         function remove_empty_fields(&$arr) {
1659                 $newarr = array();
1660
1661                 foreach($arr as $field) {
1662                         $field = trim($field);
1663                         if(empty($field)) {
1664                                 continue;
1665                         }
1666                         array_push($newarr,$field);
1667                 }
1668                 return $newarr;
1669         }
1670
1671         /**
1672          * handles attachments of various kinds when sending email
1673          */
1674         function handleAttachments() {
1675
1676
1677
1678
1679                 global $mod_strings;
1680
1681         ///////////////////////////////////////////////////////////////////////////
1682         ////    ATTACHMENTS FROM DRAFTS
1683         if(($this->type == 'out' || $this->type == 'draft') && $this->status == 'draft' && isset($_REQUEST['record'])) {
1684             $this->getNotes($_REQUEST['record']); // cn: get notes from OLD email for use in new email
1685         }
1686         ////    END ATTACHMENTS FROM DRAFTS
1687         ///////////////////////////////////////////////////////////////////////////
1688
1689         ///////////////////////////////////////////////////////////////////////////
1690         ////    ATTACHMENTS FROM FORWARDS
1691         // Bug 8034 Jenny - Need the check for type 'draft' here to handle cases where we want to save
1692         // forwarded messages as drafts.  We still need to save the original message's attachments.
1693         if(($this->type == 'out' || $this->type == 'draft') &&
1694                 isset($_REQUEST['origType']) && $_REQUEST['origType'] == 'forward' &&
1695                 isset($_REQUEST['return_id']) && !empty($_REQUEST['return_id'])
1696         ) {
1697             $this->getNotes($_REQUEST['return_id'], true);
1698         }
1699
1700         // cn: bug 8034 - attachments from forward/replies lost when saving in draft
1701         if(isset($_REQUEST['prior_attachments']) && !empty($_REQUEST['prior_attachments']) && $this->new_with_id == true) {
1702                 $exIds = explode(",", $_REQUEST['prior_attachments']);
1703                 if(!isset($_REQUEST['template_attachment'])) {
1704                         $_REQUEST['template_attachment'] = array();
1705                 }
1706                 $_REQUEST['template_attachment'] = array_merge($_REQUEST['template_attachment'], $exIds);
1707         }
1708         ////    END ATTACHMENTS FROM FORWARDS
1709         ///////////////////////////////////////////////////////////////////////////
1710
1711                 ///////////////////////////////////////////////////////////////////////////
1712                 ////    ATTACHMENTS FROM TEMPLATES
1713                 // to preserve individual email integrity, we must dupe Notes and associated files
1714                 // for each outbound email - good for integrity, bad for filespace
1715                 if(isset($_REQUEST['template_attachment']) && !empty($_REQUEST['template_attachment'])) {
1716                         $removeArr = array();
1717                         $noteArray = array();
1718
1719                         if(isset($_REQUEST['temp_remove_attachment']) && !empty($_REQUEST['temp_remove_attachment'])) {
1720                                 $removeArr = $_REQUEST['temp_remove_attachment'];
1721                         }
1722
1723
1724                         foreach($_REQUEST['template_attachment'] as $noteId) {
1725                                 if(in_array($noteId, $removeArr)) {
1726                                         continue;
1727                                 }
1728                                 $noteTemplate = new Note();
1729                                 $noteTemplate->retrieve($noteId);
1730                                 $noteTemplate->id = create_guid();
1731                                 $noteTemplate->new_with_id = true; // duplicating the note with files
1732                                 $noteTemplate->parent_id = $this->id;
1733                                 $noteTemplate->parent_type = $this->module_dir;
1734                                 $noteTemplate->date_entered = '';
1735                                 $noteTemplate->save();
1736
1737                                 $noteFile = new UploadFile();
1738                                 $noteFile->duplicate_file($noteId, $noteTemplate->id, $noteTemplate->filename);
1739                                 $noteArray[] = $noteTemplate;
1740                         }
1741                         $this->attachments = array_merge($this->attachments, $noteArray);
1742                 }
1743                 ////    END ATTACHMENTS FROM TEMPLATES
1744                 ///////////////////////////////////////////////////////////////////////////
1745
1746                 ///////////////////////////////////////////////////////////////////////////
1747                 ////    ADDING NEW ATTACHMENTS
1748                 $max_files_upload = 10;
1749         // Jenny - Bug 8211 Since attachments for drafts have already been processed,
1750         // we don't need to re-process them.
1751         if($this->status != "draft") {
1752                 $notes_list = array();
1753                 if(!empty($this->id) && !$this->new_with_id) {
1754                         $note = new Note();
1755                         $where = "notes.parent_id='{$this->id}'";
1756                         $notes_list = $note->get_full_list("", $where, true);
1757                 }
1758                 $this->attachments = array_merge($this->attachments, $notes_list);
1759         }
1760                 // cn: Bug 5995 - rudimentary error checking
1761                 $filesError = array(
1762                         0 => 'UPLOAD_ERR_OK - There is no error, the file uploaded with success.',
1763                         1 => 'UPLOAD_ERR_INI_SIZE - The uploaded file exceeds the upload_max_filesize directive in php.ini.',
1764                         2 => 'UPLOAD_ERR_FORM_SIZE - The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.',
1765                         3 => 'UPLOAD_ERR_PARTIAL - The uploaded file was only partially uploaded.',
1766                         4 => 'UPLOAD_ERR_NO_FILE - No file was uploaded.',
1767                         5 => 'UNKNOWN ERROR',
1768                         6 => 'UPLOAD_ERR_NO_TMP_DIR - Missing a temporary folder. Introduced in PHP 4.3.10 and PHP 5.0.3.',
1769                         7 => 'UPLOAD_ERR_CANT_WRITE - Failed to write file to disk. Introduced in PHP 5.1.0.',
1770                 );
1771
1772                 for($i = 0; $i < $max_files_upload; $i++) {
1773                         // cn: Bug 5995 - rudimentary error checking
1774                         if (!isset($_FILES["email_attachment{$i}"])) {
1775                                 $GLOBALS['log']->debug("Email Attachment {$i} does not exist.");
1776                                 continue;
1777                         }
1778                         if($_FILES['email_attachment'.$i]['error'] != 0 && $_FILES['email_attachment'.$i]['error'] != 4) {
1779                                 $GLOBALS['log']->debug('Email Attachment could not be attach due to error: '.$filesError[$_FILES['email_attachment'.$i]['error']]);
1780                                 continue;
1781                         }
1782
1783                         $note = new Note();
1784                         $note->parent_id = $this->id;
1785                         $note->parent_type = $this->module_dir;
1786                         $upload_file = new UploadFile('email_attachment'.$i);
1787
1788                         if(empty($upload_file)) {
1789                                 continue;
1790                         }
1791
1792                         if(isset($_FILES['email_attachment'.$i]) && $upload_file->confirm_upload()) {
1793                                 $note->filename = $upload_file->get_stored_file_name();
1794                                 $note->file = $upload_file;
1795                                 $note->name = $mod_strings['LBL_EMAIL_ATTACHMENT'].': '.$note->file->original_file_name;
1796
1797                                 $this->attachments[] = $note;
1798                         }
1799                 }
1800
1801                 $this->saved_attachments = array();
1802                 foreach($this->attachments as $note) {
1803                         if(!empty($note->id)) {
1804                                 array_push($this->saved_attachments, $note);
1805                                 continue;
1806                         }
1807                         $note->parent_id = $this->id;
1808                         $note->parent_type = 'Emails';
1809                         $note->file_mime_type = $note->file->mime_type;
1810                         $note_id = $note->save();
1811
1812                         $this->saved_attachments[] = $note;
1813
1814                         $note->id = $note_id;
1815                         $note->file->final_move($note->id);
1816                 }
1817                 ////    END NEW ATTACHMENTS
1818                 ///////////////////////////////////////////////////////////////////////////
1819
1820                 ///////////////////////////////////////////////////////////////////////////
1821                 ////    ATTACHMENTS FROM DOCUMENTS
1822                 for($i=0; $i<10; $i++) {
1823                         if(isset($_REQUEST['documentId'.$i]) && !empty($_REQUEST['documentId'.$i])) {
1824                                 $doc = new Document();
1825                                 $docRev = new DocumentRevision();
1826                                 $docNote = new Note();
1827                                 $noteFile = new UploadFile();
1828
1829                                 $doc->retrieve($_REQUEST['documentId'.$i]);
1830                                 $docRev->retrieve($doc->document_revision_id);
1831
1832                                 $this->saved_attachments[] = $docRev;
1833
1834                                 // cn: bug 9723 - Emails with documents send GUID instead of Doc name
1835                                 $docNote->name = $docRev->getDocumentRevisionNameForDisplay();
1836                                 $docNote->filename = $docRev->filename;
1837                                 $docNote->description = $doc->description;
1838                                 $docNote->parent_id = $this->id;
1839                                 $docNote->parent_type = 'Emails';
1840                                 $docNote->file_mime_type = $docRev->file_mime_type;
1841                                 $docId = $docNote = $docNote->save();
1842
1843                                 $noteFile->duplicate_file($docRev->id, $docId, $docRev->filename);
1844                         }
1845                 }
1846
1847                 ////    END ATTACHMENTS FROM DOCUMENTS
1848                 ///////////////////////////////////////////////////////////////////////////
1849
1850                 ///////////////////////////////////////////////////////////////////////////
1851                 ////    REMOVE ATTACHMENTS
1852         if(isset($_REQUEST['remove_attachment']) && !empty($_REQUEST['remove_attachment'])) {
1853             foreach($_REQUEST['remove_attachment'] as $noteId) {
1854                 $q = 'UPDATE notes SET deleted = 1 WHERE id = \''.$noteId.'\'';
1855                 $this->db->query($q);
1856             }
1857         }
1858
1859         //this will remove attachments that have been selected to be removed from drafts.
1860         if(isset($_REQUEST['removeAttachment']) && !empty($_REQUEST['removeAttachment'])) {
1861             $exRemoved = explode('::', $_REQUEST['removeAttachment']);
1862             foreach($exRemoved as $noteId) {
1863                 $q = 'UPDATE notes SET deleted = 1 WHERE id = \''.$noteId.'\'';
1864                 $this->db->query($q);
1865             }
1866         }
1867                 ////    END REMOVE ATTACHMENTS
1868                 ///////////////////////////////////////////////////////////////////////////
1869         }
1870
1871
1872         /**
1873          * Determines if an email body (HTML or Plain) has a User signature already in the content
1874          * @param array Array of signatures
1875          * @return bool
1876          */
1877         function hasSignatureInBody($sig) {
1878                 // strpos can't handle line breaks - normalize
1879                 $html = $this->removeAllNewlines($this->description_html);
1880                 $htmlSig = $this->removeAllNewlines($sig['signature_html']);
1881                 $plain = $this->removeAllNewlines($this->description);
1882                 $plainSig = $this->removeAllNewlines($sig['signature']);
1883
1884                 // cn: bug 11621 - empty sig triggers notice error
1885                 if(!empty($htmlSig) && false !== strpos($html, $htmlSig)) {
1886                         return true;
1887                 } elseif(!empty($plainSig) && false !== strpos($plain, $plainSig)) {
1888                         return true;
1889                 } else {
1890                         return false;
1891                 }
1892         }
1893
1894         /**
1895          * internal helper
1896          * @param string String to be normalized
1897          * @return string
1898          */
1899         function removeAllNewlines($str) {
1900                 $bad = array("\r\n", "\n\r", "\n", "\r");
1901                 $good = array('', '', '', '');
1902
1903                 return str_replace($bad, $good, strip_tags(br2nl(from_html($str))));
1904         }
1905
1906
1907
1908         /**
1909          * Set navigation anchors to aid DetailView record navigation (VCR buttons)
1910          * @param string uri The URI from the referring page (always ListView)
1911          * @return array start Array of the URI broken down with a special "current_view" for My Inbox Navs
1912          */
1913         function getStartPage($uri) {
1914                 if(strpos($uri, '&')) { // "&" to ensure that we can explode the GET vars - else we're gonna trigger a Notice error
1915                         $serial = substr($uri, (strpos($uri, '?')+1), strlen($uri));
1916                         $exUri = explode('&', $serial);
1917                         $start = array('module' => '', 'action' => '', 'group' => '', 'record' => '', 'type' => '');
1918
1919                         foreach($exUri as $k => $pair) {
1920                                 $exPair = explode('=', $pair);
1921                                 $start[$exPair[0]] = $exPair[1];
1922                         }
1923
1924                         // specific views for current_user
1925                         if(isset($start['assigned_user_id'])) {
1926                                 $start['current_view'] = "{$start['action']}&module={$start['module']}&assigned_user_id={$start['assigned_user_id']}&type={$start['type']}";
1927                         }
1928
1929                         return $start;
1930                 } else {
1931                         return array();
1932                 }
1933         }
1934
1935         /**
1936          * preps SMTP info for email transmission
1937          * @param object mail SugarPHPMailer object
1938          * @param string mailer_id
1939          * @param string ieId
1940          * @return object mail SugarPHPMailer object
1941          */
1942         function setMailer($mail, $mailer_id='', $ieId='') {
1943                 global $current_user;
1944
1945                 require_once("include/OutboundEmail/OutboundEmail.php");
1946                 $oe = new OutboundEmail();
1947                 $oe = $oe->getInboundMailerSettings($current_user, $mailer_id, $ieId);
1948
1949                 // ssl or tcp - keeping outside isSMTP b/c a default may inadvertantly set ssl://
1950                 $mail->protocol = ($oe->mail_smtpssl) ? "ssl://" : "tcp://";
1951         if($oe->mail_sendtype == "SMTP")
1952         {
1953                 //Set mail send type information
1954                 $mail->Mailer = "smtp";
1955                 $mail->Host = $oe->mail_smtpserver;
1956                 $mail->Port = $oe->mail_smtpport;
1957             if ($oe->mail_smtpssl == 1) {
1958                 $mail->SMTPSecure = 'ssl';
1959             } // if
1960             if ($oe->mail_smtpssl == 2) {
1961                 $mail->SMTPSecure = 'tls';
1962             } // if
1963
1964                 if($oe->mail_smtpauth_req) {
1965                         $mail->SMTPAuth = TRUE;
1966                         $mail->Username = $oe->mail_smtpuser;
1967                         $mail->Password = $oe->mail_smtppass;
1968                 }
1969         }
1970         else
1971                         $mail->Mailer = "sendmail";
1972
1973                 $mail->oe = $oe;
1974                 return $mail;
1975         }
1976
1977         /**
1978          * preps SugarPHPMailer object for HTML or Plain text sends
1979          * @param SugarPHPMailer $mail SugarPHPMailer instance
1980          */
1981         function handleBody($mail) {
1982                 global $current_user;
1983                 global $sugar_config;
1984                 ///////////////////////////////////////////////////////////////////////
1985                 ////    HANDLE EMAIL FORMAT PREFERENCE
1986                 // the if() below is HIGHLY dependent on the Javascript unchecking the Send HTML Email box
1987                 // HTML email
1988                 if( (isset($_REQUEST['setEditor']) /* from Email EditView navigation */
1989                         && $_REQUEST['setEditor'] == 1
1990                         && trim($_REQUEST['description_html']) != '')
1991                         || trim($this->description_html) != '' /* from email templates */
1992             && $current_user->getPreference('email_editor_option', 'global') !== 'plain' //user preference is not set to plain text
1993                 ) {
1994                     $this->handleBodyInHTMLformat($mail);
1995                 } else {
1996                         // plain text only
1997                         $this->description_html = '';
1998                         $mail->IsHTML(false);
1999                         $plainText = from_html($this->description);
2000                         $plainText = str_replace("&nbsp;", " ", $plainText);
2001                         $plainText = str_replace("</p>", "</p><br />", $plainText);
2002                         $plainText = strip_tags(br2nl($plainText));
2003                         $plainText = str_replace("&amp;", "&", $plainText);
2004             $plainText = str_replace("&#39;", "'", $plainText);
2005                         $mail->Body = wordwrap($plainText, 996);
2006                         $mail->Body = $this->decodeDuringSend($mail->Body);
2007                         $this->description = $mail->Body;
2008                 }
2009
2010                 // wp: if plain text version has lines greater than 998, use base64 encoding
2011                 foreach(explode("\n", ($mail->ContentType == "text/html") ? $mail->AltBody : $mail->Body) as $line) {
2012                         if(strlen($line) > 998) {
2013                                 $mail->Encoding = 'base64';
2014                                 break;
2015                         }
2016                 }
2017                 ////    HANDLE EMAIL FORMAT PREFERENCE
2018                 ///////////////////////////////////////////////////////////////////////
2019
2020                 return $mail;
2021         }
2022
2023         /**
2024          * Retrieve function from handlebody() to unit test easily
2025          * @param SugarPHPMailer $mail SugarPHPMailer instance
2026          * @return formatted $mail body
2027          */
2028         function handleBodyInHTMLformat($mail) {
2029                 global $sugar_config;
2030                 // wp: if body is html, then insert new lines at 996 characters. no effect on client side
2031                 // due to RFC 2822 which limits email lines to 998
2032                 $mail->IsHTML(true);
2033                 $body = from_html(wordwrap($this->description_html, 996));
2034                 $mail->Body = $body;
2035
2036                 // cn: bug 9725
2037                 // new plan is to use the selected type (html or plain) to fill the other
2038                 $plainText = from_html($this->description_html);
2039                 $plainText = strip_tags(br2nl($plainText));
2040                 $mail->AltBody = $plainText;
2041                 $this->description = $plainText;
2042
2043                 $mail->replaceImageByRegex("(?:{$sugar_config['site_url']})?/?cache/images/", sugar_cached("images/"));
2044
2045                 //Replace any embeded images using the secure entryPoint for src url.
2046                 $mail->replaceImageByRegex("(?:{$sugar_config['site_url']})?/?index.php[?]entryPoint=download&(?:amp;)?[^\"]+?id=", "upload://", true);
2047
2048                 $mail->Body = from_html($mail->Body);
2049         }
2050
2051         /**
2052          * Sends Email
2053          * @return bool True on success
2054          */
2055         function send() {
2056                 global $mod_strings,$app_strings;
2057                 global $current_user;
2058                 global $sugar_config;
2059                 global $locale;
2060         $OBCharset = $locale->getPrecedentPreference('default_email_charset');
2061                 $mail = new SugarPHPMailer();
2062
2063                 foreach ($this->to_addrs_arr as $addr_arr) {
2064                         if ( empty($addr_arr['display'])) {
2065                                 $mail->AddAddress($addr_arr['email'], "");
2066                         } else {
2067                                 $mail->AddAddress($addr_arr['email'],$locale->translateCharsetMIME(trim( $addr_arr['display']), 'UTF-8', $OBCharset));
2068                         }
2069                 }
2070                 foreach ($this->cc_addrs_arr as $addr_arr) {
2071                         if ( empty($addr_arr['display'])) {
2072                                 $mail->AddCC($addr_arr['email'], "");
2073                         } else {
2074                                 $mail->AddCC($addr_arr['email'],$locale->translateCharsetMIME(trim($addr_arr['display']), 'UTF-8', $OBCharset));
2075                         }
2076                 }
2077
2078                 foreach ($this->bcc_addrs_arr as $addr_arr) {
2079                         if ( empty($addr_arr['display'])) {
2080                                 $mail->AddBCC($addr_arr['email'], "");
2081                         } else {
2082                                 $mail->AddBCC($addr_arr['email'],$locale->translateCharsetMIME(trim($addr_arr['display']), 'UTF-8', $OBCharset));
2083                         }
2084                 }
2085
2086                 $mail = $this->setMailer($mail);
2087
2088                 // FROM ADDRESS
2089                 if(!empty($this->from_addr)) {
2090                         $mail->From = $this->from_addr;
2091                 } else {
2092                         $mail->From = $current_user->getPreference('mail_fromaddress');
2093                         $this->from_addr = $mail->From;
2094                 }
2095                 // FROM NAME
2096                 if(!empty($this->from_name)) {
2097                         $mail->FromName = $this->from_name;
2098                 } else {
2099                         $mail->FromName =  $current_user->getPreference('mail_fromname');
2100                         $this->from_name = $mail->FromName;
2101                 }
2102
2103                 //Reply to information for case create and autoreply.
2104                 if(!empty($this->reply_to_name)) {
2105                         $ReplyToName = $this->reply_to_name;
2106                 } else {
2107                         $ReplyToName = $mail->FromName;
2108                 }
2109                 if(!empty($this->reply_to_addr)) {
2110                         $ReplyToAddr = $this->reply_to_addr;
2111                 } else {
2112                         $ReplyToAddr = $mail->From;
2113                 }
2114                 $mail->Sender = $mail->From; /* set Return-Path field in header to reduce spam score in emails sent via Sugar's Email module */
2115                 $mail->AddReplyTo($ReplyToAddr,$locale->translateCharsetMIME(trim($ReplyToName), 'UTF-8', $OBCharset));
2116
2117                 //$mail->Subject = html_entity_decode($this->name, ENT_QUOTES, 'UTF-8');
2118                 $mail->Subject = $this->name;
2119
2120                 ///////////////////////////////////////////////////////////////////////
2121                 ////    ATTACHMENTS
2122                 foreach($this->saved_attachments as $note) {
2123                         $mime_type = 'text/plain';
2124                         if($note->object_name == 'Note') {
2125                                 if(!empty($note->file->temp_file_location) && is_file($note->file->temp_file_location)) { // brandy-new file upload/attachment
2126                                         $file_location = "upload://$note->id";
2127                                         $filename = $note->file->original_file_name;
2128                                         $mime_type = $note->file->mime_type;
2129                                 } else { // attachment coming from template/forward
2130                                         $file_location = "upload://{$note->id}";
2131                                         // cn: bug 9723 - documents from EmailTemplates sent with Doc Name, not file name.
2132                                         $filename = !empty($note->filename) ? $note->filename : $note->name;
2133                                         $mime_type = $note->file_mime_type;
2134                                 }
2135                         } elseif($note->object_name == 'DocumentRevision') { // from Documents
2136                                 $filePathName = $note->id;
2137                                 // cn: bug 9723 - Emails with documents send GUID instead of Doc name
2138                                 $filename = $note->getDocumentRevisionNameForDisplay();
2139                                 $file_location = "upload://$note->id";
2140                                 $mime_type = $note->file_mime_type;
2141                         }
2142
2143                         // strip out the "Email attachment label if exists
2144                         $filename = str_replace($mod_strings['LBL_EMAIL_ATTACHMENT'].': ', '', $filename);
2145             $file_ext = pathinfo($filename, PATHINFO_EXTENSION);
2146                         //is attachment in our list of bad files extensions?  If so, append .txt to file location
2147                         //check to see if this is a file with extension located in "badext"
2148                         foreach($sugar_config['upload_badext'] as $badExt) {
2149                         if(strtolower($file_ext) == strtolower($badExt)) {
2150                                 //if found, then append with .txt to filename and break out of lookup
2151                                 //this will make sure that the file goes out with right extension, but is stored
2152                                 //as a text in db.
2153                                 $file_location = $file_location . ".txt";
2154                                 break; // no need to look for more
2155                         }
2156                 }
2157                         $mail->AddAttachment($file_location,$locale->translateCharsetMIME(trim($filename), 'UTF-8', $OBCharset), 'base64', $mime_type);
2158
2159                         // embedded Images
2160                         if($note->embed_flag == true) {
2161                                 $cid = $filename;
2162                                 $mail->AddEmbeddedImage($file_location, $cid, $filename, 'base64',$mime_type);
2163                         }
2164                 }
2165                 ////    END ATTACHMENTS
2166                 ///////////////////////////////////////////////////////////////////////
2167
2168                 $mail = $this->handleBody($mail);
2169
2170                 $GLOBALS['log']->debug('Email sending --------------------- ');
2171
2172                 ///////////////////////////////////////////////////////////////////////
2173                 ////    I18N TRANSLATION
2174                 $mail->prepForOutbound();
2175                 ////    END I18N TRANSLATION
2176                 ///////////////////////////////////////////////////////////////////////
2177
2178                 if($mail->Send()) {
2179                         ///////////////////////////////////////////////////////////////////
2180                         ////    INBOUND EMAIL HANDLING
2181                         // mark replied
2182                         if(!empty($_REQUEST['inbound_email_id'])) {
2183                                 $ieMail = new Email();
2184                                 $ieMail->retrieve($_REQUEST['inbound_email_id']);
2185                                 $ieMail->status = 'replied';
2186                                 $ieMail->save();
2187                         }
2188                         $GLOBALS['log']->debug(' --------------------- buh bye -- sent successful');
2189                         ////    END INBOUND EMAIL HANDLING
2190                         ///////////////////////////////////////////////////////////////////
2191                         return true;
2192                 }
2193             $GLOBALS['log']->debug($app_strings['LBL_EMAIL_ERROR_PREPEND'].$mail->ErrorInfo);
2194                 return false;
2195         }
2196
2197
2198         function listviewACLHelper(){
2199                 $array_assign = parent::listviewACLHelper();
2200                 $is_owner = false;
2201                 if(!empty($this->parent_name)){
2202
2203                         if(!empty($this->parent_name_owner)){
2204                                 global $current_user;
2205                                 $is_owner = $current_user->id == $this->parent_name_owner;
2206                         }
2207                 }
2208                 if(!ACLController::moduleSupportsACL($this->parent_type) || ACLController::checkAccess($this->parent_type, 'view', $is_owner)){
2209                         $array_assign['PARENT'] = 'a';
2210                 } else {
2211                         $array_assign['PARENT'] = 'span';
2212                 }
2213                 $is_owner = false;
2214                 if(!empty($this->contact_name)) {
2215                         if(!empty($this->contact_name_owner)) {
2216                                 global $current_user;
2217                                 $is_owner = $current_user->id == $this->contact_name_owner;
2218                         }
2219                 }
2220                 if(ACLController::checkAccess('Contacts', 'view', $is_owner)) {
2221                         $array_assign['CONTACT'] = 'a';
2222                 } else {
2223                         $array_assign['CONTACT'] = 'span';
2224                 }
2225
2226                 return $array_assign;
2227         }
2228
2229         function getSystemDefaultEmail() {
2230                 $email = array();
2231
2232                 $r1 = $this->db->query('SELECT config.value FROM config WHERE name=\'fromaddress\'');
2233                 $r2 = $this->db->query('SELECT config.value FROM config WHERE name=\'fromname\'');
2234                 $a1 = $this->db->fetchByAssoc($r1);
2235                 $a2 = $this->db->fetchByAssoc($r2);
2236
2237                 $email['email'] = $a1['value'];
2238                 $email['name']  = $a2['value'];
2239
2240                 return $email;
2241         }
2242
2243
2244     function create_new_list_query($order_by, $where,$filter=array(),$params=array(), $show_deleted = 0,$join_type='', $return_array = false,$parentbean=null, $singleSelect = false) {
2245
2246                 if ($return_array) {
2247                         return parent::create_new_list_query($order_by, $where,$filter,$params, $show_deleted,$join_type, $return_array,$parentbean, $singleSelect);
2248                 }
2249         $custom_join = $this->getCustomJoin();
2250
2251                 $query = "SELECT ".$this->table_name.".*, users.user_name as assigned_user_name\n";
2252
2253         $query .= $custom_join['select'];
2254         $query .= " FROM emails\n";
2255         if ($where != "" && (strpos($where, "contacts.first_name") > 0))  {
2256                         $query .= " LEFT JOIN emails_beans ON emails.id = emails_beans.email_id\n";
2257         }
2258
2259         $query .= " LEFT JOIN users ON emails.assigned_user_id=users.id \n";
2260         if ($where != "" && (strpos($where, "contacts.first_name") > 0))  {
2261
2262         $query .= " JOIN contacts ON contacts.id= emails_beans.bean_id AND emails_beans.bean_module='Contacts' and contacts.deleted=0 \n";
2263         }
2264
2265         $query .= $custom_join['join'];
2266
2267                 if($show_deleted == 0) {
2268                         $where_auto = " emails.deleted=0 \n";
2269                 }else if($show_deleted == 1){
2270                         $where_auto = " emails.deleted=1 \n";
2271                 }
2272
2273         if($where != "")
2274                         $query .= "WHERE $where AND ".$where_auto;
2275                 else
2276                         $query .= "WHERE ".$where_auto;
2277
2278                 if($order_by != "")
2279                         $query .= " ORDER BY $order_by";
2280                 else
2281                         $query .= " ORDER BY date_sent DESC";
2282
2283                 return $query;
2284     } // fn
2285
2286
2287         function fill_in_additional_list_fields() {
2288                 global $timedate, $mod_strings;
2289                 $this->fill_in_additional_detail_fields();
2290
2291                 $this->link_action = 'DetailView';
2292                 ///////////////////////////////////////////////////////////////////////
2293                 //populate attachment_image, used to display attachment icon.
2294                 $query =  "select 1 from notes where notes.parent_id = '$this->id' and notes.deleted = 0";
2295                 $result =$this->db->query($query,true," Error filling in additional list fields: ");
2296
2297                 $row = $this->db->fetchByAssoc($result);
2298
2299         if ($row) {
2300             $this->attachment_image = SugarThemeRegistry::current()->getImage(
2301                 'attachment',
2302                 '',
2303                 null,
2304                 null,
2305                 '.gif',
2306                 translate('LBL_ATTACHMENT', 'Emails')
2307             );
2308         } else {
2309             $this->attachment_image = '';
2310         }
2311
2312                 ///////////////////////////////////////////////////////////////////////
2313                 if(empty($this->contact_id) && !empty($this->parent_id) && !empty($this->parent_type) && $this->parent_type === 'Contacts' && !empty($this->parent_name) ){
2314                         $this->contact_id = $this->parent_id;
2315                         $this->contact_name = $this->parent_name;
2316                 }
2317         }
2318
2319         function fill_in_additional_detail_fields() {
2320                 global $app_list_strings,$mod_strings;
2321                 // Fill in the assigned_user_name
2322                 $this->assigned_user_name = get_assigned_user_name($this->assigned_user_id, '');
2323                 //if ($this->parent_type == 'Contacts') {
2324                         $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 ";
2325                         $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";
2326                         if(!empty($this->parent_id)){
2327                                 $query .= " AND contacts.id= '".$this->parent_id."' ";
2328                         }else if(!empty($_REQUEST['record'])){
2329                                 $query .= " AND contacts.id= '".$_REQUEST['record']."' ";
2330                         }
2331                         $result =$this->db->query($query,true," Error filling in additional detail fields: ");
2332
2333                         // Get the id and the name.
2334                         $row = $this->db->fetchByAssoc($result);
2335                         if($row != null)
2336                         {
2337
2338                                 $contact = new Contact();
2339                                 $contact->retrieve($row['id']);
2340                                 $this->contact_name = $contact->full_name;
2341                                 $this->contact_phone = $row['phone_work'];
2342                                 $this->contact_id = $row['id'];
2343                                 $this->contact_email = $contact->emailAddress->getPrimaryAddress($contact);
2344                                 $this->contact_name_owner = $row['contact_name_owner'];
2345                                 $this->contact_name_mod = $row['contact_name_mod'];
2346                                 $GLOBALS['log']->debug("Call($this->id): contact_name = $this->contact_name");
2347                                 $GLOBALS['log']->debug("Call($this->id): contact_phone = $this->contact_phone");
2348                                 $GLOBALS['log']->debug("Call($this->id): contact_id = $this->contact_id");
2349                                 $GLOBALS['log']->debug("Call($this->id): contact_email1 = $this->contact_email");
2350                         }
2351                         else {
2352                                 $this->contact_name = '';
2353                                 $this->contact_phone = '';
2354                                 $this->contact_id = '';
2355                                 $this->contact_email = '';
2356                                 $this->contact_name_owner = '';
2357                                 $this->contact_name_mod = '';
2358                                 $GLOBALS['log']->debug("Call($this->id): contact_name = $this->contact_name");
2359                                 $GLOBALS['log']->debug("Call($this->id): contact_phone = $this->contact_phone");
2360                                 $GLOBALS['log']->debug("Call($this->id): contact_id = $this->contact_id");
2361                                 $GLOBALS['log']->debug("Call($this->id): contact_email1 = $this->contact_email");
2362                         }
2363                 //}
2364                 $this->created_by_name = get_assigned_user_name($this->created_by);
2365                 $this->modified_by_name = get_assigned_user_name($this->modified_user_id);
2366
2367                 $this->link_action = 'DetailView';
2368
2369                 if(!empty($this->type)) {
2370                         if($this->type == 'out' && $this->status == 'send_error') {
2371                                 $this->type_name = $mod_strings['LBL_NOT_SENT'];
2372                         } else {
2373                                 $this->type_name = $app_list_strings['dom_email_types'][$this->type];
2374                         }
2375
2376                         if(($this->type == 'out' && $this->status == 'send_error') || $this->type == 'draft') {
2377                                 $this->link_action = 'EditView';
2378                         }
2379                 }
2380
2381                 //todo this  isset( $app_list_strings['dom_email_status'][$this->status]) is hack for 3261.
2382                 if(!empty($this->status) && isset( $app_list_strings['dom_email_status'][$this->status])) {
2383                         $this->status_name = $app_list_strings['dom_email_status'][$this->status];
2384                 }
2385
2386                 if ( empty($this->name ) &&  empty($_REQUEST['record'])) {
2387                         $this->name = $mod_strings['LBL_NO_SUBJECT'];
2388                 }
2389
2390                 $this->fill_in_additional_parent_fields();
2391         }
2392
2393
2394
2395         function create_export_query(&$order_by, &$where)
2396     {
2397                 $contact_required = stristr($where, "contacts");
2398                 $custom_join = $this->getCustomJoin(true, true, $where);
2399
2400                 if($contact_required) {
2401                         $query = "SELECT emails.*, contacts.first_name, contacts.last_name";
2402             $query .= $custom_join['select'];
2403
2404                         $query .= " FROM contacts, emails, emails_contacts ";
2405                         $where_auto = "emails_contacts.contact_id = contacts.id AND emails_contacts.email_id = emails.id AND emails.deleted=0 AND contacts.deleted=0";
2406                 } else {
2407                         $query = 'SELECT emails.*';
2408             $query .= $custom_join['select'];
2409
2410             $query .= ' FROM emails ';
2411             $where_auto = "emails.deleted=0";
2412                 }
2413
2414         $query .= $custom_join['join'];
2415
2416                 if($where != "")
2417                         $query .= "where $where AND ".$where_auto;
2418         else
2419                         $query .= "where ".$where_auto;
2420
2421         if($order_by != "")
2422                         $query .= " ORDER BY $order_by";
2423         else
2424                         $query .= " ORDER BY emails.name";
2425         return $query;
2426     }
2427
2428         function get_list_view_data() {
2429                 global $app_list_strings;
2430                 global $theme;
2431                 global $current_user;
2432                 global $timedate;
2433                 global $mod_strings;
2434
2435                 $email_fields = $this->get_list_view_array();
2436                 $this->retrieveEmailText();
2437                 $email_fields['FROM_ADDR'] = $this->from_addr_name;
2438                 $mod_strings = return_module_language($GLOBALS['current_language'], 'Emails'); // hard-coding for Home screen ListView
2439
2440                 if($this->status != 'replied') {
2441                         $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>';
2442                         $email_fields['STATUS'] = ($email_fields['REPLY_TO_STATUS'] == 1 ? $mod_strings['LBL_REPLIED'] : $email_fields['STATUS']);
2443                 } else {
2444                         $email_fields['QUICK_REPLY'] = $mod_strings['LBL_REPLIED'];
2445                 }
2446                 if(!empty($this->parent_type)) {
2447                         $email_fields['PARENT_MODULE'] = $this->parent_type;
2448                 } else {
2449                         switch($this->intent) {
2450                                 case 'support':
2451                                         $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>';
2452                                 break;
2453
2454                                 case 'sales':
2455                                         $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>';
2456                                 break;
2457
2458                                 case 'contact':
2459                                         $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>';
2460                                 break;
2461
2462                                 case 'bug':
2463                                         $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>';
2464                                 break;
2465
2466                                 case 'task':
2467                                         $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>';
2468                                 break;
2469
2470                                 case 'bounce':
2471                                 break;
2472
2473                                 case 'pick':
2474                                 // break;
2475
2476                                 case 'info':
2477                                 //break;
2478
2479                                 default:
2480                                         $email_fields['CREATE_RELATED'] = $this->quickCreateForm();
2481                                 break;
2482                         }
2483
2484                 }
2485
2486                 //BUG 17098 - MFH changed $this->from_addr to $this->to_addrs
2487                 $email_fields['CONTACT_NAME']           = empty($this->contact_name) ? '</a>'.$this->trimLongTo($this->to_addrs).'<a>' : $this->contact_name;
2488                 $email_fields['CONTACT_ID']             = empty($this->contact_id) ? '' : $this->contact_id;
2489                 $email_fields['ATTACHMENT_IMAGE']       = $this->attachment_image;
2490                 $email_fields['LINK_ACTION']            = $this->link_action;
2491
2492         if(isset($this->type_name))
2493                 $email_fields['TYPE_NAME'] = $this->type_name;
2494
2495                 return $email_fields;
2496         }
2497
2498     function quickCreateForm() {
2499         global $mod_strings, $app_strings, $currentModule, $current_language;
2500
2501         // Coming from the home page via Dashlets
2502         if($currentModule != 'Email')
2503                 $mod_strings = return_module_language($current_language, 'Emails');
2504         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>";
2505     }
2506
2507     /**
2508      * Searches all imported emails and returns the result set as an array.
2509      *
2510      */
2511     function searchImportedEmails($sort = '', $direction='')
2512     {
2513         require_once('include/TimeDate.php');
2514                 global $timedate;
2515                 global $current_user;
2516                 global $beanList;
2517                 global $sugar_config;
2518                 global $app_strings;
2519
2520                 $emailSettings = $current_user->getPreference('emailSettings', 'Emails');
2521                 // cn: default to a low number until user specifies otherwise
2522                 if(empty($emailSettings['showNumInList']))
2523                         $pageSize = 20;
2524         else
2525             $pageSize = $emailSettings['showNumInList'];
2526
2527         if( isset($_REQUEST['start']) && isset($_REQUEST['limit']) )
2528                $page = ceil($_REQUEST['start'] / $_REQUEST['limit']) + 1;
2529             else
2530                $page = 1;
2531
2532              //Determine sort ordering
2533
2534              //Sort ordering parameters in the request do not coincide with actual column names
2535              //so we need to remap them.
2536              $hrSortLocal = array(
2537             'flagged' => 'type',
2538             'status'  => 'reply_to_status',
2539             'from'    => 'emails_text.from_addr',
2540             'subject' => 'name',
2541             'date'    => 'date_sent',
2542             'AssignedTo' => 'assigned_user_id',
2543             'flagged' => 'flagged'
2544         );
2545
2546              $sort = !empty($_REQUEST['sort']) ? $this->db->getValidDBName($_REQUEST['sort']) : "";
2547          $direction = !empty($_REQUEST['dir'])  && in_array(strtolower($_REQUEST['dir']), array("asc", "desc")) ? $_REQUEST['dir'] : "";
2548
2549          $order = ( !empty($sort) && !empty($direction) ) ? " ORDER BY {$hrSortLocal[$sort]} {$direction}" : "";
2550
2551          //Get our main query.
2552                 $fullQuery = $this->_genereateSearchImportedEmailsQuery();
2553
2554                 //Perform a count query needed for pagination.
2555                 $countQuery = $this->create_list_count_query($fullQuery);
2556                 
2557                 $count_rs = $this->db->query($countQuery, false, 'Error executing count query for imported emails search');
2558                 $count_row = $this->db->fetchByAssoc($count_rs);
2559                 $total_count = ($count_row != null) ? $count_row['c'] : 0;
2560
2561         $start = ($page - 1) * $pageSize;
2562
2563         //Execute the query
2564                 $rs = $this->db->limitQuery($fullQuery . $order, $start, $pageSize);
2565
2566                 $return = array();
2567
2568                 while($a = $this->db->fetchByAssoc($rs)) {
2569                         $temp = array();
2570                         $temp['flagged'] = (is_null($a['flagged']) || $a['flagged'] == '0') ? '' : 1;
2571                         $temp['status'] = (is_null($a['reply_to_status']) || $a['reply_to_status'] == '0') ? '' : 1;
2572                         $temp['subject'] = $a['name'];
2573                         $temp['date']   = $timedate->to_display_date_time($a['date_sent']);
2574                         $temp['uid'] = $a['id'];
2575                         $temp['ieId'] = $a['mailbox_id'];
2576                         $temp['site_url'] = $sugar_config['site_url'];
2577                         $temp['seen'] = ($a['status'] == 'unread') ? 0 : 1;
2578                         $temp['type'] = $a['type'];
2579                         $temp['mbox'] = 'sugar::Emails';
2580                         $temp['hasAttach'] =  $this->doesImportedEmailHaveAttachment($a['id']);
2581                         //To and from addresses may be stored in emails_text, if nothing is found, revert to
2582                         //regular email addresses.
2583                         $temp['to_addrs'] = preg_replace('/[\x00-\x08\x0B-\x1F]/', '', $a['to_addrs']);
2584                         $temp['from']   = preg_replace('/[\x00-\x08\x0B-\x1F]/', '', $a['from_addr']);
2585                         if( empty($temp['from']) || empty($temp['to_addrs']) )
2586                         {
2587                         //Retrieve email addresses seperatly.
2588                         $tmpEmail = new Email();
2589                         $tmpEmail->id = $a['id'];
2590                         $tmpEmail->retrieveEmailAddresses();
2591                         $temp['from'] = $tmpEmail->from_addr;
2592                         $temp['to_addrs'] = $tmpEmail->to_addrs;
2593                         }
2594
2595                         $return[] = $temp;
2596                 }
2597
2598                 $metadata = array();
2599                 $metadata['totalCount'] = $total_count;
2600                 $metadata['out'] = $return;
2601
2602                 return $metadata;
2603     }
2604
2605     /**
2606      * Determine if an imported email has an attachment by examining the relationship to notes.
2607      *
2608      * @param string $id
2609      * @return boolean
2610      */
2611     function doesImportedEmailHaveAttachment($id)
2612         {
2613            $hasAttachment = FALSE;
2614            $query = "SELECT id FROM notes where parent_id='$id' AND parent_type='Emails' AND file_mime_type is not null AND deleted=0";
2615            $rs = $this->db->limitQuery($query, 0, 1);
2616            $row = $this->db->fetchByAssoc($rs);
2617            if( !empty($row['id']) )
2618                $hasAttachment = TRUE;
2619
2620            return (int) $hasAttachment;
2621         }
2622
2623     /**
2624      * Generate the query used for searching imported emails.
2625      *
2626      * @return String Query to be executed.
2627      */
2628     function _genereateSearchImportedEmailsQuery()
2629     {
2630                 global $timedate;
2631
2632         $additionalWhereClause = $this->_generateSearchImportWhereClause();
2633
2634         $query = array();
2635         $fullQuery = "";
2636         $query['select'] = "emails.id , emails.mailbox_id, emails.name, emails.date_sent, emails.status, emails.type, emails.flagged, emails.reply_to_status,
2637                                       emails_text.from_addr, emails_text.to_addrs  FROM emails ";
2638
2639         $query['joins'] = " JOIN emails_text on emails.id = emails_text.email_id ";
2640
2641         //Handle from and to addr joins
2642         if( !empty($_REQUEST['from_addr']) )
2643         {
2644             $from_addr = $this->db->quote(strtolower($_REQUEST['from_addr']));
2645             $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
2646                                 AND er_from.address_type='from' AND emails_text.from_addr LIKE '%" . $from_addr . "%'";
2647         }
2648
2649         if( !empty($_REQUEST['to_addrs'])  )
2650         {
2651             $to_addrs = $this->db->quote(strtolower($_REQUEST['to_addrs']));
2652             $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
2653                                     AND er_to.address_type='to' AND ea_to.email_address LIKE '%" . $to_addrs . "%'";
2654         }
2655
2656         $query['where'] = " WHERE (emails.type= 'inbound' OR emails.type='archived' OR emails.type='out') AND emails.deleted = 0 ";
2657                 if( !empty($additionalWhereClause) )
2658             $query['where'] .= "AND $additionalWhereClause";
2659
2660         //If we are explicitly looking for attachments.  Do not use a distinct query as the to_addr is defined
2661         //as a text which equals clob in oracle and the distinct query can not be executed correctly.
2662         $addDistinctKeyword = "";
2663         if( !empty($_REQUEST['attachmentsSearch']) &&  $_REQUEST['attachmentsSearch'] == 1) //1 indicates yes
2664             $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 )";
2665         else if( !empty($_REQUEST['attachmentsSearch']) &&  $_REQUEST['attachmentsSearch'] == 2 )
2666              $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 )";
2667
2668         $fullQuery = "SELECT " . $query['select'] . " " . $query['joins'] . " " . $query['where'];
2669         
2670         return $fullQuery;
2671     }
2672         /**
2673      * Generate the where clause for searching imported emails.
2674      *
2675      */
2676     function _generateSearchImportWhereClause()
2677     {
2678         global $timedate;
2679
2680         //The clear button was removed so if a user removes the asisgned user name, do not process the id.
2681         if( empty($_REQUEST['assigned_user_name']) && !empty($_REQUEST['assigned_user_id'])  )
2682             unset($_REQUEST['assigned_user_id']);
2683
2684         $availableSearchParam = array('name' => array('table_name' =>'emails'),
2685                                       'data_parent_id_search' => array('table_name' =>'emails','db_key' => 'parent_id','opp' => '='),
2686                                       'assigned_user_id' => array('table_name' => 'emails', 'opp' => '=') );
2687
2688                 $additionalWhereClause = array();
2689                 foreach ($availableSearchParam as $key => $properties)
2690                 {
2691                       if( !empty($_REQUEST[$key]) )
2692                       {
2693                           $db_key =  isset($properties['db_key']) ? $properties['db_key'] : $key;
2694                   $searchValue = $this->db->quote($_REQUEST[$key]);
2695
2696                           $opp = isset($properties['opp']) ? $properties['opp'] : 'like';
2697                           if($opp == 'like')
2698                               $searchValue = "%" . $searchValue . "%";
2699
2700                           $additionalWhereClause[] = "{$properties['table_name']}.$db_key $opp '$searchValue' ";
2701                       }
2702         }
2703         
2704         
2705
2706         $isDateFromSearchSet = !empty($_REQUEST['searchDateFrom']);
2707         $isdateToSearchSet = !empty($_REQUEST['searchDateTo']);
2708         $bothDateRangesSet = $isDateFromSearchSet & $isdateToSearchSet;
2709
2710         //Hanlde date from and to separately
2711         if($bothDateRangesSet)
2712         {
2713             $dbFormatDateFrom = $timedate->to_db_date($_REQUEST['searchDateFrom'], false);
2714             $dbFormatDateFrom = db_convert("'" . $dbFormatDateFrom . "'",'datetime');
2715
2716             $dbFormatDateTo = $timedate->to_db_date($_REQUEST['searchDateTo'], false);
2717             $dbFormatDateTo = db_convert("'" . $dbFormatDateTo . "'",'datetime');
2718
2719             $additionalWhereClause[] = "( emails.date_sent >= $dbFormatDateFrom AND
2720                                           emails.date_sent <= $dbFormatDateTo )";
2721         }
2722         elseif ($isdateToSearchSet)
2723         {
2724             $dbFormatDateTo = $timedate->to_db_date($_REQUEST['searchDateTo'], false);
2725             $dbFormatDateTo = db_convert("'" . $dbFormatDateTo . "'",'datetime');
2726             $additionalWhereClause[] = "emails.date_sent <= $dbFormatDateTo ";
2727         }
2728         elseif ($isDateFromSearchSet)
2729         {
2730             $dbFormatDateFrom = $timedate->to_db_date($_REQUEST['searchDateFrom'], false);
2731             $dbFormatDateFrom = db_convert("'" . $dbFormatDateFrom . "'",'datetime');
2732             $additionalWhereClause[] = "emails.date_sent >= $dbFormatDateFrom ";
2733         }
2734
2735         $additionalWhereClause = implode(" AND ", $additionalWhereClause);
2736
2737         return $additionalWhereClause;
2738     }
2739
2740
2741
2742         /**
2743          * takes a long TO: string of emails and returns the first appended by an
2744          * elipse
2745          */
2746         function trimLongTo($str) {
2747                 if(strpos($str, ',')) {
2748                         $exStr = explode(',', $str);
2749                         return $exStr[0].'...';
2750                 } elseif(strpos($str, ';')) {
2751                         $exStr = explode(';', $str);
2752                         return $exStr[0].'...';
2753                 } else {
2754                         return $str;
2755                 }
2756         }
2757
2758         function get_summary_text() {
2759                 return $this->name;
2760         }
2761
2762
2763
2764         function distributionForm($where) {
2765                 global $app_list_strings;
2766                 global $app_strings;
2767                 global $mod_strings;
2768                 global $theme;
2769                 global $current_user;
2770
2771                 $distribution   = get_select_options_with_id($app_list_strings['dom_email_distribution'], '');
2772                 $_SESSION['distribute_where'] = $where;
2773
2774
2775                 $out = '<form name="Distribute" id="Distribute">';
2776                 $out .= get_form_header($mod_strings['LBL_DIST_TITLE'], '', false);
2777                 $out .=<<<eoq
2778                 <script>
2779                         enableQS(true);
2780                 </script>
2781 eoq;
2782                 $out .= '
2783                 <table cellpadding="0" cellspacing="0" width="100%" border="0">
2784                         <tr>
2785                                 <td>
2786                                         <script type="text/javascript">
2787
2788
2789                                                 function checkDeps(form) {
2790                                                         return;
2791                                                 }
2792
2793                                                 function mySubmit() {
2794                                                         var assform = document.getElementById("Distribute");
2795                                                         var select = document.getElementById("userSelect");
2796                                                         var assign1 = assform.r1.checked;
2797                                                         var assign2 = assform.r2.checked;
2798                                                         var dist = assform.dm.value;
2799                                                         var assign = false;
2800                                                         var users = false;
2801                                                         var rules = false;
2802                                                         var warn1 = "'.$mod_strings['LBL_WARN_NO_USERS'].'";
2803                                                         var warn2 = "";
2804
2805                                                         if(assign1 || assign2) {
2806                                                                 assign = true;
2807
2808                                                         }
2809
2810                                                         for(i=0; i<select.options.length; i++) {
2811                                                                 if(select.options[i].selected == true) {
2812                                                                         users = true;
2813                                                                         warn1 = "";
2814                                                                 }
2815                                                         }
2816
2817                                                         if(dist != "") {
2818                                                                 rules = true;
2819                                                         } else {
2820                                                                 warn2 = "'.$mod_strings['LBL_WARN_NO_DIST'].'";
2821                                                         }
2822
2823                                                         if(assign && users && rules) {
2824
2825                                                                 if(document.getElementById("r1").checked) {
2826                                                                         var mu = document.getElementById("MassUpdate");
2827                                                                         var grabbed = "";
2828
2829                                                                         for(i=0; i<mu.elements.length; i++) {
2830                                                                                 if(mu.elements[i].type == "checkbox" && mu.elements[i].checked && mu.elements[i].name.value != "massall") {
2831                                                                                         if(grabbed != "") { grabbed += "::"; }
2832                                                                                         grabbed += mu.elements[i].value;
2833                                                                                 }
2834                                                                         }
2835                                                                         var formgrab = document.getElementById("grabbed");
2836                                                                         formgrab.value = grabbed;
2837                                                                 }
2838                                                                 assform.submit();
2839                                                         } else {
2840                                                                 alert("'.$mod_strings['LBL_ASSIGN_WARN'].'" + "\n" + warn1 + "\n" + warn2);
2841                                                         }
2842                                                 }
2843
2844                                                 function submitDelete() {
2845                                                         if(document.getElementById("r1").checked) {
2846                                                                 var mu = document.getElementById("MassUpdate");
2847                                                                 var grabbed = "";
2848
2849                                                                 for(i=0; i<mu.elements.length; i++) {
2850                                                                         if(mu.elements[i].type == "checkbox" && mu.elements[i].checked && mu.elements[i].name != "massall") {
2851                                                                                 if(grabbed != "") { grabbed += "::"; }
2852                                                                                 grabbed += mu.elements[i].value;
2853                                                                         }
2854                                                                 }
2855                                                                 var formgrab = document.getElementById("grabbed");
2856                                                                 formgrab.value = grabbed;
2857                                                         }
2858                                                         if(grabbed == "") {
2859                                                                 alert("'.$mod_strings['LBL_MASS_DELETE_ERROR'].'");
2860                                                         } else {
2861                                                                 document.getElementById("Distribute").submit();
2862                                                         }
2863                                                 }
2864
2865                                         </script>
2866                                                 <input type="hidden" name="module" value="Emails">
2867                                                 <input type="hidden" name="action" id="action">
2868                                                 <input type="hidden" name="grabbed" id="grabbed">
2869
2870                                         <table cellpadding="1" cellspacing="0" width="100%" border="0" class="edit view">
2871                                                 <tr height="20">
2872                                                         <td scope="col" scope="row" NOWRAP align="center">
2873                                                                 &nbsp;'.$mod_strings['LBL_ASSIGN_SELECTED_RESULTS_TO'].'&nbsp;';
2874                                         $out .= $this->userSelectTable();
2875                                         $out .= '</td>
2876                                                         <td scope="col" scope="row" NOWRAP align="left">
2877                                                                 &nbsp;'.$mod_strings['LBL_USING_RULES'].'&nbsp;
2878                                                                 <select name="distribute_method" id="dm" onChange="checkDeps(this.form);">'.$distribution.'</select>
2879                                                         </td>';
2880
2881
2882                                         $out .= '</td>
2883                                                         </tr>';
2884
2885
2886                                         $out .= '<tr>
2887                                                                 <td scope="col" width="50%" scope="row" NOWRAP align="right" colspan="2">
2888                                                                 <input title="'.$mod_strings['LBL_BUTTON_DISTRIBUTE_TITLE'].'"
2889                                                                         id="dist_button"
2890                                                                         class="button" onClick="AjaxObject.detailView.handleAssignmentDialogAssignAction();"
2891                                                                         type="button" name="button"
2892                                                                         value="  '.$mod_strings['LBL_BUTTON_DISTRIBUTE'].'  ">';
2893                                         $out .= '</tr>
2894                                         </table>
2895                                 </td>
2896                         </tr>
2897                 </table>
2898                 </form>';
2899         return $out;
2900         }
2901
2902         function userSelectTable() {
2903                 global $theme;
2904                 global $mod_strings;
2905
2906                 $colspan = 1;
2907                 $setTeamUserFunction = '';
2908
2909
2910                 // get users
2911                 $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");
2912
2913                 $userTable = '<table cellpadding="0" cellspacing="0" border="0">';
2914                 $userTable .= '<tr><td colspan="2"><b>'.$mod_strings['LBL_USER_SELECT'].'</b></td></tr>';
2915                 $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>';
2916                 $userTable .= '<tr><td colspan="2"><select style="visibility:hidden;" name="users[]" id="userSelect" multiple size="12">';
2917
2918                 while($a = $this->db->fetchByAssoc($r)) {
2919                         $userTable .= '<option value="'.$a['id'].'" id="'.$a['id'].'">'.$a['first_name'].' '.$a['last_name'].'</option>';
2920                 }
2921                 $userTable .= '</select></td></tr>';
2922                 $userTable .= '</table>';
2923
2924                 $out  = '<script type="text/javascript">';
2925                 $out .= $setTeamUserFunction;
2926                 $out .= '
2927                                         function setCheckMark() {
2928                                                 var select = document.getElementById("userSelect");
2929
2930                                                 for(i=0 ; i<select.options.length; i++) {
2931                                                         if(select.options[i].selected == true) {
2932                                                                 document.getElementById("checkMark").style.display="";
2933                                                                 return;
2934                                                         }
2935                                                 }
2936
2937                                                 document.getElementById("checkMark").style.display="none";
2938                                                 return;
2939                                         }
2940
2941                                         function showUserSelect() {
2942                                                 var targetTable = document.getElementById("user_select");
2943                                                 targetTable.style.visibility="visible";
2944                                                 var userSelectTable = document.getElementById("userSelect");
2945                                                 userSelectTable.style.visibility="visible";
2946                                                 return;
2947                                         }
2948                                         function hideUserSelect() {
2949                                                 var targetTable = document.getElementById("user_select");
2950                                                 targetTable.style.visibility="hidden";
2951                                                 var userSelectTable = document.getElementById("userSelect");
2952                                                 userSelectTable.style.visibility="hidden";
2953                                                 return;
2954                                         }
2955                                         function toggleAll(toggle) {
2956                                                 if(toggle.checked) {
2957                                                         var stat = true;
2958                                                 } else {
2959                                                         var stat = false;
2960                                                 }
2961                                                 var form = document.getElementById("userSelect");
2962                                                 for(i=0; i<form.options.length; i++) {
2963                                                         form.options[i].selected = stat;
2964                                                 }
2965                                         }
2966
2967
2968                                 </script>
2969                         <span id="showUsersDiv" style="position:relative;">
2970                                 <a href="#" id="showUsers" onClick="javascript:showUserSelect();">
2971                                         '.SugarThemeRegistry::current()->getImage('Users', '', null, null, ".gif", $mod_strings['LBL_USERS']).'</a>&nbsp;
2972                                 <a href="#" id="showUsers" onClick="javascript:showUserSelect();">
2973                                         <span style="display:none;" id="checkMark">'.SugarThemeRegistry::current()->getImage('check_inline', 'border="0"', null, null, ".gif", $mod_strings['LBL_CHECK_INLINE']).'</span>
2974                                 </a>
2975
2976
2977                                 <div id="user_select" style="width:200px;position:absolute;left:2;top:2;visibility:hidden;z-index:1000;">
2978                                 <table cellpadding="0" cellspacing="0" border="0" class="list view">
2979                                         <tr height="20">
2980                                                 <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\';">
2981                                                         <a href="#" onClick="javascript:hideUserSelect();">'.SugarThemeRegistry::current()->getImage('close', 'border="0"', null, null, ".gif", $mod_strings['LBL_CLOSE']).'</a>
2982                                                         '.$mod_strings['LBL_USER_SELECT'].'
2983                                                 </td>
2984                                         </tr>
2985                                         <tr>';
2986 //<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\';">
2987                 $out .= '               <td style="padding:5px" class="oddListRowS1" bgcolor="#fdfdfd" valign="top" align="left" style="left:0;top:0;">
2988                                                         '.$userTable.'
2989                                                 </td>
2990                                         </tr>
2991                                 </table></div>
2992                         </span>';
2993                 return $out;
2994         }
2995
2996         function checkInbox($type) {
2997                 global $theme;
2998                 global $mod_strings;
2999                 $out = '<div><input     title="'.$mod_strings['LBL_BUTTON_CHECK_TITLE'].'"
3000                                                 class="button"
3001                                                 type="button" name="button"
3002                                                 onClick="window.location=\'index.php?module=Emails&action=Check&type='.$type.'\';"
3003                                                 style="margin-bottom:2px"
3004                                                 value="  '.$mod_strings['LBL_BUTTON_CHECK'].'  "></div>';
3005                 return $out;
3006         }
3007
3008         /**
3009          * Guesses Primary Parent id from From: email address.  Cascades guesses from Accounts to Contacts to Leads to
3010          * Users.  This will not affect the many-to-many relationships already constructed as this is, at best,
3011          * informational linking.
3012          */
3013         function fillPrimaryParentFields() {
3014                 if(empty($this->from_addr))
3015                         return;
3016
3017                 $GLOBALS['log']->debug("*** Email trying to guess Primary Parent from address [ {$this->from_addr} ]");
3018
3019                 $tables = array('accounts');
3020                 $ret = array();
3021                 // loop through types to get hits
3022                 foreach($tables as $table) {
3023                         $q = "SELECT name, id FROM {$table} WHERE email1 = '{$this->from_addr}' OR email2 = '{$this->from_addr}' AND deleted = 0";
3024                         $r = $this->db->query($q);
3025                         while($a = $this->db->fetchByAssoc($r)) {
3026                                 if(!empty($a['name']) && !empty($a['id'])) {
3027                                         $this->parent_type      = ucwords($table);
3028                                         $this->parent_id        = $a['id'];
3029                                         $this->parent_name      = $a['name'];
3030                                         return;
3031                                 }
3032                         }
3033                 }
3034         }
3035
3036         /**
3037          * Convert reference to inline image (stored as Note) to URL link
3038          * Enter description here ...
3039          * @param string $note ID of the note
3040          * @param string $ext type of the note
3041          */
3042         public function cid2Link($noteId, $noteType)
3043         {
3044             if(empty($this->description_html)) return;
3045                         list($type, $subtype) = explode('/', $noteType);
3046                         if(strtolower($type) != 'image') {
3047                             return;
3048                         }
3049             $upload = new UploadFile();
3050                         $this->description_html = preg_replace("#class=\"image\" src=\"cid:$noteId\.(.+?)\"#", "class=\"image\" src=\"{$this->imagePrefix}{$noteId}.\\1\"", $this->description_html);
3051                 // ensure the image is in the cache
3052                         $imgfilename = sugar_cached("images/")."$noteId.".strtolower($subtype);
3053                         $src = "upload://$noteId";
3054                         if(!file_exists($imgfilename) && file_exists($src)) {
3055                                 copy($src, $imgfilename);
3056                         }
3057         }
3058
3059         /**
3060          * Convert all cid: links in this email into URLs
3061          */
3062         function cids2Links()
3063         {
3064             if(empty($this->description_html)) return;
3065             $q = "SELECT id, file_mime_type FROM notes WHERE parent_id = '{$this->id}' AND deleted = 0";
3066                 $r = $this->db->query($q);
3067             while($a = $this->db->fetchByAssoc($r)) {
3068                 $this->cid2Link($a['id'], $a['file_mime_type']);
3069             }
3070         }
3071
3072     /**
3073      * Bugs 50972, 50973
3074      * Sets the field def for a field to allow null values
3075      *
3076      * @todo Consider moving to SugarBean to allow other models to set fields to NULL
3077      * @param string $field The field name to modify
3078      * @return void
3079      */
3080     public function setFieldNullable($field)
3081     {
3082         if (isset($this->field_defs[$field]) && is_array($this->field_defs[$field]))
3083         {
3084             if (empty($this->modifiedFieldDefs[$field]))
3085             {
3086                 if (
3087                     isset($this->field_defs[$field]['isnull']) &&
3088                     (strtolower($this->field_defs[$field]['isnull']) == 'false' || $this->field_defs[$field]['isnull'] === false)
3089                 )
3090                 {
3091                     $this->modifiedFieldDefs[$field]['isnull'] = $this->field_defs[$field]['isnull'];
3092                     unset($this->field_defs[$field]['isnull']);
3093                 }
3094
3095                 if (isset($this->field_defs[$field]['dbType']) && $this->field_defs[$field]['dbType'] == 'id')
3096                 {
3097                     $this->modifiedFieldDefs[$field]['dbType'] = $this->field_defs[$field]['dbType'];
3098                     unset($this->field_defs[$field]['dbType']);
3099                 }
3100             }
3101         }
3102     }
3103
3104     /**
3105      * Bugs 50972, 50973
3106      * Set the field def back to the way it was prior to modification
3107      *
3108      * @param $field
3109      * @return void
3110      */
3111     public function revertFieldNullable($field)
3112     {
3113         if (!empty($this->modifiedFieldDefs[$field]) && is_array($this->modifiedFieldDefs[$field]))
3114         {
3115             foreach ($this->modifiedFieldDefs[$field] as $k => $v)
3116             {
3117                 $this->field_defs[$field][$k] = $v;
3118             }
3119
3120             unset($this->modifiedFieldDefs[$field]);
3121             }
3122         }
3123 } // end class def