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