]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/SugarEmailAddress/SugarEmailAddress.php
Release 6.2.3
[Github/sugarcrm.git] / include / SugarEmailAddress / SugarEmailAddress.php
1 <?php
2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 /*********************************************************************************
4  * SugarCRM Community Edition is a customer relationship management program developed by
5  * SugarCRM, Inc. Copyright (C) 2004-2011 SugarCRM Inc.
6  * 
7  * This program is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU Affero General Public License version 3 as published by the
9  * Free Software Foundation with the addition of the following permission added
10  * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
11  * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
12  * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
13  * 
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
17  * details.
18  * 
19  * You should have received a copy of the GNU Affero General Public License along with
20  * this program; if not, see http://www.gnu.org/licenses or write to the Free
21  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22  * 02110-1301 USA.
23  * 
24  * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
25  * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
26  * 
27  * The interactive user interfaces in modified source and object code versions
28  * of this program must display Appropriate Legal Notices, as required under
29  * Section 5 of the GNU Affero General Public License version 3.
30  * 
31  * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
32  * these Appropriate Legal Notices must retain the display of the "Powered by
33  * SugarCRM" logo. If the display of the logo is not reasonably feasible for
34  * technical reasons, the Appropriate Legal Notices must display the words
35  * "Powered by SugarCRM".
36  ********************************************************************************/
37
38 /*********************************************************************************
39
40  * Description:
41  * Portions created by SugarCRM are Copyright (C) SugarCRM, Inc. All Rights
42  * Reserved. Contributor(s): ______________________________________..
43  *********************************************************************************/
44
45
46 require_once("include/JSON.php");
47
48
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';
54
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     // FG - Bug 44338 - Changed RegEx for optimizations
58     // Old Regex : var $regex = "/^(['\.\-\+&'#!\$\*=\?\^_`\{\}~\/\w]+)*@((\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|\w+([\.-]?\w+)*(\.[\w-]{2,})+)\$/";
59     // Changes : 1) Removed character "'", since it appears twice
60     //           2) Added "?:" after every open parenthesis, since we don't need to catch groups
61     //           3) Removed the "*" just before "@", since it double the work of the previous "+", slowing down the evaluation
62     var $regex = "/^(?:['\.\-\+&#!\$\*=\?\^_`\{\}~\/\w]+)@(?:(?:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|\w+(?:[\.-]*\w+)*(?:\.[\w-]{2,})+)\$/";
63     var $disable_custom_fields = true;
64     var $db;
65     var $smarty;
66     var $addresses = array(); // array of emails
67     var $view = '';
68
69     static $count = 0;
70
71     /**
72      * Sole constructor
73      */
74     function SugarEmailAddress() {
75         parent::SugarBean();
76         $this->index = self::$count;
77         self::$count++;
78     }
79
80     /**
81      * Legacy email address handling.  This is to allow support for SOAP or customizations
82      * @param string $id
83      * @param string $module
84      */
85     function handleLegacySave($bean, $prefix = "") {
86         if(!isset($_REQUEST) || !isset($_REQUEST['useEmailWidget'])) {
87             if (empty($this->addresses) || !isset($_REQUEST['massupdate'])) {
88                 $this->addresses = array();
89                 $optOut = (isset($bean->email_opt_out) && $bean->email_opt_out == "1") ? true : false;
90                 $invalid = (isset($bean->invalid_email) && $bean->invalid_email == "1") ? true : false;
91
92                 $isPrimary = true;
93                 for($i = 1; $i <= 10; $i++){
94                     $email = 'email'.$i;
95                     if(isset($bean->$email) && !empty($bean->$email)){
96                         $opt_out_field = $email.'_opt_out';
97                         $invalid_field = $email.'_invalid';
98                         $field_optOut = (isset($bean->$opt_out_field)) ? $bean->$opt_out_field : $optOut;
99                         $field_invalid = (isset($bean->$invalid_field)) ? $bean->$invalid_field : $invalid;
100                         $this->addAddress($bean->$email, $isPrimary, false, $field_invalid, $field_optOut);
101                         $isPrimary = false;
102                     }
103                 }
104             }
105         }
106         $this->populateAddresses($bean->id, $bean->module_dir, array(),'');
107         if(isset($_REQUEST) && isset($_REQUEST['useEmailWidget'])) {
108             $this->populateLegacyFields($bean);
109         }
110     }
111
112     /**
113      * Fills standard email1 legacy fields
114      * @param string id
115      * @param string module
116      * @return object
117      */
118     function handleLegacyRetrieve(&$bean) {
119         $module_dir = $this->getCorrectedModule($bean->module_dir);
120         $this->addresses = $this->getAddressesByGUID($bean->id, $module_dir);
121         $this->populateLegacyFields($bean);
122
123         return;
124     }
125
126     function populateLegacyFields(&$bean){
127         $primary_found = false;
128         $alternate_found = false;
129         $alternate2_found = false;
130         foreach($this->addresses as $k=>$address) {
131             if ($primary_found && $alternate_found)
132                 break;
133             if ($address['primary_address'] == 1 && !$primary_found) {
134                 $primary_index = $k;
135                 $primary_found = true;
136             } elseif (!$alternate_found) {
137                 $alternate_index = $k;
138                 $alternate_found = true;
139             } elseif (!$alternate2_found){
140                 $alternate2_index = $k;
141                 $alternate2_found = true;
142             }
143         }
144
145         if ($primary_found) {
146             $bean->email1 = $this->addresses[$primary_index]['email_address'];
147             $bean->email_opt_out = $this->addresses[$primary_index]['opt_out'];
148             $bean->invalid_email = $this->addresses[$primary_index]['invalid_email'];
149             if ($alternate_found) {
150                 $bean->email2 = $this->addresses[$alternate_index]['email_address'];
151             }
152         } elseif ($alternate_found) {
153             // Use the first found alternate as email1.
154             $bean->email1 = $this->addresses[$alternate_index]['email_address'];
155             $bean->email_opt_out = $this->addresses[$alternate_index]['opt_out'];
156             $bean->invalid_email = $this->addresses[$alternate_index]['invalid_email'];
157             if ($alternate2_found) {
158                 $bean->email2 = $this->addresses[$alternate2_index]['email_address'];
159             }
160         }
161     }
162
163     /**
164      * Saves email addresses for a parent bean
165      * @param string $id Parent bean ID
166      * @param string $module Parent bean's module
167      * @param array $addresses Override of $_REQUEST vars, used to handle non-standard bean saves
168      * @param string $primary GUID of primary address
169      * @param string $replyTo GUID of reply-to address
170      * @param string $invalid GUID of invalid address
171      */
172     function save($id, $module, $new_addrs=array(), $primary='', $replyTo='', $invalid='', $optOut='', $in_workflow=false) {
173         if(empty($this->addresses) || $in_workflow){
174             $this->populateAddresses($id, $module, $new_addrs,$primary);
175         }
176
177         //find all email addresses..
178         $current_links=array();
179         // Need to correct this to handle the Employee/User split
180         $module = $this->getCorrectedModule($module);
181         $q2="select *  from email_addr_bean_rel eabr WHERE eabr.bean_id = '{$id}' AND eabr.bean_module = '{$module}' and eabr.deleted=0";
182         $r2 = $this->db->query($q2);
183         while(($row2=$this->db->fetchByAssoc($r2)) != null ) {
184             $current_links[$row2['email_address_id']]=$row2;
185         }
186
187         if (!empty($this->addresses)) {
188             // insert new relationships and create email address record, if they don't exist
189             foreach($this->addresses as $address) {
190                 if(!empty($address['email_address'])) {
191                     $guid = create_guid();
192                     $emailId = $this->AddUpdateEmailAddress($address['email_address'],$address['invalid_email'],$address['opt_out']);// this will save the email address if not found
193
194                     //verify linkage and flags.
195                     $upd_eabr="";
196                     if (isset($current_links[$emailId])) {
197                         if ($address['primary_address'] != $current_links[$emailId]['primary_address'] or $address['reply_to_address'] != $current_links[$emailId]['reply_to_address'] ) {
198                             $upd_eabr="UPDATE email_addr_bean_rel SET primary_address='{$address['primary_address']}', reply_to_address='{$address['reply_to_address']}' WHERE id='{$current_links[$emailId]['id']}'";
199                         }
200
201                         unset($current_links[$emailId]);
202                     } else {
203                         $now = TimeDate::getInstance()->nowDb();
204                         $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('{$guid}', '{$emailId}', '{$id}', '{$module}', {$address['primary_address']}, {$address['reply_to_address']}, '$now', '$now', 0)";
205                     }
206
207                     if (!empty($upd_eabr)) {
208                         $r2 = $this->db->query($upd_eabr);
209                     }
210                 }
211             }
212         }
213
214         //delete link to dropped email address.
215         if (!empty($current_links)) {
216
217             $delete="";
218             foreach ($current_links as $eabr) {
219
220                 $delete.=empty($delete) ? "'".$eabr['id'] . "' " : ",'" . $eabr['id'] . "'";
221             }
222
223             $eabr_unlink="update email_addr_bean_rel set deleted=1 where id in ({$delete})";
224             $this->db->query($eabr_unlink);
225         }
226         return;
227     }
228
229     /**
230      * returns the number of email addresses found for a specifed bean
231      *
232      * @param  string $email       Address to match
233      * @param  object $bean        Bean to query against
234      * @param  string $addresstype Optional, pass a 1 to query against the primary address, 0 for the other addresses
235      * @return int                 Count of records found
236      */
237     function getCountEmailAddressByBean(
238         $email,
239         $bean,
240         $addresstype
241         )
242     {
243         $emailCaps = strtoupper(trim($email));
244         if(empty($emailCaps))
245             return 0;
246
247         $q = "SELECT *
248                 FROM email_addr_bean_rel eabl JOIN email_addresses ea
249                         ON (ea.id = eabl.email_address_id)
250                     JOIN {$bean->table_name} bean
251                         ON (eabl.bean_id = bean.id)
252                 WHERE ea.email_address_caps = '{$emailCaps}'
253                     and eabl.bean_module = '{$bean->module_dir}'
254                     and eabl.primary_address = '{$addresstype}'
255                     and eabl.deleted=0 ";
256
257         $r = $this->db->query($q);
258
259         // do it this way to make the count accurate in oracle
260         $i = 0;
261         while ($this->db->fetchByAssoc($r)) ++$i;
262
263         return $i;
264     }
265
266     /**
267      * This function returns a contact or user ID if a matching email is found
268      * @param   $email      the email address to match
269      * @param   $table      which table to query
270      */
271     function getRelatedId($email, $module) {
272         $email = trim(strtoupper($email));
273         $module = ucfirst($module);
274
275         $q = "SELECT bean_id FROM email_addr_bean_rel eabr
276                 JOIN email_addresses ea ON (eabr.email_address_id = ea.id)
277                 WHERE bean_module = '{$module}' AND ea.email_address_caps = '{$email}' AND eabr.deleted=0";
278
279         $r = $this->db->query($q, true);
280
281         $retArr = array();
282         while($a = $this->db->fetchByAssoc($r)) {
283             $retArr[] = $a['bean_id'];
284         }
285         if(count($retArr) > 0) {
286             return $retArr;
287         } else {
288             return false;
289         }
290     }
291
292     /**
293      * returns a collection of beans matching the email address
294      * @param string $email Address to match
295      * @return array
296      */
297     function getBeansByEmailAddress($email) {
298         global $beanList;
299         global $beanFiles;
300
301         $ret = array();
302
303         $email = trim($email);
304
305         if(empty($email)) {
306             return array();
307         }
308
309         $emailCaps = strtoupper($email);
310         $q = "SELECT * FROM email_addr_bean_rel eabl JOIN email_addresses ea ON (ea.id = eabl.email_address_id)
311                 WHERE ea.email_address_caps = '{$emailCaps}' and eabl.deleted=0 ";
312         $r = $this->db->query($q);
313
314         while($a = $this->db->fetchByAssoc($r)) {
315             if(isset($beanList[$a['bean_module']]) && !empty($beanList[$a['bean_module']])) {
316                 $className = $beanList[$a['bean_module']];
317
318                 if(isset($beanFiles[$className]) && !empty($beanFiles[$className])) {
319                     if(!class_exists($className)) {
320                         require_once($beanFiles[$className]);
321                     }
322
323                     $bean = new $className();
324                     $bean->retrieve($a['bean_id']);
325
326                     $ret[] = $bean;
327                 } else {
328                     $GLOBALS['log']->fatal("SUGAREMAILADDRESS: could not find valid class file for [ {$className} ]");
329                 }
330             } else {
331                 $GLOBALS['log']->fatal("SUGAREMAILADDRESS: could not find valid class [ {$a['bean_module']} ]");
332             }
333         }
334
335         return $ret;
336     }
337
338     /**
339      * Saves email addresses for a parent bean
340      * @param string $id Parent bean ID
341      * @param string $module Parent bean's module
342      * @param array $addresses Override of $_REQUEST vars, used to handle non-standard bean saves
343      * @param string $primary GUID of primary address
344      * @param string $replyTo GUID of reply-to address
345      * @param string $invalid GUID of invalid address
346      */
347     function populateAddresses($id, $module, $new_addrs=array(), $primary='', $replyTo='', $invalid='', $optOut='') {
348         $module = $this->getCorrectedModule($module);
349         //One last check for the ConvertLead action in which case we need to change $module to 'Leads'
350         $module = (isset($_REQUEST) && isset($_REQUEST['action']) && $_REQUEST['action'] == 'ConvertLead') ? 'Leads' : $module;
351
352         $post_from_email_address_widget = (isset($_REQUEST) && isset($_REQUEST['emailAddressWidget'])) ? true : false;
353         $primaryValue = $primary;
354         $widgetCount = 0;
355         $hasEmailValue = false;
356
357         if (isset($_REQUEST) && isset($_REQUEST[$module .'_email_widget_id'])) {
358
359             $fromRequest = false;
360             // determine which array to process
361             foreach($_REQUEST as $k => $v) {
362                 if(strpos($k, 'emailAddress') !== false) {
363                    $fromRequest = true;
364                    break;
365                 }
366             }
367             $widget_id = $_REQUEST[$module .'_email_widget_id'];
368
369
370             //Iterate over the widgets for this module, in case there are multiple email widgets for this module
371             while(isset($_REQUEST[$module . $widget_id . "emailAddress" . $widgetCount]))
372             {
373                 if (empty($_REQUEST[$module . $widget_id . "emailAddress" . $widgetCount])) {
374                     $widgetCount++;
375                     continue;
376                 }
377
378                 $hasEmailValue = true;
379
380                 $eId = $module . $widget_id;
381                 if(isset($_REQUEST[$eId . 'emailAddressPrimaryFlag'])) {
382                    $primaryValue = $_REQUEST[$eId . 'emailAddressPrimaryFlag'];
383                 } else if(isset($_REQUEST[$module . 'emailAddressPrimaryFlag'])) {
384                    $primaryValue = $_REQUEST[$module . 'emailAddressPrimaryFlag'];
385                 }
386
387                 $optOutValues = array();
388                 if(isset($_REQUEST[$eId .'emailAddressOptOutFlag'])) {
389                    $optOutValues = $_REQUEST[$eId .'emailAddressOptOutFlag'];
390                 } else if(isset($_REQUEST[$module . 'emailAddressOptOutFlag'])) {
391                    $optOutValues = $_REQUEST[$module . 'emailAddressOptOutFlag'];
392                 }
393
394                 $invalidValues = array();
395                 if(isset($_REQUEST[$eId .'emailAddressInvalidFlag'])) {
396                    $invalidValues = $_REQUEST[$eId .'emailAddressInvalidFlag'];
397                 } else if(isset($_REQUEST[$module . 'emailAddressInvalidFlag'])) {
398                    $invalidValues = $_REQUEST[$module . 'emailAddressInvalidFlag'];
399                 }
400
401                 $deleteValues = array();
402                 if(isset($_REQUEST[$eId .'emailAddressDeleteFlag'])) {
403                    $deleteValues = $_REQUEST[$eId .'emailAddressDeleteFlag'];
404                 } else if(isset($_REQUEST[$module . 'emailAddressDeleteFlag'])) {
405                    $deleteValues = $_REQUEST[$module . 'emailAddressDeleteFlag'];
406                 }
407
408                 // prep from form save
409                 $primaryField = $primary;
410                 $replyToField = '';
411                 $invalidField = '';
412                 $optOutField = '';
413                 if($fromRequest && empty($primary) && isset($primaryValue)) {
414                     $primaryField = $primaryValue;
415                 }
416
417                 if($fromRequest && empty($replyTo)) {
418                     if(isset($_REQUEST[$eId .'emailAddressReplyToFlag'])) {
419                        $replyToField = $_REQUEST[$eId .'emailAddressReplyToFlag'];
420                     } else if(isset($_REQUEST[$module . 'emailAddressReplyToFlag'])) {
421                        $replyToField = $_REQUEST[$module . 'emailAddressReplyToFlag'];
422                     }
423                 }
424                 if($fromRequest && empty($new_addrs)) {
425                     foreach($_REQUEST as $k => $v) {
426                         if(preg_match('/'.$eId.'emailAddress[0-9]+$/i', $k) && !empty($v)) {
427                             $new_addrs[$k] = $v;
428                         }
429                     }
430                 }
431
432                 if($fromRequest && empty($new_addrs)) {
433                     foreach($_REQUEST as $k => $v) {
434                         if(preg_match('/'.$eId.'emailAddressVerifiedValue[0-9]+$/i', $k) && !empty($v)) {
435                             $validateFlag = str_replace("Value", "Flag", $k);
436                             if (isset($_REQUEST[$validateFlag]) && $_REQUEST[$validateFlag] == "true")
437                               $new_addrs[$k] = $v;
438                         }
439                     }
440                 }
441
442                 //empty the addresses array if the post happened from email address widget.
443                 if($post_from_email_address_widget) {
444                     $this->addresses=array();  //this gets populated during retrieve of the contact bean.
445                 } else {
446                     $optOutValues = array();
447                     $invalidValues = array();
448                     foreach($new_addrs as $k=>$email) {
449                        preg_match('/emailAddress([0-9])+$/', $k, $matches);
450                        $count = $matches[1];
451                        $result = $this->db->query("SELECT opt_out, invalid_email from email_addresses where email_address_caps = '" . strtoupper($email) . "'");
452                        if(!empty($result)) {
453                           $row=$this->db->fetchByAssoc($result);
454                           if(!empty($row['opt_out'])) {
455                              $optOutValues[$k] = "emailAddress$count";
456                           }
457                           if(!empty($row['invalid_email'])) {
458                              $invalidValues[$k] = "emailAddress$count";
459                           }
460                        }
461                     }
462                 }
463                 // Re-populate the addresses class variable if we have new address(es).
464                 if (!empty($new_addrs)) {
465                     foreach($new_addrs as $k => $reqVar) {
466                         //$key = preg_match("/^$eId/s", $k) ? substr($k, strlen($eId)) : $k;
467                         $reqVar = trim($reqVar);
468                         if(strpos($k, 'emailAddress') !== false) {
469                             if(!empty($reqVar) && !in_array($k, $deleteValues)) {
470                                 $primary    = ($k == $primaryValue) ? true : false;
471                                 $replyTo    = ($k == $replyToField) ? true : false;
472                                 $invalid    = (in_array($k, $invalidValues)) ? true : false;
473                                 $optOut     = (in_array($k, $optOutValues)) ? true : false;
474                                 $this->addAddress(trim($new_addrs[$k]), $primary, $replyTo, $invalid, $optOut);
475                             }
476                         }
477                     } //foreach
478                 }
479
480                 $widgetCount++;
481             }//End of Widget for loop
482         }
483
484         //If no widgets, set addresses array to empty
485         if($post_from_email_address_widget && !$hasEmailValue) {
486            $this->addresses = array();
487         }
488     }
489
490     /**
491      * Preps internal array structure for email addresses
492      * @param string $addr Email address
493      * @param bool $primary Default false
494      * @param bool $replyTo Default false
495      */
496     function addAddress($addr, $primary=false, $replyTo=false, $invalid=false, $optOut=false) {
497         $addr = html_entity_decode($addr, ENT_QUOTES);
498         if(preg_match($this->regex, $addr)) {
499             $primaryFlag = ($primary) ? '1' : '0';
500             $replyToFlag = ($replyTo) ? '1' : '0';
501             $invalidFlag = ($invalid) ? '1' : '0';
502             $optOutFlag = ($optOut) ? '1' : '0';
503
504             $addr = trim($addr);
505
506             // If we have such address already, remove it and add new one in.
507             foreach ($this->addresses as $k=>$address) {
508                 if ($address['email_address'] == $addr) {
509                     unset($this->addresses[$k]);
510                 } elseif ($primary && $address['primary_address'] == '1') {
511                     // We should only have one primary. If we are adding a primary but
512                     // we find an existing primary, reset this one's primary flag.
513                     $address['primary_address'] = '0';
514                 }
515             }
516
517             $this->addresses[] = array(
518                 'email_address' => $addr,
519                 'primary_address' => $primaryFlag,
520                 'reply_to_address' => $replyToFlag,
521                 'invalid_email' => $invalidFlag,
522                 'opt_out' => $optOutFlag,
523             );
524         } else {
525             $GLOBALS['log']->fatal("SUGAREMAILADDRESS: address did not validate [ {$addr} ]");
526         }
527     }
528
529     /**
530      * Updates invalid_email and opt_out flags for each address
531      */
532     function updateFlags() {
533         if(!empty($this->addresses)) {
534             foreach($this->addresses as $addressMeta) {
535                 if(isset($addressMeta['email_address']) && !empty($addressMeta['email_address'])) {
536                     $address = $this->_cleanAddress($addressMeta['email_address']);
537
538                     $q = "SELECT * FROM email_addresses WHERE email_address = '{$address}'";
539                     $r = $this->db->query($q);
540                     $a = $this->db->fetchByAssoc($r);
541
542                     if(!empty($a)) {
543                         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']) {
544                             $qUpdate = "UPDATE email_addresses SET invalid_email = {$addressMeta['invalid_email']}, opt_out = {$addressMeta['opt_out']}, date_modified = '".TimeDate::getInstance()->nowDb()."' WHERE id = '{$a['id']}'";
545                             $rUpdate = $this->db->query($qUpdate);
546                         }
547                     }
548                 }
549             }
550         }
551     }
552
553     public function splitEmailAddress($addr)
554     {
555         $email = $this->_cleanAddress($addr);
556         if(!preg_match($this->regex, $email)) {
557             $email = ''; // remove bad email addr
558         }
559         $name = trim(str_replace(array($email, '<', '>', '"', "'"), '', $addr));
560         return array("name" => $name, "email" => strtolower($email));
561     }
562
563     /**
564      * PRIVATE UTIL
565      * Normalizes an RFC-clean email address, returns a string that is the email address only
566      * @param string $addr Dirty email address
567      * @return string clean email address
568      */
569     function _cleanAddress($addr) {
570         $addr = trim(from_html($addr));
571
572         if(strpos($addr, "<") !== false && strpos($addr, ">") !== false) {
573             $address = trim(substr($addr, strrpos($addr, "<") +1, strrpos($addr, ">") - strrpos($addr, "<") -1));
574         } else {
575             $address = trim($addr);
576         }
577
578         return $address;
579     }
580
581     /**
582      * preps a passed email address for email address storage
583      * @param array $addr Address in focus, must be RFC compliant
584      * @return string $id email_addresses ID
585      */
586     function getEmailGUID($addr) {
587         $address = $this->_cleanAddress($addr);
588         $addressCaps = strtoupper($address);
589
590         $q = "SELECT id FROM email_addresses WHERE email_address_caps = '{$addressCaps}'";
591         $r = $this->db->query($q);
592         $a = $this->db->fetchByAssoc($r);
593
594         if(!empty($a) && !empty($a['id'])) {
595             return $a['id'];
596         } else {
597             $guid = '';
598             if(!empty($address)){
599                 $guid = create_guid();
600                 $address = $GLOBALS['db']->quote($address);
601                 $addressCaps = $GLOBALS['db']->quote($addressCaps);
602                 $now = TimeDate::getInstance()->nowDb();
603                 $qa = "INSERT INTO email_addresses (id, email_address, email_address_caps, date_created, date_modified, deleted)
604                         VALUES('{$guid}', '{$address}', '{$addressCaps}', '$now', '$now', 0)";
605                 $ra = $this->db->query($qa);
606             }
607             return $guid;
608         }
609     }
610
611     function AddUpdateEmailAddress($addr,$invalid=0,$opt_out=0) {
612
613         $address = $this->_cleanAddress($addr);
614         $addressCaps = strtoupper($this->db->quoteForEmail($address));
615
616         $q = "SELECT * FROM email_addresses WHERE email_address_caps = '{$addressCaps}' and deleted=0";
617         $r = $this->db->query($q);
618         $a = $this->db->fetchByAssoc($r);
619         if(!empty($a) && !empty($a['id'])) {
620             //verify the opt out and invalid flags.
621            //bug# 39378- did not allow change of case of an email address
622             if ($a['invalid_email'] != $invalid or $a['opt_out'] != $opt_out or strcasecmp(trim($a['email_address']), trim($address))==0) {
623                 $upd_q="update email_addresses set email_address='{$address}', invalid_email={$invalid}, opt_out={$opt_out},date_modified = '".gmdate($GLOBALS['timedate']->get_db_date_time_format())."' where id='{$a['id']}'";
624                 $upd_r= $this->db->query($upd_q);
625             }
626             return $a['id'];
627         } else {
628             $guid = '';
629             if(!empty($address)){
630                 $guid = create_guid();
631                 $address = $GLOBALS['db']->quote($address);
632                 $addressCaps = $GLOBALS['db']->quote($addressCaps);
633                 $now = TimeDate::getInstance()->nowDb();
634                 $qa = "INSERT INTO email_addresses (id, email_address, email_address_caps, date_created, date_modified, deleted, invalid_email, opt_out)
635                         VALUES('{$guid}', '{$address}', '{$addressCaps}', '$now', '$now', 0 , $invalid, $opt_out)";
636                 $this->db->query($qa);
637             }
638             return $guid;
639         }
640     }
641
642     /**
643      * Returns Primary or newest email address
644      * @param object $focus Object in focus
645      * @return string email
646      */
647     function getPrimaryAddress($focus,$parent_id=null,$parent_type=null) {
648
649         $parent_type=empty($parent_type) ? $focus->module_dir : $parent_type;
650         $parent_id=empty($parent_id) ? $focus->id : $parent_id;
651
652         $q = "SELECT ea.email_address FROM email_addresses ea
653                 LEFT JOIN email_addr_bean_rel ear ON ea.id = ear.email_address_id
654                 WHERE ear.bean_module = '{$parent_type}'
655                 AND ear.bean_id = '{$parent_id}'
656                 AND ear.deleted = 0
657                 AND ea.invalid_email = 0
658                 ORDER BY ear.primary_address DESC";
659         $r = $this->db->limitQuery($q, 0, 1);
660         $a = $this->db->fetchByAssoc($r);
661
662         if(isset($a['email_address'])) {
663             return $a['email_address'];
664         }
665         return '';
666     }
667
668     function getReplyToAddress($focus) {
669         $q = "SELECT ea.email_address FROM email_addresses ea
670                 LEFT JOIN email_addr_bean_rel ear ON ea.id = ear.email_address_id
671                 WHERE ear.bean_module = '{$focus->module_dir}'
672                 AND ear.bean_id = '{$focus->id}'
673                 AND ear.deleted = 0
674                 AND ea.invalid_email = 0
675                 ORDER BY ear.reply_to_address DESC";
676         $r = $this->db->query($q);
677         $a = $this->db->fetchByAssoc($r);
678
679         if(isset($a['email_address'])) {
680             return $a['email_address'];
681         }
682         return '';
683     }
684
685     /**
686      * Returns all email addresses by parent's GUID
687      * @param string $id Parent's GUID
688      * @param string $module Parent's module
689      * @return array
690      */
691     function getAddressesByGUID($id, $module) {
692         $return = array();
693         $module = $this->getCorrectedModule($module);
694
695         $q = "SELECT ea.email_address, ea.email_address_caps, ea.invalid_email, ea.opt_out, ea.date_created, ea.date_modified,
696                 ear.id, ear.email_address_id, ear.bean_id, ear.bean_module, ear.primary_address, ear.reply_to_address, ear.deleted
697                 FROM email_addresses ea LEFT JOIN email_addr_bean_rel ear ON ea.id = ear.email_address_id
698                 WHERE ear.bean_module = '{$module}'
699                 AND ear.bean_id = '{$id}'
700                 AND ear.deleted = 0
701                 ORDER BY ear.reply_to_address, ear.primary_address DESC";
702         $r = $this->db->query($q);
703
704         while($a = $this->db->fetchByAssoc($r)) {
705             $return[] = $a;
706         }
707
708         return $return;
709     }
710
711     /**
712      * Returns the HTML/JS for the EmailAddress widget
713      * @param string $parent_id ID of parent bean, generally $focus
714      * @param string $module $focus' module
715      * @param bool asMetadata Default false
716      * @return string HTML/JS for widget
717      */
718     function getEmailAddressWidgetEditView($id, $module, $asMetadata=false, $tpl='',$tabindex='')
719     {
720         if ( !($this->smarty instanceOf Sugar_Smarty ) )
721             $this->smarty = new Sugar_Smarty();
722
723         global $app_strings, $dictionary, $beanList;
724
725         $prefill = 'false';
726
727         $prefillData = 'new Object()';
728         $passedModule = $module;
729         $module = $this->getCorrectedModule($module);
730         $saveModule = $module;
731         if(isset($_POST['is_converted']) && $_POST['is_converted']==true){
732             $id=$_POST['return_id'];
733             $module=$_POST['return_module'];
734         }
735         $prefillDataArr = array();
736         if(!empty($id)) {
737             $prefillDataArr = $this->getAddressesByGUID($id, $module);
738             //When coming from convert leads, sometimes module is Contacts while the id is for a lead.
739             if (empty($prefillDataArr) && $module == "Contacts")
740                 $prefillDataArr = $this->getAddressesByGUID($id, "Leads");
741         } else if(isset($_REQUEST['full_form']) && !empty($_REQUEST['emailAddressWidget'])){
742             $widget_id = isset($_REQUEST[$module . '_email_widget_id']) ? $_REQUEST[$module . '_email_widget_id'] : '0';
743             $count = 0;
744             $key = $module . $widget_id . 'emailAddress'.$count;
745             while(isset($_REQUEST[$key])) {
746                    $email = $_REQUEST[$key];
747                    $prefillDataArr[] =  array('email_address'=>$email,
748                                              'primary_address'=>isset($_REQUEST['emailAddressPrimaryFlag']) && $_REQUEST['emailAddressPrimaryFlag'] == $key,
749                                              'invalid_email'=>isset($_REQUEST['emailAddressInvalidFlag']) && in_array($key, $_REQUEST['emailAddressInvalidFlag']),
750                                              'opt_out'=>isset($_REQUEST['emailAddressOptOutFlag']) && in_array($key, $_REQUEST['emailAddressOptOutFlag']),
751                                              'reply_to_address'=>false
752                                         );
753                    $key = $module . $widget_id . 'emailAddress' . ++$count;
754             } //while
755         }
756
757         if(!empty($prefillDataArr)) {
758             $json = new JSON(JSON_LOOSE_TYPE);
759             $prefillData = $json->encode($prefillDataArr);
760             $prefill = !empty($prefillDataArr) ? 'true' : 'false';
761         }
762
763         $required = false;
764         $vardefs = $dictionary[$beanList[$passedModule]]['fields'];
765         if (!empty($vardefs['email1']) && isset($vardefs['email1']['required']) && $vardefs['email1']['required'])
766             $required = true;
767         $this->smarty->assign('required', $required);
768
769         $this->smarty->assign('module', $saveModule);
770         $this->smarty->assign('index', $this->index);
771         $this->smarty->assign('app_strings', $app_strings);
772         $this->smarty->assign('prefillEmailAddresses', $prefill);
773         $this->smarty->assign('prefillData', $prefillData);
774         $this->smarty->assign('tabindex', $tabindex);
775         //Set addDefaultAddress flag (do not add if it's from the Email module)
776         $this->smarty->assign('addDefaultAddress', (isset($_REQUEST['module']) && $_REQUEST['module'] == 'Emails') ? 'false' : 'true');
777         $form = $this->view;
778
779         if ($this->view == "QuickCreate")
780         $form = 'form_'.$this->view .'_'.$module;
781         $this->smarty->assign('emailView', $form);
782
783         if($module == 'Users') {
784             $this->smarty->assign('useReplyTo', true);
785         } else {
786             $this->smarty->assign('useOptOut', true);
787             $this->smarty->assign('useInvalid', true);
788         }
789
790         $template = empty($tpl) ? "include/SugarEmailAddress/templates/forEditView.tpl" : $tpl;
791         $newEmail = $this->smarty->fetch($template);
792
793
794         if($asMetadata) {
795             // used by Email 2.0
796             $ret = array();
797             $ret['prefillData'] = $prefillDataArr;
798             $ret['html'] = $newEmail;
799
800             return $ret;
801         }
802
803         return $newEmail;
804     }
805
806
807     /**
808      * Returns the HTML/JS for the EmailAddress widget
809      * @param object $focus Bean in focus
810      * @return string HTML/JS for widget
811      */
812     function getEmailAddressWidgetDetailView($focus, $tpl='')
813     {
814         if ( !($this->smarty instanceOf Sugar_Smarty ) )
815             $this->smarty = new Sugar_Smarty();
816
817         global $app_strings;
818         global $current_user;
819         $assign = array();
820         if(empty($focus->id))return '';
821         $prefillData = $this->getAddressesByGUID($focus->id, $focus->module_dir);
822
823         foreach($prefillData as $addressItem) {
824             $key = ($addressItem['primary_address'] == 1) ? 'primary' : "";
825             $key = ($addressItem['reply_to_address'] == 1) ? 'reply_to' : $key;
826             $key = ($addressItem['opt_out'] == 1) ? 'opt_out' : $key;
827             $key = ($addressItem['invalid_email'] == 1) ? 'invalid' : $key;
828             $key = ($addressItem['opt_out'] == 1) && ($addressItem['invalid_email'] == 1) ? 'opt_out_invalid' : $key;
829
830             $assign[] = array('key' => $key, 'address' => $current_user->getEmailLink2($addressItem['email_address'], $focus).$addressItem['email_address']."</a>");
831         }
832
833
834         $this->smarty->assign('app_strings', $app_strings);
835         $this->smarty->assign('emailAddresses', $assign);
836         $templateFile = empty($tpl) ? "include/SugarEmailAddress/templates/forDetailView.tpl" : $tpl;
837         $return = $this->smarty->fetch($templateFile);
838         return $return;
839     }
840
841
842     /**
843      * getEmailAddressWidgetDuplicatesView($focus)
844      * @param object $focus Bean in focus
845      * @return string HTML that contains hidden input values based off of HTML request
846      */
847     function getEmailAddressWidgetDuplicatesView($focus)
848     {
849         if ( !($this->smarty instanceOf Sugar_Smarty ) )
850             $this->smarty = new Sugar_Smarty();
851
852         $count = 0;
853         $emails = array();
854         $primary = null;
855         $optOut = array();
856         $invalid = array();
857         $mod = isset($focus) ? $focus->module_dir : "";
858
859         $widget_id = $_POST[$mod .'_email_widget_id'];
860         $this->smarty->assign('email_widget_id',$widget_id);
861         $this->smarty->assign('emailAddressWidget',$_POST['emailAddressWidget']);
862
863         if(isset($_POST[$mod . $widget_id . 'emailAddressPrimaryFlag'])) {
864            $primary = $_POST[$mod . $widget_id . 'emailAddressPrimaryFlag'];
865         }
866
867         while(isset($_POST[$mod . $widget_id . "emailAddress" . $count])) {
868             $emails[] = $_POST[$mod . $widget_id . 'emailAddress' . $count];
869             $count++;
870         }
871
872         if($count == 0) {
873            return "";
874         }
875
876         if(isset($_POST[$mod . $widget_id . 'emailAddressOptOutFlag'])) {
877            foreach($_POST[$mod . $widget_id . 'emailAddressOptOutFlag'] as $v) {
878               $optOut[] = $v;
879            }
880         }
881
882         if(isset($_POST[$mod . $widget_id . 'emailAddressInvalidFlag'])) {
883            foreach($_POST[$mod . $widget_id . 'emailAddressInvalidFlag'] as $v) {
884               $invalid[] = $v;
885            }
886         }
887
888         if(isset($_POST[$mod . $widget_id . 'emailAddressReplyToFlag'])) {
889            foreach($_POST[$mod . $widget_id . 'emailAddressReplyToFlag'] as $v) {
890               $replyTo[] = $v;
891            }
892         }
893
894         if(isset($_POST[$mod . $widget_id . 'emailAddressDeleteFlag'])) {
895            foreach($_POST[$mod . $widget_id . 'emailAddressDeleteFlag'] as $v) {
896               $delete[] = $v;
897            }
898         }
899
900         while(isset($_POST[$mod . $widget_id . "emailAddressVerifiedValue" . $count])) {
901             $verified[] = $_POST[$mod . $widget_id . 'emailAddressVerifiedValue' . $count];
902             $count++;
903         }
904
905         $this->smarty->assign('emails', $emails);
906         $this->smarty->assign('primary', $primary);
907         $this->smarty->assign('optOut', $optOut);
908         $this->smarty->assign('invalid', $invalid);
909         $this->smarty->assign('replyTo', $invalid);
910         $this->smarty->assign('delete', $invalid);
911         $this->smarty->assign('verified', $invalid);
912         $this->smarty->assign('moduleDir', $mod);
913
914         return $this->smarty->fetch("include/SugarEmailAddress/templates/forDuplicatesView.tpl");
915     }
916
917     /**
918      * getFormBaseURL
919      *
920      */
921     function getFormBaseURL($focus) {
922         $get = "";
923         $count = 0;
924         $mod = isset($focus) ? $focus->module_dir : "";
925
926         $widget_id = $_POST[$mod .'_email_widget_id'];
927         $get .= '&' . $mod . '_email_widget_id='. $widget_id;
928         $get .= '&emailAddressWidget='.$_POST['emailAddressWidget'];
929
930         while(isset($_REQUEST[$mod . $widget_id . 'emailAddress' . $count])) {
931               $get .= "&" . $mod . $widget_id . "emailAddress" . $count . "=" . urlencode($_REQUEST[$mod . $widget_id . 'emailAddress' . $count]);
932               $count++;
933         } //while
934
935         while(isset($_REQUEST[$mod . $widget_id . 'emailAddressVerifiedValue' . $count])) {
936               $get .= "&" . $mod . $widget_id . "emailAddressVerifiedValue" . $count . "=" . urlencode($_REQUEST[$mod . $widget_id . 'emailAddressVerifiedValue' . $count]);
937               $count++;
938         } //while
939
940         $options = array('emailAddressPrimaryFlag', 'emailAddressOptOutFlag', 'emailAddressInvalidFlag', 'emailAddressDeleteFlag', 'emailAddressReplyToFlag');
941
942         foreach($options as $option) {
943             $count = 0;
944             $optionIdentifier = $mod.$widget_id.$option;
945             if(isset($_REQUEST[$optionIdentifier])) {
946                if(is_array($_REQUEST[$optionIdentifier])) {
947                    foreach($_REQUEST[$optionIdentifier] as $optOut) {
948                       $get .= "&" . $optionIdentifier . "[" . $count . "]=" . $optOut;
949                       $count++;
950                    } //foreach
951                } else {
952                    $get .= "&" . $optionIdentifier . "=" . $_REQUEST[$optionIdentifier];
953                }
954             } //if
955         } //foreach
956         return $get;
957
958     }
959
960     function setView($view) {
961        $this->view = $view;
962     }
963
964 /**
965  * This function is here so the Employees/Users division can be handled cleanly in one place
966  * @param object $focus SugarBean
967  * @return string The value for the bean_module column in the email_addr_bean_rel table
968  */
969     function getCorrectedModule(&$module) {
970         return ($module == "Employees")? "Users" : $module;
971     }
972 } // end class def
973
974
975 /**
976  * Convenience function for MVC (Mystique)
977  * @param object $focus SugarBean
978  * @param string $field unused
979  * @param string $value unused
980  * @param string $view DetailView or EditView
981  * @return string
982  */
983 function getEmailAddressWidget($focus, $field, $value, $view, $tabindex='') {
984     $sea = new SugarEmailAddress();
985     $sea->setView($view);
986
987         if($view == 'EditView' || $view == 'QuickCreate' || $view == 'ConvertLead') {
988             $module = $focus->module_dir;
989             if ($view == 'ConvertLead' && $module == "Contacts")  $module = "Leads";
990
991             return $sea->getEmailAddressWidgetEditView($focus->id, $module, false,'',$tabindex);
992         }
993
994     return $sea->getEmailAddressWidgetDetailView($focus);
995 }
996
997 ?>