', '|', '$',];
public $currentCache;
public $defaultSort = 'date';
public $defaultDirection = 'DESC';
public $hrSort = [
0 => 'flagged',
1 => 'status',
2 => 'from',
3 => 'subj',
4 => 'date',
];
public $hrSortLocal = [
'flagged' => 'flagged',
'status' => 'answered',
'from' => 'fromaddr',
'subject' => 'subject',
'date' => 'senddate',
];
public $transferEncoding = [
0 => '7BIT',
1 => '8BIT',
2 => 'BINARY',
3 => 'BASE64',
4 => 'QUOTED-PRINTABLE',
5 => 'OTHER'
];
// concatenation of messageID and deliveredToEmail
public $compoundMessageId;
public $serverConnectString;
public $disable_row_level_security = true;
public $InboundEmailCachePath;
public $EmailCachePath;
public $InboundEmailCacheFile = 'InboundEmail.cache.php';
public $object_name = 'InboundEmail';
public $module_dir = 'InboundEmail';
public $table_name = 'inbound_email';
public $new_schema = true;
public $process_save_dates = true;
public $order_by;
public $dbManager;
public $field_defs;
public $column_fields;
public $required_fields = [
'name' => 'name',
'server_url' => 'server_url',
'mailbox' => 'mailbox',
'user' => 'user',
'port' => 'port',
];
public $imageTypes = ['JPG', 'JPEG', 'GIF', 'PNG'];
public $inlineImages = [];
public $defaultEmailNumAutoreplies24Hours = 10;
public $maxEmailNumAutoreplies24Hours = 10;
// Custom ListView attributes
public $mailbox_type_name;
public $global_personal_string;
// Service attributes
public $tls;
public $ca;
public $ssl;
public $protocol;
public $keyForUsersDefaultIEAccount = 'defaultIEAccount';
// Prefix to use when importing inline images in emails
public $imagePrefix;
public $job_name = 'function::pollMonitoredInboxes';
/**
*
* @var ImapHandlerInterface
*/
protected $imap;
/**
* @var MailMimeParser
*/
private $mailParser;
/**
* @var Overview
*/
private $overview;
/**
* @var string|null
*/
public $from_addr;
/**
* @var string|null
*/
public $from_name;
/**
* @var string|null
*/
public $reply_to_name;
/**
* @var string|null
*/
public $reply_to_addr;
/**
* @var string|null
*/
public $only_since;
/**
* @var string|null
*/
public $filter_domain;
/**
* @var string|null
*/
public $trashFolder;
/**
* @var string|null
*/
public $sentFolder;
/**
* @var string|null
*/
public $distrib_method;
/**
* @var string|null
*/
public $distribution_user_id;
/**
* @var string|null
*/
public $distribution_options;
/**
* @var string|null
*/
public $create_case_template_id;
/**
* @var int|null
*/
public $email_num_autoreplies_24_hours;
/**
* @var bool|null
*/
public $is_auto_import;
/**
* @var bool|null
*/
public $is_create_case;
/**
* @var bool|string|null
*/
public $allow_outbound_group_usage;
/**
* @var string|null
*/
public $outbound_email_id;
/**
* @var bool|string|null
*/
public $leave_messages_on_mail_server;
/**
* @var string|null
*/
public $type;
/**
* @var int|null
*/
public $is_default;
/**
* @var string|null
*/
public $external_oauth_connection_id;
/**
* @var string|null
*/
public $auth_type;
/**
* @var string|null
*/
public $connection_string;
/**
* @var bool|null
*/
public $move_messages_to_trash_after_import;
/**
* Email constructor
* @param ImapHandlerInterface|null $imapHandler
* @param MailMimeParser|null $mailParser
* @throws ImapHandlerException
*/
public function __construct(ImapHandlerInterface $imapHandler = null, MailMimeParser $mailParser = null)
{
global $sugar_config;
if (null === $mailParser) {
$mailParser = new MailMimeParser();
}
$this->mailParser = $mailParser;
if (!empty($imapHandler)) {
$this->imap = $imapHandler;
}
$this->InboundEmailCachePath = sugar_cached('modules/InboundEmail');
$this->EmailCachePath = sugar_cached('modules/Emails');
parent::__construct();
$this->smarty = new Sugar_Smarty();
$this->overview = new Overview();
if (isset($sugar_config['site_url'])) {
$this->imagePrefix = $sugar_config['site_url'] . '/cache/images/';
}
}
/**
*
* @param ImapHandlerInterface|null $imap
* @return ImapHandlerInterface
* @throws ImapHandlerException
*/
public function getImap(ImapHandlerInterface $imap = null)
{
if (null === $this->imap) {
if (null === $imap) {
$imapFactory = new ImapHandlerFactory();
$handlerType = $this->getImapHandlerType();
$imap = $imapFactory->getImapHandler(null, $handlerType);
if ($imap->isAvailable()) {
/*
* 1: Open
* 2: Read
* 3: Write
* 4: Close
*/
$imap->setTimeout(1, 5);
$imap->setTimeout(2, 5);
$imap->setTimeout(3, 5);
}
}
$this->imap = $imap;
}
return $this->imap;
}
/**
* retrieves I-E bean
* @param int $id
* @param bool $encode
* @param bool $deleted
* @return object Bean
*/
public function retrieve($id = -1, $encode = true, $deleted = true)
{
$ret = parent::retrieve($id, $encode, $deleted);
// If I-E bean exist
if ($ret !== null) {
$this->email_password = blowfishDecode(blowfishGetKey('InboundEmail'), $this->email_password);
$this->retrieveMailBoxFolders();
}
if (!empty($ret) && !$this->checkPersonalAccountAccess()) {
$this->logPersonalAccountAccessDenied('retrieve');
return null;
}
return $ret;
}
/**
* wraps SugarBean->save()
* @param string ID of saved bean
*/
public function save($check_notify = false)
{
if (!$this->checkPersonalAccountAccess()) {
$this->logPersonalAccountAccessDenied('save');
throw new RuntimeException('Access Denied');
}
$this->clearAuthTypeDependantFields();
$this->keepWriteOnlyFieldValues();
// generate cache table for email 2.0
$multiDImArray = $this->generateMultiDimArrayFromFlatArray(
explode(",", $this->mailbox),
$this->retrieveDelimiter()
);
$raw = $this->generateFlatArrayFromMultiDimArray($multiDImArray, $this->retrieveDelimiter());
sort($raw);
$raw = $this->filterMailBoxFromRaw(explode(",", $this->mailbox), $raw);
$this->mailbox = implode(',', $raw);
if (!empty($this->email_password)) {
$this->email_password = blowfishEncode(blowfishGetKey('InboundEmail'), $this->email_password);
}
$ret = parent::save($check_notify);
return $ret;
}
/**
* Check if user has access to personal account
* @return bool
*/
public function checkPersonalAccountAccess() : bool {
global $current_user;
if (is_admin($current_user)) {
return true;
}
if (!isTrue($this->is_personal ?? false)) {
return true;
}
if (empty($this->created_by)) {
return true;
}
if($this->created_by === $current_user->id) {
return true;
}
return false;
}
/**
* Log personal account access denied
* @param string $action
* @return void
*/
public function logPersonalAccountAccessDenied(string $action) : void {
global $log, $current_user;
$log->fatal("InboundEmail | Access denied. Non-admin user trying to access personal account. Action: '" . $action . "' | Current user id: '" . $current_user->id . "' | record: '" . $this->id . "'" );
}
/**
* @inheritDoc
*/
public function ACLAccess($view, $is_owner = 'not_set', $in_group = 'not_set')
{
global $current_user;
$isNotAllowAction = $this->isNotAllowedAction($view);
if ($isNotAllowAction === true) {
return false;
}
if (!$this->checkPersonalAccountAccess()) {
$this->logPersonalAccountAccessDenied("ACLAccess-$view");
return false;
}
$isPersonal = isTrue($this->is_personal);
$isAdmin = is_admin($current_user);
if ($isPersonal === true && $this->checkPersonalAccountAccess()) {
return true;
}
$isAdminOnlyAction = $this->isAdminOnlyAction($view);
if (!$isPersonal && !$isAdmin && $isAdminOnlyAction === true) {
return false;
}
$hasActionAclsDefined = has_group_action_acls_defined('InboundEmail', 'view');
$isSecurityGroupBasedAction = $this->isSecurityGroupBasedAction($view);
if (!$isPersonal && !$isAdmin && !$hasActionAclsDefined && $isSecurityGroupBasedAction === true) {
return false;
}
return parent::ACLAccess($view, $is_owner, $in_group);
}
/**
* @return void
*/
protected function keepWriteOnlyFieldValues(): void
{
if (empty($this->fetched_row)) {
return;
}
foreach ($this->field_defs as $field => $field_def) {
if (empty($field_def['display']) || $field_def['display'] !== 'writeonly') {
continue;
}
if (empty($this->fetched_row[$field])) {
continue;
}
if (!empty($this->$field)) {
continue;
}
$this->$field = $this->fetched_row[$field];
}
}
/**
* @return void
*/
protected function clearAuthTypeDependantFields(): void
{
if (empty($this->auth_type)) {
return;
}
if ($this->auth_type === 'basic') {
$this->external_oauth_connection_id = '';
}
if ($this->auth_type === 'oauth') {
$this->email_password = '';
}
}
public function filterMailBoxFromRaw($mailboxArray, $rawArray)
{
$newArray = array_intersect($mailboxArray, $rawArray);
sort($newArray);
return $newArray;
} // fn
/**
* Overrides SugarBean's mark_deleted() to drop the related cache table
* @param string $id GUID of I-E instance
*/
public function mark_deleted($id)
{
parent::mark_deleted($id);
//bug52021 we need to keep the reference to the folders table in order for emails module to function properly
$this->deleteCache();
}
/**
* Mark cached email answered (replied)
* @param string $mailid (uid for imap, message_id for pop3)
*/
public function mark_answered($mailid, $type = 'smtp')
{
switch ($type) {
case 'smtp':
$q = "update email_cache set answered = 1 WHERE imap_uid = $mailid and ie_id = '{$this->id}'";
$this->db->query($q);
break;
case 'pop3':
$q = "update email_cache set answered = 1 WHERE message_id = '$mailid' and ie_id = '{$this->id}'";
$this->db->query($q);
break;
}
}
/**
* Renames an IMAP mailbox
* @param string $newName
*/
public function renameFolder($oldName, $newName)
{
//$this->mailbox = "INBOX"
$this->connectMailserver();
$oldConnect = $this->getConnectString('', $oldName);
$newConnect = $this->getConnectString('', $newName);
$errorLevelStored = error_reporting();
error_reporting(0);
$imapRenameMailbox = $this->getImap()->renameMailbox($oldConnect, $newConnect);
error_reporting($errorLevelStored);
if (!$imapRenameMailbox) {
$GLOBALS['log']->debug("***INBOUNDEMAIL: failed to rename mailbox [ {$oldConnect} ] to [ {$newConnect} ]");
} else {
$this->mailbox = str_replace($oldName, $newName, $this->mailbox);
$this->save();
$sessionFoldersString = $this->getSessionInboundFoldersString(
$this->server_url,
$this->email_user,
$this->port,
$this->protocol
);
$sessionFoldersString = str_replace($oldName, $newName, $sessionFoldersString);
$this->setSessionInboundFoldersString(
$this->server_url,
$this->email_user,
$this->port,
$this->protocol,
$sessionFoldersString
);
}
return $imapRenameMailbox;
}
/**
* @return bool
*/
public function isPersonalEmailAccount()
{
return (bool)$this->is_personal;
}
/**
* @return bool
*/
public function isGroupEmailAccount()
{
return !$this->isPersonalEmailAccount();
}
/**
* @param int $offset
* @param int $pageSize
* @param array $order
* @param array $filter
* @return array
*/
public function checkWithPagination(
$offset = 0,
$pageSize = 20,
$order = array(),
$filter = array(),
$columns = array()
) {
--$pageSize;
$mailboxInfo = array('Nmsgs' => 0);
if ($this->connectMailserver() !== 'true') {
LoggerManager::getLogger()->error('Unable to connect to IMAP server.');
return false;
}
[$sortCriteria, $sortCRM, $sortOrder] = $this->getSortCriteria($order);
$filterCriteria = $this->getFilterCriteria($filter);
$emailHeaders = $this->getImap()->getMessageList(
$filterCriteria,
$sortCriteria,
$sortOrder,
$offset,
$pageSize,
$mailboxInfo,
$columns
);
return array(
"data" => $emailHeaders,
"mailbox_info" => json_decode(json_encode($mailboxInfo), true),
);
}
/**
* @param $imapStructure
* @return bool
*/
public function messageStructureHasAttachment($imapStructure)
{
if(empty($imapStructure)){
return false;
}
if (($imapStructure->type !== 0) && ($imapStructure->type !== 1)) {
return true;
}
$attachments = [];
if(empty($imapStructure->parts)){
return false;
}
foreach ($imapStructure->parts as $i => $part) {
if(empty($part->dparameters)){
continue;
}
if (is_string($part->dparameters[0]->value)) {
$attachments[] = $part->dparameters[0]->value;
}
}
return !empty($attachments);
}
///////////////////////////////////////////////////////////////////////////
//// CUSTOM LOGIC HOOKS
/**
* Called from $this->getMessageText()
* Allows upgrade-safe custom processing of message text.
*
* To use:
* 1. Create a directory path: ./custom/modules/InboundEmail if it does not exist
* 2. Create a file in the ./custom/InboundEmail/ folder called "getMessageText.php"
* 3. Define a function named "custom_getMessageText()" that takes a string as an argument and returns a string
*
* @param string $msgPart
* @return string
*/
public function customGetMessageText($msgPart)
{
$custom = "custom/modules/InboundEmail/getMessageText.php";
if (file_exists($custom)) {
include_once($custom);
if (function_exists("custom_getMessageText")) {
$GLOBALS['log']->debug("*** INBOUND EMAIL-CUSTOM_LOGIC: calling custom_getMessageText()");
$msgPart = custom_getMessageText($msgPart);
}
}
return $msgPart;
}
//// END CUSTOM LOGIC HOOKS
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
//// EMAIL 2.0 SPECIFIC
/**
* constructs a nicely formatted version of raw source
* @param int $uid UID of email
* @return string
*/
public function getFormattedRawSource($uid)
{
global $app_strings;
if (empty($this->id)) {
$q = "SELECT raw_source FROM emails_text WHERE email_id = '{$uid}'";
$r = $this->db->query($q);
$a = $this->db->fetchByAssoc($r);
$ret = array();
// Protect against the database fetch failing.
if ($a === false) {
$raw = null;
} else {
$raw = $this->convertToUtf8($a['raw_source']);
}
if (empty($raw)) {
$raw = $app_strings['LBL_EMAIL_ERROR_VIEW_RAW_SOURCE'];
}
} else {
if ($this->isPop3Protocol()) {
$uid = $this->getCorrectMessageNoForPop3($uid);
}
if (!$this->getImap()->isValidStream($this->conn)) {
LoggerManager::getLogger()->fatal('Inbound Email connection is not a resource for getting Formatted Raw Source');
return null;
}
$raw = $this->getImap()->fetchHeader($uid, FT_UID + FT_PREFETCHTEXT);
$raw .= $this->convertToUtf8($this->getImap()->getBody($uid, FT_UID));
} // else
$raw = to_html($raw);
$raw = nl2br($raw);
//}
return $raw;
}
/**
* helper method to convert text to utf-8 if necessary
*
* @param string $input text
* @return string output text
*/
public function convertToUtf8($input)
{
$charset = $GLOBALS['locale']->detectCharset($input, true);
// we haven't a clue due to missing package?, just return as itself
if ($charset === false) {
return $input;
}
// convert if we can or must
return $this->handleCharsetTranslation($input, $charset);
}
/**
* constructs a nicely formatted version of email headers.
* @param int $uid
* @return string
*/
public function getFormattedHeaders($uid)
{
global $app_strings;
//if($this->protocol == 'pop3') {
// $header = $app_strings['LBL_EMAIL_VIEW_UNSUPPORTED'];
//} else {
if ($this->isPop3Protocol()) {
$uid = $this->getCorrectMessageNoForPop3($uid);
}
if (!$this->getImap()->isValidStream($this->conn)) {
LoggerManager::getLogger()->fatal('Inbound Email connection is not a resource');
return null;
}
$headers = $this->getImap()->fetchHeader($uid, FT_UID);
$lines = explode("\n", $headers);
$header = "