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.
7 * This program is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU Affero General Public License version 3 as published by the
9 * Free Software Foundation with the addition of the following permission added
10 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
11 * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
12 * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
14 * This program is distributed in the hope that it will be useful, but WITHOUT
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16 * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
19 * You should have received a copy of the GNU Affero General Public License along with
20 * this program; if not, see http://www.gnu.org/licenses or write to the Free
21 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
24 * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
25 * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
27 * The interactive user interfaces in modified source and object code versions
28 * of this program must display Appropriate Legal Notices, as required under
29 * Section 5 of the GNU Affero General Public License version 3.
31 * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
32 * these Appropriate Legal Notices must retain the display of the "Powered by
33 * SugarCRM" logo. If the display of the logo is not reasonably feasible for
34 * technical reasons, the Appropriate Legal Notices must display the words
35 * "Powered by SugarCRM".
36 ********************************************************************************/
39 require_once('include/SugarPHPMailer.php');
40 require_once 'include/upload_file.php';
42 class Email extends SugarBean {
43 /* SugarBean schema */
47 var $assigned_user_id;
48 var $assigned_user_name;
49 var $modified_user_id;
61 var $type = 'archived';
72 var $description_html;
82 var $date_start; // legacy
83 var $time_start; // legacy
96 var $bcc_addrs_emails;
100 /* Archive Email attrs */
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';
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";
115 var $cacheFile = 'robin.cache.php';
116 var $replyDelimiter = "> ";
117 var $emailDescription;
118 var $emailDescriptionHTML;
122 var $attachments = array();
124 /* to support Email 2.0 */
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',
141 var $et; // EmailUI object
142 // prefix to use when importing inlinge images in emails
146 * Used for keeping track of field defs that have been modified
150 public $modifiedFieldDefs = array();
157 global $current_user;
158 $this->cachePath = sugar_cached('modules/Emails');
161 $this->emailAddress = new SugarEmailAddress();
163 $this->imagePrefix = rtrim($GLOBALS['sugar_config']['site_url'], "/")."/cache/images/";
166 function email2init() {
167 require_once('modules/Emails/EmailUI.php');
168 $this->et = new EmailUI();
171 function bean_implements($interface){
173 case 'ACL': return true;
174 default: return false;
180 * Presaves one attachment for new email 2.0 spec
181 * DOES NOT CREATE A NOTE
182 * @return string ID of note associated with the attachment
184 public function email2saveAttachment()
186 $email_uploads = "modules/Emails/{$GLOBALS['current_user']->id}";
187 $upload = new UploadFile('email_attachment');
188 if(!$upload->confirm_upload()) {
189 $err = $upload->get_upload_error();
191 $GLOBALS['log']->error("Email Attachment could not be attached due to error: $err");
196 $guid = create_guid();
197 $fileName = $upload->create_stored_filename();
198 $GLOBALS['log']->debug("Email Attachment [$fileName]");
199 if($upload->final_move($guid)) {
200 copy("upload://$guid", sugar_cached("$email_uploads/$guid"));
203 'name' => $GLOBALS['db']->quote($fileName),
204 'nameForDisplay' => $fileName
207 $GLOBALS['log']->debug("Email Attachment [$fileName] could not be moved to upload dir");
212 function safeAttachmentName($filename) {
213 global $sugar_config;
214 $badExtension = false;
215 //get position of last "." in file name
216 $file_ext_beg = strrpos($filename, ".");
220 if($file_ext_beg !== false) {
221 $file_ext = substr($filename, $file_ext_beg + 1);
224 //check to see if this is a file with extension located in "badext"
225 foreach($sugar_config['upload_badext'] as $badExt) {
226 if(strtolower($file_ext) == strtolower($badExt)) {
227 //if found, then append with .txt and break out of lookup
228 $filename = $filename . ".txt";
229 $badExtension = true;
230 break; // no need to look for more
234 return $badExtension;
238 * takes output from email 2.0 to/cc/bcc fields and returns appropriate arrays for usage by PHPMailer
239 * @param string addresses
242 function email2ParseAddresses($addresses) {
243 $addresses = from_html($addresses);
244 $addresses = $this->et->unifyEmailString($addresses);
246 $pattern = '/@.*,/U';
247 preg_match_all($pattern, $addresses, $matchs);
248 if (!empty($matchs[0])){
250 foreach ($total as $match) {
251 $convertedPattern = str_replace(',', '::;::', $match);
252 $addresses = str_replace($match, $convertedPattern, $addresses);
256 $exAddr = explode("::;::", $addresses);
259 $clean = array("<", ">");
260 $dirty = array("<", ">");
262 foreach($exAddr as $addr) {
265 $addr = str_replace($dirty, $clean, $addr);
267 if((strpos($addr, "<") === false) && (strpos($addr, ">") === false)) {
270 $address = substr($addr, strpos($addr, "<") + 1, strpos($addr, ">") - 1 - strpos($addr, "<"));
271 $name = substr($addr, 0, strpos($addr, "<"));
275 $addrTemp['email'] = trim($address);
276 $addrTemp['display'] = trim($name);
284 * takes output from email 2.0 to/cc/bcc fields and returns appropriate arrays for usage by PHPMailer
285 * @param string addresses
288 function email2ParseAddressesForAddressesOnly($addresses) {
289 $addresses = from_html($addresses);
290 $pattern = '/@.*,/U';
291 preg_match_all($pattern, $addresses, $matchs);
292 if (!empty($matchs[0])){
294 foreach ($total as $match) {
295 $convertedPattern = str_replace(',', '::;::', $match);
296 $addresses = str_replace($match, $convertedPattern, $addresses);
300 $exAddr = explode("::;::", $addresses);
303 $clean = array("<", ">");
304 $dirty = array("<", ">");
306 foreach($exAddr as $addr) {
309 $addr = str_replace($dirty, $clean, $addr);
311 if(strpos($addr, "<") && strpos($addr, ">")) {
312 $address = substr($addr, strpos($addr, "<") + 1, strpos($addr, ">") - 1 - strpos($addr, "<"));
317 $ret[] = trim($address);
324 * Determines MIME-type encoding as possible.
325 * @param string $fileLocation relative path to file
326 * @return string MIME-type
328 function email2GetMime($fileLocation) {
329 if(!is_readable($fileLocation)) {
330 return 'application/octet-stream';
332 if(function_exists('mime_content_type')) {
333 $mime = mime_content_type($fileLocation);
334 } elseif(function_exists('ext2mime')) {
335 $mime = ext2mime($fileLocation);
337 $mime = 'application/octet-stream';
343 function sendEmailTest($mailserver_url, $port, $ssltls, $smtp_auth_req, $smtp_username, $smtppassword, $fromaddress, $toaddress, $mail_sendtype = 'smtp', $fromname = '') {
344 global $current_user,$app_strings;
345 $mod_strings = return_module_language($GLOBALS['current_language'], 'Emails'); //Called from EmailMan as well.
346 $mail = new SugarPHPMailer();
347 $mail->Mailer = strtolower($mail_sendtype);
348 if($mail->Mailer == 'smtp')
350 $mail->Host = $mailserver_url;
352 if (isset($ssltls) && !empty($ssltls)) {
353 $mail->protocol = "ssl://";
355 $mail->SMTPSecure = 'ssl';
358 $mail->SMTPSecure = 'tls';
361 $mail->protocol = "tcp://";
363 if ($smtp_auth_req) {
364 $mail->SMTPAuth = TRUE;
365 $mail->Username = $smtp_username;
366 $mail->Password = $smtppassword;
370 $mail->Mailer = 'sendmail';
372 $mail->Subject = from_html($mod_strings['LBL_TEST_EMAIL_SUBJECT']);
373 $mail->From = $fromaddress;
375 if ($fromname != '') {
376 $mail->FromName = html_entity_decode($fromname,ENT_QUOTES);
378 $mail->FromName = $current_user->name;
381 $mail->Sender = $mail->From;
382 $mail->AddAddress($toaddress);
383 $mail->Body = $mod_strings['LBL_TEST_EMAIL_BODY'];
389 $return['status'] = false;
390 $return['errorMessage'] = $app_strings['LBL_EMAIL_ERROR_PREPEND']. $mail->ErrorInfo;
393 $return['status'] = true;
397 function decodeDuringSend($htmlData) {
398 $htmlData = str_replace("sugarLessThan", "<", $htmlData);
399 $htmlData = str_replace("sugarGreaterThan", ">", $htmlData);
404 * Returns true or false if this email is a draft.
406 * @param array $request
407 * @return bool True indicates this email is a draft.
409 function isDraftEmail($request)
411 return ( isset($request['saveDraft']) || ($this->type == 'draft' && $this->status == 'draft') );
415 * Sends Email for Email 2.0
417 function email2Send($request) {
420 global $current_user;
421 global $sugar_config;
426 $OBCharset = $locale->getPrecedentPreference('default_email_charset');
428 /**********************************************************************
434 if(!empty($this->id)) {
435 $orignialId = $this->id;
438 if(empty($this->id)) {
439 $this->id = create_guid();
440 $this->new_with_id = true;
443 /* satisfy basic HTML email requirements */
444 $this->name = $request['sendSubject'];
445 $this->description_html = '<html><body>'.$request['sendDescription'].'</body></html>';
447 /**********************************************************************
450 $mail = new SugarPHPMailer();
451 $mail = $this->setMailer($mail, '', $_REQUEST['fromAccount']);
452 if (empty($mail->Host) && !$this->isDraftEmail($request))
454 $this->status = 'send_error';
456 if ($mail->oe->type == 'system')
457 echo($app_strings['LBL_EMAIL_ERROR_PREPEND']. $app_strings['LBL_EMAIL_INVALID_SYSTEM_OUTBOUND']);
459 echo($app_strings['LBL_EMAIL_ERROR_PREPEND']. $app_strings['LBL_EMAIL_INVALID_PERSONAL_OUTBOUND']);
464 $subject = $this->name;
465 $mail->Subject = from_html($this->name);
467 // work-around legacy code in SugarPHPMailer
468 if($_REQUEST['setEditor'] == 1) {
469 $_REQUEST['description_html'] = $_REQUEST['sendDescription'];
470 $this->description_html = $_REQUEST['description_html'];
472 $this->description_html = '';
473 $this->description = $_REQUEST['sendDescription'];
477 if ( $this->isDraftEmail($request) )
479 if($this->type != 'draft' && $this->status != 'draft') {
480 $this->id = create_guid();
481 $this->new_with_id = true;
482 $this->date_entered = "";
484 $q1 = "update emails_email_addr_rel set deleted = 1 WHERE email_id = '{$this->id}'";
485 $r1 = $this->db->query($q1);
488 if (isset($request['saveDraft'])) {
489 $this->type = 'draft';
490 $this->status = 'draft';
493 /* Apply Email Templates */
494 // do not parse email templates if the email is being saved as draft....
495 $toAddresses = $this->email2ParseAddresses($_REQUEST['sendTo']);
496 $sea = new SugarEmailAddress();
497 $object_arr = array();
499 if( isset($_REQUEST['parent_type']) && !empty($_REQUEST['parent_type']) &&
500 isset($_REQUEST['parent_id']) && !empty($_REQUEST['parent_id']) &&
501 ($_REQUEST['parent_type'] == 'Accounts' ||
502 $_REQUEST['parent_type'] == 'Contacts' ||
503 $_REQUEST['parent_type'] == 'Leads' ||
504 $_REQUEST['parent_type'] == 'Users' ||
505 $_REQUEST['parent_type'] == 'Prospects')) {
506 if(isset($beanList[$_REQUEST['parent_type']]) && !empty($beanList[$_REQUEST['parent_type']])) {
507 $className = $beanList[$_REQUEST['parent_type']];
508 if(isset($beanFiles[$className]) && !empty($beanFiles[$className])) {
509 if(!class_exists($className)) {
510 require_once($beanFiles[$className]);
512 $bean = new $className();
513 $bean->retrieve($_REQUEST['parent_id']);
514 $object_arr[$bean->module_dir] = $bean->id;
518 foreach($toAddresses as $addrMeta) {
519 $addr = $addrMeta['email'];
520 $beans = $sea->getBeansByEmailAddress($addr);
521 foreach($beans as $bean) {
522 if (!isset($object_arr[$bean->module_dir])) {
523 $object_arr[$bean->module_dir] = $bean->id;
528 /* template parsing */
529 if (empty($object_arr)) {
530 $object_arr= array('Contacts' => '123');
532 $object_arr['Users'] = $current_user->id;
533 $this->description_html = EmailTemplate::parse_template($this->description_html, $object_arr);
534 $this->name = EmailTemplate::parse_template($this->name, $object_arr);
535 $this->description = EmailTemplate::parse_template($this->description, $object_arr);
536 $this->description = html_entity_decode($this->description,ENT_COMPAT,'UTF-8');
537 if($this->type != 'draft' && $this->status != 'draft') {
538 $this->id = create_guid();
539 $this->date_entered = "";
540 $this->new_with_id = true;
542 $this->status = 'sent';
546 if(isset($_REQUEST['parent_type']) && empty($_REQUEST['parent_type']) &&
547 isset($_REQUEST['parent_id']) && empty($_REQUEST['parent_id']) ) {
548 $this->parent_id = "";
549 $this->parent_type = "";
553 $mail->Subject = $this->name;
554 $mail = $this->handleBody($mail);
555 $mail->Subject = $this->name;
556 $this->description_html = from_html($this->description_html);
557 $this->description_html = $this->decodeDuringSend($this->description_html);
558 $this->description = $this->decodeDuringSend($this->description);
561 $replyToAddress = $current_user->emailAddress->getReplyToAddress($current_user, true);
563 if(empty($request['fromAccount'])) {
564 $defaults = $current_user->getPreferredEmail();
565 $mail->From = $defaults['email'];
566 $mail->FromName = $defaults['name'];
567 $replyToName = $mail->FromName;
568 //$replyToAddress = $current_user->emailAddress->getReplyToAddress($current_user);
570 // passed -> user -> system default
571 $ie = new InboundEmail();
572 $ie->retrieve($request['fromAccount']);
573 $storedOptions = unserialize(base64_decode($ie->stored_options));
577 //$replyToAddress = "";
578 if (!empty($storedOptions)) {
579 $fromAddress = $storedOptions['from_addr'];
580 $fromName = from_html($storedOptions['from_name']);
581 $replyToAddress = (isset($storedOptions['reply_to_addr']) ? $storedOptions['reply_to_addr'] : "");
582 $replyToName = (isset($storedOptions['reply_to_name']) ? from_html($storedOptions['reply_to_name']) : "");
584 $defaults = $current_user->getPreferredEmail();
585 // Personal Account doesn't have reply To Name and Reply To Address. So add those columns on UI
586 // After adding remove below code
589 if ($ie->is_personal)
591 if (empty($replyToAddress))
593 $replyToAddress = $current_user->emailAddress->getReplyToAddress($current_user, true);
595 if (empty($replyToName))
597 $replyToName = $defaults['name'];
599 //Personal accounts can have a reply_address, which should
600 //overwrite the users set default.
601 if( !empty($storedOptions['reply_to_addr']) )
602 $replyToAddress = $storedOptions['reply_to_addr'];
605 // end of code to remove
606 $mail->From = (!empty($fromAddress)) ? $fromAddress : $defaults['email'];
607 $mail->FromName = (!empty($fromName)) ? $fromName : $defaults['name'];
608 $replyToName = (!empty($replyToName)) ? $replyToName : $mail->FromName;
611 $mail->Sender = $mail->From; /* set Return-Path field in header to reduce spam score in emails sent via Sugar's Email module */
613 if (!empty($replyToAddress)) {
614 $mail->AddReplyTo($replyToAddress,$locale->translateCharsetMIME(trim( $replyToName), 'UTF-8', $OBCharset));
616 $mail->AddReplyTo($mail->From,$locale->translateCharsetMIME(trim( $mail->FromName), 'UTF-8', $OBCharset));
618 $emailAddressCollection = array(); // used in linking to beans below
620 foreach($this->email2ParseAddresses($request['sendTo']) as $addr_arr) {
621 if(empty($addr_arr['email'])) continue;
623 if(empty($addr_arr['display'])) {
624 $mail->AddAddress($addr_arr['email'], "");
626 $mail->AddAddress($addr_arr['email'],$locale->translateCharsetMIME(trim( $addr_arr['display']), 'UTF-8', $OBCharset));
628 $emailAddressCollection[] = $addr_arr['email'];
630 foreach($this->email2ParseAddresses($request['sendCc']) as $addr_arr) {
631 if(empty($addr_arr['email'])) continue;
633 if(empty($addr_arr['display'])) {
634 $mail->AddCC($addr_arr['email'], "");
636 $mail->AddCC($addr_arr['email'],$locale->translateCharsetMIME(trim( $addr_arr['display']), 'UTF-8', $OBCharset));
638 $emailAddressCollection[] = $addr_arr['email'];
641 foreach($this->email2ParseAddresses($request['sendBcc']) as $addr_arr) {
642 if(empty($addr_arr['email'])) continue;
644 if(empty($addr_arr['display'])) {
645 $mail->AddBCC($addr_arr['email'], "");
647 $mail->AddBCC($addr_arr['email'],$locale->translateCharsetMIME(trim( $addr_arr['display']), 'UTF-8', $OBCharset));
649 $emailAddressCollection[] = $addr_arr['email'];
653 /* parse remove attachments array */
654 $removeAttachments = array();
655 if(!empty($request['templateAttachmentsRemove'])) {
656 $exRemove = explode("::", $request['templateAttachmentsRemove']);
658 foreach($exRemove as $file) {
659 $removeAttachments = substr($file, 0, 36);
663 /* handle attachments */
664 if(!empty($request['attachments'])) {
665 $exAttachments = explode("::", $request['attachments']);
667 foreach($exAttachments as $file) {
668 $file = trim(from_html($file));
669 $file = str_replace("\\", "", $file);
671 //$fileLocation = $this->et->userCacheDir."/{$file}";
672 $fileGUID = preg_replace('/[^a-z0-9\-]/', "", substr($file, 0, 36));
673 $fileLocation = $this->et->userCacheDir."/{$fileGUID}";
674 $filename = substr($file, 36, strlen($file)); // strip GUID for PHPMailer class to name outbound file
676 $mail->AddAttachment($fileLocation,$filename, 'base64', $this->email2GetMime($fileLocation));
677 //$mail->AddAttachment($fileLocation, $filename, 'base64');
679 // only save attachments if we're archiving or drafting
680 if((($this->type == 'draft') && !empty($this->id)) || (isset($request['saveToSugar']) && $request['saveToSugar'] == 1)) {
682 $note->id = create_guid();
683 $note->new_with_id = true; // duplicating the note with files
684 $note->parent_id = $this->id;
685 $note->parent_type = $this->module_dir;
686 $note->name = $filename;
687 $note->filename = $filename;
688 $note->file_mime_type = $this->email2GetMime($fileLocation);
689 $dest = "upload://{$note->id}";
690 if(!copy($fileLocation, $dest)) {
691 $GLOBALS['log']->debug("EMAIL 2.0: could not copy attachment file to $fileLocation => $dest");
700 /* handle sugar documents */
701 if(!empty($request['documents'])) {
702 $exDocs = explode("::", $request['documents']);
704 foreach($exDocs as $docId) {
705 $docId = trim($docId);
707 $doc = new Document();
708 $docRev = new DocumentRevision();
709 $doc->retrieve($docId);
710 $docRev->retrieve($doc->document_revision_id);
712 $filename = $docRev->filename;
713 $docGUID = preg_replace('/[^a-z0-9\-]/', "", $docRev->id);
714 $fileLocation = "upload://{$docGUID}";
715 $mime_type = $docRev->file_mime_type;
716 $mail->AddAttachment($fileLocation,$locale->translateCharsetMIME(trim($filename), 'UTF-8', $OBCharset), 'base64', $mime_type);
718 // only save attachments if we're archiving or drafting
719 if((($this->type == 'draft') && !empty($this->id)) || (isset($request['saveToSugar']) && $request['saveToSugar'] == 1)) {
721 $note->id = create_guid();
722 $note->new_with_id = true; // duplicating the note with files
723 $note->parent_id = $this->id;
724 $note->parent_type = $this->module_dir;
725 $note->name = $filename;
726 $note->filename = $filename;
727 $note->file_mime_type = $mime_type;
728 $dest = "upload://{$note->id}";
729 if(!copy($fileLocation, $dest)) {
730 $GLOBALS['log']->debug("EMAIL 2.0: could not copy SugarDocument revision file $fileLocation => $dest");
739 /* handle template attachments */
740 if(!empty($request['templateAttachments'])) {
742 $exNotes = explode("::", $request['templateAttachments']);
743 foreach($exNotes as $noteId) {
744 $noteId = trim($noteId);
745 if(!empty($noteId)) {
747 $note->retrieve($noteId);
748 if (!empty($note->id)) {
749 $filename = $note->filename;
750 $noteGUID = preg_replace('/[^a-z0-9\-]/', "", $note->id);
751 $fileLocation = "upload://{$noteGUID}";
752 $mime_type = $note->file_mime_type;
753 if (!$note->embed_flag) {
754 $mail->AddAttachment($fileLocation,$filename, 'base64', $mime_type);
755 // only save attachments if we're archiving or drafting
756 if((($this->type == 'draft') && !empty($this->id)) || (isset($request['saveToSugar']) && $request['saveToSugar'] == 1)) {
758 if ($note->parent_id != $this->id)
759 $this->saveTempNoteAttachments($filename,$fileLocation, $mime_type);
764 //$fileLocation = $this->et->userCacheDir."/{$file}";
765 $fileGUID = preg_replace('/[^a-z0-9\-]/', "", substr($noteId, 0, 36));
766 $fileLocation = $this->et->userCacheDir."/{$fileGUID}";
767 //$fileLocation = $this->et->userCacheDir."/{$noteId}";
768 $filename = substr($noteId, 36, strlen($noteId)); // strip GUID for PHPMailer class to name outbound file
770 $mail->AddAttachment($fileLocation,$locale->translateCharsetMIME(trim($filename), 'UTF-8', $OBCharset), 'base64', $this->email2GetMime($fileLocation));
772 //If we are saving an email we were going to forward we need to save the attachments as well.
773 if( (($this->type == 'draft') && !empty($this->id))
774 || (isset($request['saveToSugar']) && $request['saveToSugar'] == 1))
776 $mimeType = $this->email2GetMime($fileLocation);
777 $this->saveTempNoteAttachments($filename,$fileLocation, $mimeType);
786 /**********************************************************************
789 /* save email to sugar? */
792 if($this->type == 'draft' && !isset($request['saveDraft'])) {
793 // sending a draft email
795 $this->status = 'sent';
797 } elseif(isset($request['saveDraft'])) {
798 $this->type = 'draft';
799 $this->status = 'draft';
803 /**********************************************************************
804 * SEND EMAIL (finally!)
807 if ($this->type != 'draft') {
808 $mail->prepForOutbound();
809 $mail->Body = $this->decodeDuringSend($mail->Body);
810 $mail->AltBody = $this->decodeDuringSend($mail->AltBody);
811 if (!$mail->Send()) {
812 $this->status = 'send_error';
814 echo($app_strings['LBL_EMAIL_ERROR_PREPEND']. $mail->ErrorInfo);
819 if ((!(empty($orignialId) || isset($request['saveDraft']) || ($this->type == 'draft' && $this->status == 'draft'))) &&
820 (($_REQUEST['composeType'] == 'reply') || ($_REQUEST['composeType'] == 'replyAll') || ($_REQUEST['composeType'] == 'replyCase')) && ($orignialId != $this->id)) {
821 $originalEmail = new Email();
822 $originalEmail->retrieve($orignialId);
823 $originalEmail->reply_to_status = 1;
824 $originalEmail->save();
825 $this->reply_to_status = 0;
828 if ($_REQUEST['composeType'] == 'reply' || $_REQUEST['composeType'] == 'replyCase') {
829 if (isset($_REQUEST['ieId']) && isset($_REQUEST['mbox'])) {
830 $emailFromIe = new InboundEmail();
831 $emailFromIe->retrieve($_REQUEST['ieId']);
832 $emailFromIe->mailbox = $_REQUEST['mbox'];
833 if (isset($emailFromIe->id) && $emailFromIe->is_personal) {
834 if ($emailFromIe->isPop3Protocol()) {
835 $emailFromIe->mark_answered($this->uid, 'pop3');
837 elseif ($emailFromIe->connectMailserver() == 'true') {
838 $emailFromIe->markEmails($this->uid, 'answered');
839 $emailFromIe->mark_answered($this->uid);
847 $this->type == 'draft' ||
848 (isset($request['saveToSugar']) && $request['saveToSugar'] == 1)) {
850 // saving a draft OR saving a sent email
851 $decodedFromName = mb_decode_mimeheader($mail->FromName);
852 $this->from_addr = "{$decodedFromName} <{$mail->From}>";
853 $this->from_addr_name = $this->from_addr;
854 $this->to_addrs = $_REQUEST['sendTo'];
855 $this->to_addrs_names = $_REQUEST['sendTo'];
856 $this->cc_addrs = $_REQUEST['sendCc'];
857 $this->cc_addrs_names = $_REQUEST['sendCc'];
858 $this->bcc_addrs = $_REQUEST['sendBcc'];
859 $this->bcc_addrs_names = $_REQUEST['sendBcc'];
860 $this->assigned_user_id = $current_user->id;
862 $this->date_sent = $timedate->now();
863 ///////////////////////////////////////////////////////////////////
864 //// LINK EMAIL TO SUGARBEANS BASED ON EMAIL ADDY
866 if( isset($_REQUEST['parent_type']) && !empty($_REQUEST['parent_type']) &&
867 isset($_REQUEST['parent_id']) && !empty($_REQUEST['parent_id']) ) {
868 $this->parent_id = $_REQUEST['parent_id'];
869 $this->parent_type = $_REQUEST['parent_type'];
870 $q = "SELECT count(*) c FROM emails_beans WHERE email_id = '{$this->id}' AND bean_id = '{$_REQUEST['parent_id']}' AND bean_module = '{$_REQUEST['parent_type']}'";
871 $r = $this->db->query($q);
872 $a = $this->db->fetchByAssoc($r);
874 if(isset($beanList[$_REQUEST['parent_type']]) && !empty($beanList[$_REQUEST['parent_type']])) {
875 $className = $beanList[$_REQUEST['parent_type']];
876 if(isset($beanFiles[$className]) && !empty($beanFiles[$className])) {
877 if(!class_exists($className)) {
878 require_once($beanFiles[$className]);
880 $bean = new $className();
881 $bean->retrieve($_REQUEST['parent_id']);
882 if($bean->load_relationship('emails')) {
883 $bean->emails->add($this->id);
893 if(!class_exists('aCase')) {
898 if($caseId = InboundEmail::getCaseIdFromCaseNumber($mail->Subject, $c)) {
899 $c->retrieve($caseId);
900 $c->load_relationship('emails');
901 $c->emails->add($this->id);
902 $this->parent_type = "Cases";
903 $this->parent_id = $caseId;
909 //// LINK EMAIL TO SUGARBEANS BASED ON EMAIL ADDY
910 ///////////////////////////////////////////////////////////////////
914 if(!empty($request['fromAccount'])) {
915 if (isset($ie->id) && !$ie->isPop3Protocol() && $mail->oe->mail_smtptype != 'gmail') {
916 $sentFolder = $ie->get_stored_options("sentFolder");
917 if (!empty($sentFolder)) {
918 $data = $mail->CreateHeader() . "\r\n" . $mail->CreateBody() . "\r\n";
919 $ie->mailbox = $sentFolder;
920 if ($ie->connectMailserver() == 'true') {
921 $connectString = $ie->getConnectString($ie->getServiceString(), $ie->mailbox);
922 $returnData = imap_append($ie->conn,$connectString, $data, "\\Seen");
924 $GLOBALS['log']->debug("could not copy email to {$ie->mailbox} for {$ie->name}");
927 $GLOBALS['log']->debug("could not connect to mail serve for folder {$ie->mailbox} for {$ie->name}");
930 $GLOBALS['log']->debug("could not copy email to {$ie->mailbox} sent folder as its empty");
938 * Generates a config-specified separated name and addresses to be used in compose email screen for
939 * contacts or leads from listview
940 * By default, use comma, but allow for non-standard delimiters as specified in email_address_separator
942 * @param $module string module name
943 * @param $idsArray array of record ids to get the email address for
944 * @return string (config-specified) delimited list of email addresses
946 public function getNamePlusEmailAddressesForCompose($module, $idsArray)
951 foreach ($idsArray as $id)
954 $bean = BeanFactory::getBean($module, $id);
959 // For CE, just get primary e-mail address
960 $emailAddress = $bean->email1;
963 // If we have an e-mail address loaded
964 if (!empty($emailAddress))
966 // Use bean name by default
967 $fullName = $bean->name;
969 // Depending on module, format the name
970 if (in_array($module, array('Users', 'Employees')))
972 $fullName = from_html(
973 $locale->getLocaleFormattedName(
981 else if (SugarModule::get($module)->moduleImplements('Person'))
983 $fullName = from_html(
984 $locale->getLocaleFormattedName(
993 // Make e-mail address in format "Name <@email>"
994 $result[$bean->id] = $fullName . " <" . from_html($emailAddress) . ">";
999 // Broken out of method to facilitate unit testing
1000 return $this->_arrayToDelimitedString($result);
1004 * @param Array $arr - list of strings
1005 * @return string the list of strings delimited by email_address_separator
1007 function _arrayToDelimitedString($arr)
1009 // bug 51804: outlook does not respect the correct email address separator (',') , so let
1010 // clients override the default.
1011 $separator = (isset($GLOBALS['sugar_config']['email_address_separator']) &&
1012 !empty($GLOBALS['sugar_config']['email_address_separator'])) ?
1013 $GLOBALS['sugar_config']['email_address_separator'] :
1016 return join($separator, array_values($arr));
1022 ///////////////////////////////////////////////////////////////////////////
1024 function save($check_notify = false) {
1025 global $current_user;
1027 if($this->isDuplicate) {
1028 $GLOBALS['log']->debug("EMAIL - tried to save a duplicate Email record");
1031 if(empty($this->id)) {
1032 $this->id = create_guid();
1033 $this->new_with_id = true;
1035 $this->from_addr_name = $this->cleanEmails($this->from_addr_name);
1036 $this->to_addrs_names = $this->cleanEmails($this->to_addrs_names);
1037 $this->cc_addrs_names = $this->cleanEmails($this->cc_addrs_names);
1038 $this->bcc_addrs_names = $this->cleanEmails($this->bcc_addrs_names);
1039 $this->reply_to_addr = $this->cleanEmails($this->reply_to_addr);
1040 $this->description = SugarCleaner::cleanHtml($this->description);
1041 $this->description_html = SugarCleaner::cleanHtml($this->description_html, true);
1042 $this->raw_source = SugarCleaner::cleanHtml($this->raw_source, true);
1043 $this->saveEmailText();
1044 $this->saveEmailAddresses();
1046 $GLOBALS['log']->debug('-------------------------------> Email called save()');
1048 // handle legacy concatenation of date and time fields
1049 //Bug 39503 - SugarBean is not setting date_sent when seconds missing
1050 if(empty($this->date_sent)) {
1052 $date_sent_obj = $timedate->fromUser($timedate->merge_date_time($this->date_start, $this->time_start), $current_user);
1053 if (!empty($date_sent_obj) && ($date_sent_obj instanceof SugarDateTime)) {
1054 $this->date_sent = $date_sent_obj->asDb();
1057 //set date_entered to date_sent if this is a new email being archived
1058 //that way emails archived to sugar by plugins like opacus mail will
1059 //have the correct ordering according to email incoming date.
1060 if ($this->new_with_id) {
1061 $this->date_entered = $this->date_sent;
1065 parent::save($check_notify);
1067 if(!empty($this->parent_type) && !empty($this->parent_id)) {
1068 if(!empty($this->fetched_row) && !empty($this->fetched_row['parent_id']) && !empty($this->fetched_row['parent_type'])) {
1069 if($this->fetched_row['parent_id'] != $this->parent_id || $this->fetched_row['parent_type'] != $this->parent_type) {
1070 $mod = strtolower($this->fetched_row['parent_type']);
1071 $rel = array_key_exists($mod, $this->field_defs) ? $mod : $mod . "_activities_emails"; //Custom modules rel name
1072 if($this->load_relationship($rel) ) {
1073 $this->$rel->delete($this->id, $this->fetched_row['parent_id']);
1077 $mod = strtolower($this->parent_type);
1078 $rel = array_key_exists($mod, $this->field_defs) ? $mod : $mod . "_activities_emails"; //Custom modules rel name
1079 if($this->load_relationship($rel) ) {
1080 $this->$rel->add($this->parent_id);
1084 $GLOBALS['log']->debug('-------------------------------> Email save() done');
1088 * Helper function to save temporary attachments assocaited to an email as note.
1090 * @param string $filename
1091 * @param string $fileLocation
1092 * @param string $mimeType
1094 function saveTempNoteAttachments($filename,$fileLocation, $mimeType)
1096 $tmpNote = new Note();
1097 $tmpNote->id = create_guid();
1098 $tmpNote->new_with_id = true;
1099 $tmpNote->parent_id = $this->id;
1100 $tmpNote->parent_type = $this->module_dir;
1101 $tmpNote->name = $filename;
1102 $tmpNote->filename = $filename;
1103 $tmpNote->file_mime_type = $mimeType;
1104 $noteFile = "upload://{$tmpNote->id}";
1105 if(!copy($fileLocation, $noteFile)) {
1106 $GLOBALS['log']->fatal("EMAIL 2.0: could not copy SugarDocument revision file $fileLocation => $noteFile");
1111 * Handles normalization of Email Addressess
1113 function saveEmailAddresses() {
1114 // from, single address
1115 $fromId = $this->emailAddress->getEmailGUID(from_html($this->from_addr));
1116 if(!empty($fromId)){
1117 $this->linkEmailToAddress($fromId, 'from');
1121 $replace = array(",",";");
1122 $toaddrs = str_replace($replace, "::", from_html($this->to_addrs));
1123 $exToAddrs = explode("::", $toaddrs);
1125 if(!empty($exToAddrs)) {
1126 foreach($exToAddrs as $toaddr) {
1127 $toaddr = trim($toaddr);
1128 if(!empty($toaddr)) {
1129 $toId = $this->emailAddress->getEmailGUID($toaddr);
1130 $this->linkEmailToAddress($toId, 'to');
1136 $ccAddrs = str_replace($replace, "::", from_html($this->cc_addrs));
1137 $exccAddrs = explode("::", $ccAddrs);
1139 if(!empty($exccAddrs)) {
1140 foreach($exccAddrs as $ccAddr) {
1141 $ccAddr = trim($ccAddr);
1142 if(!empty($ccAddr)) {
1143 $ccId = $this->emailAddress->getEmailGUID($ccAddr);
1144 $this->linkEmailToAddress($ccId, 'cc');
1150 $bccAddrs = str_replace($replace, "::", from_html($this->bcc_addrs));
1151 $exbccAddrs = explode("::", $bccAddrs);
1152 if(!empty($exbccAddrs)) {
1153 foreach($exbccAddrs as $bccAddr) {
1154 $bccAddr = trim($bccAddr);
1155 if(!empty($bccAddr)) {
1156 $bccId = $this->emailAddress->getEmailGUID($bccAddr);
1157 $this->linkEmailToAddress($bccId, 'bcc');
1163 function linkEmailToAddress($id, $type) {
1164 // TODO: make this update?
1165 $q1 = "SELECT * FROM emails_email_addr_rel WHERE email_id = '{$this->id}' AND email_address_id = '{$id}' AND address_type = '{$type}' AND deleted = 0";
1166 $r1 = $this->db->query($q1);
1167 $a1 = $this->db->fetchByAssoc($r1);
1169 if(!empty($a1) && !empty($a1['id'])) {
1172 $guid = create_guid();
1173 $q2 = "INSERT INTO emails_email_addr_rel VALUES('{$guid}', '{$this->id}', '{$type}', '{$id}', 0)";
1174 $r2 = $this->db->query($q2);
1180 protected $email_to_text = array(
1182 "description" => "description",
1183 "description_html" => "description_html",
1184 "raw_source" => "raw_source",
1185 "from_addr" => "from_addr_name",
1186 "reply_to_addr" => "reply_to_addr",
1187 "to_addrs" => "to_addrs_names",
1188 "cc_addrs" => "cc_addrs_names",
1189 "bcc_addrs" => "bcc_addrs_names",
1192 function cleanEmails($emails)
1194 if(empty($emails)) return '';
1195 $emails = str_replace(array(",",";"), "::", from_html($emails));
1196 $addrs = explode("::", $emails);
1198 foreach($addrs as $addr) {
1199 $parts = $this->emailAddress->splitEmailAddress($addr);
1200 if(empty($parts["email"])) {
1203 if(!empty($parts["name"])) {
1204 $res[] = "{$parts['name']} <{$parts['email']}>";
1206 $res[] .= $parts["email"];
1209 return join(", ", $res);
1212 protected function saveEmailText()
1214 $text = SugarModule::get("EmailText")->loadBean();
1215 foreach($this->email_to_text as $textfield=>$mailfield) {
1216 $text->$textfield = $this->$mailfield;
1218 $text->email_id = $this->id;
1219 if(!$this->new_with_id) {
1220 $this->db->update($text);
1222 $this->db->insert($text);
1226 ///////////////////////////////////////////////////////////////////////////
1228 function retrieve($id, $encoded=true, $deleted=true) {
1229 // cn: bug 11915, return SugarBean's retrieve() call bean instead of $this
1230 $ret = parent::retrieve($id, $encoded, $deleted);
1233 $ret->retrieveEmailText();
1234 //$ret->raw_source = SugarCleaner::cleanHtml($ret->raw_source);
1235 $ret->description = to_html($ret->description);
1236 //$ret->description_html = SugarCleaner::cleanHtml($ret->description_html);
1237 $ret->retrieveEmailAddresses();
1239 $ret->date_start = '';
1240 $ret->time_start = '';
1241 $dateSent = explode(' ', $ret->date_sent);
1242 if (!empty($dateSent)) {
1243 $ret->date_start = $dateSent[0];
1244 if ( isset($dateSent[1]) )
1245 $ret->time_start = $dateSent[1];
1248 foreach($ret as $k => $v) {
1257 * Retrieves email addresses from GUIDs
1259 function retrieveEmailAddresses() {
1262 $q = "SELECT email_address, address_type
1263 FROM emails_email_addr_rel eam
1264 JOIN email_addresses ea ON ea.id = eam.email_address_id
1265 WHERE eam.email_id = '{$this->id}' AND eam.deleted=0";
1266 $r = $this->db->query($q);
1268 while($a = $this->db->fetchByAssoc($r)) {
1269 if(!isset($return[$a['address_type']])) {
1270 $return[$a['address_type']] = array();
1272 $return[$a['address_type']][] = $a['email_address'];
1275 if(count($return) > 0) {
1276 if(isset($return['from'])) {
1277 $this->from_addr = implode(", ", $return['from']);
1279 if(isset($return['to'])) {
1280 $this->to_addrs = implode(", ", $return['to']);
1282 if(isset($return['cc'])) {
1283 $this->cc_addrs = implode(", ", $return['cc']);
1285 if(isset($return['bcc'])) {
1286 $this->bcc_addrs = implode(", ", $return['bcc']);
1292 * Handles longtext fields
1294 function retrieveEmailText() {
1295 $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}'";
1296 $r = $this->db->query($q);
1297 $a = $this->db->fetchByAssoc($r, false);
1299 $this->description = $a['description'];
1300 $this->description_html = $a['description_html'];
1301 $this->raw_source = $a['raw_source'];
1302 $this->from_addr_name = $a['from_addr'];
1303 $this->reply_to_addr = $a['reply_to_addr'];
1304 $this->to_addrs_names = $a['to_addrs'];
1305 $this->cc_addrs_names = $a['cc_addrs'];
1306 $this->bcc_addrs_names = $a['bcc_addrs'];
1309 function delete($id='') {
1313 $q = "UPDATE emails SET deleted = 1 WHERE id = '{$id}'";
1314 $qt = "UPDATE emails_text SET deleted = 1 WHERE email_id = '{$id}'";
1315 $r = $this->db->query($q);
1316 $rt = $this->db->query($qt);
1320 * creates the standard "Forward" info at the top of the forwarded message
1323 function getForwardHeader() {
1324 global $mod_strings;
1325 global $current_user;
1327 //$from = str_replace(array(">","<"), array(")","("), $this->from_name);
1328 $from = to_html($this->from_name);
1329 $subject = to_html($this->name);
1330 $ret = "<br /><br />";
1331 $ret .= $this->replyDelimiter."{$mod_strings['LBL_FROM']} {$from}<br />";
1332 $ret .= $this->replyDelimiter."{$mod_strings['LBL_DATE_SENT']} {$this->date_sent}<br />";
1333 $ret .= $this->replyDelimiter."{$mod_strings['LBL_TO']} {$this->to_addrs}<br />";
1334 $ret .= $this->replyDelimiter."{$mod_strings['LBL_CC']} {$this->cc_addrs}<br />";
1335 $ret .= $this->replyDelimiter."{$mod_strings['LBL_SUBJECT']} {$subject}<br />";
1336 $ret .= $this->replyDelimiter."<br />";
1339 //return from_html($ret);
1343 * retrieves Notes that belong to this Email and stuffs them into the "attachments" attribute
1345 function getNotes($id, $duplicate=false) {
1346 if(!class_exists('Note')) {
1350 $exRemoved = array();
1351 if(isset($_REQUEST['removeAttachment'])) {
1352 $exRemoved = explode('::', $_REQUEST['removeAttachment']);
1355 $noteArray = array();
1356 $q = "SELECT id FROM notes WHERE parent_id = '".$id."'";
1357 $r = $this->db->query($q);
1359 while($a = $this->db->fetchByAssoc($r)) {
1360 if(!in_array($a['id'], $exRemoved)) {
1362 $note->retrieve($a['id']);
1364 // duplicate actual file when creating forwards
1366 if(!class_exists('UploadFile')) {
1367 require_once('include/upload_file.php');
1369 // save a brand new Note
1370 $noteDupe->id = create_guid();
1371 $noteDupe->new_with_id = true;
1372 $noteDupe->parent_id = $this->id;
1373 $noteDupe->parent_type = $this->module_dir;
1375 $noteFile = new UploadFile();
1376 $noteFile->duplicate_file($a['id'], $note->id, $note->filename);
1380 // add Note to attachments array
1381 $this->attachments[] = $note;
1387 * creates the standard "Reply" info at the top of the forwarded message
1390 function getReplyHeader() {
1391 global $mod_strings;
1392 global $current_user;
1394 $from = str_replace(array(">","<", ">","<"), array(")","(",")","("), $this->from_name);
1395 $ret = "<br>{$mod_strings['LBL_REPLY_HEADER_1']} {$this->date_start}, {$this->time_start}, {$from} {$mod_strings['LBL_REPLY_HEADER_2']}";
1397 return from_html($ret);
1401 * Quotes plain-text email text
1402 * @param string $text
1405 function quotePlainTextEmail($text) {
1409 $desc = nl2br(trim($text));
1410 $exDesc = explode('<br />', $desc);
1412 foreach($exDesc as $k => $line) {
1413 $quoted .= '> '.trim($line)."\r";
1420 * "quotes" (i.e., "> my text yadda" the HTML part of an email
1421 * @param string $text HTML text to quote
1424 function quoteHtmlEmail($text) {
1425 $text = trim(from_html($text));
1430 $out = "<div style='border-left:1px solid #00c; padding:5px; margin-left:10px;'>{$text}</div>";
1436 * "quotes" (i.e., "> my text yadda" the HTML part of an email
1437 * @param string $text HTML text to quote
1440 function quoteHtmlEmailForNewEmailUI($text) {
1441 $text = trim($text);
1446 $text = str_replace("\n", "<BR/>", $text);
1447 $out = "<div style='border-left:1px solid #00c; padding:5px; margin-left:10px;'>{$text}</div>";
1453 * Ensures that the user is able to send outbound emails
1455 function check_email_settings() {
1456 global $current_user;
1458 $mail_fromaddress = $current_user->emailAddress->getPrimaryAddress($current_user);
1459 $replyToName = $current_user->getPreference('mail_fromname');
1460 $mail_fromname = (!empty($replyToName)) ? $current_user->getPreference('mail_fromname') : $current_user->full_name;
1462 if(empty($mail_fromaddress)) {
1465 if(empty($mail_fromname)) {
1469 $send_type = $current_user->getPreference('mail_sendtype') ;
1470 if (!empty($send_type) && $send_type == "SMTP") {
1471 $mail_smtpserver = $current_user->getPreference('mail_smtpserver');
1472 $mail_smtpport = $current_user->getPreference('mail_smtpport');
1473 $mail_smtpauth_req = $current_user->getPreference('mail_smtpauth_req');
1474 $mail_smtpuser = $current_user->getPreference('mail_smtpuser');
1475 $mail_smtppass = $current_user->getPreference('mail_smtppass');
1476 if (empty($mail_smtpserver) ||
1477 empty($mail_smtpport) ||
1478 (!empty($mail_smtpauth_req) && ( empty($mail_smtpuser) || empty($mail_smtppass)))
1487 * outputs JS to set fields in the MassUpdate form in the "My Inbox" view
1489 function js_set_archived() {
1490 global $mod_strings;
1492 <script type="text/javascript" language="JavaScript"><!-- Begin
1493 function setArchived() {
1494 var form = document.getElementById("MassUpdate");
1495 var status = document.getElementById("mass_status");
1498 for(var i=0; i < form.elements.length; i++) {
1499 if(form.elements[i].name == "mass[]") {
1500 if(form.elements[i].checked == true) {
1507 var user = document.getElementById("mass_assigned_user_name");
1508 var team = document.getElementById("team");
1511 for(var j=0; j<status.length; j++) {
1512 if(status.options[j].value == "archived") {
1513 status.options[j].selected = true;
1514 status.selectedIndex = j; // for IE
1520 alert("'.$mod_strings['ERR_ARCHIVE_EMAIL'].'");
1524 // End --></script>';
1529 * replaces the javascript in utils.php - more specialized
1531 function u_get_clear_form_js($type='', $group='', $assigned_user_id='') {
1534 $uAssigned_user_id = '';
1536 if(!empty($type)) { $uType = '&type='.$type; }
1537 if(!empty($group)) { $uGroup = '&group='.$group; }
1538 if(!empty($assigned_user_id)) { $uAssigned_user_id = '&assigned_user_id='.$assigned_user_id; }
1541 <script type="text/javascript" language="JavaScript"><!-- Begin
1542 function clear_form(form) {
1543 var newLoc = "index.php?action=" + form.action.value + "&module=" + form.module.value + "&query=true&clear_query=true'.$uType.$uGroup.$uAssigned_user_id.'";
1544 if(typeof(form.advanced) != "undefined"){
1545 newLoc += "&advanced=" + form.advanced.value;
1547 document.location.href= newLoc;
1549 // End --></script>';
1553 function pickOneButton() {
1555 global $mod_strings;
1556 $out = '<div><input title="'.$mod_strings['LBL_BUTTON_GRAB_TITLE'].'"
1558 type="button" name="button"
1559 onClick="window.location=\'index.php?module=Emails&action=Grab\';"
1560 style="margin-bottom:2px"
1561 value=" '.$mod_strings['LBL_BUTTON_GRAB'].' "></div>';
1566 * Determines what Editor (HTML or Plain-text) the current_user uses;
1567 * @return string Editor type
1569 function getUserEditorPreference() {
1570 global $sugar_config;
1571 global $current_user;
1575 if(!isset($sugar_config['email_default_editor'])) {
1576 $sugar_config = $current_user->setDefaultsInConfig();
1579 $userEditor = $current_user->getPreference('email_editor_option');
1580 $systemEditor = $sugar_config['email_default_editor'];
1582 if($userEditor != '') {
1583 $editor = $userEditor;
1585 $editor = $systemEditor;
1592 * takes the mess we pass from EditView and tries to create some kind of order
1593 * @param array addrs
1594 * @param array addrs_ids (from contacts)
1595 * @param array addrs_names (from contacts);
1596 * @param array addrs_emails (from contacts);
1597 * @return array Parsed assoc array to feed to PHPMailer
1599 function parse_addrs($addrs, $addrs_ids, $addrs_names, $addrs_emails) {
1600 // cn: bug 9406 - enable commas to separate email addresses
1601 $addrs = str_replace(",", ";", $addrs);
1603 $ltgt = array('<','>');
1604 $gtlt = array('<','>');
1607 $addrs = str_replace($ltgt, '', $addrs);
1608 $addrs_arr = explode(";",$addrs);
1609 $addrs_arr = $this->remove_empty_fields($addrs_arr);
1610 $addrs_ids_arr = explode(";",$addrs_ids);
1611 $addrs_ids_arr = $this->remove_empty_fields($addrs_ids_arr);
1612 $addrs_emails_arr = explode(";",$addrs_emails);
1613 $addrs_emails_arr = $this->remove_empty_fields($addrs_emails_arr);
1614 $addrs_names_arr = explode(";",$addrs_names);
1615 $addrs_names_arr = $this->remove_empty_fields($addrs_names_arr);
1617 ///////////////////////////////////////////////////////////////////////
1618 //// HANDLE EMAILS HAND-WRITTEN
1619 $contactRecipients = array();
1620 $knownEmails = array();
1622 foreach($addrs_arr as $i => $v) {
1624 continue; // skip any "blanks" - will always have 1
1626 $recipient = array();
1628 //// get the email to see if we're dealing with a dupe
1629 //// what crappy coding
1630 preg_match("/[A-Z0-9._%-\']+@[A-Z0-9.-]+\.[A-Z]{2,}/i",$v, $match);
1633 if(!empty($match[0]) && !in_array(trim($match[0]), $knownEmails)) {
1634 $knownEmails[] = $match[0];
1635 $recipient['email'] = $match[0];
1637 //// handle the Display name
1638 $display = trim(str_replace($match[0], '', $v));
1640 //// only trigger a "displayName" <email@address> when necessary
1641 if(isset($addrs_names_arr[$i])){
1642 $recipient['display'] = $addrs_names_arr[$i];
1644 else if(!empty($display)) {
1645 $recipient['display'] = $display;
1647 if(isset($addrs_ids_arr[$i]) && $addrs_emails_arr[$i] == $match[0]){
1648 $recipient['contact_id'] = $addrs_ids_arr[$i];
1650 $return[] = $recipient;
1657 function remove_empty_fields(&$arr) {
1660 foreach($arr as $field) {
1661 $field = trim($field);
1665 array_push($newarr,$field);
1671 * handles attachments of various kinds when sending email
1673 function handleAttachments() {
1678 global $mod_strings;
1680 ///////////////////////////////////////////////////////////////////////////
1681 //// ATTACHMENTS FROM DRAFTS
1682 if(($this->type == 'out' || $this->type == 'draft') && $this->status == 'draft' && isset($_REQUEST['record'])) {
1683 $this->getNotes($_REQUEST['record']); // cn: get notes from OLD email for use in new email
1685 //// END ATTACHMENTS FROM DRAFTS
1686 ///////////////////////////////////////////////////////////////////////////
1688 ///////////////////////////////////////////////////////////////////////////
1689 //// ATTACHMENTS FROM FORWARDS
1690 // Bug 8034 Jenny - Need the check for type 'draft' here to handle cases where we want to save
1691 // forwarded messages as drafts. We still need to save the original message's attachments.
1692 if(($this->type == 'out' || $this->type == 'draft') &&
1693 isset($_REQUEST['origType']) && $_REQUEST['origType'] == 'forward' &&
1694 isset($_REQUEST['return_id']) && !empty($_REQUEST['return_id'])
1696 $this->getNotes($_REQUEST['return_id'], true);
1699 // cn: bug 8034 - attachments from forward/replies lost when saving in draft
1700 if(isset($_REQUEST['prior_attachments']) && !empty($_REQUEST['prior_attachments']) && $this->new_with_id == true) {
1701 $exIds = explode(",", $_REQUEST['prior_attachments']);
1702 if(!isset($_REQUEST['template_attachment'])) {
1703 $_REQUEST['template_attachment'] = array();
1705 $_REQUEST['template_attachment'] = array_merge($_REQUEST['template_attachment'], $exIds);
1707 //// END ATTACHMENTS FROM FORWARDS
1708 ///////////////////////////////////////////////////////////////////////////
1710 ///////////////////////////////////////////////////////////////////////////
1711 //// ATTACHMENTS FROM TEMPLATES
1712 // to preserve individual email integrity, we must dupe Notes and associated files
1713 // for each outbound email - good for integrity, bad for filespace
1714 if(isset($_REQUEST['template_attachment']) && !empty($_REQUEST['template_attachment'])) {
1715 $removeArr = array();
1716 $noteArray = array();
1718 if(isset($_REQUEST['temp_remove_attachment']) && !empty($_REQUEST['temp_remove_attachment'])) {
1719 $removeArr = $_REQUEST['temp_remove_attachment'];
1723 foreach($_REQUEST['template_attachment'] as $noteId) {
1724 if(in_array($noteId, $removeArr)) {
1727 $noteTemplate = new Note();
1728 $noteTemplate->retrieve($noteId);
1729 $noteTemplate->id = create_guid();
1730 $noteTemplate->new_with_id = true; // duplicating the note with files
1731 $noteTemplate->parent_id = $this->id;
1732 $noteTemplate->parent_type = $this->module_dir;
1733 $noteTemplate->date_entered = '';
1734 $noteTemplate->save();
1736 $noteFile = new UploadFile();
1737 $noteFile->duplicate_file($noteId, $noteTemplate->id, $noteTemplate->filename);
1738 $noteArray[] = $noteTemplate;
1740 $this->attachments = array_merge($this->attachments, $noteArray);
1742 //// END ATTACHMENTS FROM TEMPLATES
1743 ///////////////////////////////////////////////////////////////////////////
1745 ///////////////////////////////////////////////////////////////////////////
1746 //// ADDING NEW ATTACHMENTS
1747 $max_files_upload = 10;
1748 // Jenny - Bug 8211 Since attachments for drafts have already been processed,
1749 // we don't need to re-process them.
1750 if($this->status != "draft") {
1751 $notes_list = array();
1752 if(!empty($this->id) && !$this->new_with_id) {
1754 $where = "notes.parent_id='{$this->id}'";
1755 $notes_list = $note->get_full_list("", $where, true);
1757 $this->attachments = array_merge($this->attachments, $notes_list);
1759 // cn: Bug 5995 - rudimentary error checking
1760 $filesError = array(
1761 0 => 'UPLOAD_ERR_OK - There is no error, the file uploaded with success.',
1762 1 => 'UPLOAD_ERR_INI_SIZE - The uploaded file exceeds the upload_max_filesize directive in php.ini.',
1763 2 => 'UPLOAD_ERR_FORM_SIZE - The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.',
1764 3 => 'UPLOAD_ERR_PARTIAL - The uploaded file was only partially uploaded.',
1765 4 => 'UPLOAD_ERR_NO_FILE - No file was uploaded.',
1766 5 => 'UNKNOWN ERROR',
1767 6 => 'UPLOAD_ERR_NO_TMP_DIR - Missing a temporary folder. Introduced in PHP 4.3.10 and PHP 5.0.3.',
1768 7 => 'UPLOAD_ERR_CANT_WRITE - Failed to write file to disk. Introduced in PHP 5.1.0.',
1771 for($i = 0; $i < $max_files_upload; $i++) {
1772 // cn: Bug 5995 - rudimentary error checking
1773 if (!isset($_FILES["email_attachment{$i}"])) {
1774 $GLOBALS['log']->debug("Email Attachment {$i} does not exist.");
1777 if($_FILES['email_attachment'.$i]['error'] != 0 && $_FILES['email_attachment'.$i]['error'] != 4) {
1778 $GLOBALS['log']->debug('Email Attachment could not be attach due to error: '.$filesError[$_FILES['email_attachment'.$i]['error']]);
1783 $note->parent_id = $this->id;
1784 $note->parent_type = $this->module_dir;
1785 $upload_file = new UploadFile('email_attachment'.$i);
1787 if(empty($upload_file)) {
1791 if(isset($_FILES['email_attachment'.$i]) && $upload_file->confirm_upload()) {
1792 $note->filename = $upload_file->get_stored_file_name();
1793 $note->file = $upload_file;
1794 $note->name = $mod_strings['LBL_EMAIL_ATTACHMENT'].': '.$note->file->original_file_name;
1796 $this->attachments[] = $note;
1800 $this->saved_attachments = array();
1801 foreach($this->attachments as $note) {
1802 if(!empty($note->id)) {
1803 array_push($this->saved_attachments, $note);
1806 $note->parent_id = $this->id;
1807 $note->parent_type = 'Emails';
1808 $note->file_mime_type = $note->file->mime_type;
1809 $note_id = $note->save();
1811 $this->saved_attachments[] = $note;
1813 $note->id = $note_id;
1814 $note->file->final_move($note->id);
1816 //// END NEW ATTACHMENTS
1817 ///////////////////////////////////////////////////////////////////////////
1819 ///////////////////////////////////////////////////////////////////////////
1820 //// ATTACHMENTS FROM DOCUMENTS
1821 for($i=0; $i<10; $i++) {
1822 if(isset($_REQUEST['documentId'.$i]) && !empty($_REQUEST['documentId'.$i])) {
1823 $doc = new Document();
1824 $docRev = new DocumentRevision();
1825 $docNote = new Note();
1826 $noteFile = new UploadFile();
1828 $doc->retrieve($_REQUEST['documentId'.$i]);
1829 $docRev->retrieve($doc->document_revision_id);
1831 $this->saved_attachments[] = $docRev;
1833 // cn: bug 9723 - Emails with documents send GUID instead of Doc name
1834 $docNote->name = $docRev->getDocumentRevisionNameForDisplay();
1835 $docNote->filename = $docRev->filename;
1836 $docNote->description = $doc->description;
1837 $docNote->parent_id = $this->id;
1838 $docNote->parent_type = 'Emails';
1839 $docNote->file_mime_type = $docRev->file_mime_type;
1840 $docId = $docNote = $docNote->save();
1842 $noteFile->duplicate_file($docRev->id, $docId, $docRev->filename);
1846 //// END ATTACHMENTS FROM DOCUMENTS
1847 ///////////////////////////////////////////////////////////////////////////
1849 ///////////////////////////////////////////////////////////////////////////
1850 //// REMOVE ATTACHMENTS
1851 if(isset($_REQUEST['remove_attachment']) && !empty($_REQUEST['remove_attachment'])) {
1852 foreach($_REQUEST['remove_attachment'] as $noteId) {
1853 $q = 'UPDATE notes SET deleted = 1 WHERE id = \''.$noteId.'\'';
1854 $this->db->query($q);
1858 //this will remove attachments that have been selected to be removed from drafts.
1859 if(isset($_REQUEST['removeAttachment']) && !empty($_REQUEST['removeAttachment'])) {
1860 $exRemoved = explode('::', $_REQUEST['removeAttachment']);
1861 foreach($exRemoved as $noteId) {
1862 $q = 'UPDATE notes SET deleted = 1 WHERE id = \''.$noteId.'\'';
1863 $this->db->query($q);
1866 //// END REMOVE ATTACHMENTS
1867 ///////////////////////////////////////////////////////////////////////////
1872 * Determines if an email body (HTML or Plain) has a User signature already in the content
1873 * @param array Array of signatures
1876 function hasSignatureInBody($sig) {
1877 // strpos can't handle line breaks - normalize
1878 $html = $this->removeAllNewlines($this->description_html);
1879 $htmlSig = $this->removeAllNewlines($sig['signature_html']);
1880 $plain = $this->removeAllNewlines($this->description);
1881 $plainSig = $this->removeAllNewlines($sig['signature']);
1883 // cn: bug 11621 - empty sig triggers notice error
1884 if(!empty($htmlSig) && false !== strpos($html, $htmlSig)) {
1886 } elseif(!empty($plainSig) && false !== strpos($plain, $plainSig)) {
1895 * @param string String to be normalized
1898 function removeAllNewlines($str) {
1899 $bad = array("\r\n", "\n\r", "\n", "\r");
1900 $good = array('', '', '', '');
1902 return str_replace($bad, $good, strip_tags(br2nl(from_html($str))));
1908 * Set navigation anchors to aid DetailView record navigation (VCR buttons)
1909 * @param string uri The URI from the referring page (always ListView)
1910 * @return array start Array of the URI broken down with a special "current_view" for My Inbox Navs
1912 function getStartPage($uri) {
1913 if(strpos($uri, '&')) { // "&" to ensure that we can explode the GET vars - else we're gonna trigger a Notice error
1914 $serial = substr($uri, (strpos($uri, '?')+1), strlen($uri));
1915 $exUri = explode('&', $serial);
1916 $start = array('module' => '', 'action' => '', 'group' => '', 'record' => '', 'type' => '');
1918 foreach($exUri as $k => $pair) {
1919 $exPair = explode('=', $pair);
1920 $start[$exPair[0]] = $exPair[1];
1923 // specific views for current_user
1924 if(isset($start['assigned_user_id'])) {
1925 $start['current_view'] = "{$start['action']}&module={$start['module']}&assigned_user_id={$start['assigned_user_id']}&type={$start['type']}";
1935 * preps SMTP info for email transmission
1936 * @param object mail SugarPHPMailer object
1937 * @param string mailer_id
1938 * @param string ieId
1939 * @return object mail SugarPHPMailer object
1941 function setMailer($mail, $mailer_id='', $ieId='') {
1942 global $current_user;
1944 require_once("include/OutboundEmail/OutboundEmail.php");
1945 $oe = new OutboundEmail();
1946 $oe = $oe->getInboundMailerSettings($current_user, $mailer_id, $ieId);
1948 // ssl or tcp - keeping outside isSMTP b/c a default may inadvertantly set ssl://
1949 $mail->protocol = ($oe->mail_smtpssl) ? "ssl://" : "tcp://";
1950 if($oe->mail_sendtype == "SMTP")
1952 //Set mail send type information
1953 $mail->Mailer = "smtp";
1954 $mail->Host = $oe->mail_smtpserver;
1955 $mail->Port = $oe->mail_smtpport;
1956 if ($oe->mail_smtpssl == 1) {
1957 $mail->SMTPSecure = 'ssl';
1959 if ($oe->mail_smtpssl == 2) {
1960 $mail->SMTPSecure = 'tls';
1963 if($oe->mail_smtpauth_req) {
1964 $mail->SMTPAuth = TRUE;
1965 $mail->Username = $oe->mail_smtpuser;
1966 $mail->Password = $oe->mail_smtppass;
1970 $mail->Mailer = "sendmail";
1977 * preps SugarPHPMailer object for HTML or Plain text sends
1978 * @param SugarPHPMailer $mail SugarPHPMailer instance
1980 function handleBody($mail) {
1981 global $current_user;
1982 global $sugar_config;
1983 ///////////////////////////////////////////////////////////////////////
1984 //// HANDLE EMAIL FORMAT PREFERENCE
1985 // the if() below is HIGHLY dependent on the Javascript unchecking the Send HTML Email box
1987 if( (isset($_REQUEST['setEditor']) /* from Email EditView navigation */
1988 && $_REQUEST['setEditor'] == 1
1989 && trim($_REQUEST['description_html']) != '')
1990 || trim($this->description_html) != '' /* from email templates */
1991 && $current_user->getPreference('email_editor_option', 'global') !== 'plain' //user preference is not set to plain text
1993 $this->handleBodyInHTMLformat($mail);
1996 $this->description_html = '';
1997 $mail->IsHTML(false);
1998 $plainText = from_html($this->description);
1999 $plainText = str_replace(" ", " ", $plainText);
2000 $plainText = str_replace("</p>", "</p><br />", $plainText);
2001 $plainText = strip_tags(br2nl($plainText));
2002 $plainText = str_replace("&", "&", $plainText);
2003 $plainText = str_replace("'", "'", $plainText);
2004 $mail->Body = wordwrap($plainText, 996);
2005 $mail->Body = $this->decodeDuringSend($mail->Body);
2006 $this->description = $mail->Body;
2009 // wp: if plain text version has lines greater than 998, use base64 encoding
2010 foreach(explode("\n", ($mail->ContentType == "text/html") ? $mail->AltBody : $mail->Body) as $line) {
2011 if(strlen($line) > 998) {
2012 $mail->Encoding = 'base64';
2016 //// HANDLE EMAIL FORMAT PREFERENCE
2017 ///////////////////////////////////////////////////////////////////////
2023 * Retrieve function from handlebody() to unit test easily
2024 * @param SugarPHPMailer $mail SugarPHPMailer instance
2025 * @return formatted $mail body
2027 function handleBodyInHTMLformat($mail) {
2028 global $sugar_config;
2029 // wp: if body is html, then insert new lines at 996 characters. no effect on client side
2030 // due to RFC 2822 which limits email lines to 998
2031 $mail->IsHTML(true);
2032 $body = from_html(wordwrap($this->description_html, 996));
2033 $mail->Body = $body;
2036 // new plan is to use the selected type (html or plain) to fill the other
2037 $plainText = from_html($this->description_html);
2038 $plainText = strip_tags(br2nl($plainText));
2039 $mail->AltBody = $plainText;
2040 $this->description = $plainText;
2042 $mail->replaceImageByRegex("(?:{$sugar_config['site_url']})?/?cache/images/", sugar_cached("images/"));
2044 //Replace any embeded images using the secure entryPoint for src url.
2045 $mail->replaceImageByRegex("(?:{$sugar_config['site_url']})?/?index.php[?]entryPoint=download&(?:amp;)?[^\"]+?id=", "upload://", true);
2047 $mail->Body = from_html($mail->Body);
2052 * @return bool True on success
2055 global $mod_strings,$app_strings;
2056 global $current_user;
2057 global $sugar_config;
2059 $OBCharset = $locale->getPrecedentPreference('default_email_charset');
2060 $mail = new SugarPHPMailer();
2062 foreach ($this->to_addrs_arr as $addr_arr) {
2063 if ( empty($addr_arr['display'])) {
2064 $mail->AddAddress($addr_arr['email'], "");
2066 $mail->AddAddress($addr_arr['email'],$locale->translateCharsetMIME(trim( $addr_arr['display']), 'UTF-8', $OBCharset));
2069 foreach ($this->cc_addrs_arr as $addr_arr) {
2070 if ( empty($addr_arr['display'])) {
2071 $mail->AddCC($addr_arr['email'], "");
2073 $mail->AddCC($addr_arr['email'],$locale->translateCharsetMIME(trim($addr_arr['display']), 'UTF-8', $OBCharset));
2077 foreach ($this->bcc_addrs_arr as $addr_arr) {
2078 if ( empty($addr_arr['display'])) {
2079 $mail->AddBCC($addr_arr['email'], "");
2081 $mail->AddBCC($addr_arr['email'],$locale->translateCharsetMIME(trim($addr_arr['display']), 'UTF-8', $OBCharset));
2085 $mail = $this->setMailer($mail);
2088 if(!empty($this->from_addr)) {
2089 $mail->From = $this->from_addr;
2091 $mail->From = $current_user->getPreference('mail_fromaddress');
2092 $this->from_addr = $mail->From;
2095 if(!empty($this->from_name)) {
2096 $mail->FromName = $this->from_name;
2098 $mail->FromName = $current_user->getPreference('mail_fromname');
2099 $this->from_name = $mail->FromName;
2102 //Reply to information for case create and autoreply.
2103 if(!empty($this->reply_to_name)) {
2104 $ReplyToName = $this->reply_to_name;
2106 $ReplyToName = $mail->FromName;
2108 if(!empty($this->reply_to_addr)) {
2109 $ReplyToAddr = $this->reply_to_addr;
2111 $ReplyToAddr = $mail->From;
2113 $mail->Sender = $mail->From; /* set Return-Path field in header to reduce spam score in emails sent via Sugar's Email module */
2114 $mail->AddReplyTo($ReplyToAddr,$locale->translateCharsetMIME(trim($ReplyToName), 'UTF-8', $OBCharset));
2116 //$mail->Subject = html_entity_decode($this->name, ENT_QUOTES, 'UTF-8');
2117 $mail->Subject = $this->name;
2119 ///////////////////////////////////////////////////////////////////////
2121 foreach($this->saved_attachments as $note) {
2122 $mime_type = 'text/plain';
2123 if($note->object_name == 'Note') {
2124 if(!empty($note->file->temp_file_location) && is_file($note->file->temp_file_location)) { // brandy-new file upload/attachment
2125 $file_location = "upload://$note->id";
2126 $filename = $note->file->original_file_name;
2127 $mime_type = $note->file->mime_type;
2128 } else { // attachment coming from template/forward
2129 $file_location = "upload://{$note->id}";
2130 // cn: bug 9723 - documents from EmailTemplates sent with Doc Name, not file name.
2131 $filename = !empty($note->filename) ? $note->filename : $note->name;
2132 $mime_type = $note->file_mime_type;
2134 } elseif($note->object_name == 'DocumentRevision') { // from Documents
2135 $filePathName = $note->id;
2136 // cn: bug 9723 - Emails with documents send GUID instead of Doc name
2137 $filename = $note->getDocumentRevisionNameForDisplay();
2138 $file_location = "upload://$note->id";
2139 $mime_type = $note->file_mime_type;
2142 // strip out the "Email attachment label if exists
2143 $filename = str_replace($mod_strings['LBL_EMAIL_ATTACHMENT'].': ', '', $filename);
2144 $file_ext = pathinfo($filename, PATHINFO_EXTENSION);
2145 //is attachment in our list of bad files extensions? If so, append .txt to file location
2146 //check to see if this is a file with extension located in "badext"
2147 foreach($sugar_config['upload_badext'] as $badExt) {
2148 if(strtolower($file_ext) == strtolower($badExt)) {
2149 //if found, then append with .txt to filename and break out of lookup
2150 //this will make sure that the file goes out with right extension, but is stored
2152 $file_location = $file_location . ".txt";
2153 break; // no need to look for more
2156 $mail->AddAttachment($file_location,$locale->translateCharsetMIME(trim($filename), 'UTF-8', $OBCharset), 'base64', $mime_type);
2159 if($note->embed_flag == true) {
2161 $mail->AddEmbeddedImage($file_location, $cid, $filename, 'base64',$mime_type);
2164 //// END ATTACHMENTS
2165 ///////////////////////////////////////////////////////////////////////
2167 $mail = $this->handleBody($mail);
2169 $GLOBALS['log']->debug('Email sending --------------------- ');
2171 ///////////////////////////////////////////////////////////////////////
2172 //// I18N TRANSLATION
2173 $mail->prepForOutbound();
2174 //// END I18N TRANSLATION
2175 ///////////////////////////////////////////////////////////////////////
2178 ///////////////////////////////////////////////////////////////////
2179 //// INBOUND EMAIL HANDLING
2181 if(!empty($_REQUEST['inbound_email_id'])) {
2182 $ieMail = new Email();
2183 $ieMail->retrieve($_REQUEST['inbound_email_id']);
2184 $ieMail->status = 'replied';
2187 $GLOBALS['log']->debug(' --------------------- buh bye -- sent successful');
2188 //// END INBOUND EMAIL HANDLING
2189 ///////////////////////////////////////////////////////////////////
2192 $GLOBALS['log']->debug($app_strings['LBL_EMAIL_ERROR_PREPEND'].$mail->ErrorInfo);
2197 function listviewACLHelper(){
2198 $array_assign = parent::listviewACLHelper();
2200 if(!empty($this->parent_name)){
2202 if(!empty($this->parent_name_owner)){
2203 global $current_user;
2204 $is_owner = $current_user->id == $this->parent_name_owner;
2207 if(!ACLController::moduleSupportsACL($this->parent_type) || ACLController::checkAccess($this->parent_type, 'view', $is_owner)){
2208 $array_assign['PARENT'] = 'a';
2210 $array_assign['PARENT'] = 'span';
2213 if(!empty($this->contact_name)) {
2214 if(!empty($this->contact_name_owner)) {
2215 global $current_user;
2216 $is_owner = $current_user->id == $this->contact_name_owner;
2219 if(ACLController::checkAccess('Contacts', 'view', $is_owner)) {
2220 $array_assign['CONTACT'] = 'a';
2222 $array_assign['CONTACT'] = 'span';
2225 return $array_assign;
2228 function getSystemDefaultEmail() {
2231 $r1 = $this->db->query('SELECT config.value FROM config WHERE name=\'fromaddress\'');
2232 $r2 = $this->db->query('SELECT config.value FROM config WHERE name=\'fromname\'');
2233 $a1 = $this->db->fetchByAssoc($r1);
2234 $a2 = $this->db->fetchByAssoc($r2);
2236 $email['email'] = $a1['value'];
2237 $email['name'] = $a2['value'];
2243 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 if ($return_array) {
2246 return parent::create_new_list_query($order_by, $where,$filter,$params, $show_deleted,$join_type, $return_array,$parentbean, $singleSelect);
2248 $custom_join = $this->getCustomJoin();
2250 $query = "SELECT ".$this->table_name.".*, users.user_name as assigned_user_name\n";
2252 $query .= $custom_join['select'];
2253 $query .= " FROM emails\n";
2254 if ($where != "" && (strpos($where, "contacts.first_name") > 0)) {
2255 $query .= " LEFT JOIN emails_beans ON emails.id = emails_beans.email_id\n";
2258 $query .= " LEFT JOIN users ON emails.assigned_user_id=users.id \n";
2259 if ($where != "" && (strpos($where, "contacts.first_name") > 0)) {
2261 $query .= " JOIN contacts ON contacts.id= emails_beans.bean_id AND emails_beans.bean_module='Contacts' and contacts.deleted=0 \n";
2264 $query .= $custom_join['join'];
2266 if($show_deleted == 0) {
2267 $where_auto = " emails.deleted=0 \n";
2268 }else if($show_deleted == 1){
2269 $where_auto = " emails.deleted=1 \n";
2273 $query .= "WHERE $where AND ".$where_auto;
2275 $query .= "WHERE ".$where_auto;
2278 $query .= " ORDER BY $order_by";
2280 $query .= " ORDER BY date_sent DESC";
2286 function fill_in_additional_list_fields() {
2287 global $timedate, $mod_strings;
2288 $this->fill_in_additional_detail_fields();
2290 $this->link_action = 'DetailView';
2291 ///////////////////////////////////////////////////////////////////////
2292 //populate attachment_image, used to display attachment icon.
2293 $query = "select 1 from notes where notes.parent_id = '$this->id' and notes.deleted = 0";
2294 $result =$this->db->query($query,true," Error filling in additional list fields: ");
2296 $row = $this->db->fetchByAssoc($result);
2297 $this->attachment_image = ($row !=null) ? SugarThemeRegistry::current()->getImage('attachment',"","","") : "";
2300 $this->attachment_image = SugarThemeRegistry::current()->getImage('attachment',"","","",'.gif',translate('LBL_ATTACHMENT', 'Emails'));
2303 ///////////////////////////////////////////////////////////////////////
2304 if(empty($this->contact_id) && !empty($this->parent_id) && !empty($this->parent_type) && $this->parent_type === 'Contacts' && !empty($this->parent_name) ){
2305 $this->contact_id = $this->parent_id;
2306 $this->contact_name = $this->parent_name;
2310 function fill_in_additional_detail_fields() {
2311 global $app_list_strings,$mod_strings;
2312 // Fill in the assigned_user_name
2313 $this->assigned_user_name = get_assigned_user_name($this->assigned_user_id, '');
2314 //if ($this->parent_type == 'Contacts') {
2315 $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 ";
2316 $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";
2317 if(!empty($this->parent_id)){
2318 $query .= " AND contacts.id= '".$this->parent_id."' ";
2319 }else if(!empty($_REQUEST['record'])){
2320 $query .= " AND contacts.id= '".$_REQUEST['record']."' ";
2322 $result =$this->db->query($query,true," Error filling in additional detail fields: ");
2324 // Get the id and the name.
2325 $row = $this->db->fetchByAssoc($result);
2329 $contact = new Contact();
2330 $contact->retrieve($row['id']);
2331 $this->contact_name = $contact->full_name;
2332 $this->contact_phone = $row['phone_work'];
2333 $this->contact_id = $row['id'];
2334 $this->contact_email = $contact->emailAddress->getPrimaryAddress($contact);
2335 $this->contact_name_owner = $row['contact_name_owner'];
2336 $this->contact_name_mod = $row['contact_name_mod'];
2337 $GLOBALS['log']->debug("Call($this->id): contact_name = $this->contact_name");
2338 $GLOBALS['log']->debug("Call($this->id): contact_phone = $this->contact_phone");
2339 $GLOBALS['log']->debug("Call($this->id): contact_id = $this->contact_id");
2340 $GLOBALS['log']->debug("Call($this->id): contact_email1 = $this->contact_email");
2343 $this->contact_name = '';
2344 $this->contact_phone = '';
2345 $this->contact_id = '';
2346 $this->contact_email = '';
2347 $this->contact_name_owner = '';
2348 $this->contact_name_mod = '';
2349 $GLOBALS['log']->debug("Call($this->id): contact_name = $this->contact_name");
2350 $GLOBALS['log']->debug("Call($this->id): contact_phone = $this->contact_phone");
2351 $GLOBALS['log']->debug("Call($this->id): contact_id = $this->contact_id");
2352 $GLOBALS['log']->debug("Call($this->id): contact_email1 = $this->contact_email");
2355 $this->created_by_name = get_assigned_user_name($this->created_by);
2356 $this->modified_by_name = get_assigned_user_name($this->modified_user_id);
2358 $this->link_action = 'DetailView';
2360 if(!empty($this->type)) {
2361 if($this->type == 'out' && $this->status == 'send_error') {
2362 $this->type_name = $mod_strings['LBL_NOT_SENT'];
2364 $this->type_name = $app_list_strings['dom_email_types'][$this->type];
2367 if(($this->type == 'out' && $this->status == 'send_error') || $this->type == 'draft') {
2368 $this->link_action = 'EditView';
2372 //todo this isset( $app_list_strings['dom_email_status'][$this->status]) is hack for 3261.
2373 if(!empty($this->status) && isset( $app_list_strings['dom_email_status'][$this->status])) {
2374 $this->status_name = $app_list_strings['dom_email_status'][$this->status];
2377 if ( empty($this->name ) && empty($_REQUEST['record'])) {
2378 $this->name = $mod_strings['LBL_NO_SUBJECT'];
2381 $this->fill_in_additional_parent_fields();
2386 function create_export_query(&$order_by, &$where)
2388 $contact_required = stristr($where, "contacts");
2389 $custom_join = $this->getCustomJoin(true, true, $where);
2391 if($contact_required) {
2392 $query = "SELECT emails.*, contacts.first_name, contacts.last_name";
2393 $query .= $custom_join['select'];
2395 $query .= " FROM contacts, emails, emails_contacts ";
2396 $where_auto = "emails_contacts.contact_id = contacts.id AND emails_contacts.email_id = emails.id AND emails.deleted=0 AND contacts.deleted=0";
2398 $query = 'SELECT emails.*';
2399 $query .= $custom_join['select'];
2401 $query .= ' FROM emails ';
2402 $where_auto = "emails.deleted=0";
2405 $query .= $custom_join['join'];
2408 $query .= "where $where AND ".$where_auto;
2410 $query .= "where ".$where_auto;
2413 $query .= " ORDER BY $order_by";
2415 $query .= " ORDER BY emails.name";
2419 function get_list_view_data() {
2420 global $app_list_strings;
2422 global $current_user;
2424 global $mod_strings;
2426 $email_fields = $this->get_list_view_array();
2427 $this->retrieveEmailText();
2428 $email_fields['FROM_ADDR'] = $this->from_addr_name;
2429 $mod_strings = return_module_language($GLOBALS['current_language'], 'Emails'); // hard-coding for Home screen ListView
2431 if($this->status != 'replied') {
2432 $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>';
2433 $email_fields['STATUS'] = ($email_fields['REPLY_TO_STATUS'] == 1 ? $mod_strings['LBL_REPLIED'] : $email_fields['STATUS']);
2435 $email_fields['QUICK_REPLY'] = $mod_strings['LBL_REPLIED'];
2437 if(!empty($this->parent_type)) {
2438 $email_fields['PARENT_MODULE'] = $this->parent_type;
2440 switch($this->intent) {
2442 $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>';
2446 $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>';
2450 $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>';
2454 $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>';
2458 $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>';
2471 $email_fields['CREATE_RELATED'] = $this->quickCreateForm();
2477 //BUG 17098 - MFH changed $this->from_addr to $this->to_addrs
2478 $email_fields['CONTACT_NAME'] = empty($this->contact_name) ? '</a>'.$this->trimLongTo($this->to_addrs).'<a>' : $this->contact_name;
2479 $email_fields['CONTACT_ID'] = empty($this->contact_id) ? '' : $this->contact_id;
2480 $email_fields['ATTACHMENT_IMAGE'] = $this->attachment_image;
2481 $email_fields['LINK_ACTION'] = $this->link_action;
2483 if(isset($this->type_name))
2484 $email_fields['TYPE_NAME'] = $this->type_name;
2486 return $email_fields;
2489 function quickCreateForm() {
2490 global $mod_strings, $app_strings, $currentModule, $current_language;
2492 // Coming from the home page via Dashlets
2493 if($currentModule != 'Email')
2494 $mod_strings = return_module_language($current_language, 'Emails');
2495 return $mod_strings['LBL_QUICK_CREATE']." <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>";
2499 * Searches all imported emails and returns the result set as an array.
2502 function searchImportedEmails($sort = '', $direction='')
2504 require_once('include/TimeDate.php');
2506 global $current_user;
2508 global $sugar_config;
2509 global $app_strings;
2511 $emailSettings = $current_user->getPreference('emailSettings', 'Emails');
2512 // cn: default to a low number until user specifies otherwise
2513 if(empty($emailSettings['showNumInList']))
2516 $pageSize = $emailSettings['showNumInList'];
2518 if( isset($_REQUEST['start']) && isset($_REQUEST['limit']) )
2519 $page = ceil($_REQUEST['start'] / $_REQUEST['limit']) + 1;
2523 //Determine sort ordering
2525 //Sort ordering parameters in the request do not coincide with actual column names
2526 //so we need to remap them.
2527 $hrSortLocal = array(
2528 'flagged' => 'type',
2529 'status' => 'reply_to_status',
2530 'from' => 'emails_text.from_addr',
2531 'subject' => 'name',
2532 'date' => 'date_sent',
2533 'AssignedTo' => 'assigned_user_id',
2534 'flagged' => 'flagged'
2537 $sort = !empty($_REQUEST['sort']) ? $this->db->getValidDBName($_REQUEST['sort']) : "";
2538 $direction = !empty($_REQUEST['dir']) && in_array(strtolower($_REQUEST['dir']), array("asc", "desc")) ? $_REQUEST['dir'] : "";
2540 $order = ( !empty($sort) && !empty($direction) ) ? " ORDER BY {$hrSortLocal[$sort]} {$direction}" : "";
2542 //Get our main query.
2543 $fullQuery = $this->_genereateSearchImportedEmailsQuery();
2545 //Perform a count query needed for pagination.
2546 $countQuery = $this->create_list_count_query($fullQuery);
2548 $count_rs = $this->db->query($countQuery, false, 'Error executing count query for imported emails search');
2549 $count_row = $this->db->fetchByAssoc($count_rs);
2550 $total_count = ($count_row != null) ? $count_row['c'] : 0;
2552 $start = ($page - 1) * $pageSize;
2555 $rs = $this->db->limitQuery($fullQuery . $order, $start, $pageSize);
2559 while($a = $this->db->fetchByAssoc($rs)) {
2561 $temp['flagged'] = (is_null($a['flagged']) || $a['flagged'] == '0') ? '' : 1;
2562 $temp['status'] = (is_null($a['reply_to_status']) || $a['reply_to_status'] == '0') ? '' : 1;
2563 $temp['subject'] = $a['name'];
2564 $temp['date'] = $timedate->to_display_date_time($a['date_sent']);
2565 $temp['uid'] = $a['id'];
2566 $temp['ieId'] = $a['mailbox_id'];
2567 $temp['site_url'] = $sugar_config['site_url'];
2568 $temp['seen'] = ($a['status'] == 'unread') ? 0 : 1;
2569 $temp['type'] = $a['type'];
2570 $temp['mbox'] = 'sugar::Emails';
2571 $temp['hasAttach'] = $this->doesImportedEmailHaveAttachment($a['id']);
2572 //To and from addresses may be stored in emails_text, if nothing is found, revert to
2573 //regular email addresses.
2574 $temp['to_addrs'] = preg_replace('/[\x00-\x08\x0B-\x1F]/', '', $a['to_addrs']);
2575 $temp['from'] = preg_replace('/[\x00-\x08\x0B-\x1F]/', '', $a['from_addr']);
2576 if( empty($temp['from']) || empty($temp['to_addrs']) )
2578 //Retrieve email addresses seperatly.
2579 $tmpEmail = new Email();
2580 $tmpEmail->id = $a['id'];
2581 $tmpEmail->retrieveEmailAddresses();
2582 $temp['from'] = $tmpEmail->from_addr;
2583 $temp['to_addrs'] = $tmpEmail->to_addrs;
2589 $metadata = array();
2590 $metadata['totalCount'] = $total_count;
2591 $metadata['out'] = $return;
2597 * Determine if an imported email has an attachment by examining the relationship to notes.
2602 function doesImportedEmailHaveAttachment($id)
2604 $hasAttachment = FALSE;
2605 $query = "SELECT id FROM notes where parent_id='$id' AND parent_type='Emails' AND file_mime_type is not null AND deleted=0";
2606 $rs = $this->db->limitQuery($query, 0, 1);
2607 $row = $this->db->fetchByAssoc($rs);
2608 if( !empty($row['id']) )
2609 $hasAttachment = TRUE;
2611 return (int) $hasAttachment;
2615 * Generate the query used for searching imported emails.
2617 * @return String Query to be executed.
2619 function _genereateSearchImportedEmailsQuery()
2623 $additionalWhereClause = $this->_generateSearchImportWhereClause();
2627 $query['select'] = "emails.id , emails.mailbox_id, emails.name, emails.date_sent, emails.status, emails.type, emails.flagged, emails.reply_to_status,
2628 emails_text.from_addr, emails_text.to_addrs FROM emails ";
2630 $query['joins'] = " JOIN emails_text on emails.id = emails_text.email_id ";
2632 //Handle from and to addr joins
2633 if( !empty($_REQUEST['from_addr']) )
2635 $from_addr = $this->db->quote(strtolower($_REQUEST['from_addr']));
2636 $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
2637 AND er_from.address_type='from' AND emails_text.from_addr LIKE '%" . $from_addr . "%'";
2640 if( !empty($_REQUEST['to_addrs']) )
2642 $to_addrs = $this->db->quote(strtolower($_REQUEST['to_addrs']));
2643 $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
2644 AND er_to.address_type='to' AND ea_to.email_address LIKE '%" . $to_addrs . "%'";
2647 $query['where'] = " WHERE (emails.type= 'inbound' OR emails.type='archived' OR emails.type='out') AND emails.deleted = 0 ";
2648 if( !empty($additionalWhereClause) )
2649 $query['where'] .= "AND $additionalWhereClause";
2651 //If we are explicitly looking for attachments. Do not use a distinct query as the to_addr is defined
2652 //as a text which equals clob in oracle and the distinct query can not be executed correctly.
2653 $addDistinctKeyword = "";
2654 if( !empty($_REQUEST['attachmentsSearch']) && $_REQUEST['attachmentsSearch'] == 1) //1 indicates yes
2655 $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 )";
2656 else if( !empty($_REQUEST['attachmentsSearch']) && $_REQUEST['attachmentsSearch'] == 2 )
2657 $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 )";
2659 $fullQuery = "SELECT " . $query['select'] . " " . $query['joins'] . " " . $query['where'];
2664 * Generate the where clause for searching imported emails.
2667 function _generateSearchImportWhereClause()
2671 //The clear button was removed so if a user removes the asisgned user name, do not process the id.
2672 if( empty($_REQUEST['assigned_user_name']) && !empty($_REQUEST['assigned_user_id']) )
2673 unset($_REQUEST['assigned_user_id']);
2675 $availableSearchParam = array('name' => array('table_name' =>'emails'),
2676 'data_parent_id_search' => array('table_name' =>'emails','db_key' => 'parent_id','opp' => '='),
2677 'assigned_user_id' => array('table_name' => 'emails', 'opp' => '=') );
2679 $additionalWhereClause = array();
2680 foreach ($availableSearchParam as $key => $properties)
2682 if( !empty($_REQUEST[$key]) )
2684 $db_key = isset($properties['db_key']) ? $properties['db_key'] : $key;
2685 $searchValue = $this->db->quote($_REQUEST[$key]);
2687 $opp = isset($properties['opp']) ? $properties['opp'] : 'like';
2689 $searchValue = "%" . $searchValue . "%";
2691 $additionalWhereClause[] = "{$properties['table_name']}.$db_key $opp '$searchValue' ";
2697 $isDateFromSearchSet = !empty($_REQUEST['searchDateFrom']);
2698 $isdateToSearchSet = !empty($_REQUEST['searchDateTo']);
2699 $bothDateRangesSet = $isDateFromSearchSet & $isdateToSearchSet;
2701 //Hanlde date from and to separately
2702 if($bothDateRangesSet)
2704 $dbFormatDateFrom = $timedate->to_db_date($_REQUEST['searchDateFrom'], false);
2705 $dbFormatDateFrom = db_convert("'" . $dbFormatDateFrom . "'",'datetime');
2707 $dbFormatDateTo = $timedate->to_db_date($_REQUEST['searchDateTo'], false);
2708 $dbFormatDateTo = db_convert("'" . $dbFormatDateTo . "'",'datetime');
2710 $additionalWhereClause[] = "( emails.date_sent >= $dbFormatDateFrom AND
2711 emails.date_sent <= $dbFormatDateTo )";
2713 elseif ($isdateToSearchSet)
2715 $dbFormatDateTo = $timedate->to_db_date($_REQUEST['searchDateTo'], false);
2716 $dbFormatDateTo = db_convert("'" . $dbFormatDateTo . "'",'datetime');
2717 $additionalWhereClause[] = "emails.date_sent <= $dbFormatDateTo ";
2719 elseif ($isDateFromSearchSet)
2721 $dbFormatDateFrom = $timedate->to_db_date($_REQUEST['searchDateFrom'], false);
2722 $dbFormatDateFrom = db_convert("'" . $dbFormatDateFrom . "'",'datetime');
2723 $additionalWhereClause[] = "emails.date_sent >= $dbFormatDateFrom ";
2726 $additionalWhereClause = implode(" AND ", $additionalWhereClause);
2728 return $additionalWhereClause;
2734 * takes a long TO: string of emails and returns the first appended by an
2737 function trimLongTo($str) {
2738 if(strpos($str, ',')) {
2739 $exStr = explode(',', $str);
2740 return $exStr[0].'...';
2741 } elseif(strpos($str, ';')) {
2742 $exStr = explode(';', $str);
2743 return $exStr[0].'...';
2749 function get_summary_text() {
2755 function distributionForm($where) {
2756 global $app_list_strings;
2757 global $app_strings;
2758 global $mod_strings;
2760 global $current_user;
2762 $distribution = get_select_options_with_id($app_list_strings['dom_email_distribution'], '');
2763 $_SESSION['distribute_where'] = $where;
2766 $out = '<form name="Distribute" id="Distribute">';
2767 $out .= get_form_header($mod_strings['LBL_DIST_TITLE'], '', false);
2774 <table cellpadding="0" cellspacing="0" width="100%" border="0">
2777 <script type="text/javascript">
2780 function checkDeps(form) {
2784 function mySubmit() {
2785 var assform = document.getElementById("Distribute");
2786 var select = document.getElementById("userSelect");
2787 var assign1 = assform.r1.checked;
2788 var assign2 = assform.r2.checked;
2789 var dist = assform.dm.value;
2793 var warn1 = "'.$mod_strings['LBL_WARN_NO_USERS'].'";
2796 if(assign1 || assign2) {
2801 for(i=0; i<select.options.length; i++) {
2802 if(select.options[i].selected == true) {
2811 warn2 = "'.$mod_strings['LBL_WARN_NO_DIST'].'";
2814 if(assign && users && rules) {
2816 if(document.getElementById("r1").checked) {
2817 var mu = document.getElementById("MassUpdate");
2820 for(i=0; i<mu.elements.length; i++) {
2821 if(mu.elements[i].type == "checkbox" && mu.elements[i].checked && mu.elements[i].name.value != "massall") {
2822 if(grabbed != "") { grabbed += "::"; }
2823 grabbed += mu.elements[i].value;
2826 var formgrab = document.getElementById("grabbed");
2827 formgrab.value = grabbed;
2831 alert("'.$mod_strings['LBL_ASSIGN_WARN'].'" + "\n" + warn1 + "\n" + warn2);
2835 function submitDelete() {
2836 if(document.getElementById("r1").checked) {
2837 var mu = document.getElementById("MassUpdate");
2840 for(i=0; i<mu.elements.length; i++) {
2841 if(mu.elements[i].type == "checkbox" && mu.elements[i].checked && mu.elements[i].name != "massall") {
2842 if(grabbed != "") { grabbed += "::"; }
2843 grabbed += mu.elements[i].value;
2846 var formgrab = document.getElementById("grabbed");
2847 formgrab.value = grabbed;
2850 alert("'.$mod_strings['LBL_MASS_DELETE_ERROR'].'");
2852 document.getElementById("Distribute").submit();
2857 <input type="hidden" name="module" value="Emails">
2858 <input type="hidden" name="action" id="action">
2859 <input type="hidden" name="grabbed" id="grabbed">
2861 <table cellpadding="1" cellspacing="0" width="100%" border="0" class="edit view">
2863 <td scope="col" scope="row" NOWRAP align="center">
2864 '.$mod_strings['LBL_ASSIGN_SELECTED_RESULTS_TO'].' ';
2865 $out .= $this->userSelectTable();
2867 <td scope="col" scope="row" NOWRAP align="left">
2868 '.$mod_strings['LBL_USING_RULES'].'
2869 <select name="distribute_method" id="dm" onChange="checkDeps(this.form);">'.$distribution.'</select>
2878 <td scope="col" width="50%" scope="row" NOWRAP align="right" colspan="2">
2879 <input title="'.$mod_strings['LBL_BUTTON_DISTRIBUTE_TITLE'].'"
2881 class="button" onClick="AjaxObject.detailView.handleAssignmentDialogAssignAction();"
2882 type="button" name="button"
2883 value=" '.$mod_strings['LBL_BUTTON_DISTRIBUTE'].' ">';
2893 function userSelectTable() {
2895 global $mod_strings;
2898 $setTeamUserFunction = '';
2902 $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");
2904 $userTable = '<table cellpadding="0" cellspacing="0" border="0">';
2905 $userTable .= '<tr><td colspan="2"><b>'.$mod_strings['LBL_USER_SELECT'].'</b></td></tr>';
2906 $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>';
2907 $userTable .= '<tr><td colspan="2"><select style="visibility:hidden;" name="users[]" id="userSelect" multiple size="12">';
2909 while($a = $this->db->fetchByAssoc($r)) {
2910 $userTable .= '<option value="'.$a['id'].'" id="'.$a['id'].'">'.$a['first_name'].' '.$a['last_name'].'</option>';
2912 $userTable .= '</select></td></tr>';
2913 $userTable .= '</table>';
2915 $out = '<script type="text/javascript">';
2916 $out .= $setTeamUserFunction;
2918 function setCheckMark() {
2919 var select = document.getElementById("userSelect");
2921 for(i=0 ; i<select.options.length; i++) {
2922 if(select.options[i].selected == true) {
2923 document.getElementById("checkMark").style.display="";
2928 document.getElementById("checkMark").style.display="none";
2932 function showUserSelect() {
2933 var targetTable = document.getElementById("user_select");
2934 targetTable.style.visibility="visible";
2935 var userSelectTable = document.getElementById("userSelect");
2936 userSelectTable.style.visibility="visible";
2939 function hideUserSelect() {
2940 var targetTable = document.getElementById("user_select");
2941 targetTable.style.visibility="hidden";
2942 var userSelectTable = document.getElementById("userSelect");
2943 userSelectTable.style.visibility="hidden";
2946 function toggleAll(toggle) {
2947 if(toggle.checked) {
2952 var form = document.getElementById("userSelect");
2953 for(i=0; i<form.options.length; i++) {
2954 form.options[i].selected = stat;
2960 <span id="showUsersDiv" style="position:relative;">
2961 <a href="#" id="showUsers" onClick="javascript:showUserSelect();">
2962 '.SugarThemeRegistry::current()->getImage('Users', '', null, null, ".gif", $mod_strings['LBL_USERS']).'</a>
2963 <a href="#" id="showUsers" onClick="javascript:showUserSelect();">
2964 <span style="display:none;" id="checkMark">'.SugarThemeRegistry::current()->getImage('check_inline', 'border="0"', null, null, ".gif", $mod_strings['LBL_CHECK_INLINE']).'</span>
2968 <div id="user_select" style="width:200px;position:absolute;left:2;top:2;visibility:hidden;z-index:1000;">
2969 <table cellpadding="0" cellspacing="0" border="0" class="list view">
2971 <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\';">
2972 <a href="#" onClick="javascript:hideUserSelect();">'.SugarThemeRegistry::current()->getImage('close', 'border="0"', null, null, ".gif", $mod_strings['LBL_CLOSE']).'</a>
2973 '.$mod_strings['LBL_USER_SELECT'].'
2977 //<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\';">
2978 $out .= ' <td style="padding:5px" class="oddListRowS1" bgcolor="#fdfdfd" valign="top" align="left" style="left:0;top:0;">
2987 function checkInbox($type) {
2989 global $mod_strings;
2990 $out = '<div><input title="'.$mod_strings['LBL_BUTTON_CHECK_TITLE'].'"
2992 type="button" name="button"
2993 onClick="window.location=\'index.php?module=Emails&action=Check&type='.$type.'\';"
2994 style="margin-bottom:2px"
2995 value=" '.$mod_strings['LBL_BUTTON_CHECK'].' "></div>';
3000 * Guesses Primary Parent id from From: email address. Cascades guesses from Accounts to Contacts to Leads to
3001 * Users. This will not affect the many-to-many relationships already constructed as this is, at best,
3002 * informational linking.
3004 function fillPrimaryParentFields() {
3005 if(empty($this->from_addr))
3008 $GLOBALS['log']->debug("*** Email trying to guess Primary Parent from address [ {$this->from_addr} ]");
3010 $tables = array('accounts');
3012 // loop through types to get hits
3013 foreach($tables as $table) {
3014 $q = "SELECT name, id FROM {$table} WHERE email1 = '{$this->from_addr}' OR email2 = '{$this->from_addr}' AND deleted = 0";
3015 $r = $this->db->query($q);
3016 while($a = $this->db->fetchByAssoc($r)) {
3017 if(!empty($a['name']) && !empty($a['id'])) {
3018 $this->parent_type = ucwords($table);
3019 $this->parent_id = $a['id'];
3020 $this->parent_name = $a['name'];
3028 * Convert reference to inline image (stored as Note) to URL link
3029 * Enter description here ...
3030 * @param string $note ID of the note
3031 * @param string $ext type of the note
3033 public function cid2Link($noteId, $noteType)
3035 if(empty($this->description_html)) return;
3036 list($type, $subtype) = explode('/', $noteType);
3037 if(strtolower($type) != 'image') {
3040 $upload = new UploadFile();
3041 $this->description_html = preg_replace("#class=\"image\" src=\"cid:$noteId\.(.+?)\"#", "class=\"image\" src=\"{$this->imagePrefix}{$noteId}.\\1\"", $this->description_html);
3042 // ensure the image is in the cache
3043 $imgfilename = sugar_cached("images/")."$noteId.".strtolower($subtype);
3044 $src = "upload://$noteId";
3045 if(!file_exists($imgfilename) && file_exists($src)) {
3046 copy($src, $imgfilename);
3051 * Convert all cid: links in this email into URLs
3053 function cids2Links()
3055 if(empty($this->description_html)) return;
3056 $q = "SELECT id, file_mime_type FROM notes WHERE parent_id = '{$this->id}' AND deleted = 0";
3057 $r = $this->db->query($q);
3058 while($a = $this->db->fetchByAssoc($r)) {
3059 $this->cid2Link($a['id'], $a['file_mime_type']);
3065 * Sets the field def for a field to allow null values
3067 * @todo Consider moving to SugarBean to allow other models to set fields to NULL
3068 * @param string $field The field name to modify
3071 public function setFieldNullable($field)
3073 if (isset($this->field_defs[$field]) && is_array($this->field_defs[$field]))
3075 if (empty($this->modifiedFieldDefs[$field]))
3078 isset($this->field_defs[$field]['isnull']) &&
3079 (strtolower($this->field_defs[$field]['isnull']) == 'false' || $this->field_defs[$field]['isnull'] === false)
3082 $this->modifiedFieldDefs[$field]['isnull'] = $this->field_defs[$field]['isnull'];
3083 unset($this->field_defs[$field]['isnull']);
3086 if (isset($this->field_defs[$field]['dbType']) && $this->field_defs[$field]['dbType'] == 'id')
3088 $this->modifiedFieldDefs[$field]['dbType'] = $this->field_defs[$field]['dbType'];
3089 unset($this->field_defs[$field]['dbType']);
3097 * Set the field def back to the way it was prior to modification
3102 public function revertFieldNullable($field)
3104 if (!empty($this->modifiedFieldDefs[$field]) && is_array($this->modifiedFieldDefs[$field]))
3106 foreach ($this->modifiedFieldDefs[$field] as $k => $v)
3108 $this->field_defs[$field][$k] = $v;
3111 unset($this->modifiedFieldDefs[$field]);