<?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');
}

/**
 * EmailsDataAddressCollector
 *
 * @author gyula
 */
class EmailsDataAddressCollector
{
    const ERR_INVALID_INBOUND_EMAIL_TYPE = 201;
    const ERR_STORED_OUTBOUND_EMAIL_NOT_SET = 202;
    const ERR_STORED_OUTBOUND_EMAIL_ID_IS_INVALID = 203;
    const ERR_REPLY_TO_ADDR_NOT_FOUND = 204;
    const ERR_REPLY_TO_FORMAT_INVALID_SPLITS = 205;
    const ERR_STORED_OUTBOUND_EMAIL_NOT_FOUND = 206;
    const ERR_REPLY_TO_FORMAT_INVALID_NO_NAME = 207;
    const ERR_REPLY_TO_FORMAT_INVALID_NO_ADDR = 208;
    const ERR_REPLY_TO_FORMAT_INVALID_AS_FROM = 209;

    /**
     *
     * @var User
     */
    protected $currentUser;

    /**
     *
     * @var array
     */
    protected $sugarConfig;

    /**
     *
     * @var int
     */
    protected $err;

    /**
     *
     * @var OutboundEmail
     */
    protected $oe;

    // ------------------ FROM DATA STRUCT -------------------

    /**
     *
     * @var string
     */
    protected $replyTo;

    /**
     *
     * @var string
     */
    protected $fromAddr;

    /**
     *
     * @var string
     */
    protected $fromName;

    /**
     *
     * @var string
     */
    protected $oeId;

    /**
     *
     * @var string
     */
    protected $oeName;

    // -------------------------------------------------------

    /**
     *
     * @param User $currentUser
     * @param array $sugarConfig
     */
    public function __construct(User $currentUser, $sugarConfig)
    {
        $this->currentUser = $currentUser;
        $this->sugarConfig = $sugarConfig;
    }

    /**
     * @param $ieAccounts
     * @param $showFolders
     * @param $prependSignature
     * @param $emailSignatures
     * @param $defaultEmailSignature
     * @return array
     * @throws EmailValidatorException
     */
    public function collectDataAddressesFromIEAccounts(
        $ieAccounts,
        $showFolders,
        $prependSignature,
        $emailSignatures,
        $defaultEmailSignature
    ) {
        $dataAddresses = array();
        foreach ($ieAccounts as $inboundEmail) {
            $this->validateInboundEmail($inboundEmail);

            if (in_array($inboundEmail->id, $showFolders, false)) {
                $storedOptions = sugar_unserialize(base64_decode($inboundEmail->stored_options));
                $isGroupEmailAccount = $inboundEmail->isGroupEmailAccount();
                $isPersonalEmailAccount = $inboundEmail->isPersonalEmailAccount();

                $this->getOutboundEmailOrError($storedOptions, $inboundEmail);
                $this->retrieveFromDataStruct($storedOptions);

                $emailFromValidator = new EmailFromValidator();

                $this->logReplyToError($emailFromValidator);


                $dataAddress = $this->getDataAddressFromIEAccounts(
                    $inboundEmail,
                    $storedOptions,
                    $prependSignature,
                    $isPersonalEmailAccount,
                    $isGroupEmailAccount,
                    $emailSignatures,
                    $defaultEmailSignature
                );

                $dataAddresses[] = $dataAddress;
            }
        }

        return $this->fillDataAddress($dataAddresses, $defaultEmailSignature, $prependSignature);
    }


    /**
     *
     * @param InboundEmail $inboundEmail
     * @throws InvalidArgumentException
     */
    protected function validateInboundEmail($inboundEmail = null)
    {
        if (!$inboundEmail instanceof InboundEmail) {
            throw new InvalidArgumentException(
                'Inbound Email Account should be a valid Inbound Email. ' . gettype($inboundEmail) . ' given.',
                self::ERR_INVALID_INBOUND_EMAIL_TYPE
            );
        }
    }

