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