SuiteCRM-Core/public/legacy/include/SugarEmailAddress/SugarEmailAddress.php

2406 lines
90 KiB
PHP
Raw Normal View History

<?php
/**
*
* SugarCRM Community Edition is a customer relationship management program developed by
* SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
*
* SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd.
* Copyright (C) 2011 - 2018 SalesAgility Ltd.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by the
* Free Software Foundation with the addition of the following permission added
* to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
* IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
* OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along with
* this program; if not, see http://www.gnu.org/licenses or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
* You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
* SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "Powered by
* SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not
* reasonably feasible for technical reasons, the Appropriate Legal Notices must
* display the words "Powered by SugarCRM" and "Supercharged by SuiteCRM".
*/
if (!defined('sugarEntry') || !sugarEntry) {
die('Not A Valid Entry Point');
}
require_once("include/JSON.php");
class SugarEmailAddress extends SugarBean
{
const ERR_INVALID_REQUEST_NO_USER_PROFILE_PAGE_SAVE_ACTION = 1;
const ERR_INVALID_REQUEST_NO_REQUEST = 2;
const ERR_INVALID_REQUEST_NO_EMAIL_INFOS = 3;
const ERR_INVALID_REQUEST_NO_VALID_EMAIL_ADDR_IN_REQUEST = 4;
const ERR_INVALID_REQUEST_VALID_USER_IS_SET_BUT_NO_IN_REQUEST = 5;
const ERR_PRIMARY_EMAIL_IS_NOT_SELECTED = 6;
const ERR_REPLYTO_EMAIL_IS_NOT_SELECTED = 7;
const ERR_INVALID_REQUEST_NO_VALID_USER_IN_REQUEST = 8;
const ERR_INVALID_REQUEST_MORE_THAN_ONE_USER_IN_REQUEST = 9;
const ERR_SOME_EMAILS_WERE_NOT_SAVED_OR_UPDATED = 10;
// Opt In Flags (for Ticks)
const COI_FLAG_OPT_IN = 'OPT_IN';
const COI_FLAG_OPT_IN_DISABLED = 'OPT_IN_DISABLED';
const COI_FLAG_OPT_IN_PENDING_EMAIL_CONFIRMED = 'OPT_IN_PENDING_EMAIL_CONFIRMED';
const COI_FLAG_OPT_IN_PENDING_EMAIL_FAILED = 'OPT_IN_PENDING_EMAIL_FAILED';
const COI_FLAG_OPT_IN_PENDING_EMAIL_NOT_SENT = 'OPT_IN_PENDING_EMAIL_NOT_SENT';
const COI_FLAG_OPT_IN_PENDING_EMAIL_SENT = 'OPT_IN_PENDING_EMAIL_SENT';
const COI_FLAG_OPT_OUT = 'OPT_OUT';
const COI_FLAG_UNKNOWN_OPT_IN_STATUS = 'UNKNOWN_OPT_IN_STATUS';
const COI_FLAG_INVALID = 'INVALID';
const COI_FLAG_NO_OPT_IN_STATUS = 'NO_OPT_IN_STATUS';
// Opt In Status
const COI_STAT_DISABLED = 'not-opt-in';
const COI_STAT_OPT_IN = 'opt-in';
const COI_STAT_CONFIRMED_OPT_IN = 'confirmed-opt-in';
/** @var boolean $tracker_visibility */
public $tracker_visibility = false;
/**
* @var string $table_name
*/
public $table_name = 'email_addresses';
/**
* @var string $module_name
*/
public $module_name = "EmailAddresses";
/** @var string $module_dir */
public $module_dir = 'EmailAddresses';
/** @var string $object_name */
public $object_name = 'EmailAddress';
/**
* bug 40068, According to rules in page 6 of http://www.apps.ietf.org/rfc/rfc3696.html#sec-3,
* allowed special characters ! # $ % & ' * + - / = ? ^ _ ` . { | } ~ in local part
* @var string $regex
*/
public $regex = "/^(?:['\.\-\+&#!\$\*=\?\^_`\{\}~\/\w]+)@(?:(?:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|\w+(?:[\.-]*\w+)*(?:\.[\w-]{2,})+)\$/";
/** @var bool $disable_custom_fields */
public $disable_custom_fields = true;
/**
* @var DBManager
*/
public $db;
/**
* @var Sugar_Smarty $smarty
*/
public $smarty;
/** @var EmailAddress[] $addresses email addresses*/
public $addresses = array();
/**
* @var string $view
*/
public $view = '';
/**
* @var
*/
private $stateBeforeWorkflow;
/**
* @var string $email_address
*/
public $email_address;
/**
* @var string $email_address_caps
*/
public $email_address_caps;
public static $count = 0;
/**
* @var int
*/
public $index;
/**
* @see SugarEmailAddress::COI_STAT_DISABLED
* @see SugarEmailAddress::COI_STAT_OPT_IN
* @see SugarEmailAddress::COI_STAT_CONFIRMED_OPT_IN
* @var string $confirm_opt_in
*/
public $confirm_opt_in = '';
/**
* @var int|bool $opt_out
*/
public $opt_out;
/**
* @var int|bool $invalid_email
*/
public $invalid_email;
/**
* @var TimeDate $confirm_opt_in_date
*/
public $confirm_opt_in_date;
/**
* @var TimeDate $confirm_opt_in_sent_date
*/
public $confirm_opt_in_sent_date;
/**
* @var TimeDate $confirm_opt_in_fail_date
*/
public $confirm_opt_in_fail_date;
/**
*
* @var string
*/
public $confirm_opt_in_token;
/**
* @var array $doNotDisplayOptInTickForModule
*/
protected static $doNotDisplayOptInTickForModule = array(
'Users',
'Employees'
);
/**
* For saveAtUserProfile() method to telling what
* went wrong at the last call.
*
* @var array
*/
public $lastSaveAtUserProfileErrors = [];
/**
* Sole constructor
*/
public function __construct()
{
parent::__construct();
$this->index = self::$count;
self::$count++;
}
/**
* Legacy email address handling. This is to allow support for SOAP or customizations
* @param SugarBean $bean
*/
public function handleLegacySave($bean)
{
if ($this->needsToParseLegacyAddresses($bean)) {
$this->parseLegacyEmailAddresses($bean);
}
$this->populateAddresses($bean->id, $bean->module_dir, array(), '');
if (isset($_REQUEST) && isset($_REQUEST[$bean->module_dir . '_email_widget_id'])) {
$this->populateLegacyFields($bean);
}
}
/**
* @param SugarBean $bean
* @return bool
*/
private function needsToParseLegacyAddresses($bean)
{
if (
!isset($_REQUEST)
|| !isset($_REQUEST[$bean->module_dir . '_email_widget_id'])
|| !isset($_REQUEST['massupdate'])
) {
Squashed 'public/legacy/' changes from 5a66316965..b065977c61 b065977c61 SuiteCRM 7.12.5 Release fd07950be0 Fix #8366 - V8 API Filtering W/ OR Operator Chained Conditions f8504d0a42 Fix #9445 - More than 10 tabs in a views creates a loop ec3c758b49 Fix #9451 - Missing duplicate merge filter options in Studio 3739e28428 Fix #9468 - Adding Security Suite subpanels to new custom modules 0742615e61 Fix #9427 - Adding missing help popup help strings in Studio c79a3a6109 Add accessors for the Results, Query fields in SearchResultsController a507575097 Fix #4075 - No way to add email signature after adding email template 9b8f5b46b2 Fix #9480 - Slow to load imap mailbox with a million email records 8184b82060 Fixed #2857 - No dynamic refreshing in dashboard ece5919449 Fix #9508 - Legacy search fields are incorrect size 5699ad47ff Fix #9478 - Update GitHub Templates 37d54ca1ab SuiteCRM 7.12.4 Release 5a7f66f1fc Fix #9482 - Update list of modules to normalize 1807751e16 Fix #9482 - Only save update fields on utf encoding repair 08c5a32e07 Fix #9482 - Add partial bean save a25efff51f Fix #7842 - Do not reset email addresses list upon saving 4e5b509a30 Add ExtensionManager with static method to compile ext files 77b2940fd9 Fix #9061 - Custom Labels can't be overwritten in Studio 2f40449702 Fix #9496 - Cannot save dropdown values 32c6e4a04b Merge next into suite 8 2812bd315a [Legacy] Fix user wizard finish screen re-direction 9dc1a2f017 [Legacy] User Wizard Styling Fixes 0b91cb9a53 [Legacy] Event Delegates Selector Box Styling Fixes 32d7408e93 [Legacy] Add New Task Modal Styling Fixes e121b602da [Legacy] Workflow Styling Fixes 86ef0fae66 [Legacy] Projects Resource Panel Styling Fixes 56eb694629 [Legacy] map legacy to front-end user action called wizard 4c7ff07fcc [Legacy] Rescheduler Popup Styling Fixes 1b76260971 [Legacy] Project Gantt Chart Delete Button Styling Fixes 09959f1078 [Legacy] Configuration Settings Styling Fixes 711ded6a70 [Legacy] Notes modules styling fixes a0aa6affc8 [Legacy] Calls Module Styling Fixes a468cede8b [Legacy] AdminPanel Border Radius Styling Fixes bfc8a443b7 [Legacy] Bump version to 8.0.1 git-subtree-dir: public/legacy git-subtree-split: b065977c6116e68cea907dc099205d0b32ac99f4
2022-03-01 14:37:14 +00:00
if (empty($this->addresses)) {
$this->addresses = array();
$optOut = (isset($bean->email_opt_out) && $bean->email_opt_out == '1');
$invalid = (isset($bean->invalid_email) && $bean->invalid_email == '1');
$isPrimary = true;
for ($i = 1; $i <= 10; $i++) {
$email = 'email' . $i;
if (isset($bean->$email) && !empty($bean->$email)) {
$opt_out_field = $email . '_opt_out';
$invalid_field = $email . '_invalid';
$field_optOut = (isset($bean->$opt_out_field)) ? $bean->$opt_out_field : $optOut;
$field_invalid = (isset($bean->$invalid_field)) ? $bean->$invalid_field : $invalid;
$this->addAddress($bean->$email, $isPrimary, false, $field_invalid, $field_optOut);
$isPrimary = false;
}
}
}
}
}
/**
* User Profile specific save email addresses,
* returns:
* true - success
* false - error
*
* Note:
* This function could head to many errors but return
* value is false in each case.
* It is confusing because the ambiguous return value.
* This function also stores the error code(s) in
* array SugarEmailAddress::$lastSaveAtUserProfileErrors
*
* @param array $request $_REQUEST
* @return bool
*/
public function saveAtUserProfile($request)
{
$this->lastSaveAtUserProfileErrors = [];
// validate the request first
if (!$this->isUserProfileEditViewPageSaveAction($request)) {
$GLOBALS['log']->error('Invalid Referrer: '.
'expected the Save action to be called from the User\'s Profile Edit View');
$this->lastSaveAtUserProfileErrors[] = self::ERR_INVALID_REQUEST_NO_USER_PROFILE_PAGE_SAVE_ACTION;
return false;
}
if (!$request) {
$GLOBALS['log']->error('This function requires a request array');
$this->lastSaveAtUserProfileErrors[] = self::ERR_INVALID_REQUEST_NO_REQUEST;
return false;
}
// first grab the needed information from a messy request
$neededRequest = array();
foreach ($request as $key => $value) {
if (preg_match('/^Users\d+emailAddress/', $key)) {
$neededRequest[$key] = $value;
}
}
if (!$neededRequest) {
$GLOBALS['log']->error('Email info is not found in request');
$this->lastSaveAtUserProfileErrors[] = self::ERR_INVALID_REQUEST_NO_EMAIL_INFOS;
return false;
}
// re-parsing the request and convert into a useful format
$usefulRequest = array();
foreach ($neededRequest as $key => $value) {
if (preg_match('/^Users(\d+)emailAddress(\d+)/', $key, $matches)) {
$usefulRequest['Users'][$matches[1]]['emailAddress'][$matches[2]] = array(
'email' => $neededRequest["Users{$matches[1]}emailAddress{$matches[2]}"],
'id' => $neededRequest["Users{$matches[1]}emailAddressId{$matches[2]}"],
'primary' => false,
'replyTo' => false,
);
}
}
if (!$usefulRequest) {
$GLOBALS['log']->error('Cannot find valid email address(es) in request');
$this->lastSaveAtUserProfileErrors[] = self::ERR_INVALID_REQUEST_NO_VALID_EMAIL_ADDR_IN_REQUEST;
return false;
}
if (!isset($usefulRequest['Users']) || !$usefulRequest['Users']) {
$GLOBALS['log']->error('Cannot find valid user in request');
$this->lastSaveAtUserProfileErrors[] = self::ERR_INVALID_REQUEST_VALID_USER_IS_SET_BUT_NO_IN_REQUEST;
return false;
}
// find the selected primary and replyTo
$primary = null;
$replyTo = null;
foreach ($usefulRequest['Users'] as $ukey => $user) {
if (
!$primary &&
isset($neededRequest["Users{$ukey}emailAddressPrimaryFlag"]) &&
$neededRequest["Users{$ukey}emailAddressPrimaryFlag"]
) {
$primary = $neededRequest["Users{$ukey}emailAddressPrimaryFlag"];
}
if (
!$replyTo &&
isset($neededRequest["Users{$ukey}emailAddressReplyToFlag"]) &&
$neededRequest["Users{$ukey}emailAddressReplyToFlag"]
) {
$replyTo = $neededRequest["Users{$ukey}emailAddressReplyToFlag"];
}
// founds?
if ($primary && $replyTo) {
break;
}
}
// add primary and replyTo into useful formatted request
if ($primary && preg_match('/^Users(\d+)emailAddress(\d+)$/', $primary, $matches)) {
$usefulRequest['Users'][$matches[1]]['emailAddress'][$matches[2]]['primary'] = true;
} else {
$GLOBALS['log']->warn("Primary email is not selected.");
$this->lastSaveAtUserProfileErrors[] = self::ERR_PRIMARY_EMAIL_IS_NOT_SELECTED;
}
if ($replyTo && preg_match('/^Users(\d+)emailAddress(\d+)$/', $replyTo, $matches)) {
$usefulRequest['Users'][$matches[1]]['emailAddress'][$matches[2]]['replyTo'] = true;
} else {
$GLOBALS['log']->warn("Reply-to email is not selected.");
$this->lastSaveAtUserProfileErrors[] = self::ERR_REPLYTO_EMAIL_IS_NOT_SELECTED;
}
if (count($usefulRequest['Users']) < 1) {
$GLOBALS['log']->error("Cannot find valid user in request");
$this->lastSaveAtUserProfileErrors[] = self::ERR_INVALID_REQUEST_NO_VALID_USER_IN_REQUEST;
return false;
}
if (count($usefulRequest['Users']) > 1) {
$GLOBALS['log']->warn("Expected only one user in request");
$this->lastSaveAtUserProfileErrors[] = self::ERR_INVALID_REQUEST_MORE_THAN_ONE_USER_IN_REQUEST;
}
$return = true;
foreach ($usefulRequest['Users'] as $user) {
foreach ($user['emailAddress'] as $email) {
if (!$this->handleEmailSaveAtUserProfile($email['id'], $email['email'], $email['primary'], $email['replyTo'])) {
$GLOBALS['log']->warn("Some emails were not saved or updated: {$email['id']} ({$email['email']})");
$return = false;
}
}
}
if ($return === false) {
$this->lastSaveAtUserProfileErrors[] = self::ERR_SOME_EMAILS_WERE_NOT_SAVED_OR_UPDATED;
}
return $return;
}
/**
* Handle save on User Profile specific Email Addresses,
* returns:
* true - success
* false - error
*
* @param string $id Email address ID
* @param string $address Valid Email address
* @param bool $primary
* @param bool $replyTo
* @return bool
*/
protected function handleEmailSaveAtUserProfile($id, $address, $primary, $replyTo)
{
global $current_user;
// first validations
if (!$id) {
$GLOBALS['log']->error("Missing email ID");
return false;
}
if (!$address) {
$GLOBALS['log']->error("Missing email address");
return false;
}
if (!$this->isValidEmail($address)) {
$GLOBALS['log']->error("Invalid email address format");
return false;
}
$email = new SugarEmailAddress();
if (!$email->retrieve($id)) {
$GLOBALS['log']->error('Email retrieve error, please ensure that the email ID is correct');
return false;
}
$db = DBManagerFactory::getInstance();
$query = sprintf("SELECT * FROM email_addresses WHERE id = %s AND deleted = 0", $db->quoted($id));
if ($db->getOne($query) === false) {
$GLOBALS['log']->error("Missing Email ID ($id)");
return false;
}
// do we have to update the address?
if ($email->email_address != $address) {
$_address = $db->quote($address);
$_addressCaps = $db->quote(strtoupper($address));
$_id = $db->quoted($id);
$query =
"UPDATE email_addresses
SET
email_address = '$_address',
email_address_caps = '$_addressCaps'
WHERE
Squashed 'public/legacy/' changes from 5ea3ab0333..e2aa790472 e2aa790472 SuiteCRM 7.14.5 Release 0c4f67a31f Add check for current user on session preferences 500990c68f Add check for current user on preferences 1bbdaa5802 Check for disabled languages on wizard e787d57892 Fix Disabled Language set on login 460032a8f5 Add check for preference on Sugar Authenticate 55e2804698 Fix formatting on login language check 46c8d5bc2a Default to user preference language on login 960eba23f6 Fix language setting for incorrect user f67d446682 Add login language config to default configs 151782135f Add User Language to Login Wizard 10bac245c5 Set Preference on login 17608b7fc7 Add check on login for language to show a2c8c4ce16 Set User Language in App and Preference 500228fe98 Add User Language selection to profile e672f71ccd Fix Events on Calendar 81cc87f117 Fix #9855 - non-admin not accessing external oauth connection f0ec330b30 Fix #9855 - Add type label 8aa790143d Fix #9855 - Error if type of oauth not equal to type of inbound account 56deda814b Fix #10441 - VAT Values display incorrectly on Quotes->Service Line Items, if Significant Figures = 0 1a563c9613 Fix #10467 - Date range not working b49101e853 Fix #9829 - Fatal error during upgrade to 7.12.8 58e9061105 Fix #10315 - More logical code structure, don't need to fill variable if gets overwritten later e321a5e639 Fix #10319 - Json API SQL error when filtering by custom fields d77391b746 Fix salesagility#10335 - Incorrect codification in the names of events displayed in the Calendar bfe151fca9 Fix #10339 - Inconsistent application of trim function on name & varchar fields 00e8c512de Fix #9078 - Favorites adds record twice to the sidebar and to the db 34d78051f6 Fix #10372 - Ensure int is passed to mb_substr when working with decimal vardef length ccf112bbb7 Fix #7150 - Show subpanels correctly 0a34481cfe Fix #10390 - URL is not clickable 9d5c09b7ba Fix #2828 - Not translatable messages in Dashlet parameters 0aaba42109 Fix #2175 - No gif and shifted text after saving task in gants view 7c35dfed35 Fix #10404 - Compare parentenum_value more precisely e0bfca3991 Fix #5653 - VAT Display in PDF Templates wrong when Currency Significant Digits set to 0 fc651eaf01 Fix #10433 - Email Address ID being double quoted ef87a84bf6 Fix #10437 - Calendar - 'Today' left-hand side bar option loads the week 5df8622406 Fix #10443 - Incorrect lengths in emails_text vardefs dd39dd1172 Fix #10268 - Access token wil not refresh for Oauth2 password clients b264df3bca Fix #6376 - Problem with date start and finish in project task filters 22e925f57a Fix #9177 - Cant use edit inline in UserType field git-subtree-dir: public/legacy git-subtree-split: e2aa790472c617631008d29849f4624e3658d873
2024-07-24 11:43:51 +00:00
id = {$_id} AND
deleted = 0";
$result = $db->query($query);
if (!$result) {
$GLOBALS['log']->warn("Undefined behavior: Missing error information about email save (1)");
}
if ($db->getAffectedRowCount($result) != 1) {
$GLOBALS['log']->debug("Email address has not change");
}
}
// update primary and replyTo
$_primary = (bool)$primary ? '1' : '0';
$_replyTo = (bool)$replyTo ? '1' : '0';
$_id = $db->quoted($id);
$query =
"UPDATE email_addr_bean_rel
SET
primary_address = '{$_primary}',
reply_to_address = '{$_replyTo}'
WHERE
email_address_id = {$_id} AND
bean_module = 'Users' AND
bean_id = '{$current_user->id}' AND
deleted = 0";
$result = $db->query($query);
if (!$result) {
$GLOBALS['log']->warn("Undefined behavior: Missing error information about email save (2)");
}
if ($db->getAffectedRowCount($result) != 1) {
$GLOBALS['log']->debug("Primary or reply-to Email address has not change");
}
return true;
}
/**
* Check a valid email format,
* return false if the email validation failed
*
* @param string $email
* @return mixed
*/
protected function isValidEmail($email)
{
$return = filter_var($email, FILTER_VALIDATE_EMAIL);
return $return;
}
/**
* Check for User Profile EditView / Save action for
* Email Addresses updates
* returns:
* true - User Profile Save action called by request
*
* @param array $request $_REQUEST
* @return bool
*/
protected function isUserProfileEditViewPageSaveAction($request)
{
$return =
(isset($request['page']) && $request['page'] == 'EditView') &&
(isset($request['module']) && $request['module'] == 'Users') &&
(isset($request['action']) && $request['action'] == 'Save');
return $return;
}
/**
* Fills standard email1 legacy fields
* @param string id
* @param string module
* @return object
*/
public function handleLegacyRetrieve(&$bean)
{
$module_dir = $this->getCorrectedModule($bean->module_dir);
$this->addresses = $this->getAddressesByGUID($bean->id, $module_dir);
$this->populateLegacyFields($bean);
Squashed 'public/legacy/' changes from c009bb4487..52926f2943 52926f2943 PHP 8.3 - Throw exception on Projects Gantt Table 7f5ad0158a PHP 8.3 - Fix PHP warning on import 282a1e0f89 PHP 8.3 - Fix currency formatting on listview dbed1d82cf PHP 8.3 - Fix PHP errors 07eaa3ff60 PHP 8.3 - Fix workflow date conversion error 13ccac1a3c PHP 8.3 - Fix Dates Error on calls 2b92f2cb15 Fix #10264 - Error `Assigned_user_name` `SearchView.tpl` (`{php}` tag now crashes Smarty 4) 660d546c2b Fix #10264 - Error Assigned_user_name SearchView.tpl ({php} tag now crashes Smarty 4) ecb6a6d89b PHP 8.3 - Fix report value not showing fece2fd35e PHP 8.3 - Fix PDF templates breaking detail view e9550a99a1 PHP 8.3 - Set same default as imap called function 17bde6e5a0 PHP 8.3 - Add default to cookie on susoap 01768fc962 PHP 8.3 - Remove deprecated param setting f3f62431fa PHP 8.3 - Remove deprecated ini setting 721fa93977 PHP 8.3 - Fix readability of if f9f7352a1a PHP 8.3 - Fix pdf template breaking listview 8bffb4d83a PHP 8.3 - Update null to -1 on preg split db663af17c PHP 8.3 - Remove unused time field on calls date start 32b6a6289b PHP 8.3 - don't show project task table if no tasks 04257e7663 PHP 8.3 - Fix Reports merge error 514319c6c9 PHP 8.3 - Add entry point variable to controller f37e6af3fb PHP 8.3 - Add quotes to array keys 955a6f8790 PHP 8.3 - Update deprecated use of self b81b1247ec PHP 8.3 - Fix invitee id on reminder 18375148b9 PHP 8.3 - Update OAuth searchdefs f5fdda17f5 PHP 8.3 - Fix margin reading as empty string ad144cb20a PHP 8.3 - Update function variable types f174ae27a8 PHP 8.3 - Update deprecated filter variable 29eb104ec5 PHP 8.3 - Update deprecated date functions 41ed417a94 PHP 8.3 - Update checks to not pass null 48169c4768 PHP 8.3 - Update Missing Labels b62e4a3d17 PHP 8.3 - Fix dashlet warnings and deprecations 5e745b6491 PHP 8.3 - Add and update variables on ListView b7dee66ccf PHP 8.3 - Update labels on tpl 2630420e9b PHP 8.3 - Add checks and defaults for editview field tpls 85aa7efd57 PHP 8.3 - Add checks for detailview field tpls 59d90f871e PHP 8.3 - Add extra checks on recurring message tpls 90fab9c2ac PHP 8.3 - Fix Duplicate variable init git-subtree-dir: public/legacy git-subtree-split: 52926f2943e25633d31237ea715b4f2a31c2762b
2024-11-29 13:24:11 +00:00
if (empty($bean->fetched_row)){
return;
}
if (isset($bean->email1) && !isset($bean->fetched_row['email1'])) {
$bean->fetched_row['email1'] = $bean->email1;
}
return;
}
public function populateLegacyFields(&$bean)
{
$primary_found = false;
$alternate_found = false;
$alternate2_found = false;
foreach ($this->addresses as $k => $address) {
if ($primary_found && $alternate_found) {
break;
}
if ($address['primary_address'] == 1 && !$primary_found) {
$primary_index = $k;
$primary_found = true;
} elseif (!$alternate_found) {
$alternate_index = $k;
$alternate_found = true;
} elseif (!$alternate2_found) {
$alternate2_index = $k;
$alternate2_found = true;
}
}
if ($primary_found) {
$bean->email1 = $this->addresses[$primary_index]['email_address'];
$bean->email_opt_out = $this->addresses[$primary_index]['opt_out'];
$bean->invalid_email = $this->addresses[$primary_index]['invalid_email'];
if ($alternate_found) {
$bean->email2 = $this->addresses[$alternate_index]['email_address'];
}
} elseif ($alternate_found) {
// Use the first found alternate as email1.
$bean->email1 = $this->addresses[$alternate_index]['email_address'];
$bean->email_opt_out = $this->addresses[$alternate_index]['opt_out'];
$bean->invalid_email = $this->addresses[$alternate_index]['invalid_email'];
if ($alternate2_found) {
$bean->email2 = $this->addresses[$alternate2_index]['email_address'];
}
}
}
/**
* @deprecated
* @param bool $check_notify
* @return null
*/
public function save($check_notify = false)
{
$deprecatedMessage = 'SugarEmailAddress::save() function calls are deprecated use SugarEmailAddress::saveEmail() function instead';
if (isset($GLOBALS['log'])) {
$GLOBALS['log']->deprecated($deprecatedMessage);
} else {
trigger_error($deprecatedMessage, E_USER_DEPRECATED);
}
list($id, $module, $new_addrs, $primary, $replyTo, $invalid, $optOut, $in_workflow) = func_get_args();
return $this->saveEmail($id, $module, $new_addrs, $primary, $replyTo, $invalid, $optOut, $in_workflow);
}
/**
* Saves email addresses for a parent bean.
* The base class SugarBean::save($check_notify) method is never called from SugarEmailAddresses::saveEmail(...)
* The method's signature has been changed to correctly represent the save method call for SugarEmailAddress.
* @param string $id Parent bean ID
* @param string $module Parent bean's module
* @param array $new_addrs Override of $_REQUEST vars, used to handle non-standard bean saves
* @param string $primary GUID of primary address
* @param string $replyTo GUID of reply-to address
* @param string $invalid GUID of invalid address
* @param string $optOut
* @param bool $in_workflow
* @param bool|null $opt_in
* @return null
*/
public function saveEmail(
$id,
$module,
$new_addrs = array(),
$primary = '',
$replyTo = '',
$invalid = '',
$optOut = '',
$in_workflow = false,
$optIn = null
) {
if (gettype($id) == "boolean") {
$GLOBALS['log']->fatal('SugarEmailAddress::saveEmail() Invalid arguments - Parent method SugarBean::save
($checknotify) is not implemented. Please pass the correct arguments into SugarEmailAddress::saveEmail()');
}
if (
empty($this->addresses)
|| $in_workflow === true
) {
$this->populateAddresses($id, $module, $new_addrs, $primary);
}
// handle the Employee/User split
$module = $this->getCorrectedModule($module);
// find all email addresses
$current_links = array();
$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";
$r2 = $this->db->query($q2);
while (($row2 = $this->db->fetchByAssoc($r2)) != null) {
$current_links[$row2['email_address_id']] = $row2;
}
$isConversion = (isset($_REQUEST) && isset($_REQUEST['action']) && $_REQUEST['action'] == 'ConvertLead') ? true : false;
if (!empty($this->addresses)) {
// insert new relationships and create email address record, if they don't exist
foreach ($this->addresses as $address) {
if (!empty($address['email_address'])) {
$guid = create_guid();
$emailId = isset($address['email_address_id'])
&& isset($current_links[$address['email_address_id']])
? $address['email_address_id'] : null;
$emailId = $this->AddUpdateEmailAddress(
$address['email_address'],
$address['invalid_email'],
$address['opt_out'],
$emailId,
!is_null($optIn) ? $address['confirm_opt_in_flag'] : null
);// this will save the email address if not found
//verify linkage and flags.
$upd_eabr = "";
if (isset($current_links[$emailId])) {
if (!$isConversion) { // do not update anything if this is for lead conversion
if ($address['primary_address'] != $current_links[$emailId]['primary_address'] or $address['reply_to_address'] != $current_links[$emailId]['reply_to_address']) {
$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']) . "'";
}
unset($current_links[$emailId]);
}
} else {
$primary = $address['primary_address'];
if (!empty($current_links) && $isConversion) {
foreach ($current_links as $eabr) {
if ($eabr['primary_address'] == 1) {
// for lead conversion, if there is already a primary email, do not insert another primary email
$primary = 0;
break;
}
}
}
$now = $this->db->now();
$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) . "', " . (int)$primary . ", " . (int)$address['reply_to_address'] . ", $now, $now, 0)";
}
if (!empty($upd_eabr)) {
$r2 = $this->db->query($upd_eabr);
}
}
}
}
//delete link to dropped email address.
// for lead conversion, do not delete email addresses
if (!empty($current_links) && !$isConversion) {
$delete = "";
foreach ($current_links as $eabr) {
$delete .= empty($delete) ? "'" . $this->db->quote($eabr['id']) . "' " : ",'" . $this->db->quote($eabr['id']) . "'";
}
$eabr_unlink = "update email_addr_bean_rel set deleted=1 where id in ({$delete})";
$this->db->query($eabr_unlink);
}
$this->stateBeforeWorkflow = null;
}
/**
* returns the number of email addresses found for a specifed bean
*
* @param string $email Address to match
* @param SugarBean $bean Bean to query against
* @param string $addressType Optional, pass a 1 to query against the primary address, 0 for the other addresses
* @return int Count of records found
* @throws \InvalidArgumentException
*/
public function getCountEmailAddressByBean(
$email,
$bean,
$addressType
) {
$addressTypeInt = (int)$addressType;
if ($addressType != 0 && $addressType != 1) {
throw new InvalidArgumentException(
'Invalid Address Type Argument: ' .
'pass a 1 to query against the primary address, 0 for the other addresses'
);
}
$emailCaps = strtoupper(trim($email));
if (empty($emailCaps)) {
return 0;
}
$q = "SELECT *
FROM email_addr_bean_rel eabl JOIN email_addresses ea
ON (ea.id = eabl.email_address_id)
JOIN {$bean->table_name} bean
ON (eabl.bean_id = bean.id)
WHERE ea.email_address_caps = '" . $this->db->quote($emailCaps) . "'
and eabl.bean_module = '" . $this->db->quote($bean->module_dir) . "'
and eabl.primary_address = '" . $this->db->quote($addressTypeInt) . "'
and eabl.deleted=0 ";
$r = $this->db->query($q);
// do it this way to make the count accurate in oracle
$i = 0;
while ($this->db->fetchByAssoc($r)) {
++$i;
}
return $i;
}
/**
* This function returns a contact or user ID if a matching email is found
* @param string $email the email address to match
* @param string $table which table to query
*/
public function getRelatedId($email, $module)
{
$email = $this->db->quote(trim(strtoupper($email)));
$module = $this->db->quote(ucfirst($module));
$q = "SELECT bean_id FROM email_addr_bean_rel eabr
JOIN email_addresses ea ON (eabr.email_address_id = ea.id)
WHERE bean_module = '$module' AND ea.email_address_caps = '$email' AND eabr.deleted=0";
$r = $this->db->query($q, true);
$returnArray = array();
while ($a = $this->db->fetchByAssoc($r)) {
$returnArray[] = $a['bean_id'];
}
if (count($returnArray) > 0) {
return $returnArray;
} else {
return false;
}
}
/**
* returns a collection of beans matching the email address
* @param string $email Address to match
* @return array
*/
public function getBeansByEmailAddress($email)
{
global $beanList;
global $beanFiles;
$return = array();
$email = trim($email);
if (empty($email)) {
return array();
}
$emailCaps = "'" . $this->db->quote(strtoupper($email)) . "'";
$q = "SELECT * FROM email_addr_bean_rel eabl JOIN email_addresses ea ON (ea.id = eabl.email_address_id and ea.deleted = 0)
WHERE ea.email_address_caps = $emailCaps and eabl.deleted=0 ";
$r = $this->db->query($q);
while ($a = $this->db->fetchByAssoc($r)) {
if (isset($beanList[$a['bean_module']]) && !empty($beanList[$a['bean_module']])) {
$className = $beanList[$a['bean_module']];
if (isset($beanFiles[$className]) && !empty($beanFiles[$className])) {
if (!class_exists($className)) {
require_once($beanFiles[$className]);
}
$bean = BeanFactory::getBean($a['bean_module'], $a['bean_id']);
if ($bean !== false) {
$return[] = $bean;
}
} else {
$GLOBALS['log']->fatal("SUGAREMAILADDRESS: could not find valid class file for [ {$className} ]");
}
} else {
$GLOBALS['log']->fatal("SUGAREMAILADDRESS: could not find valid class [ {$a['bean_module']} ]");
}
}
return $return;
}
/**
* Saves email addresses for a parent bean
* @param string $id Parent bean ID
* @param string $module Parent bean's module
* @param array $new_addrs Override of $_REQUEST vars, used to handle non-standard bean saves
* @param string $primary GUID of primary address
* @param string $replyTo GUID of reply-to address
*/
public function populateAddresses(
$id,
$module,
$new_addrs = array(),
$primary = '',
$replyTo = '',
$invalid = '',
$optOut = ''
) {
if (!is_array($new_addrs)) {
$new_addrs = array($new_addrs);
}
$module = $this->getCorrectedModule($module);
//One last check for the ConvertLead action in which case we need to change $module to 'Leads'
$module = (isset($_REQUEST) && isset($_REQUEST['action']) && $_REQUEST['action'] === 'ConvertLead') ? 'Leads' : $module;
$post_from_email_address_widget = (isset($_REQUEST[$module . '_email_widget_id']));
$primaryValue = $primary;
$widgetCount = 0;
$widget_id = '';
$hasEmailValue = false;
$email_ids = array();
if (isset($_REQUEST[$module . '_email_widget_id'])) {
$fromRequest = false;
// determine which array to process
foreach ($_REQUEST as $k => $v) {
if (strpos($k, 'emailAddress') !== false) {
$fromRequest = true;
break;
}
$widget_id = $_REQUEST[$module . '_email_widget_id'];
}
if (empty($widget_id)) {
$GLOBALS['log']->debug('Widget not found, so it should be an update and not a create');
}
//Iterate over the widgets for this module, in case there are multiple email widgets for this module
while (isset($_REQUEST[$module . $widget_id . 'emailAddress' . $widgetCount])) {
if (empty($_REQUEST[$module . $widget_id . 'emailAddress' . $widgetCount])) {
$widgetCount++;
continue;
}
$hasEmailValue = true;
$eId = $module . $widget_id;
if (isset($_REQUEST[$eId . 'emailAddressPrimaryFlag'])) {
$primaryValue = $_REQUEST[$eId . 'emailAddressPrimaryFlag'];
} elseif (isset($_REQUEST[$module . 'emailAddressPrimaryFlag'])) {
$primaryValue = $_REQUEST[$module . 'emailAddressPrimaryFlag'];
}
$optOutValues = array();
if (isset($_REQUEST[$eId . 'emailAddressOptOutFlag'])) {
$optOutValues = $_REQUEST[$eId . 'emailAddressOptOutFlag'];
} elseif (isset($_REQUEST[$module . 'emailAddressOptOutFlag'])) {
$optOutValues = $_REQUEST[$module . 'emailAddressOptOutFlag'];
}
$invalidValues = array();
if (isset($_REQUEST[$eId . 'emailAddressInvalidFlag'])) {
$invalidValues = $_REQUEST[$eId . 'emailAddressInvalidFlag'];
} elseif (isset($_REQUEST[$module . 'emailAddressInvalidFlag'])) {
$invalidValues = $_REQUEST[$module . 'emailAddressInvalidFlag'];
}
$optInValues = array();
if (isset($_REQUEST[$eId . 'emailAddressOptInFlag'])) {
$optInValues = $_REQUEST[$eId . 'emailAddressOptInFlag'];
} elseif (isset($_REQUEST[$module . 'emailAddressOptInFlag'])) {
$optInValues = $_REQUEST[$module . 'emailAddressOptInFlag'];
}
$deleteValues = array();
if (isset($_REQUEST[$eId . 'emailAddressDeleteFlag'])) {
$deleteValues = $_REQUEST[$eId . 'emailAddressDeleteFlag'];
} elseif (isset($_REQUEST[$module . 'emailAddressDeleteFlag'])) {
$deleteValues = $_REQUEST[$module . 'emailAddressDeleteFlag'];
}
// prep from form save
$replyToField = '';
$invalidField = '';
$optOutField = '';
$optInField = '';
if ($fromRequest && empty($primary) && isset($primaryValue)) {
$primaryField = $primaryValue;
}
if ($fromRequest && empty($replyTo)) {
if (isset($_REQUEST[$eId . 'emailAddressReplyToFlag'])) {
$replyToField = $_REQUEST[$eId . 'emailAddressReplyToFlag'];
} elseif (isset($_REQUEST[$module . 'emailAddressReplyToFlag'])) {
$replyToField = $_REQUEST[$module . 'emailAddressReplyToFlag'];
}
}
if ($fromRequest && empty($new_addrs)) {
foreach ($_REQUEST as $k => $v) {
if (preg_match('/' . $eId . 'emailAddress[0-9]+$/i', $k) && !empty($v)) {
$new_addrs[$k] = $v;
}
}
}
if ($fromRequest && empty($email_ids)) {
foreach ($_REQUEST as $k => $v) {
if (preg_match('/' . $eId . 'emailAddressId[0-9]+$/i', $k) && !empty($v)) {
$key = str_replace('emailAddressId', 'emailAddress', $k);
$email_ids[$key] = $v;
}
}
}
// NOTE: probably it's never gonna happen:
// $fromRequest became true if there is any emailAddress in request but
// $new_addrs never empty because it's got a value if there is any emailAddress
if ($fromRequest && empty($new_addrs)) {
foreach ($_REQUEST as $k => $v) {
if (preg_match('/' . $eId . 'emailAddressVerifiedValue[0-9]+$/i', $k) && !empty($v)) {
$validateFlag = str_replace("Value", "Flag", $k);
if (isset($_REQUEST[$validateFlag]) && $_REQUEST[$validateFlag] == "true") {
$new_addrs[$k] = $v;
}
}
}
}
//empty the addresses array if the post happened from email address widget.
if ($post_from_email_address_widget) {
$this->addresses = array(); //this gets populated during retrieve of the contact bean.
} else {
$optOutValues = array();
$invalidValues = array();
foreach ($new_addrs as $k => $email) {
preg_match('/emailAddress([0-9])+$/', $k, $matches);
$count = $matches[1];
$query = "SELECT opt_out, invalid_email, confirm_opt_in FROM email_addresses WHERE email_address_caps = '" . $this->db->quote(strtoupper($email)) . "'";
$result = $this->db->query($query);
if (!empty($result)) {
$row = $this->db->fetchByAssoc($result);
if (!empty($row['opt_out'])) {
$optOutValues[$k] = "emailAddress$count";
}
if (!empty($row['invalid_email'])) {
$invalidValues[$k] = "emailAddress$count";
}
if (!empty($row['confirm_opt_in'])) {
$optInValues[$k] = "emailAddress$count";
}
}
}
}
// Re-populate the addresses class variable if we have new address(es).
if (!empty($new_addrs)) {
foreach ($new_addrs as $k => $reqVar) {
//$key = preg_match("/^$eId/s", $k) ? substr($k, strlen($eId)) : $k;
$reqVar = trim($reqVar);
if (strpos($k, 'emailAddress') !== false) {
if (!is_array($deleteValues)) {
$GLOBALS['log']->fatal('Invalid Argument: Delete Values to be an array, ' . gettype($deleteValues) . ' given.');
} else {
if (!empty($reqVar) && !in_array($k, $deleteValues)) {
$email_id = (array_key_exists($k, $email_ids)) ? $email_ids[$k] : null;
$primary = ($k == $primaryValue) ? true : false;
$replyTo = ($k == $replyToField) ? true : false;
$invalid = (in_array($k, (array)$invalidValues)) ? true : false;
$optOut = (in_array($k, (array)$optOutValues)) ? true : false;
$optIn = (in_array($k, $optInValues)) ? true : false;
$this->addAddress(
trim($new_addrs[$k]),
$primary,
$replyTo,
$invalid,
$optOut,
$email_id,
$optIn
);
}
}
}
} //foreach
}
$widgetCount++;
}//End of Widget for loop
}
//If no widgets, set addresses array to empty
if ($post_from_email_address_widget && !$hasEmailValue) {
$this->addresses = array();
}
}
/**
* Preps internal array structure for email addresses
* @param string $addr Email address
* @param bool $primary Default false
* @param bool $replyTo Default false
* @param bool $invalid Default false
* @param bool $optOut Default false
* @param string $email_id
* @param bool $optIn Default false
*/
public function addAddress(
$addr,
$primary = false,
$replyTo = false,
$invalid = false,
$optOut = false,
$email_id = null,
$optIn = null
) {
$addr = html_entity_decode($addr, ENT_QUOTES);
if (empty($addr)) {
return;
}
if (preg_match($this->regex, $addr)) {
$primaryFlag = ($primary) ? '1' : '0';
$replyToFlag = ($replyTo) ? '1' : '0';
$invalidFlag = ($invalid) ? '1' : '0';
$optOutFlag = ($optOut) ? '1' : '0';
if (!is_null($optIn)) {
$optInFlag = ($optIn) ? '1' : '0';
}
$addr = trim($addr);
// If we have such address already, remove it and add new one in.
foreach ($this->addresses as $k => $address) {
if ($address['email_address'] == $addr) {
unset($this->addresses[$k]);
} elseif ($primary && $address['primary_address'] == '1') {
// We should only have one primary. If we are adding a primary but
// we find an existing primary, reset this one's primary flag.
$this->addresses[$k]['primary_address'] = '0';
}
}
$addr = array(
'email_address' => $addr,
'primary_address' => $primaryFlag,
'reply_to_address' => $replyToFlag,
'invalid_email' => $invalidFlag,
'opt_out' => $optOutFlag,
'email_address_id' => $email_id,
'confirm_opt_in_flag' => null,
);
if (!is_null($optIn)) {
$addr['confirm_opt_in_flag'] = $optInFlag;
}
$this->addresses[] = $addr;
} else {
$GLOBALS['log']->fatal("SUGAREMAILADDRESS: address did not valid [ {$addr} ]");
}
}
/**
* Updates invalid_email and opt_out flags for each address
*/
public function updateFlags()
{
if (!empty($this->addresses)) {
foreach ($this->addresses as $addressMeta) {
if (isset($addressMeta['email_address']) && !empty($addressMeta['email_address'])) {
$address = $this->db->quote($this->_cleanAddress($addressMeta['email_address']));
$q = "SELECT * FROM email_addresses WHERE email_address = '{$address}'";
$r = $this->db->query($q);
$a = $this->db->fetchByAssoc($r);
if (
!empty($a) &&
(
isset($a['invalid_email']) &&
isset($addressMeta['invalid_email']) &&
isset($addressMeta['opt_out']) &&
$a['invalid_email'] != $addressMeta['invalid_email'] ||
$a['opt_out'] != $addressMeta['opt_out']
)
) {
$addressMetaInvalidEmailInt = (int)$addressMeta['invalid_email'];
$addressMetaOptOutInt = (int)$addressMeta['opt_out'];
$now = TimeDate::getInstance()->nowDb();
$id = $this->db->quote($a['id']);
$qUpdate = /** @lang sql */
"UPDATE email_addresses SET
invalid_email = {$addressMetaInvalidEmailInt},
opt_out = {$addressMetaOptOutInt},
date_modified = '{$now}'
WHERE id = '{$id}'";
$this->db->query($qUpdate);
}
}
}
}
}
public function splitEmailAddress($addr)
{
$email = $this->_cleanAddress($addr);
if (!preg_match($this->regex, $email)) {
$email = ''; // remove bad email addr
}
$name = trim(str_replace(array($email, '<', '>', '"', "'"), '', $addr));
return array("name" => $name, "email" => strtolower($email));
}
/**
* PRIVATE UTIL
* Normalizes an RFC-clean email address, returns a string that is the email address only
* @param string $addr Dirty email address
* @return string clean email address
*/
public function _cleanAddress($addr)
{
$addr = trim(from_html($addr));
if (strpos($addr, "<") !== false && strpos($addr, ">") !== false) {
$address = trim(substr($addr, strrpos($addr, "<") + 1, strrpos($addr, ">") - strrpos($addr, "<") - 1));
} else {
$address = trim($addr);
}
return $address;
}
/**
* preps a passed email address for email address storage
* @param string $addr Address in focus, must be RFC compliant
* @return string $id email_addresses ID
*/
public function getEmailGUID($addr)
{
$address = $this->db->quote($this->_cleanAddress($addr));
$addressCaps = strtoupper($address);
$q = "SELECT id FROM email_addresses WHERE email_address_caps = '{$addressCaps}'";
$r = $this->db->query($q);
$a = $this->db->fetchByAssoc($r);
if (!empty($a) && !empty($a['id'])) {
return $a['id'];
} else {
$guid = '';
if (!empty($address)) {
$guid = create_guid();
$now = TimeDate::getInstance()->nowDb();
$qa = "INSERT INTO email_addresses (id, email_address, email_address_caps, date_created, date_modified, deleted)
VALUES('{$guid}', '{$address}', '{$addressCaps}', '$now', '$now', 0)";
$ra = $this->db->query($qa);
}
return $guid;
}
}
/**
* Creates or Updates an entry in the email_addresses table, depending
* on if the email address submitted matches a previous entry (case-insensitive)
* @param string $addr - email address
* @param int $invalid - is the email address marked as Invalid?
* @param int $opt_out - is the email address marked as Opt-Out?
* @param string $id - the GUID of the original SugarEmailAddress bean,
* in case a "email has changed" WorkFlow has triggered - hack to allow workflow-induced changes
* to propagate to the new SugarEmailAddress - see bug 39188
* @param int|null $optInFlag
* @return string GUID of Email Address or '' if cleaned address was empty.
*/
public function AddUpdateEmailAddress($addr, $invalid = 0, $opt_out = 0, $id = null, $optInFlag = null)
{
// sanity checks to avoid SQL injection.
$invalid = (int)$invalid;
$opt_out = (int)$opt_out;
$address = $this->db->quote($this->_cleanAddress($addr));
$addressCaps = strtoupper($address);
// determine if we have a matching email address
$q = "SELECT * FROM email_addresses WHERE email_address_caps = '{$addressCaps}' and deleted=0";
$r = $this->db->query($q);
$duplicate_email = $this->db->fetchByAssoc($r);
// check if we are changing an email address, where workflow might be in play
if ($id) {
$query = "SELECT * FROM email_addresses WHERE id='" . $this->db->quote($id) . "'";
$r = $this->db->query($query);
$current_email = $this->db->fetchByAssoc($r);
} else {
$current_email = null;
}
// unless workflow made changes, assume parameters are what to use.
$new_opt_out = $opt_out;
$new_invalid = $invalid;
if (!empty($current_email['id']) && isset($this->stateBeforeWorkflow[$current_email['id']])) {
if ($current_email['invalid_email'] != $invalid ||
$current_email['opt_out'] != $opt_out
) {
// workflow could be in play
$before_email = $this->stateBeforeWorkflow[$current_email['id']];
// our logic is as follows: choose from parameter, unless workflow made a change to the value, then choose final value
if ((int)$before_email['opt_out'] != (int)$current_email['opt_out']) {
$new_opt_out = (int)$current_email['opt_out'];
}
if ((int)$before_email['invalid_email'] != (int)$current_email['invalid_email']) {
$new_invalid = (int)$current_email['invalid_email'];
}
}
}
// confirmed opt in check
if (!is_null($optInFlag)) {
$optInFlag = (int)$optInFlag;
}
$isValidEmailAddress = ($opt_out !== 1 && $invalid !== 1);
$this->retrieve($id);
$optInIndication = $this->getOptInStatus();
if (
$isValidEmailAddress
&& $this->isOptedInStatus($optInIndication)
&& (int)$optInFlag === 1
) {
$new_confirmed_opt_in = $this->getConfirmedOptInState();
} elseif (
$isValidEmailAddress
&& (int)$optInFlag === 1
) {
// In case optInFlag is set and there is a duplicate,
// copy the opt-in state from it if it has some kind of opt-in set.
// This prevents losing the confirmed opt-in state in case we
// update an existing record with "confirmed-opt-in"
if (!empty($duplicate_email['id']) && $duplicate_email['confirm_opt_in'] != self::COI_STAT_DISABLED) {
$new_confirmed_opt_in = $duplicate_email['confirm_opt_in'];
} else {
$new_confirmed_opt_in = self::COI_STAT_OPT_IN;
}
} else {
// Reset the opt in status
$new_confirmed_opt_in = self::COI_STAT_DISABLED;
}
// determine how we are going to put in this address - UPDATE or INSERT
if (!empty($duplicate_email['id'])) {
$duplicate = clone $this;
$duplicate->retrieve($duplicate_email['id']);
// address_caps matches - see if we're changing fields
if (
$duplicate_email['invalid_email'] != $new_invalid
|| $duplicate_email['opt_out'] != $new_opt_out
|| (!is_null($optInFlag) && $duplicate_email['confirm_opt_in'] != $new_confirmed_opt_in)
|| (trim($duplicate_email['email_address']) != $address)
) {
$upd_q = 'UPDATE ' . $this->table_name . ' ' .
'SET email_address=\'' . $address . '\', ' .
'invalid_email=' . $new_invalid . ', ' .
'opt_out=' . $new_opt_out . ', ' .
(!is_null($optInFlag) ? ('confirm_opt_in=\'' . $this->db->quote($new_confirmed_opt_in) . '\', ') : '') .
'date_modified=' . $this->db->now() . ' ' .
'WHERE id=\'' . $this->db->quote($duplicate_email['id']) . '\'';
// set for audit table detection
$duplicate->invalid_email = $new_invalid;
$duplicate->opt_out = $new_opt_out;
$duplicate->confirm_opt_in = $new_confirmed_opt_in;
$upd_r = $this->db->query($upd_q);
if ($new_confirmed_opt_in === self::COI_STAT_DISABLED) {
// reset confirm opt in
$upd_q = 'UPDATE ' . $this->table_name . ' ' .
'SET '.
'confirm_opt_in_date=NULL,' .
'confirm_opt_in_sent_date=NULL,' .
'confirm_opt_in_fail_date=NULL ' .
'WHERE id=\'' . $this->db->quote($duplicate_email['id']) . '\'';
$upd_r = $this->db->query($upd_q);
// set for audit table detection
$duplicate->confirm_opt_in = null;
}
}
if (!empty($this->fetched_row)) {
foreach ($this->fetched_row as $fieldName => $fieldValue) {
$this->{$fieldName} = $duplicate->{$fieldName};
}
}
$this->auditBean(true);
return $duplicate_email['id'];
} else {
// no case-insensitive address match - it's new, or undeleted.
$guid = '';
$isUpdate = true;
if (!empty($address)) {
$guid = create_guid();
$now = TimeDate::getInstance()->nowDb();
$qa = "INSERT INTO email_addresses (id, email_address, email_address_caps, date_created, date_modified, deleted, invalid_email, opt_out" . (!is_null($optInFlag) ? ", confirm_opt_in" : '') . ")
VALUES('{$guid}', '{$address}', '{$addressCaps}', '$now', '$now', 0 , $new_invalid, $new_opt_out" . (!is_null($optInFlag) ? ", '" . $this->db->quote($new_confirmed_opt_in) ."'" : '') . ")";
$this->db->query($qa);
$isUpdate = false;
}
$this->auditBean($isUpdate);
return $guid;
}
}
/**
* @return string
*/
public function getConfirmedOptInState()
{
return $this->confirm_opt_in;
}
/**
* Returns Primary or newest email address
* @param object $focus Object in focus
* @return string email
*/
public function getPrimaryAddress($focus, $parent_id = null, $parent_type = null)
{
$parent_type = empty($parent_type) ? $focus->module_dir : $parent_type;
// Bug63174: Email address is not shown in the list view for employees
$parent_type = $this->getCorrectedModule($parent_type);
$parent_id = empty($parent_id) ? $focus->id : $parent_id;
$q = "SELECT ea.email_address FROM email_addresses ea
LEFT JOIN email_addr_bean_rel ear ON ea.id = ear.email_address_id
WHERE ear.bean_module = '" . $this->db->quote($parent_type) . "'
AND ear.bean_id = '" . $this->db->quote($parent_id) . "'
AND ear.deleted = 0
AND ea.invalid_email = 0
ORDER BY ear.primary_address DESC";
$r = $this->db->limitQuery($q, 0, 1);
$a = $this->db->fetchByAssoc($r);
if (isset($a['email_address'])) {
return $a['email_address'];
}
return '';
}
/**
* As long as this function is used not only to retrieve user's Reply-To
* address, but also notification address and so on, there were added
* $replyToOnly optional parameter used to retrieve only address marked as
* Reply-To (bug #43643).
*
* @param SugarBean $focus
* @param bool $replyToOnly
* @return string
*/
public function getReplyToAddress($focus, $replyToOnly = false)
{
$q = "SELECT ea.email_address FROM email_addresses ea
LEFT JOIN email_addr_bean_rel ear ON ea.id = ear.email_address_id
WHERE ear.bean_module = '" . $this->db->quote($focus->module_dir) . "'
AND ear.bean_id = '" . $this->db->quote($focus->id) . "'
AND ear.deleted = 0
AND ea.invalid_email = 0";
if (!$replyToOnly) {
// retrieve reply-to address if it exists or any other address
// otherwise
$q .= "
ORDER BY ear.reply_to_address DESC";
} else {
// retrieve reply-to address only
$q .= "
AND ear.reply_to_address = 1";
}
$r = $this->db->query($q);
$a = $this->db->fetchByAssoc($r);
if (isset($a['email_address'])) {
return $a['email_address'];
}
return '';
}
/**
* Returns all email addresses by parent's GUID
* @param string $id Parent's GUID
* @param string $module Parent's module
* @return array
*/
public function getAddressesByGUID($id, $module)
{
$return = array();
$module = $this->getCorrectedModule($module);
$q = "SELECT
ea.email_address,
ea.email_address_caps,
ea.invalid_email,
ea.opt_out,
ea.confirm_opt_in,
ea.date_created,
ea.date_modified,
ear.id,
ear.email_address_id,
ear.bean_id,
ear.bean_module,
ear.primary_address,
ear.reply_to_address,
ear.deleted
FROM email_addresses ea LEFT JOIN email_addr_bean_rel ear ON ea.id = ear.email_address_id
WHERE
ear.bean_module = '" . $this->db->quote($module) . "'
AND ear.bean_id = '" . $this->db->quote($id) . "'
AND ear.deleted = 0
ORDER BY ear.reply_to_address, ear.primary_address DESC";
$r = $this->db->query($q);
while ($a = $this->db->fetchByAssoc($r, false)) {
$return[] = $a;
}
return $return;
}
/**
* Returns the HTML/JS for the EmailAddress widget
* @global LoggerManager $log
* @global array $app_strings
* @global array $dictionary
* @global array $beanList
* @param string $parent_id ID of parent bean, generally $focus
* @param string $module $focus' module
* @param bool asMetadata Default false
* @return string HTML/JS for widget
*/
public function getEmailAddressWidgetEditView($id, $module, $asMetadata = false, $tpl = '', $tabindex = '0')
{
if (null === $id) {
$GLOBALS['log']->debug('ID is null so it should be a create and NOT an update');
}
if (null === $module) {
$GLOBALS['log']->fatal('Invalid Argument: module');
return false;
}
if (!($this->smarty instanceof Sugar_Smarty)) {
$this->smarty = new Sugar_Smarty();
}
global $app_strings;
global $dictionary;
global $beanList;
$configurator = new Configurator();
$prefill = 'false';
$prefillData = 'new Object()';
$passedModule = $module;
$module = $this->getCorrectedModule($module);
$saveModule = $module;
if (isset($_POST['is_converted']) && $_POST['is_converted'] == true) {
if (!isset($_POST['return_id'])) {
$GLOBALS['log']->fatal('return_id not set');
$id = null;
} else {
$id = $_POST['return_id'];
}
if (!isset($_POST['return_module'])) {
$GLOBALS['log']->fatal('return_module not set');
$module = '';
} else {
$module = $_POST['return_module'];
}
}
$prefillDataArr = array();
if (!empty($id)) {
$prefillDataArr = $this->getAddressesByGUID($id, $module);
//When coming from convert leads, sometimes module is Contacts while the id is for a lead.
if (empty($prefillDataArr) && $module == "Contacts") {
$prefillDataArr = $this->getAddressesByGUID($id, "Leads");
}
} elseif (isset($_REQUEST['full_form']) && !empty($_REQUEST['emailAddressWidget'])) {
$widget_id = isset($_REQUEST[$module . '_email_widget_id']) ? $_REQUEST[$module . '_email_widget_id'] : '0';
$count = 0;
$key = $module . $widget_id . 'emailAddress' . $count;
while (isset($_REQUEST[$key])) {
$email = $_REQUEST[$key];
$prefillDataArr[] = array(
'email_address' => $email,
'primary_address' => isset($_REQUEST['emailAddressPrimaryFlag']) && $_REQUEST['emailAddressPrimaryFlag'] == $key,
'invalid_email' => isset($_REQUEST['emailAddressInvalidFlag']) && in_array(
$key,
$_REQUEST['emailAddressInvalidFlag']
),
'opt_out' => isset($_REQUEST['emailAddressOptOutFlag']) && in_array(
$key,
$_REQUEST['emailAddressOptOutFlag']
),
'reply_to_address' => false
);
$key = $module . $widget_id . 'emailAddress' . ++$count;
} //while
}
if (!empty($prefillDataArr)) {
$json = new JSON();
$prefillData = $json->encode($prefillDataArr);
$prefill = !empty($prefillDataArr) ? 'true' : 'false';
}
$required = false;
$moduleFound = true;
if (!isset($beanList[$passedModule])) {
$GLOBALS['log']->fatal('Module not found in bean list: ' . $passedModule);
$moduleFound = false;
} elseif (!isset($dictionary[$beanList[$passedModule]])) {
$GLOBALS['log']->fatal('Module bean not found in dictionary: ' . $beanList[$passedModule]);
$moduleFound = false;
}
if ($moduleFound) {
$vardefs = $dictionary[$beanList[$passedModule]]['fields'];
} else {
return false;
}
if (!empty($vardefs['email1']) && isset($vardefs['email1']['required']) && $vardefs['email1']['required']) {
$required = true;
}
$this->smarty->assign('required', $required);
$this->smarty->assign('module', $saveModule);
$this->smarty->assign('index', $this->index);
$this->smarty->assign('app_strings', $app_strings);
$this->smarty->assign('prefillEmailAddresses', $prefill);
$this->smarty->assign('prefillData', $prefillData);
$this->smarty->assign('tabindex', $tabindex);
//Set addDefaultAddress flag (do not add if it's from the Email module)
$this->smarty->assign(
'addDefaultAddress',
(isset($_REQUEST['module']) && $_REQUEST['module'] == 'Emails') ? 'false' : 'true'
);
$form = $this->view;
//determine if this should be a quickcreate form, or a quick create form under subpanels
if ($this->view == "QuickCreate") {
// Fixed #1120 - fixed email validation for: Accounts -> Contacts subpanel -> Select -> Create Contact -> Save.
// If email is required it should highlight this field and show an error message.
// It didnt because the form was named form_DCSubpanelQuickCreate_Contacts instead of expected form_SubpanelQuickCreate_Contacts
if ($this->object_name = 'EmailAddress' && $saveModule == 'Contacts') {
$form = 'form_' . $this->view . '_' . $module;
} else {
$form = 'form_DC' . $this->view . '_' . $module;
}
if (isset($_REQUEST['action']) && (isset($_REQUEST['action']) && $_REQUEST['action'] == 'SubpanelCreates' || $_REQUEST['action'] == 'SubpanelEdits')) {
$form = 'form_Subpanel' . $this->view . '_' . $module;
}
}
$this->smarty->assign('emailView', $form);
if ($module == 'Users') {
$this->smarty->assign('useReplyTo', true);
} else {
$this->smarty->assign('useOptOut', true);
$this->smarty->assign('useInvalid', true);
if (
$configurator->isOptInEnabled()
|| $configurator->isConfirmOptInEnabled()
) {
$this->smarty->assign('useOptIn', true);
} else {
$this->smarty->assign('useOptIn', false);
}
}
$template = empty($tpl) ? "include/SugarEmailAddress/templates/forEditView.tpl" : $tpl;
$newEmail = $this->smarty->fetch($template);
if ($asMetadata) {
// used by Email 2.0
$return = array();
$return['prefillData'] = $prefillDataArr;
$return['html'] = $newEmail;
return $return;
}
return $newEmail;
}
/**
* Returns the HTML/JS for the EmailAddress widget
* @param object $focus Bean in focus
* @return string HTML/JS for widget
*/
public function getEmailAddressWidgetDetailView($focus, $tpl = '')
{
if (!($this->smarty instanceof Sugar_Smarty)) {
$this->smarty = new Sugar_Smarty();
}
global $app_strings;
global $current_user;
$assign = array();
if (empty($focus->id)) {
return '';
}
$prefillData = $this->getAddressesByGUID($focus->id, $focus->module_dir);
foreach ($prefillData as $addressItem) {
$key = ($addressItem['primary_address'] == 1) ? 'primary' : '';
$key = ($addressItem['reply_to_address'] == 1) ? 'reply_to' : $key;
$key = ($addressItem['opt_out'] == 1) ? 'opt_out' : $key;
$key = ($addressItem['invalid_email'] == 1) ? 'invalid' : $key;
$key = ($addressItem['opt_out'] == 1) && ($addressItem['invalid_email'] == 1) ? 'opt_out_invalid' : $key;
$emailAddress = array(
'key' => $key,
'address' => $current_user->getEmailLink2($addressItem['email_address'], $focus)
);
if (empty($emailAddress['address'])) {
// Email Link is missing, lets just print the email address in plain text instead.
$emailAddress['address'] = $addressItem['email_address'];
}
$assign[] =$emailAddress;
}
$this->smarty->assign('app_strings', $app_strings);
$this->smarty->assign('emailAddresses', $assign);
$templateFile = empty($tpl) ? "include/SugarEmailAddress/templates/forDetailView.tpl" : $tpl;
$return = $this->smarty->fetch($templateFile);
return $return;
}
/**
* getEmailAddressWidgetDuplicatesView($focus)
* @param object $focus Bean in focus
* @return string HTML that contains hidden input values based off of HTML request
*/
public function getEmailAddressWidgetDuplicatesView($focus)
{
if (!($this->smarty instanceof Sugar_Smarty)) {
$this->smarty = new Sugar_Smarty();
}
$count = 0;
$emails = array();
$primary = null;
$optOut = array();
$invalid = array();
$mod = isset($focus) ? $focus->module_dir : "";
if (!isset($_POST) || !isset($_POST[$mod . '_email_widget_id'])) {
$GLOBALS['log']->fatal("Missing Argument: a required post variable not found: {$mod}_email_widget_id");
$widget_id = null;
} else {
$widget_id = $_POST[$mod . '_email_widget_id'];
}
$this->smarty->assign('email_widget_id', $widget_id);
$emailAddressWidget = null;
if (isset($_POST['emailAddressWidget'])) {
$emailAddressWidget = $_POST['emailAddressWidget'];
} else {
$GLOBALS['log']->fatal('Missing Argument: a required post variable not found: emailAddressWidget');
}
$this->smarty->assign('emailAddressWidget', $emailAddressWidget);
if (isset($_POST[$mod . $widget_id . 'emailAddressPrimaryFlag'])) {
$primary = $_POST[$mod . $widget_id . 'emailAddressPrimaryFlag'];
}
while (isset($_POST[$mod . $widget_id . "emailAddress" . $count])) {
$emails[] = $_POST[$mod . $widget_id . 'emailAddress' . $count];
$count++;
}
if ($count == 0) {
return "";
}
if (isset($_POST[$mod . $widget_id . 'emailAddressOptOutFlag'])) {
if (
!is_array($_POST[$mod . $widget_id . 'emailAddressOptOutFlag']) ||
!is_object($_POST[$mod . $widget_id . 'emailAddressOptOutFlag'])
) {
$GLOBALS['log']->fatal(
'Invalid Argument: post variable ' .
$mod . $widget_id . 'emailAddressOptOutFlag' .
' should be an array, ' .
gettype($_POST[$mod . $widget_id . 'emailAddressOptOutFlag']) . ' given'
);
}
foreach ((array)$_POST[$mod . $widget_id . 'emailAddressOptOutFlag'] as $v) {
$optOut[] = $v;
}
}
if (isset($_POST[$mod . $widget_id . 'emailAddressInvalidFlag'])) {
if (
!is_array($_POST[$mod . $widget_id . 'emailAddressInvalidFlag']) ||
!is_object($_POST[$mod . $widget_id . 'emailAddressInvalidFlag'])
) {
$GLOBALS['log']->fatal(
'Invalid Argument: post variable ' .
$mod . $widget_id . 'emailAddressInvalidFlag' .
' should be an array, ' .
gettype($_POST[$mod . $widget_id . 'emailAddressInvalidFlag']) . ' given'
);
}
foreach ((array)$_POST[$mod . $widget_id . 'emailAddressInvalidFlag'] as $v) {
$invalid[] = $v;
}
}
if (isset($_POST[$mod . $widget_id . 'emailAddressReplyToFlag'])) {
if (
!is_array($_POST[$mod . $widget_id . 'emailAddressReplyToFlag']) ||
!is_object($_POST[$mod . $widget_id . 'emailAddressReplyToFlag'])
) {
$GLOBALS['log']->fatal(
'Invalid Argument: post variable ' .
$mod . $widget_id . 'emailAddressReplyToFlag' .
' should be an array, ' .
gettype($_POST[$mod . $widget_id . 'emailAddressReplyToFlag']) . ' given'
);
}
foreach ((array)$_POST[$mod . $widget_id . 'emailAddressReplyToFlag'] as $v) {
$replyTo[] = $v;
}
}
if (isset($_POST[$mod . $widget_id . 'emailAddressDeleteFlag'])) {
if (
!is_array($_POST[$mod . $widget_id . 'emailAddressDeleteFlag']) ||
!is_object($_POST[$mod . $widget_id . 'emailAddressDeleteFlag'])
) {
$GLOBALS['log']->fatal(
'Invalid Argument: post variable ' .
$mod . $widget_id . 'emailAddressDeleteFlag' .
' should be an array, ' .
gettype($_POST[$mod . $widget_id . 'emailAddressDeleteFlag']) . ' given'
);
}
foreach ((array)$_POST[$mod . $widget_id . 'emailAddressDeleteFlag'] as $v) {
$delete[] = $v;
}
}
while (isset($_POST[$mod . $widget_id . "emailAddressVerifiedValue" . $count])) {
if (
!is_array($_POST[$mod . $widget_id . 'emailAddressVerifiedValue' . $count]) ||
!is_object($_POST[$mod . $widget_id . 'emailAddressVerifiedValue' . $count])
) {
$GLOBALS['log']->fatal(
'Invalid Argument: post variable ' .
$mod . $widget_id . 'emailAddressVerifiedValue' . $count .
' not found.'
);
}
$verified[] = $_POST[$mod . $widget_id . 'emailAddressVerifiedValue' . $count];
$count++;
}
$this->smarty->assign('emails', $emails);
$this->smarty->assign('primary', $primary);
$this->smarty->assign('optOut', $optOut);
$this->smarty->assign('invalid', $invalid);
$this->smarty->assign('replyTo', $invalid);
$this->smarty->assign('delete', $invalid);
$this->smarty->assign('verified', $invalid);
$this->smarty->assign('moduleDir', $mod);
return $this->smarty->fetch("include/SugarEmailAddress/templates/forDuplicatesView.tpl");
}
/**
* getFormBaseURL
*
*/
public function getFormBaseURL($focus)
{
$get = "";
$count = 0;
$mod = isset($focus) ? $focus->module_dir : "";
if (!$mod) {
$GLOBALS['log']->fatal('Invalid Argument: Missing module dir.');
return false;
}
$widget_id = '';
if (!isset($_POST[$mod . '_email_widget_id'])) {
$GLOBALS['log']->fatal('Invalid Argument: requested argument missing: "' . $mod . '_email_widget_id"');
} else {
$widget_id = $_POST[$mod . '_email_widget_id'];
}
$get .= '&' . $mod . '_email_widget_id=' . $widget_id;
if (!isset($_POST['emailAddressWidget'])) {
$GLOBALS['log']->fatal('Invalid Argument: requested argument missing: "emailAddressWidget"');
$get .= '&emailAddressWidget=';
} else {
$get .= '&emailAddressWidget=' . $_POST['emailAddressWidget'];
}
while (isset($_REQUEST[$mod . $widget_id . 'emailAddress' . $count])) {
$get .= "&" . $mod . $widget_id . "emailAddress" . $count . "=" . urlencode($_REQUEST[$mod . $widget_id . 'emailAddress' . $count]);
$count++;
} //while
while (isset($_REQUEST[$mod . $widget_id . 'emailAddressVerifiedValue' . $count])) {
$get .= "&" . $mod . $widget_id . "emailAddressVerifiedValue" . $count . "=" . urlencode($_REQUEST[$mod . $widget_id . 'emailAddressVerifiedValue' . $count]);
$count++;
} //while
$options = array(
'emailAddressPrimaryFlag',
'emailAddressOptOutFlag',
'emailAddressInvalidFlag',
'emailAddressDeleteFlag',
'emailAddressReplyToFlag'
);
foreach ($options as $option) {
$count = 0;
$optionIdentifier = $mod . $widget_id . $option;
if (isset($_REQUEST[$optionIdentifier])) {
if (is_array($_REQUEST[$optionIdentifier])) {
foreach ($_REQUEST[$optionIdentifier] as $optOut) {
$get .= "&" . $optionIdentifier . "[" . $count . "]=" . $optOut;
$count++;
} //foreach
} else {
$get .= "&" . $optionIdentifier . "=" . $_REQUEST[$optionIdentifier];
}
} //if
} //foreach
return $get;
}
public function setView($view)
{
$this->view = $view;
}
/**
* This function is here so the Employees/Users division can be handled cleanly in one place
* @param object $focus SugarBean
* @return string The value for the bean_module column in the email_addr_bean_rel table
*/
public function getCorrectedModule(&$module)
{
return ($module == "Employees") ? "Users" : $module;
}
public function stash($parentBeanId, $moduleName)
{
$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");
$this->stateBeforeWorkflow = array();
$ids = array();
while ($row = $this->db->fetchByAssoc($result, false)) {
$ids[] = $this->db->quote($row['email_address_id']); // avoid 2nd order SQL Injection
}
if (!empty($ids)) {
$ids = implode("', '", $ids);
$queryEmailData = "SELECT id, email_address, invalid_email, opt_out FROM {$this->table_name} WHERE id IN ('$ids') AND deleted=0";
$result = $this->db->query($queryEmailData);
while ($row = $this->db->fetchByAssoc($result, false)) {
$this->stateBeforeWorkflow[$row['id']] = array_diff_key($row, array('id' => null));
}
}
}
/**
* Confirm opt in
*/
public function confirmOptIn()
{
$this->confirm_opt_in_date = TimeDate::getInstance()->nowDb();
$this->confirm_opt_in = self::COI_STAT_CONFIRMED_OPT_IN;
}
/**
* Reset opt in
*/
public function resetOptIn()
{
$this->confirm_opt_in = self::COI_STAT_DISABLED;
parent::save();
}
/**
* Update Opt In state to SugarEmailAddress::COI_STAT_OPT_IN
*
* @see SugarEmailAddress::COI_STAT_OPT_IN
* @return string|bool ID or false on failed
* @throws RuntimeException this function updates an exists SugarEmailAddress bean should have ID
*/
public function optIn()
{
if (!$this->id) {
$msg = 'Trying to update opt-in email address without email address ID.';
LoggerManager::getLogger()->fatal($msg);
throw new RuntimeException($msg);
}
if (!$this->retrieve()) {
$msg = 'Retrieve email address for opt-in failed.';
LoggerManager::getLogger()->fatal($msg);
throw new RuntimeException($msg);
}
$state = $this->isConfirmedOptIn() ? self::COI_STAT_CONFIRMED_OPT_IN : self::COI_STAT_OPT_IN;
if (!$this->setConfirmedOptInState($state)) {
$msg = 'set confirm opt in state of email address "' . $this->email_address . '" failed.';
LoggerManager::getLogger()->fatal($msg);
throw new RuntimeException($msg);
}
$ret = parent::save();
return $ret;
}
/**
*
* @param string $state
* @return boolean
*/
public function setConfirmedOptInState($state)
{
$this->confirm_opt_in = $state;
$ret = parent::save();
return $ret;
}
/**
* It returns a ViewDefs for Confirm Opt In action link on DetailViews, specially for Accounts/Contacts/Leads/Prospects
*
* @param string $module module name
* @param string $returnModule optional, using module name if null
* @param string $returnAction optional, using module name if null
* @param string $moduleTab optional, using module name if null
* @return array ViewDefs for Confirm Opt In action link
*/
public static function getSendConfirmOptInEmailActionLinkDefs($module, $returnModule = null, $returnAction = null, $moduleTab = null)
{
$configurator = new Configurator();
$configOptInEnabled = $configurator->isConfirmOptInEnabled();
$disabledAttribute = $configOptInEnabled ? '' : 'disabled="disabled"';
$hiddenClass = $configOptInEnabled ? '' : 'hidden';
if (is_null($returnModule)) {
$returnModule = $module;
}
if (is_null($returnAction)) {
$returnAction = $module;
}
if (is_null($moduleTab)) {
$moduleTab = $module;
}
$ret = array(
'customCode' =>
'<input type="submit" class="button ' .
$hiddenClass . '" ' . $disabledAttribute .
' title="{$APP.LBL_SEND_CONFIRM_OPT_IN_EMAIL}" onclick="this.form.return_module.value=\'' .
$returnModule . '\'; this.form.return_action.value=\'' .
$returnAction . '\'; this.form.return_id.value=\'{$fields.id.value}\'; ' .
'this.form.action.value=\'sendConfirmOptInEmail\'; this.form.module.value=\'' .
$module . '\'; this.form.module_tab.value=\'' . $moduleTab .
'\';" name="send_confirm_opt_in_email" value="{$APP.LBL_SEND_CONFIRM_OPT_IN_EMAIL}"/>',
'sugar_html' =>
array(
'type' => 'submit',
'value' => '{$APP.LBL_SEND_CONFIRM_OPT_IN_EMAIL}',
'htmlOptions' =>
array(
'class' => 'button ' . $hiddenClass,
'id' => 'send_confirm_opt_in_email',
'title' => '{$APP.LBL_SEND_CONFIRM_OPT_IN_EMAIL}',
'onclick' => 'this.form.return_module.value=\'' . $returnModule .
'\'; this.form.return_action.value=\'DetailView\'; this.form.return_id.value=\'{$fields.id.value}\'; ' .
'this.form.action.value=\'sendConfirmOptInEmail\'; this.form.module.value=\'' . $module .
'\'; this.form.module_tab.value=\'' . $moduleTab . '\';',
'name' => 'send_confirm_opt_in_email',
),
),
);
if (!$configOptInEnabled) {
$ret['sugar_html']['htmlOptions']['disabled'] = true;
}
return $ret;
}
/**
* Uses the configuration to determine opt in status
* @return string
*/
public function getOptInStatus()
{
$configurator = new Configurator();
$enableConfirmedOptIn = null;
if (isset($configurator->config['email_enable_confirm_opt_in'])) {
$enableConfirmedOptIn = $configurator->config['email_enable_confirm_opt_in'];
} else {
LoggerManager::getLogger()->warn('EmailUI::populateComposeViewFields: $configurator->config[email_enable_confirm_opt_in] is not set');
}
$optInFromFlags = $this->getOptInIndicationFromFlags();
if ($enableConfirmedOptIn === self::COI_STAT_DISABLED) {
$ret = self::COI_FLAG_OPT_IN_DISABLED;
} elseif (
$enableConfirmedOptIn === self::COI_STAT_OPT_IN
&& $this->isOptedInStatus($optInFromFlags)
) {
$ret = self::COI_FLAG_OPT_IN;
} elseif ($enableConfirmedOptIn === self::COI_STAT_CONFIRMED_OPT_IN) {
$ret = $optInFromFlags;
} elseif ($optInFromFlags === self::COI_FLAG_INVALID) {
$ret = $optInFromFlags;
} elseif ($optInFromFlags === self::COI_FLAG_OPT_OUT) {
$ret = $optInFromFlags;
} else {
$msg = 'Invalid ENUM value of Opt In settings: ' . $enableConfirmedOptIn;
LoggerManager::getLogger()->warn($msg);
$ret = self::COI_FLAG_NO_OPT_IN_STATUS;
}
return $ret;
}
/**
* Determines the opt in status without considering the configuration
* @return string
* @throws RuntimeException
*/
private function getOptInIndicationFromFlags()
{
$log = LoggerManager::getLogger();
if (!in_array($this->module_name, self::$doNotDisplayOptInTickForModule, true)) {
if ((int)$this->invalid_email === 1) {
$ret = self::COI_FLAG_INVALID;
return $ret;
}
if ((int)$this->opt_out === 1) {
$ret = self::COI_FLAG_OPT_OUT;
return $ret;
}
if ($this->isNotOptIn()) {
$ret = self::COI_STAT_DISABLED;
return $ret;
}
$ret = self::COI_FLAG_UNKNOWN_OPT_IN_STATUS;
if ($this->isConfirmedOptIn()) {
$ret = self::COI_FLAG_OPT_IN_PENDING_EMAIL_CONFIRMED;
} elseif (
$this->isConfirmOptInEmailNotSent()
&& $this->getConfirmedOptInState() !== self::COI_STAT_DISABLED
) {
$ret = self::COI_FLAG_OPT_IN_PENDING_EMAIL_NOT_SENT;
} elseif ($this->isConfirmOptInEmailSent()) {
$ret = self::COI_FLAG_OPT_IN_PENDING_EMAIL_SENT;
} elseif ($this->isConfirmOptInEmailFailed()) {
$ret = self::COI_FLAG_OPT_IN_PENDING_EMAIL_FAILED;
}
return $ret;
}
$ret = self::COI_FLAG_NO_OPT_IN_STATUS;
return $ret;
}
/**
* @return bool true when the an confirm optin email was successfully sent
* @throws Exception
*/
private function isConfirmOptInEmailSent()
{
if (empty($this->confirm_opt_in_sent_date)) {
return false;
}
try {
$maxdate = $this->dateMax($this->confirm_opt_in_sent_date, $this->confirm_opt_in_fail_date);
if ($maxdate === $this->confirm_opt_in_fail_date) {
return false;
} elseif ($maxdate === $this->confirm_opt_in_sent_date) {
return true;
} else {
throw new Exception('its impossible email sending state');
}
} catch (RuntimeException $e) {
if (!empty($this->confirm_opt_in_fail_date)) {
throw $e;
}
return true;
}
}
/**
* @return bool true when the an confirm optin email failed to send
* @throws Exception
*/
private function isConfirmOptInEmailFailed()
{
if (empty($this->confirm_opt_in_fail_date)) {
return false;
}
try {
$maxdate = $this->dateMax($this->confirm_opt_in_sent_date, $this->confirm_opt_in_fail_date);
if ($maxdate === $this->confirm_opt_in_fail_date) {
return true;
} elseif ($maxdate === $this->confirm_opt_in_sent_date) {
return false;
} else {
throw new Exception('its impossible email sending state');
}
} catch (RuntimeException $e) {
if (!empty($this->confirm_opt_in_sent_date)) {
throw $e;
}
return false;
}
}
/**
* @return bool if confirm opt in email has not yet been sent
*/
private function isConfirmOptInEmailNotSent()
{
if (
empty($this->confirm_opt_in_sent_date)
&& empty($this->confirm_opt_in_fail_date)
) {
return true;
}
return false;
}
/**
* @return bool true when confirmed opt in has been set
*/
private function isConfirmedOptIn()
{
$ret = $this->getConfirmedOptInState() === self::COI_STAT_CONFIRMED_OPT_IN;
return $ret;
}
/**
* @return bool
*/
private function isNotOptIn()
{
return $this->confirm_opt_in === self::COI_STAT_DISABLED;
}
/**
* @param string $date1
* @param string $date2
* @return bool
* @throws \RuntimeException
*/
private function dateCompare($date1, $date2)
{
$time1 = strtotime($date1);
$time2 = strtotime($date2);
$err = 'unable to convert strtotime: ';
if ($time1 === -1) {
throw new RuntimeException($err . $date1);
}
if ($time2 === -1) {
throw new RuntimeException($err . $date2);
}
return $time1 > $time2;
}
/**
* @param string $date1
* @param string $date2
* @return string
*/
private function dateMax($date1, $date2)
{
return $this->dateCompare($date1, $date2) ? $date1 : $date2;
}
/**
* @param string $emailAddressIndicatorStatus
* @return bool
*/
private function isOptedInStatus($emailAddressIndicatorStatus = self::COI_FLAG_NO_OPT_IN_STATUS)
{
$ret = in_array($emailAddressIndicatorStatus, array(
self::COI_FLAG_OPT_IN_PENDING_EMAIL_CONFIRMED,
self::COI_FLAG_OPT_IN_PENDING_EMAIL_SENT,
self::COI_FLAG_OPT_IN_PENDING_EMAIL_NOT_SENT,
self::COI_FLAG_OPT_IN_PENDING_EMAIL_FAILED,
), true);
return $ret;
}
/**
* @global array $app_strings
* @return string
*/
public function getOptInStatusTickHTML()
{
global $app_strings;
$configurator = new Configurator();
$sugar_config = $configurator->config;
$tickHtml = '';
if (isset($sugar_config['email_enable_confirm_opt_in'])) {
$emailConfigEnableConfirmOptIn = $sugar_config['email_enable_confirm_opt_in'];
$template = new Sugar_Smarty();
$optInStatus = $this->getOptInStatus();
switch ($optInStatus) {
case self::COI_FLAG_OPT_IN:
$optInFlagClass = 'email-opt-in-confirmed';
$optInFlagTitle = $app_strings['LBL_OPT_IN'];
$optInFlagText = '<span class="suitepicon suitepicon-action-confirm"></span>';
break;
case self::COI_FLAG_OPT_IN_PENDING_EMAIL_CONFIRMED:
$optInFlagClass = 'email-opt-in-confirmed';
$optInFlagTitle = $app_strings['LBL_OPT_IN_CONFIRMED'];
$optInFlagText = '<span class="suitepicon suitepicon-action-confirm">';
$optInFlagText .= '</span><span class="suitepicon suitepicon-action-confirm"></span>';
break;
case self::COI_FLAG_OPT_IN_PENDING_EMAIL_SENT:
$optInFlagClass = 'email-opt-in-sent';
$optInFlagTitle = $app_strings['LBL_OPT_IN_PENDING_EMAIL_SENT'];
$optInFlagText = '<span class="suitepicon suitepicon-action-confirm"></span>';
break;
case self::COI_FLAG_OPT_IN_PENDING_EMAIL_NOT_SENT:
$optInFlagClass = 'email-opt-in-not-sent';
$optInFlagTitle = $app_strings['LBL_OPT_IN_PENDING_EMAIL_NOT_SENT'];
$optInFlagText = '<span class="suitepicon suitepicon-action-confirm"></span>';
break;
case self::COI_FLAG_OPT_IN_PENDING_EMAIL_FAILED:
$optInFlagClass = 'email-opt-in-failed';
$optInFlagTitle = $app_strings['LBL_OPT_IN_PENDING_EMAIL_FAILED'];
$optInFlagText = '<span class="suitepicon suitepicon-action-confirm"></span>';
break;
case self::COI_FLAG_OPT_OUT:
$optInFlagClass = 'email-opt-in-opt-out';
$optInFlagTitle = $app_strings['LBL_OPT_IN_OPT_OUT'];
$optInFlagText = '❌';
break;
case self::COI_FLAG_INVALID:
$optInFlagClass = 'email-opt-in-invalid';
$optInFlagTitle = $app_strings['LBL_OPT_IN_INVALID'];
$optInFlagText = '?';
break;
default:
$optInFlagClass = '';
$optInFlagTitle = '';
$optInFlagText = '';
break;
}
$template->assign('optInFlagClass', $optInFlagClass);
$template->assign('optInFlagTitle', $optInFlagTitle);
$template->assign('optInFlagText', $optInFlagText);
$tickHtml = $template->fetch('include/SugarEmailAddress/templates/optInStatusTick.tpl');
}
return $tickHtml;
}
/**
*
* @return string
*/
public function getConfirmOptInTokenGenerateIfNotExists()
{
if (!$this->confirm_opt_in_token) {
$this->confirm_opt_in_token = md5(time() . md5($this->email_address) . md5(mt_rand(0, 9999999))) . md5(mt_rand(0, 9999999));
$this->save();
}
return $this->confirm_opt_in_token;
}
} // end class def
require_once __DIR__.'/getEmailAddressWidget.php';