    /**
     *
     * @param array $storedOptions
     * @param InboundEmail $inboundEmail
     */
    protected function getOutboundEmailOrError($storedOptions, InboundEmail $inboundEmail)
    {
        $this->err = null;
        $this->setOe(null);
        if (!isset($storedOptions['outbound_email'])) {
            // exception
            LoggerManager::getLogger()->error('EmailController::action_getFromFields() expects an outbound email id as stored option of inbound email (' . $inboundEmail->id . ') but it isn\'t set.');
            $this->err = self::ERR_STORED_OUTBOUND_EMAIL_NOT_SET;
        } else {
            $validator = new SuiteCRM\Utility\SuiteValidator();
            if ($validator->isValidId($storedOptions['outbound_email'])) {
                // exception
                LoggerManager::getLogger()->error('EmailController::action_getFromFields() expects an outbound email id as stored option of inbound email (' . $inboundEmail->id . ') but it isn\'t valid.');
                $this->err = self::ERR_STORED_OUTBOUND_EMAIL_ID_IS_INVALID;
            } else {
                $this->setOe($this->getOutboundEmailOrErrorByStoredOptions($storedOptions));
            }
        }
    }

    /**
     *
     * @param OutboundEmail|null $oe
     */
    protected function setOe($oe)
    {
        $this->oe = $oe;
    }

    /**
     *
     * @param array $storedOptions
     */
    protected function retrieveFromDataStruct($storedOptions)
    {
        if ($this->err) {
            LoggerManager::getLogger()->error('EmailController::action_getFromFields() panic: An error occurred! (' . $this->err . ')');

            $this->replyTo = $this->getReplyToOnError($storedOptions);
            $this->fromName = $this->getFromNameOnError($storedOptions);
            $this->fromAddr = $this->getFromAddrOnError($storedOptions);
            $this->oeId = null;
            $this->oeName = null;
        } else {
            $this->replyTo = utf8_encode($storedOptions['reply_to_addr']);
            $this->fromName = utf8_encode($storedOptions['from_name']);
            $this->fromAddr = utf8_encode($storedOptions['from_addr']);
            $this->oeId = $this->oe->id;
            $this->oeName = $this->oe->name;
        }
    }

    /**
     * @param EmailFromValidator $emailFromValidator
     * @throws EmailValidatorException
     */
    protected function logReplyToError(EmailFromValidator $emailFromValidator)
    {
        if (!$this->replyTo) {
            // exception
            LoggerManager::getLogger()->error('EmailController::action_getFromFields() panic: An Outbound Email Reply-to Address is not found.');
            $replyToErr = self::ERR_REPLY_TO_ADDR_NOT_FOUND;
        } else {
            $splits = explode(' ', $this->replyTo);
            if (count($splits) !== 2) {
                LoggerManager::getLogger()->error('Incorrect "replay to" format found: ' . $this->replyTo);
                $replyToErr = self::ERR_REPLY_TO_FORMAT_INVALID_SPLITS;
            } else {
                $replyToErr = null;
                $tmpName = $this->getTmpNameForLogReplyToError($splits, $replyToErr);
                $tmpAddr = $this->getTmpAddrForLogReplyToError($splits, $replyToErr);

                $this->validateForLogReplyToError($tmpName, $tmpAddr, $emailFromValidator, $replyToErr);
            }
        }

        if (isset($replyToErr) && $replyToErr) {
            // exception
            LoggerManager::getLogger()->error('EmailController::action_getFromFields() error: ' . $replyToErr);
        }
    }

    /**
     *
     * @param array $splits
     * @param int $replyToErr
     * @return string
     */
    protected function getTmpNameForLogReplyToError($splits, &$replyToErr)
    {
        if (!isset($splits[0])) {
            LoggerManager::getLogger()->error('Reply-to name part not found: ' . $this->getReplyTo());
            $replyToErr = self::ERR_REPLY_TO_FORMAT_INVALID_NO_NAME;
        }

        return isset($splits[0]) ? $splits[0] : null;
    }

    /**
     *
     * @param array $splits
     * @param int $replyToErr
     * @return string
     */
    protected function getTmpAddrForLogReplyToError($splits, &$replyToErr)
    {
        if (!isset($splits[1])) {
            LoggerManager::getLogger()->error('Reply-to email address part not found: ' . $this->getReplyTo());
            $replyToErr = self::ERR_REPLY_TO_FORMAT_INVALID_NO_ADDR;
        }

        return isset($splits[1]) ? $splits[1] : null;
    }

    /**
     * @param $tmpName
     * @param $tmpAddr
     * @param EmailFromValidator $emailFromValidator
     * @param $replyToErr
     * @throws EmailValidatorException
     */
    protected function validateForLogReplyToError(
        $tmpName,
        $tmpAddr,
        EmailFromValidator $emailFromValidator,
        &$replyToErr
    ) {
        $tmpEmail = BeanFactory::newBean('Emails');
        $tmpEmail->FromName = $tmpEmail->from_name = $tmpName;
        $tmpEmail->From = $tmpEmail->from_addr = $tmpAddr;
        $tmpEmail->from_addr_name = $this->getReplyTo();

        if (!$emailFromValidator->isValid($tmpEmail)) {
            // exception
            LoggerManager::getLogger()->error('EmailController::action_getFromFields() panic: An Outbound Email Reply-to Address is invalid.');
            $replyToErr = self::ERR_REPLY_TO_FORMAT_INVALID_AS_FROM;
        }
    }

