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 ********************************************************************************/
38 /*********************************************************************************
41 * Portions created by SugarCRM are Copyright (C) SugarCRM, Inc. All Rights
42 * Reserved. Contributor(s): ______________________________________..
43 *********************************************************************************/
46 require_once("include/JSON.php");
49 class SugarEmailAddress extends SugarBean {
50 var $table_name = 'email_addresses';
51 var $module_name = "EmailAddresses";
52 var $module_dir = 'EmailAddresses';
53 var $object_name = 'EmailAddress';
55 //bug 40068, According to rules in page 6 of http://www.apps.ietf.org/rfc/rfc3696.html#sec-3,
56 //allowed special characters ! # $ % & ' * + - / = ? ^ _ ` . { | } ~ in local part
57 var $regex = "/^(?:['\.\-\+&#!\$\*=\?\^_`\{\}~\/\w]+)@(?:(?:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|\w+(?:[\.-]*\w+)*(?:\.[\w-]{2,})+)\$/";
58 var $disable_custom_fields = true;
61 var $addresses = array(); // array of emails
63 private $stateBeforeWorkflow;
65 public $email_address;
72 function SugarEmailAddress() {
74 $this->index = self::$count;
79 * Legacy email address handling. This is to allow support for SOAP or customizations
81 * @param string $module
83 function handleLegacySave($bean, $prefix = "") {
84 if(!isset($_REQUEST) || !isset($_REQUEST['useEmailWidget'])) {
85 if (empty($this->addresses) || !isset($_REQUEST['massupdate'])) {
86 $this->addresses = array();
87 $optOut = (isset($bean->email_opt_out) && $bean->email_opt_out == "1") ? true : false;
88 $invalid = (isset($bean->invalid_email) && $bean->invalid_email == "1") ? true : false;
91 for($i = 1; $i <= 10; $i++){
93 if(isset($bean->$email) && !empty($bean->$email)){
94 $opt_out_field = $email.'_opt_out';
95 $invalid_field = $email.'_invalid';
96 $field_optOut = (isset($bean->$opt_out_field)) ? $bean->$opt_out_field : $optOut;
97 $field_invalid = (isset($bean->$invalid_field)) ? $bean->$invalid_field : $invalid;
98 $this->addAddress($bean->$email, $isPrimary, false, $field_invalid, $field_optOut);
104 $this->populateAddresses($bean->id, $bean->module_dir, array(),'');
105 if(isset($_REQUEST) && isset($_REQUEST['useEmailWidget'])) {
106 $this->populateLegacyFields($bean);
111 * Fills standard email1 legacy fields
113 * @param string module
116 function handleLegacyRetrieve(&$bean) {
117 $module_dir = $this->getCorrectedModule($bean->module_dir);
118 $this->addresses = $this->getAddressesByGUID($bean->id, $module_dir);
119 $this->populateLegacyFields($bean);
120 if (isset($bean->email1) && !isset($bean->fetched_row['email1'])) {
121 $bean->fetched_row['email1'] = $bean->email1;
127 function populateLegacyFields(&$bean){
128 $primary_found = false;
129 $alternate_found = false;
130 $alternate2_found = false;
131 foreach($this->addresses as $k=>$address) {
132 if ($primary_found && $alternate_found)
134 if ($address['primary_address'] == 1 && !$primary_found) {
136 $primary_found = true;
137 } elseif (!$alternate_found) {
138 $alternate_index = $k;
139 $alternate_found = true;
140 } elseif (!$alternate2_found){
141 $alternate2_index = $k;
142 $alternate2_found = true;
146 if ($primary_found) {
147 $bean->email1 = $this->addresses[$primary_index]['email_address'];
148 $bean->email_opt_out = $this->addresses[$primary_index]['opt_out'];
149 $bean->invalid_email = $this->addresses[$primary_index]['invalid_email'];
150 if ($alternate_found) {
151 $bean->email2 = $this->addresses[$alternate_index]['email_address'];
153 } elseif ($alternate_found) {
154 // Use the first found alternate as email1.
155 $bean->email1 = $this->addresses[$alternate_index]['email_address'];
156 $bean->email_opt_out = $this->addresses[$alternate_index]['opt_out'];
157 $bean->invalid_email = $this->addresses[$alternate_index]['invalid_email'];
158 if ($alternate2_found) {
159 $bean->email2 = $this->addresses[$alternate2_index]['email_address'];
165 * Saves email addresses for a parent bean
166 * @param string $id Parent bean ID
167 * @param string $module Parent bean's module
168 * @param array $addresses Override of $_REQUEST vars, used to handle non-standard bean saves
169 * @param string $primary GUID of primary address
170 * @param string $replyTo GUID of reply-to address
171 * @param string $invalid GUID of invalid address
173 function save($id, $module, $new_addrs=array(), $primary='', $replyTo='', $invalid='', $optOut='', $in_workflow=false) {
174 if(empty($this->addresses) || $in_workflow){
175 $this->populateAddresses($id, $module, $new_addrs,$primary);
178 //find all email addresses..
179 $current_links=array();
180 // Need to correct this to handle the Employee/User split
181 $module = $this->getCorrectedModule($module);
182 $q2="select * from email_addr_bean_rel eabr WHERE eabr.bean_id = '".$this->db->quote($id)."' AND eabr.bean_module = '".$this->db->quote($module)."' and eabr.deleted=0";
183 $r2 = $this->db->query($q2);
184 while(($row2=$this->db->fetchByAssoc($r2)) != null ) {
185 $current_links[$row2['email_address_id']]=$row2;
188 $isConversion = (isset($_REQUEST) && isset($_REQUEST['action']) && $_REQUEST['action'] == 'ConvertLead') ? true : false;
190 if (!empty($this->addresses)) {
191 // insert new relationships and create email address record, if they don't exist
192 foreach($this->addresses as $address) {
193 if(!empty($address['email_address'])) {
194 $guid = create_guid();
195 $emailId = isset($address['email_address_id'])
196 && isset($current_links[$address['email_address_id']])
197 ? $address['email_address_id'] : null;
198 $emailId = $this->AddUpdateEmailAddress($address['email_address'],
199 $address['invalid_email'],
201 $emailId);// this will save the email address if not found
203 //verify linkage and flags.
205 if (isset($current_links[$emailId])) {
206 if (!$isConversion) { // do not update anything if this is for lead conversion
207 if ($address['primary_address'] != $current_links[$emailId]['primary_address'] or $address['reply_to_address'] != $current_links[$emailId]['reply_to_address'] ) {
208 $upd_eabr="UPDATE email_addr_bean_rel SET primary_address='".$this->db->quote($address['primary_address'])."', reply_to_address='".$this->db->quote($address['reply_to_address'])."' WHERE id='".$this->db->quote($current_links[$emailId]['id'])."'";
211 unset($current_links[$emailId]);
214 $primary = $address['primary_address'];
215 if (!empty($current_links) && $isConversion) {
216 foreach ($current_links as $eabr) {
217 if ($eabr['primary_address'] == 1) {
218 // for lead conversion, if there is already a primary email, do not insert another primary email
224 $now = $this->db->now();
225 $upd_eabr = "INSERT INTO email_addr_bean_rel (id, email_address_id,bean_id, bean_module,primary_address,reply_to_address,date_created,date_modified,deleted) VALUES('".$this->db->quote($guid)."', '".$this->db->quote($emailId)."', '".$this->db->quote($id)."', '".$this->db->quote($module)."', ".intval($primary).", ".intval($address['reply_to_address']).", $now, $now, 0)";
228 if (!empty($upd_eabr)) {
229 $r2 = $this->db->query($upd_eabr);
235 //delete link to dropped email address.
236 // for lead conversion, do not delete email addresses
237 if (!empty($current_links) && !$isConversion) {
240 foreach ($current_links as $eabr) {
242 $delete.=empty($delete) ? "'".$this->db->quote($eabr['id']) . "' " : ",'" . $this->db->quote($eabr['id']) . "'";
245 $eabr_unlink="update email_addr_bean_rel set deleted=1 where id in ({$delete})";
246 $this->db->query($eabr_unlink);
248 $this->stateBeforeWorkflow = null;
253 * returns the number of email addresses found for a specifed bean
255 * @param string $email Address to match
256 * @param object $bean Bean to query against
257 * @param string $addresstype Optional, pass a 1 to query against the primary address, 0 for the other addresses
258 * @return int Count of records found
260 function getCountEmailAddressByBean(
266 $emailCaps = strtoupper(trim($email));
267 if(empty($emailCaps))
271 FROM email_addr_bean_rel eabl JOIN email_addresses ea
272 ON (ea.id = eabl.email_address_id)
273 JOIN {$bean->table_name} bean
274 ON (eabl.bean_id = bean.id)
275 WHERE ea.email_address_caps = '".$this->db->quote($emailCaps)."'
276 and eabl.bean_module = '".$this->db->quote($bean->module_dir)."'
277 and eabl.primary_address = '".$this->db->quote($addresstype)."'
278 and eabl.deleted=0 ";
280 $r = $this->db->query($q);
282 // do it this way to make the count accurate in oracle
284 while ($this->db->fetchByAssoc($r)) ++$i;
290 * This function returns a contact or user ID if a matching email is found
291 * @param $email the email address to match
292 * @param $table which table to query
294 function getRelatedId($email, $module) {
295 $email = $this->db->quote(trim(strtoupper($email)));
296 $module = $this->db->quote(ucfirst($module));
298 $q = "SELECT bean_id FROM email_addr_bean_rel eabr
299 JOIN email_addresses ea ON (eabr.email_address_id = ea.id)
300 WHERE bean_module = '$module' AND ea.email_address_caps = '$email' AND eabr.deleted=0";
302 $r = $this->db->query($q, true);
305 while($a = $this->db->fetchByAssoc($r)) {
306 $retArr[] = $a['bean_id'];
308 if(count($retArr) > 0) {
316 * returns a collection of beans matching the email address
317 * @param string $email Address to match
320 function getBeansByEmailAddress($email) {
326 $email = trim($email);
332 $emailCaps = "'".$this->db->quote(strtoupper($email))."'";
333 $q = "SELECT * FROM email_addr_bean_rel eabl JOIN email_addresses ea ON (ea.id = eabl.email_address_id)
334 WHERE ea.email_address_caps = $emailCaps and eabl.deleted=0 ";
335 $r = $this->db->query($q);
337 while($a = $this->db->fetchByAssoc($r)) {
338 if(isset($beanList[$a['bean_module']]) && !empty($beanList[$a['bean_module']])) {
339 $className = $beanList[$a['bean_module']];
341 if(isset($beanFiles[$className]) && !empty($beanFiles[$className])) {
342 if(!class_exists($className)) {
343 require_once($beanFiles[$className]);
346 $bean = new $className();
347 $bean->retrieve($a['bean_id']);
351 $GLOBALS['log']->fatal("SUGAREMAILADDRESS: could not find valid class file for [ {$className} ]");
354 $GLOBALS['log']->fatal("SUGAREMAILADDRESS: could not find valid class [ {$a['bean_module']} ]");
362 * Saves email addresses for a parent bean
363 * @param string $id Parent bean ID
364 * @param string $module Parent bean's module
365 * @param array $addresses Override of $_REQUEST vars, used to handle non-standard bean saves
366 * @param string $primary GUID of primary address
367 * @param string $replyTo GUID of reply-to address
368 * @param string $invalid GUID of invalid address
370 function populateAddresses($id, $module, $new_addrs=array(), $primary='', $replyTo='', $invalid='', $optOut='') {
371 $module = $this->getCorrectedModule($module);
372 //One last check for the ConvertLead action in which case we need to change $module to 'Leads'
373 $module = (isset($_REQUEST) && isset($_REQUEST['action']) && $_REQUEST['action'] == 'ConvertLead') ? 'Leads' : $module;
375 $post_from_email_address_widget = (isset($_REQUEST) && isset($_REQUEST['emailAddressWidget'])) ? true : false;
376 $primaryValue = $primary;
378 $hasEmailValue = false;
379 $email_ids = array();
381 if (isset($_REQUEST) && isset($_REQUEST[$module .'_email_widget_id'])) {
383 $fromRequest = false;
384 // determine which array to process
385 foreach($_REQUEST as $k => $v) {
386 if(strpos($k, 'emailAddress') !== false) {
390 $widget_id = $_REQUEST[$module .'_email_widget_id'];
393 //Iterate over the widgets for this module, in case there are multiple email widgets for this module
394 while(isset($_REQUEST[$module . $widget_id . "emailAddress" . $widgetCount]))
396 if (empty($_REQUEST[$module . $widget_id . "emailAddress" . $widgetCount])) {
401 $hasEmailValue = true;
403 $eId = $module . $widget_id;
404 if(isset($_REQUEST[$eId . 'emailAddressPrimaryFlag'])) {
405 $primaryValue = $_REQUEST[$eId . 'emailAddressPrimaryFlag'];
406 } else if(isset($_REQUEST[$module . 'emailAddressPrimaryFlag'])) {
407 $primaryValue = $_REQUEST[$module . 'emailAddressPrimaryFlag'];
410 $optOutValues = array();
411 if(isset($_REQUEST[$eId .'emailAddressOptOutFlag'])) {
412 $optOutValues = $_REQUEST[$eId .'emailAddressOptOutFlag'];
413 } else if(isset($_REQUEST[$module . 'emailAddressOptOutFlag'])) {
414 $optOutValues = $_REQUEST[$module . 'emailAddressOptOutFlag'];
417 $invalidValues = array();
418 if(isset($_REQUEST[$eId .'emailAddressInvalidFlag'])) {
419 $invalidValues = $_REQUEST[$eId .'emailAddressInvalidFlag'];
420 } else if(isset($_REQUEST[$module . 'emailAddressInvalidFlag'])) {
421 $invalidValues = $_REQUEST[$module . 'emailAddressInvalidFlag'];
424 $deleteValues = array();
425 if(isset($_REQUEST[$eId .'emailAddressDeleteFlag'])) {
426 $deleteValues = $_REQUEST[$eId .'emailAddressDeleteFlag'];
427 } else if(isset($_REQUEST[$module . 'emailAddressDeleteFlag'])) {
428 $deleteValues = $_REQUEST[$module . 'emailAddressDeleteFlag'];
431 // prep from form save
432 $primaryField = $primary;
436 if($fromRequest && empty($primary) && isset($primaryValue)) {
437 $primaryField = $primaryValue;
440 if($fromRequest && empty($replyTo)) {
441 if(isset($_REQUEST[$eId .'emailAddressReplyToFlag'])) {
442 $replyToField = $_REQUEST[$eId .'emailAddressReplyToFlag'];
443 } else if(isset($_REQUEST[$module . 'emailAddressReplyToFlag'])) {
444 $replyToField = $_REQUEST[$module . 'emailAddressReplyToFlag'];
447 if($fromRequest && empty($new_addrs)) {
448 foreach($_REQUEST as $k => $v) {
449 if(preg_match('/'.$eId.'emailAddress[0-9]+$/i', $k) && !empty($v)) {
454 if($fromRequest && empty($email_ids)) {
455 foreach($_REQUEST as $k => $v) {
456 if(preg_match('/'.$eId.'emailAddressId[0-9]+$/i', $k) && !empty($v)) {
457 $key = str_replace('emailAddressId', 'emailAddress', $k);
458 $email_ids[$key] = $v;
463 if($fromRequest && empty($new_addrs)) {
464 foreach($_REQUEST as $k => $v) {
465 if(preg_match('/'.$eId.'emailAddressVerifiedValue[0-9]+$/i', $k) && !empty($v)) {
466 $validateFlag = str_replace("Value", "Flag", $k);
467 if (isset($_REQUEST[$validateFlag]) && $_REQUEST[$validateFlag] == "true")
473 //empty the addresses array if the post happened from email address widget.
474 if($post_from_email_address_widget) {
475 $this->addresses=array(); //this gets populated during retrieve of the contact bean.
477 $optOutValues = array();
478 $invalidValues = array();
479 foreach($new_addrs as $k=>$email) {
480 preg_match('/emailAddress([0-9])+$/', $k, $matches);
481 $count = $matches[1];
482 $result = $this->db->query("SELECT opt_out, invalid_email from email_addresses where email_address_caps = '" . $this->db->quote(strtoupper($email)) . "'");
483 if(!empty($result)) {
484 $row=$this->db->fetchByAssoc($result);
485 if(!empty($row['opt_out'])) {
486 $optOutValues[$k] = "emailAddress$count";
488 if(!empty($row['invalid_email'])) {
489 $invalidValues[$k] = "emailAddress$count";
494 // Re-populate the addresses class variable if we have new address(es).
495 if (!empty($new_addrs)) {
496 foreach($new_addrs as $k => $reqVar) {
497 //$key = preg_match("/^$eId/s", $k) ? substr($k, strlen($eId)) : $k;
498 $reqVar = trim($reqVar);
499 if(strpos($k, 'emailAddress') !== false) {
500 if(!empty($reqVar) && !in_array($k, $deleteValues)) {
501 $email_id = (array_key_exists($k, $email_ids)) ? $email_ids[$k] : null;
502 $primary = ($k == $primaryValue) ? true : false;
503 $replyTo = ($k == $replyToField) ? true : false;
504 $invalid = (in_array($k, $invalidValues)) ? true : false;
505 $optOut = (in_array($k, $optOutValues)) ? true : false;
506 $this->addAddress(trim($new_addrs[$k]), $primary, $replyTo, $invalid, $optOut, $email_id);
513 }//End of Widget for loop
516 //If no widgets, set addresses array to empty
517 if($post_from_email_address_widget && !$hasEmailValue) {
518 $this->addresses = array();
523 * Preps internal array structure for email addresses
524 * @param string $addr Email address
525 * @param bool $primary Default false
526 * @param bool $replyTo Default false
528 function addAddress($addr, $primary=false, $replyTo=false, $invalid=false, $optOut=false, $email_id = null) {
529 $addr = html_entity_decode($addr, ENT_QUOTES);
530 if(preg_match($this->regex, $addr)) {
531 $primaryFlag = ($primary) ? '1' : '0';
532 $replyToFlag = ($replyTo) ? '1' : '0';
533 $invalidFlag = ($invalid) ? '1' : '0';
534 $optOutFlag = ($optOut) ? '1' : '0';
538 // If we have such address already, remove it and add new one in.
539 foreach ($this->addresses as $k=>$address) {
540 if ($address['email_address'] == $addr) {
541 unset($this->addresses[$k]);
542 } elseif ($primary && $address['primary_address'] == '1') {
543 // We should only have one primary. If we are adding a primary but
544 // we find an existing primary, reset this one's primary flag.
545 $address['primary_address'] = '0';
549 $this->addresses[] = array(
550 'email_address' => $addr,
551 'primary_address' => $primaryFlag,
552 'reply_to_address' => $replyToFlag,
553 'invalid_email' => $invalidFlag,
554 'opt_out' => $optOutFlag,
555 'email_address_id' => $email_id,
558 $GLOBALS['log']->fatal("SUGAREMAILADDRESS: address did not validate [ {$addr} ]");
563 * Updates invalid_email and opt_out flags for each address
565 function updateFlags() {
566 if(!empty($this->addresses)) {
567 foreach($this->addresses as $addressMeta) {
568 if(isset($addressMeta['email_address']) && !empty($addressMeta['email_address'])) {
569 $address = $this->db->quote($this->_cleanAddress($addressMeta['email_address']));
571 $q = "SELECT * FROM email_addresses WHERE email_address = '{$address}'";
572 $r = $this->db->query($q);
573 $a = $this->db->fetchByAssoc($r);
576 if(isset($a['invalid_email']) && isset($addressMeta['invalid_email']) && isset($addressMeta['opt_out']) && $a['invalid_email'] != $addressMeta['invalid_email'] || $a['opt_out'] != $addressMeta['opt_out']) {
577 $qUpdate = "UPDATE email_addresses SET invalid_email = ".intval($addressMeta['invalid_email']).", opt_out = ".intval($addressMeta['opt_out']).", date_modified = '".TimeDate::getInstance()->nowDb()."' WHERE id = '".$this->db->quote($a['id'])."'";
578 $rUpdate = $this->db->query($qUpdate);
586 public function splitEmailAddress($addr)
588 $email = $this->_cleanAddress($addr);
589 if(!preg_match($this->regex, $email)) {
590 $email = ''; // remove bad email addr
592 $name = trim(str_replace(array($email, '<', '>', '"', "'"), '', $addr));
593 return array("name" => $name, "email" => strtolower($email));
598 * Normalizes an RFC-clean email address, returns a string that is the email address only
599 * @param string $addr Dirty email address
600 * @return string clean email address
602 function _cleanAddress($addr) {
603 $addr = trim(from_html($addr));
605 if(strpos($addr, "<") !== false && strpos($addr, ">") !== false) {
606 $address = trim(substr($addr, strrpos($addr, "<") +1, strrpos($addr, ">") - strrpos($addr, "<") -1));
608 $address = trim($addr);
615 * preps a passed email address for email address storage
616 * @param array $addr Address in focus, must be RFC compliant
617 * @return string $id email_addresses ID
619 function getEmailGUID($addr) {
620 $address = $this->db->quote($this->_cleanAddress($addr));
621 $addressCaps = strtoupper($address);
623 $q = "SELECT id FROM email_addresses WHERE email_address_caps = '{$addressCaps}'";
624 $r = $this->db->query($q);
625 $a = $this->db->fetchByAssoc($r);
627 if(!empty($a) && !empty($a['id'])) {
631 if(!empty($address)){
632 $guid = create_guid();
633 $now = TimeDate::getInstance()->nowDb();
634 $qa = "INSERT INTO email_addresses (id, email_address, email_address_caps, date_created, date_modified, deleted)
635 VALUES('{$guid}', '{$address}', '{$addressCaps}', '$now', '$now', 0)";
636 $ra = $this->db->query($qa);
643 * Creates or Updates an entry in the email_addresses table, depending
644 * on if the email address submitted matches a previous entry (case-insensitive)
645 * @param String $addr - email address
646 * @param int $invalid - is the email address marked as Invalid?
647 * @param int $opt_out - is the email address marked as Opt-Out?
648 * @param String $id - the GUID of the original SugarEmailAddress bean,
649 * in case a "email has changed" WorkFlow has triggered - hack to allow workflow-induced changes
650 * to propagate to the new SugarEmailAddress - see bug 39188
651 * @return String GUID of Email Address or '' if cleaned address was empty.
653 public function AddUpdateEmailAddress($addr,$invalid=0,$opt_out=0,$id=null)
655 // sanity checks to avoid SQL injection.
656 $invalid = intval($invalid);
657 $opt_out = intval($opt_out);
659 $address = $this->db->quote($this->_cleanAddress($addr));
660 $addressCaps = strtoupper($address);
662 // determine if we have a matching email address
663 $q = "SELECT * FROM email_addresses WHERE email_address_caps = '{$addressCaps}' and deleted=0";
664 $r = $this->db->query($q);
665 $duplicate_email = $this->db->fetchByAssoc($r);
667 // check if we are changing an email address, where workflow might be in play
669 $r = $this->db->query("SELECT * FROM email_addresses WHERE id='".$this->db->quote($id)."'");
670 $current_email = $this->db->fetchByAssoc($r);
673 $current_email = null;
676 // unless workflow made changes, assume parameters are what to use.
677 $new_opt_out = $opt_out;
678 $new_invalid = $invalid;
679 if (!empty($current_email['id']) && isset($this->stateBeforeWorkflow[$current_email['id']])) {
680 if ($current_email['invalid_email'] != $invalid ||
681 $current_email['opt_out'] != $opt_out) {
683 // workflow could be in play
684 $before_email = $this->stateBeforeWorkflow[$current_email['id']];
686 // our logic is as follows: choose from parameter, unless workflow made a change to the value, then choose final value
687 if (intval($before_email['opt_out']) != intval($current_email['opt_out'])) {
688 $new_opt_out = intval($current_email['opt_out']);
690 if (intval($before_email['invalid_email']) != intval($current_email['invalid_email'])) {
691 $new_invalid = intval($current_email['invalid_email']);
696 // determine how we are going to put in this address - UPDATE or INSERT
697 if (!empty($duplicate_email['id'])) {
699 // address_caps matches - see if we're changing fields
700 if ($duplicate_email['invalid_email'] != $new_invalid ||
701 $duplicate_email['opt_out'] != $new_opt_out ||
702 (trim($duplicate_email['email_address']) != $address)) {
703 $upd_q = 'UPDATE ' . $this->table_name . ' ' .
704 'SET email_address=\'' . $address . '\', ' .
705 'invalid_email=' . $new_invalid . ', ' .
706 'opt_out=' . $new_opt_out . ', ' .
707 'date_modified=' . $this->db->now() . ' ' .
708 'WHERE id=\'' . $this->db->quote($duplicate_email['id']) . '\'';
709 $upd_r = $this->db->query($upd_q);
711 return $duplicate_email['id'];
714 // no case-insensitive address match - it's new, or undeleted.
716 if(!empty($address)){
717 $guid = create_guid();
718 $now = TimeDate::getInstance()->nowDb();
719 $qa = "INSERT INTO email_addresses (id, email_address, email_address_caps, date_created, date_modified, deleted, invalid_email, opt_out)
720 VALUES('{$guid}', '{$address}', '{$addressCaps}', '$now', '$now', 0 , $new_invalid, $new_opt_out)";
721 $this->db->query($qa);
728 * Returns Primary or newest email address
729 * @param object $focus Object in focus
730 * @return string email
732 function getPrimaryAddress($focus,$parent_id=null,$parent_type=null) {
734 $parent_type=empty($parent_type) ? $focus->module_dir : $parent_type;
735 // Bug63174: Email address is not shown in the list view for employees
736 $parent_type = $this->getCorrectedModule($parent_type);
737 $parent_id=empty($parent_id) ? $focus->id : $parent_id;
739 $q = "SELECT ea.email_address FROM email_addresses ea
740 LEFT JOIN email_addr_bean_rel ear ON ea.id = ear.email_address_id
741 WHERE ear.bean_module = '".$this->db->quote($parent_type)."'
742 AND ear.bean_id = '".$this->db->quote($parent_id)."'
744 AND ea.invalid_email = 0
745 ORDER BY ear.primary_address DESC";
746 $r = $this->db->limitQuery($q, 0, 1);
747 $a = $this->db->fetchByAssoc($r);
749 if(isset($a['email_address'])) {
750 return $a['email_address'];
756 * As long as this function is used not only to retrieve user's Reply-To
757 * address, but also notification address and so on, there were added
758 * $replyToOnly optional parameter used to retrieve only address marked as
759 * Reply-To (bug #43643).
761 * @param SugarBean $focus
762 * @param bool $replyToOnly
765 function getReplyToAddress($focus, $replyToOnly = false) {
766 $q = "SELECT ea.email_address FROM email_addresses ea
767 LEFT JOIN email_addr_bean_rel ear ON ea.id = ear.email_address_id
768 WHERE ear.bean_module = '".$this->db->quote($focus->module_dir)."'
769 AND ear.bean_id = '".$this->db->quote($focus->id)."'
771 AND ea.invalid_email = 0";
775 // retrieve reply-to address if it exists or any other address
778 ORDER BY ear.reply_to_address DESC";
782 // retrieve reply-to address only
784 AND ear.reply_to_address = 1";
787 $r = $this->db->query($q);
788 $a = $this->db->fetchByAssoc($r);
790 if(isset($a['email_address'])) {
791 return $a['email_address'];
797 * Returns all email addresses by parent's GUID
798 * @param string $id Parent's GUID
799 * @param string $module Parent's module
802 function getAddressesByGUID($id, $module) {
804 $module = $this->getCorrectedModule($module);
806 $q = "SELECT ea.email_address, ea.email_address_caps, ea.invalid_email, ea.opt_out, ea.date_created, ea.date_modified,
807 ear.id, ear.email_address_id, ear.bean_id, ear.bean_module, ear.primary_address, ear.reply_to_address, ear.deleted
808 FROM email_addresses ea LEFT JOIN email_addr_bean_rel ear ON ea.id = ear.email_address_id
809 WHERE ear.bean_module = '".$this->db->quote($module)."'
810 AND ear.bean_id = '".$this->db->quote($id)."'
812 ORDER BY ear.reply_to_address, ear.primary_address DESC";
813 $r = $this->db->query($q);
815 while($a = $this->db->fetchByAssoc($r, FALSE)) {
823 * Returns the HTML/JS for the EmailAddress widget
824 * @param string $parent_id ID of parent bean, generally $focus
825 * @param string $module $focus' module
826 * @param bool asMetadata Default false
827 * @return string HTML/JS for widget
829 function getEmailAddressWidgetEditView($id, $module, $asMetadata=false, $tpl='',$tabindex='0')
831 if ( !($this->smarty instanceOf Sugar_Smarty ) )
832 $this->smarty = new Sugar_Smarty();
834 global $app_strings, $dictionary, $beanList;
838 $prefillData = 'new Object()';
839 $passedModule = $module;
840 $module = $this->getCorrectedModule($module);
841 $saveModule = $module;
842 if(isset($_POST['is_converted']) && $_POST['is_converted']==true){
843 $id=$_POST['return_id'];
844 $module=$_POST['return_module'];
846 $prefillDataArr = array();
848 $prefillDataArr = $this->getAddressesByGUID($id, $module);
849 //When coming from convert leads, sometimes module is Contacts while the id is for a lead.
850 if (empty($prefillDataArr) && $module == "Contacts")
851 $prefillDataArr = $this->getAddressesByGUID($id, "Leads");
852 } else if(isset($_REQUEST['full_form']) && !empty($_REQUEST['emailAddressWidget'])){
853 $widget_id = isset($_REQUEST[$module . '_email_widget_id']) ? $_REQUEST[$module . '_email_widget_id'] : '0';
855 $key = $module . $widget_id . 'emailAddress'.$count;
856 while(isset($_REQUEST[$key])) {
857 $email = $_REQUEST[$key];
858 $prefillDataArr[] = array('email_address'=>$email,
859 'primary_address'=>isset($_REQUEST['emailAddressPrimaryFlag']) && $_REQUEST['emailAddressPrimaryFlag'] == $key,
860 'invalid_email'=>isset($_REQUEST['emailAddressInvalidFlag']) && in_array($key, $_REQUEST['emailAddressInvalidFlag']),
861 'opt_out'=>isset($_REQUEST['emailAddressOptOutFlag']) && in_array($key, $_REQUEST['emailAddressOptOutFlag']),
862 'reply_to_address'=>false
864 $key = $module . $widget_id . 'emailAddress' . ++$count;
868 if(!empty($prefillDataArr)) {
869 $json = new JSON(JSON_LOOSE_TYPE);
870 $prefillData = $json->encode($prefillDataArr);
871 $prefill = !empty($prefillDataArr) ? 'true' : 'false';
875 $vardefs = $dictionary[$beanList[$passedModule]]['fields'];
876 if (!empty($vardefs['email1']) && isset($vardefs['email1']['required']) && $vardefs['email1']['required'])
878 $this->smarty->assign('required', $required);
880 $this->smarty->assign('module', $saveModule);
881 $this->smarty->assign('index', $this->index);
882 $this->smarty->assign('app_strings', $app_strings);
883 $this->smarty->assign('prefillEmailAddresses', $prefill);
884 $this->smarty->assign('prefillData', $prefillData);
885 $this->smarty->assign('tabindex', $tabindex);
886 //Set addDefaultAddress flag (do not add if it's from the Email module)
887 $this->smarty->assign('addDefaultAddress', (isset($_REQUEST['module']) && $_REQUEST['module'] == 'Emails') ? 'false' : 'true');
890 //determine if this should be a quickcreate form, or a quick create form under subpanels
891 if ($this->view == "QuickCreate"){
892 $form = 'form_DC'.$this->view .'_'.$module;
893 if(isset($_REQUEST['action']) && $_REQUEST['action']=='SubpanelCreates' || $_REQUEST['action']=='SubpanelEdits'){
894 $form = 'form_Subpanel'.$this->view .'_'.$module;
898 $this->smarty->assign('emailView', $form);
900 if($module == 'Users') {
901 $this->smarty->assign('useReplyTo', true);
903 $this->smarty->assign('useOptOut', true);
904 $this->smarty->assign('useInvalid', true);
907 $template = empty($tpl) ? "include/SugarEmailAddress/templates/forEditView.tpl" : $tpl;
908 $newEmail = $this->smarty->fetch($template);
914 $ret['prefillData'] = $prefillDataArr;
915 $ret['html'] = $newEmail;
925 * Returns the HTML/JS for the EmailAddress widget
926 * @param object $focus Bean in focus
927 * @return string HTML/JS for widget
929 function getEmailAddressWidgetDetailView($focus, $tpl='')
931 if ( !($this->smarty instanceOf Sugar_Smarty ) )
932 $this->smarty = new Sugar_Smarty();
935 global $current_user;
937 if(empty($focus->id))return '';
938 $prefillData = $this->getAddressesByGUID($focus->id, $focus->module_dir);
940 foreach($prefillData as $addressItem) {
941 $key = ($addressItem['primary_address'] == 1) ? 'primary' : "";
942 $key = ($addressItem['reply_to_address'] == 1) ? 'reply_to' : $key;
943 $key = ($addressItem['opt_out'] == 1) ? 'opt_out' : $key;
944 $key = ($addressItem['invalid_email'] == 1) ? 'invalid' : $key;
945 $key = ($addressItem['opt_out'] == 1) && ($addressItem['invalid_email'] == 1) ? 'opt_out_invalid' : $key;
947 $assign[] = array('key' => $key, 'address' => $current_user->getEmailLink2($addressItem['email_address'], $focus).$addressItem['email_address']."</a>");
951 $this->smarty->assign('app_strings', $app_strings);
952 $this->smarty->assign('emailAddresses', $assign);
953 $templateFile = empty($tpl) ? "include/SugarEmailAddress/templates/forDetailView.tpl" : $tpl;
954 $return = $this->smarty->fetch($templateFile);
960 * getEmailAddressWidgetDuplicatesView($focus)
961 * @param object $focus Bean in focus
962 * @return string HTML that contains hidden input values based off of HTML request
964 function getEmailAddressWidgetDuplicatesView($focus)
966 if ( !($this->smarty instanceOf Sugar_Smarty ) )
967 $this->smarty = new Sugar_Smarty();
974 $mod = isset($focus) ? $focus->module_dir : "";
976 $widget_id = $_POST[$mod .'_email_widget_id'];
977 $this->smarty->assign('email_widget_id',$widget_id);
978 $this->smarty->assign('emailAddressWidget',$_POST['emailAddressWidget']);
980 if(isset($_POST[$mod . $widget_id . 'emailAddressPrimaryFlag'])) {
981 $primary = $_POST[$mod . $widget_id . 'emailAddressPrimaryFlag'];
984 while(isset($_POST[$mod . $widget_id . "emailAddress" . $count])) {
985 $emails[] = $_POST[$mod . $widget_id . 'emailAddress' . $count];
993 if(isset($_POST[$mod . $widget_id . 'emailAddressOptOutFlag'])) {
994 foreach($_POST[$mod . $widget_id . 'emailAddressOptOutFlag'] as $v) {
999 if(isset($_POST[$mod . $widget_id . 'emailAddressInvalidFlag'])) {
1000 foreach($_POST[$mod . $widget_id . 'emailAddressInvalidFlag'] as $v) {
1005 if(isset($_POST[$mod . $widget_id . 'emailAddressReplyToFlag'])) {
1006 foreach($_POST[$mod . $widget_id . 'emailAddressReplyToFlag'] as $v) {
1011 if(isset($_POST[$mod . $widget_id . 'emailAddressDeleteFlag'])) {
1012 foreach($_POST[$mod . $widget_id . 'emailAddressDeleteFlag'] as $v) {
1017 while(isset($_POST[$mod . $widget_id . "emailAddressVerifiedValue" . $count])) {
1018 $verified[] = $_POST[$mod . $widget_id . 'emailAddressVerifiedValue' . $count];
1022 $this->smarty->assign('emails', $emails);
1023 $this->smarty->assign('primary', $primary);
1024 $this->smarty->assign('optOut', $optOut);
1025 $this->smarty->assign('invalid', $invalid);
1026 $this->smarty->assign('replyTo', $invalid);
1027 $this->smarty->assign('delete', $invalid);
1028 $this->smarty->assign('verified', $invalid);
1029 $this->smarty->assign('moduleDir', $mod);
1031 return $this->smarty->fetch("include/SugarEmailAddress/templates/forDuplicatesView.tpl");
1038 function getFormBaseURL($focus) {
1041 $mod = isset($focus) ? $focus->module_dir : "";
1043 $widget_id = $_POST[$mod .'_email_widget_id'];
1044 $get .= '&' . $mod . '_email_widget_id='. $widget_id;
1045 $get .= '&emailAddressWidget='.$_POST['emailAddressWidget'];
1047 while(isset($_REQUEST[$mod . $widget_id . 'emailAddress' . $count])) {
1048 $get .= "&" . $mod . $widget_id . "emailAddress" . $count . "=" . urlencode($_REQUEST[$mod . $widget_id . 'emailAddress' . $count]);
1052 while(isset($_REQUEST[$mod . $widget_id . 'emailAddressVerifiedValue' . $count])) {
1053 $get .= "&" . $mod . $widget_id . "emailAddressVerifiedValue" . $count . "=" . urlencode($_REQUEST[$mod . $widget_id . 'emailAddressVerifiedValue' . $count]);
1057 $options = array('emailAddressPrimaryFlag', 'emailAddressOptOutFlag', 'emailAddressInvalidFlag', 'emailAddressDeleteFlag', 'emailAddressReplyToFlag');
1059 foreach($options as $option) {
1061 $optionIdentifier = $mod.$widget_id.$option;
1062 if(isset($_REQUEST[$optionIdentifier])) {
1063 if(is_array($_REQUEST[$optionIdentifier])) {
1064 foreach($_REQUEST[$optionIdentifier] as $optOut) {
1065 $get .= "&" . $optionIdentifier . "[" . $count . "]=" . $optOut;
1069 $get .= "&" . $optionIdentifier . "=" . $_REQUEST[$optionIdentifier];
1077 function setView($view) {
1078 $this->view = $view;
1082 * This function is here so the Employees/Users division can be handled cleanly in one place
1083 * @param object $focus SugarBean
1084 * @return string The value for the bean_module column in the email_addr_bean_rel table
1086 function getCorrectedModule(&$module) {
1087 return ($module == "Employees")? "Users" : $module;
1090 public function stash($parentBeanId, $moduleName)
1092 $result = $this->db->query("select email_address_id from email_addr_bean_rel eabr WHERE eabr.bean_id = '".$this->db->quote($parentBeanId)."' AND eabr.bean_module = '".$this->db->quote($moduleName)."' and eabr.deleted=0");
1093 $this->stateBeforeWorkflow = array();
1095 while ($row = $this->db->fetchByAssoc($result, false))
1097 $ids[] =$this->db->quote($row['email_address_id']); // avoid 2nd order SQL Injection
1101 $ids = implode("', '", $ids);
1102 $queryEmailData = "SELECT id, email_address, invalid_email, opt_out FROM {$this->table_name} WHERE id IN ('$ids') AND deleted=0";
1103 $result = $this->db->query($queryEmailData);
1104 while ($row = $this->db->fetchByAssoc($result, false))
1106 $this->stateBeforeWorkflow[$row['id']] = array_diff_key($row, array('id' => null));
1114 * Convenience function for MVC (Mystique)
1115 * @param object $focus SugarBean
1116 * @param string $field unused
1117 * @param string $value unused
1118 * @param string $view DetailView or EditView
1121 function getEmailAddressWidget($focus, $field, $value, $view, $tabindex='0') {
1122 $sea = new SugarEmailAddress();
1123 $sea->setView($view);
1125 if($view == 'EditView' || $view == 'QuickCreate' || $view == 'ConvertLead') {
1126 $module = $focus->module_dir;
1127 if ($view == 'ConvertLead' && $module == "Contacts") $module = "Leads";
1129 return $sea->getEmailAddressWidgetEditView($focus->id, $module, false,'',$tabindex);
1132 return $sea->getEmailAddressWidgetDetailView($focus);