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