    /**
     *
     * @return string
     */
    protected function getReplyTo()
    {
        return $this->replyTo;
    }

    /**
     *
     * @param InboundEmail $inboundEmail
     * @param array $storedOptions
     * @param string $prependSignature
     * @param bool $isPersonalEmailAccount
     * @param bool $isGroupEmailAccount
     * @param array $emailSignatures
     * @param array $defaultEmailSignature
     * @return array
     */
    protected function getDataAddressFromIEAccounts(
        InboundEmail $inboundEmail,
        $storedOptions,
        $prependSignature,
        $isPersonalEmailAccount,
        $isGroupEmailAccount,
        $emailSignatures,
        $defaultEmailSignature
    ) {
        $dataAddress = $this->getDataAddressArrayFromIEAccounts(
            $inboundEmail,
            $storedOptions,
            $prependSignature,
            $isPersonalEmailAccount,
            $isGroupEmailAccount
        );

        $emailSignatureId = $this->getEmailSignatureId($emailSignatures, $inboundEmail);

        $signature = $this->currentUser->getSignature($emailSignatureId);
        if (!$signature) {
            if ($defaultEmailSignature['no_default_available'] === true) {
                $dataAddress['emailSignatures'] = $defaultEmailSignature;
            } else {
                $dataAddress['emailSignatures'] = array(
                    'html' => utf8_encode(html_entity_decode($defaultEmailSignature['signature_html'])),
                    'plain' => $defaultEmailSignature['signature'],
                );
            }
        } else {
            $dataAddress['emailSignatures'] = array(
                'html' => utf8_encode(html_entity_decode($signature['signature_html'])),
                'plain' => $signature['signature'],
            );
        }

        return $dataAddress;
    }


    /**
     *
     * @param InboundEmail $inboundEmail
     * @param array $storedOptions
     * @param string $prependSignature
     * @param bool $isPersonalEmailAccount
     * @param bool $isGroupEmailAccount
     * @return array
     */
    protected function getDataAddressArrayFromIEAccounts(
        InboundEmail $inboundEmail,
        $storedOptions,
        $prependSignature,
        $isPersonalEmailAccount,
        $isGroupEmailAccount
    ) {
        $dataAddress = new EmailsDataAddress();

        return $dataAddress->getDataArray(
            $inboundEmail->module_name,
            $inboundEmail->id,
            $storedOptions['reply_to_addr'],
            $storedOptions['from_addr'],
            $storedOptions['from_name'],
            null,
            $prependSignature,
            $isPersonalEmailAccount,
            $isGroupEmailAccount,
            $this->getOeId(),
            $this->getOeName(),
            []
        );
    }

    /**
     *
     * @return string
     */
    protected function getOeId()
    {
        return $this->oeId;
    }

    /**
     *
     * @return string
     */
    protected function getOeName()
    {
        return $this->oeName;
    }

    /**
     *
     * @param array $emailSignatures
     * @param InboundEmail $inboundEmail
     * @return string
     */
    protected function getEmailSignatureId($emailSignatures, InboundEmail $inboundEmail)
    {

        // Include signature
        if (isset($emailSignatures[$inboundEmail->id]) && !empty($emailSignatures[$inboundEmail->id])) {
            $emailSignatureId = $emailSignatures[$inboundEmail->id];
        } else {
            $emailSignatureId = '';
        }

        return $emailSignatureId;
    }

    /**
     *
     * @param array $dataAddresses
     * @param array $defaultEmailSignature
     * @param string $prependSignature
     * @return array
     */
    protected function fillDataAddress($dataAddresses, $defaultEmailSignature, $prependSignature)
    {
        $dataAddressesWithUserAddresses = $this->fillDataAddressFromUserAddresses(
            $dataAddresses,
            $defaultEmailSignature,
            $prependSignature
        );
        $dataAddressesWithUserAddressesAndSystem = $this->fillDataAddressWithSystemMailerSettings(
            $dataAddressesWithUserAddresses,
            $defaultEmailSignature
        );

        return
            $this->fillDataAddressFromPersonal(
                $dataAddressesWithUserAddressesAndSystem
            );
    }

    /**
     *
     * @param array $dataAddresses
     * @param array $defaultEmailSignature
     * @param string $prependSignature
     * @return array
     */
    protected function fillDataAddressFromUserAddresses($dataAddresses, $defaultEmailSignature, $prependSignature)
    {
        if (isset($this->sugarConfig['email_allow_send_as_user']) && $this->sugarConfig['email_allow_send_as_user']) {
            $sugarEmailAddress = new SugarEmailAddress();
            $userAddressesArr = $sugarEmailAddress->getAddressesByGUID($this->currentUser->id, 'Users');
            $dataAddresses = $this->collectDataAddressesFromUserAddresses(
                $dataAddresses,
                $userAddressesArr,
                $defaultEmailSignature,
                $prependSignature
            );
        }

        return $dataAddresses;
    }


    /**
     *
     * @param array $dataAddresses
     * @param array $userAddressesArr
     * @param array $defaultEmailSignature
     * @param string $prependSignature
     * @return array
     */
    protected function collectDataAddressesFromUserAddresses(
        $dataAddresses,
        $userAddressesArr,
        $defaultEmailSignature,
        $prependSignature
    ) {
        foreach ($userAddressesArr as $userAddress) {
            if (!isset($userAddress['reply_to_addr']) || !$userAddress['reply_to_addr']) {
                LoggerManager::getLogger()->error('EmailController::action_getFromFields() is Panicking: Reply-To address is not filled.');
            }
            $fromString = $this->getFromString($userAddress);
            $signatureHtml = $this->getSignatureHtml($defaultEmailSignature);
            $signatureTxt = $this->getSignatureTxt($defaultEmailSignature);

            $dataAddresses[] = $this->getCollectDataAddressArrayFromUserAddresses(
                $userAddress,
                $fromString,
                $prependSignature,
                $signatureHtml,
                $signatureTxt
            );
        }

        return $dataAddresses;
    }

    /**
     *
     * @param array $userAddress
     * @return string
     */
    protected function getFromString($userAddress)
    {
        if (isset($userAddress['reply_to_addr']) && $userAddress['reply_to_addr'] === '1') {
            $fromString = $this->currentUser->full_name . ' &lt;' . $userAddress['email_address'] . '&gt;';
        } else {
            $fromString = $this->currentUser->full_name . ' &lt;' . $this->currentUser->email1 . '&gt;';
        }

        return $fromString;
    }

    /**
     * @param $email
     * @return string
     */
    protected function addCurrentUserToEmailString($email)
    {
        return $this->currentUser->full_name . ' &lt;' . $email . '&gt;';
    }

    /**
     *
     * @param array $defaultEmailSignature
     * @return string
     */
    protected function getSignatureHtml($defaultEmailSignature)
    {
        if (!isset($defaultEmailSignature['signature_html'])) {
            LoggerManager::getLogger()->warn('EmailController::action_getFromFields() is Panicking: Default email signature array does not have index as signature_html');
            $signatureHtml = null;
        } else {
            $signatureHtml = $defaultEmailSignature['signature_html'];
        }

        return $signatureHtml;
    }

    /**
     *
     * @param array $defaultEmailSignature
     * @return string
     */
    protected function getSignatureTxt($defaultEmailSignature)
    {
        if (!isset($defaultEmailSignature['signature'])) {
            LoggerManager::getLogger()->warn('EmailController::action_getFromFields() is Panicking: Default email signature array does not have index as signature');
            $signatureTxt = null;
        } else {
            $signatureTxt = $defaultEmailSignature['signature'];
        }

        return $signatureTxt;
    }

    /**
     *
     * @param array $userAddress
     * @param string $fromString
     * @param string $prependSignature
     * @param string $signatureHtml
     * @param string $signatureTxt
     * @return array
     */
    protected function getCollectDataAddressArrayFromUserAddresses(
        $userAddress,
        $fromString,
        $prependSignature,
        $signatureHtml,
        $signatureTxt
    ) {
        $dataAddress = new EmailsDataAddress();

        return $dataAddress->getDataArray(
            'personal',
            $userAddress['email_address_id'],
            $this->currentUser->full_name . ' &lt;' . $userAddress['email_address'] . '&gt;',
            $fromString,
            $this->currentUser->full_name,
            null,
            $prependSignature,
            true,
            false,
            null,
            null,
            [
                'html' => utf8_encode(html_entity_decode($signatureHtml)),
                'plain' => $signatureTxt,
            ]
        );
    }

    /**
     *
     * @param array $dataAddresses
     * @param array $defaultEmailSignature
     * @return array
     */
    protected function fillDataAddressWithSystemMailerSettings($dataAddresses, $defaultEmailSignature)
    {
        $this->setOe(new OutboundEmail());
        if ($this->getOe()->isAllowUserAccessToSystemDefaultOutbound()) {
            $system = $this->getOe()->getSystemMailerSettings();
            $dataAddresses[] = $this->getFillDataAddressArray(
                $system->id,
                $system->name,
                $system->smtp_from_name,
                $system->smtp_from_addr,
                $system->mail_smtpuser,
                $defaultEmailSignature
            );
        }

        return $dataAddresses;
    }

    /**
     * @param $dataAddresses
     * @return mixed
     */
    protected function fillDataAddressFromPersonal($dataAddresses)
    {
        foreach ($dataAddresses as $address => $userAddress) {
            if ($userAddress['type'] !== 'system') {
                $emailInfo = $userAddress['attributes'];
                $fromString = $emailInfo['from'];
                $replyString = $emailInfo['reply_to'];

                $dataAddresses[$address]['attributes'] = [
                    'from' => $fromString,
                    'name' => $userAddress['attributes']['name'],
                    'oe' => $userAddress['attributes']['oe'],
                    'reply_to' => $replyString
                ];
            }
        }

        return $dataAddresses;
    }

    /**
     *
     * @return OutboundEmail
     */
    protected function getOe()
    {
        return $this->oe;
    }


    /**
     *
     * @param string $id
     * @param string $name
     * @param string $fromName
     * @param string $fromAddr
     * @param string $mailUser
     * @param array $defaultEmailSignature
     * @return array
     */
    protected function getFillDataAddressArray(
        $id,
        $name,
        $fromName,
        $fromAddr,
        $mailUser,
        $defaultEmailSignature
    ) {
        $dataAddress = new EmailsDataAddress();

        return $dataAddress->getDataArray(
            'system',
            $id,
            "$fromName &lt;$fromAddr&gt;",
            "$fromName &lt;$fromAddr&gt;",
            $fromName,
            false,
            false,
            true,
            $id,
            $name,
            $mailUser,
            $defaultEmailSignature
        );
    }

    /**
     *
     * @param array $storedOptions
     * @return OutboundEmail
     */
    protected function getOutboundEmailOrErrorByStoredOptions($storedOptions)
    {
        $this->oe = new OutboundEmail();
        if (!$this->oe->retrieve($storedOptions['outbound_email'])) {
            // exception
            LoggerManager::getLogger()->error('Trying to retrieve an OutboundEmail by ID: ' . $storedOptions['outbound_email'] . ' but it is not found.');
            $this->err = self::ERR_STORED_OUTBOUND_EMAIL_NOT_FOUND;
        }

        return $this->oe;
    }

    /**
     *
     * @param array $storedOptions
     * @return string
     */
    protected function getReplyToOnError($storedOptions)
    {
        if (!isset($storedOptions['reply_to_addr'])) {
            LoggerManager::getLogger()->warn('Stored reply to address is not set.');
        } elseif (!$storedOptions['reply_to_addr']) {
            LoggerManager::getLogger()->warn('Stored reply to address is not filled.');
        }
        $this->replyTo = isset($storedOptions['reply_to_addr']) ? $storedOptions['reply_to_addr'] : null;

        return $this->replyTo;
    }

    /**
     *
     * @param array $storedOptions
     * @return string
     */
    protected function getFromNameOnError($storedOptions)
    {
        if (!isset($storedOptions['from_name'])) {
            LoggerManager::getLogger()->warn('Stored from name is not set.');
        } elseif (!$storedOptions['from_name']) {
            LoggerManager::getLogger()->warn('Stored from name is not filled.');
        }
        $this->fromName = isset($storedOptions['from_name']) ? $storedOptions['from_name'] : null;

        return $this->fromName;
    }

    /**
     *
     * @param array $storedOptions
     * @return string
     */
    protected function getFromAddrOnError($storedOptions)
    {
        if (!isset($storedOptions['from_addr'])) {
            LoggerManager::getLogger()->warn('Stored from address is not set.');
        } elseif (!$storedOptions['from_addr']) {
            LoggerManager::getLogger()->warn('Stored from address is not filled.');
        }
        $this->fromAddr = isset($storedOptions['from_addr']) ? $storedOptions['from_addr'] : null;

        return $this->fromAddr;
    }
}
