<?php

namespace IMATHUZH\Qfq\Core\Report;

use IMATHUZH\Qfq\Core\Database\Database;
use IMATHUZH\Qfq\Core\Helper\HelperFile;
use IMATHUZH\Qfq\Core\Helper\OnArray;
use IMATHUZH\Qfq\Core\Helper\OnString;
use IMATHUZH\Qfq\Core\Helper\Path;
use IMATHUZH\Qfq\Core\Helper\Sanitize;
use IMATHUZH\Qfq\Core\Helper\Support;
use IMATHUZH\Qfq\Core\Store\Store;

const SENDMAIL_HTML_TOKEN = '<html>';

/**
 * Class SendMail
 * @package qfq
 */
class SendMail {

    /**
     * @var Store
     */
    private $store = null;

    /**
     * Sends a mail as specified in $mailarr.
     * If there is no receiver specified as 'TO': no mail is sent. This is ok and no error.
     * Logs every send mail as a record in table `MailLog`. Additionally a `grId`, `xId` , `xId2` and `xId3`  can be specified
     *   to assign the log entry to a specific action.
     * The log record also contains some information who/where generates the mail (form/formElement or QFQ query).
     *
     * @throws \CodeException
     * @throws \UserFormException
     * @throws \UserReportException
     */
    public function __construct() {

        $this->store = Store::getInstance('');
    }


    /**
     * @param array $mailConfig
     * @throws \CodeException
     * @throws \DbException
     * @throws \DownloadException
     * @throws \UserFormException
     * @throws \UserReportException
     * @throws \PhpOffice\PhpSpreadsheet\Exception
     * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
     * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
     */
    public function process(array $mailConfig) {

        // If there is no 'Receiver': do not send a mail.
        if (!isset($mailConfig[SENDMAIL_TOKEN_RECEIVER]) || $mailConfig[SENDMAIL_TOKEN_RECEIVER] === '') {
            return;
        }

        $forceSmtpSender = $this->store->getVar(SYSTEM_FORCE_SMTP_SENDER, STORE_SYSTEM . STORE_EMPTY);

        if (!empty($forceSmtpSender)) {
            $this->forceSmtpSender($forceSmtpSender, $mailConfig);
        }

        if ($mailConfig[SENDMAIL_TOKEN_SENDER] === '' || $mailConfig[SENDMAIL_TOKEN_SUBJECT] === '' || $mailConfig[SENDMAIL_TOKEN_BODY] === '') {
            throw new \UserFormException("Error sendmail missing one of: sender, subject or body", ERROR_SENDMAIL_MISSING_VALUE);
        }

        $redirectAllMail = $this->getRedirectAllMailTo();
        if (!empty($redirectAllMail)) {

            foreach ([SENDMAIL_TOKEN_RECEIVER, SENDMAIL_TOKEN_RECEIVER_CC, SENDMAIL_TOKEN_RECEIVER_BCC] as $key) {

                if (isset($mailConfig[$key])) {
                    $mailConfig[$key] = str_replace(',', ', ', $mailConfig[$key]);
                }
            }

            $addBody = "All QFQ outgoing mails are caught and redirected to you." . PHP_EOL . "Original receiver(s) are ..." . PHP_EOL;
            if (!empty($forceSmtpSender)) {
                $addBody .= "Mode forceSmtpSender is active!!!" . PHP_EOL;
            }
            $addBody .= 'TO: ' . ($mailConfig[SENDMAIL_TOKEN_RECEIVER] ?? '') . PHP_EOL;
            $addBody .= 'CC: ' . ($mailConfig[SENDMAIL_TOKEN_RECEIVER_CC] ?? '') . PHP_EOL;
            $addBody .= 'BCC: ' . ($mailConfig[SENDMAIL_TOKEN_RECEIVER_BCC] ?? '') . PHP_EOL;
            $addBody .= 'SENDER: ' . ($mailConfig[SENDMAIL_TOKEN_SENDER] ?? '') . PHP_EOL;
            $addBody .= 'REPLY TO: ' . ($mailConfig[SENDMAIL_TOKEN_REPLY_TO] ?? '') . PHP_EOL;
            $addBody .= 'WEBSITE: ' . $this->store->getVar(SYSTEM_BASE_URL, STORE_SYSTEM) . PHP_EOL;
            $addBody .= PHP_EOL . "==========================================" . PHP_EOL . PHP_EOL;

            // Check if the given body is a HTML body.
            if (isset($mailConfig[SENDMAIL_TOKEN_BODY_MODE]) && $mailConfig[SENDMAIL_TOKEN_BODY_MODE] === SENDMAIL_TOKEN_BODY_MODE_HTML) {
                $addBody = str_replace(PHP_EOL, '<br>', $addBody);
            }

            // Mails in plaintext: only '\n' should be used. Often, expecially in Templates saved via textarea, there are '\r\n' inside. Remove '\r'.
            $mailConfig[SENDMAIL_TOKEN_BODY] = str_replace("\\r", "", $addBody . $mailConfig[SENDMAIL_TOKEN_BODY]);

            $mailConfig[SENDMAIL_TOKEN_RECEIVER] = $redirectAllMail;
            $mailConfig[SENDMAIL_TOKEN_RECEIVER_CC] = '';
            $mailConfig[SENDMAIL_TOKEN_RECEIVER_BCC] = '';
            // This makes trouble in case of forceSmtpSender. Original intention to set this is also not clear.
            // $mailConfig[SENDMAIL_TOKEN_SENDER] = $redirectAllMail;
        }

        $mailConfig = $this->setDefault($mailConfig);

        $logAttachments = $this->sendEmail($mailConfig);
        $this->mailLog($mailConfig, $logAttachments);
    }

    /**
     * Process $list ($config[SYSTEM_REDIRECT_ALL_MAIL_TO]): it might be empty, a single email or a comma seperated list of emails.
     * Compare in the order given with TYPO3_BE_USER_EMAIL and TYPO3_FE_USER_EMAIL. If a match is found, return that one.
     * If no match is found, return the last address (fallback).
     *
     * @return string
     */
    public function getRedirectAllMailTo(): string {

        $catchAll = $this->store::getVar(SYSTEM_REDIRECT_ALL_MAIL_TO, STORE_SYSTEM . STORE_EMPTY);
        $arr = explode(',', $catchAll);

        if (count($arr) > 1) {
            $t3 = $this->store::getStore(STORE_TYPO3);
        } else {
            // none or only one email defined
            return $catchAll;
        }

        // Search list of emails for given FE or BE user.
        foreach ($arr as $email) {
            $email = trim($email);
            if (!empty($email) && ($email == ($t3[TYPO3_BE_USER_EMAIL] ?? '') || $email == ($t3[TYPO3_FE_USER_EMAIL] ?? ''))) {
                return $email;
            }
        }

        // Fallback: last named email
        return $email;
    }


    /**
     * @param array $mailConfig
     * @return array
     */
    private function setDefault(array $mailConfig) {

        if (empty($mailConfig[SENDMAIL_TOKEN_FLAG_AUTO_SUBMIT]) || $mailConfig[SENDMAIL_TOKEN_FLAG_AUTO_SUBMIT] === '') {
            $mailConfig[SENDMAIL_TOKEN_FLAG_AUTO_SUBMIT] = 'on';
        }

        // Subject
        if (empty($mailConfig[SENDMAIL_TOKEN_SUBJECT_HTML_ENTITY]) ||
            ($mailConfig[SENDMAIL_TOKEN_SUBJECT_HTML_ENTITY] !== MODE_ENCODE &&
                $mailConfig[SENDMAIL_TOKEN_SUBJECT_HTML_ENTITY] !== MODE_NONE)) {
            $mailConfig[SENDMAIL_TOKEN_SUBJECT_HTML_ENTITY] = MODE_DECODE;
        }

        // Body
        if (empty($mailConfig[SENDMAIL_TOKEN_BODY_HTML_ENTITY]) ||
            ($mailConfig[SENDMAIL_TOKEN_BODY_HTML_ENTITY] !== MODE_ENCODE &&
                $mailConfig[SENDMAIL_TOKEN_BODY_HTML_ENTITY] !== MODE_NONE)) {
            $mailConfig[SENDMAIL_TOKEN_BODY_HTML_ENTITY] = MODE_DECODE;
        }

        return $mailConfig;
    }

    /**
     * Use the programm 'sendEmail' - http://caspian.dotconf.net/menu/Software/SendEmail
     * Body and Subject is UTF8 encoded. Append attachments with '-a <file>'.
     *
     * @param array $mailConfig
     * @return string
     * @throws \CodeException
     * @throws \DbException
     * @throws \DownloadException
     * @throws \PhpOffice\PhpSpreadsheet\Exception
     * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
     * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
     * @throws \Twig\Error\LoaderError
     * @throws \Twig\Error\RuntimeError
     * @throws \Twig\Error\SyntaxError
     * @throws \UserFormException
     * @throws \UserReportException
     */
    private function sendEmail(array $mailConfig) {
        $args = array();
        $attachments = array();
        $cmdAttachments = '';

        $mailConfig[SENDMAIL_TOKEN_SUBJECT] = Support::htmlEntityEncodeDecode($mailConfig[SENDMAIL_TOKEN_SUBJECT_HTML_ENTITY], $mailConfig[SENDMAIL_TOKEN_SUBJECT]);
        $mailConfig[SENDMAIL_TOKEN_BODY] = Support::htmlEntityEncodeDecode($mailConfig[SENDMAIL_TOKEN_BODY_HTML_ENTITY], $mailConfig[SENDMAIL_TOKEN_BODY]);

        if (isset($mailConfig[SENDMAIL_TOKEN_BODY_MODE]) && $mailConfig[SENDMAIL_TOKEN_BODY_MODE] === SENDMAIL_TOKEN_BODY_MODE_HTML) {
            $mailConfig[SENDMAIL_TOKEN_BODY] = Support::wrapTag(SENDMAIL_HTML_TOKEN, $mailConfig[SENDMAIL_TOKEN_BODY]);
            // At least HTML code cleaned by QNL2BR() is missing any '\r\n'. A typicall MTA will inject a line break every 1000 Characters -
            // this might be in the middle of a word or URL. To prevent this, reinsert '\r\n' after each <br>.
            // TinyMce or other potential applications delivers '<br />' instead of '<br>'
            $mailConfig[SENDMAIL_TOKEN_BODY] = str_ireplace("<br />", "<br>", $mailConfig[SENDMAIL_TOKEN_BODY]);

            // First normalize existing "<br>\r\n" to "<br>", than replace "<br>" by "<br>\r\n".
            $mailConfig[SENDMAIL_TOKEN_BODY] = str_ireplace("<br>", "<br>\r\n",
                str_ireplace("<br>\r\n", "<br>", $mailConfig[SENDMAIL_TOKEN_BODY]));
        }

        foreach ($mailConfig as $key => $value) {
            if (is_array($value)) {
                continue;
            }
            if ($key != SENDMAIL_TOKEN_SUBJECT) { // do not escape double ticks in subject - this breaks the UTF8 encoding later
                $mailConfig[$key] = Support::escapeDoubleTick($value);
            }
        }

        $args[] = '-f "' . $mailConfig[SENDMAIL_TOKEN_SENDER] . '"';
        $args[] = '-t "' . $mailConfig[SENDMAIL_TOKEN_RECEIVER] . '"';
        $args[] = '-o message-charset="utf-8"';

        $logFilePathAbsolute = Path::absoluteMailLogFile();
        if ($logFilePathAbsolute != '' && $logFilePathAbsolute !== false) {
            $args[] = '-l "' . $logFilePathAbsolute . '"';
        }

        if (!empty($mailConfig[SENDMAIL_TOKEN_RECEIVER_CC])) {
            $args[] = '-cc "' . $mailConfig[SENDMAIL_TOKEN_RECEIVER_CC] . '"';
        }

        if (!empty($mailConfig[SENDMAIL_TOKEN_RECEIVER_BCC])) {
            $args[] = '-bcc "' . $mailConfig[SENDMAIL_TOKEN_RECEIVER_BCC] . '"';
        }

        if (!empty($mailConfig[SENDMAIL_TOKEN_SUBJECT])) {

            // The subject needs to be encoded to UTF-8 separately - https://stackoverflow.com/questions/4389676/email-from-php-has-broken-subject-header-encoding/27648245#27648245
            $preferences = ["scheme" => "Q", "input-charset" => "UTF-8", "output-charset" => "UTF-8"];
            $encodedSubject = iconv_mime_encode("Subject", $mailConfig[SENDMAIL_TOKEN_SUBJECT], $preferences);
            $encodedSubject = substr($encodedSubject, 9); // remove 'Subject: '

            $encodedSubject = Support::escapeDoubleTick($encodedSubject); // now, escape double ticks.

            $args[] = '-u "' . $encodedSubject . '"';
        }

        if (!empty($mailConfig[SENDMAIL_TOKEN_BODY])) {

            $args[] = '-m "' . $mailConfig[SENDMAIL_TOKEN_BODY] . '"';;
        }

        if (!empty($mailConfig[SENDMAIL_TOKEN_REPLY_TO])) {
            $args[] = '-o reply-to="' . $mailConfig[SENDMAIL_TOKEN_REPLY_TO] . '"';;
        }

        if ($mailConfig[SENDMAIL_TOKEN_FLAG_AUTO_SUBMIT] === 'on') {
            $args[] = '-o message-header="Auto-Submitted: auto-send"';
        }

        if (!empty($mailConfig[SENDMAIL_TOKEN_ATTACHMENT])) {
            $attachments = $this->attachmentsBuild($mailConfig[SENDMAIL_TOKEN_ATTACHMENT]);
            if (!empty($attachments)) {
                $cmdAttachments = '-a ' . implode(' -a ', $attachments);
                $args[] = $cmdAttachments;
            }
        }

        if (!empty($mailConfig[SENDMAIL_TOKEN_HEADER])) {
            $args[] = '-o message-header="' . $mailConfig[SENDMAIL_TOKEN_HEADER] . '"';
        }

        $sendEmail = Path::absoluteExt(Path::EXT_TO_SEND_EMAIL_FILE);
        if (empty($sendEmail)) {
            throw new \UserFormException("Missing 'sendEmail'", ERROR_SENDMAIL);
        }

        $sendEmailOptions = $this->store->getVar(SYSTEM_SEND_E_MAIL_OPTIONS, STORE_SYSTEM);
        if (!empty($sendEmailOptions)) {
            $args[] = $sendEmailOptions;
        }

        OnArray::arrayEscapeshellarg($args);
        $cmd = $sendEmail . ' ' . implode(' ', $args);
        $cmd = str_replace('`', '\`', $cmd); // escape backticks (escapeshellcmd would be too thorough)

        $output = Support::qfqExec($cmd, $rc);
        if ($rc != 0) {

            // After first installation of QFQ extension, the PERL script is not executable: is this the problem here?
            clearstatcache();
            $perms = fileperms($sendEmail);
            if (!($perms & 0x0040)) {

                chmod($sendEmail, 0755);
                clearstatcache();

                $output = Support::qfqExec($cmd, $rc);

                if ($rc != 0) {
                    if (!is_file($sendEmail) || !is_readable($sendEmail)) {
                        throw new \UserFormException(json_encode([ERROR_MESSAGE_TO_USER => "Command 'sendEmail' not found."
                            , ERROR_MESSAGE_TO_DEVELOPER => $sendEmail]), ERROR_SENDMAIL);
                    }

                    if (!is_executable($sendEmail)) {
                        throw new \UserFormException(json_encode([ERROR_MESSAGE_TO_USER => "Command 'sendEmail' not executable."
                            , ERROR_MESSAGE_TO_DEVELOPER => $sendEmail]), ERROR_SENDMAIL);
                    }
                }
            }

            if ($rc != 0) {
                throw new \UserFormException(json_encode([ERROR_MESSAGE_TO_USER => '"Error sendmail failed'
                    , ERROR_MESSAGE_TO_DEVELOPER => "[cmd=$cmd]$output"]), ERROR_SENDMAIL);
            }
        }

        HelperFile::cleanTempFiles($attachments);

        return $cmdAttachments;
    }


    /**
     * Creates a new MailLog Record based on $mailArr / $header.
     *
     * @param array $mailConfig
     * @param string $attachmentsLine
     *
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
     */
    private function mailLog(array $mailConfig, $attachmentsLine = '') {

        $log = array();

        $attachments = '';
        if (!empty($mailConfig[SENDMAIL_TOKEN_ATTACHMENT]) && is_array($mailConfig[SENDMAIL_TOKEN_ATTACHMENT])) {
            $attachments = OnArray::toString($mailConfig[SENDMAIL_TOKEN_ATTACHMENT]);
        }

        $header = 'OoO:' . $mailConfig[SENDMAIL_TOKEN_FLAG_AUTO_SUBMIT];
        if (!empty($mailConfig[SENDMAIL_TOKEN_HEADER])) {
            $header .= PHP_EOL . 'Custom: ' . $mailConfig[SENDMAIL_TOKEN_HEADER];
        }
        if (!empty($mailConfig[SENDMAIL_TOKEN_ATTACHMENT])) {
            $header .= PHP_EOL . 'Attachment: ' . $attachments;
        }

        // Log
        $log[] = $mailConfig[SENDMAIL_TOKEN_RECEIVER];
        $log[] = $mailConfig[SENDMAIL_TOKEN_RECEIVER_CC] ?? '';
        $log[] = $mailConfig[SENDMAIL_TOKEN_RECEIVER_BCC] ?? '';
        $log[] = $mailConfig[SENDMAIL_TOKEN_SENDER];
        $log[] = $mailConfig[SENDMAIL_TOKEN_SUBJECT];
        $log[] = $mailConfig[SENDMAIL_TOKEN_BODY];
        $log[] = $header;
        $log[] = $attachmentsLine;
        $log[] = empty($mailConfig[SENDMAIL_TOKEN_GR_ID]) ? 0 : $mailConfig[SENDMAIL_TOKEN_GR_ID];
        $log[] = empty($mailConfig[SENDMAIL_TOKEN_X_ID]) ? 0 : $mailConfig[SENDMAIL_TOKEN_X_ID];
        $log[] = empty($mailConfig[SENDMAIL_TOKEN_X_ID2]) ? 0 : $mailConfig[SENDMAIL_TOKEN_X_ID2];
        $log[] = empty($mailConfig[SENDMAIL_TOKEN_X_ID3]) ? 0 : $mailConfig[SENDMAIL_TOKEN_X_ID3];
        $log[] = empty($mailConfig[SENDMAIL_TOKEN_SRC]) ? 0 : $mailConfig[SENDMAIL_TOKEN_SRC];

        $db = new Database();
        $db->sql('INSERT INTO `MailLog` (`receiver`, `cc`, `bcc`, `sender`, `subject`, `body`, `header`, `attach`, `grId`, `xId`, `xId2`, `xId3`, `src`, `modified`, `created`) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW() )', ROW_REGULAR, $log);

    }

    /**
     *
     * @param array $attachments Array of attachments. Per attachment, different & multiple sources are possible.
     *              [ [ 0 -> 'F:/etc/hostname' ],
     *                [ 0 -> 'u:http://nzz.ch', 1 -> 'd:nzz.pdf' ],
     *                [ 0 -> 'p:id=detailPerson&form=Person&r=1&_sip=1', 1 -> 'F:/etc/hostname'  ]
     *                [ 0 -> 'd:all.pdf', 1 -> 'U:?id=detailPerson&form=Person&r=1&_sip=1', 2 -> 'F:/etc/hostname'  ] ]
     * @return array Array of filenames. Those files has to be deleted later, if they are temporary files.
     * @throws \CodeException
     * @throws \DbException
     * @throws \DownloadException
     * @throws \PhpOffice\PhpSpreadsheet\Exception
     * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
     * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
     * @throws \Twig\Error\LoaderError
     * @throws \Twig\Error\RuntimeError
     * @throws \Twig\Error\SyntaxError
     * @throws \UserFormException
     * @throws \UserReportException
     */
    private function attachmentsBuild(array $attachments) {
        $files = array();
        $download = new Download();
        $tmpDir = sys_get_temp_dir();

        // Several attachments are possible. Process one by one.
        foreach ($attachments as $attach) {
            $vars = array();

            // Extract Filename
            $exportFilename = '';
            $downloadMode = '';

            // Per attachment: extract 'mode','filename'. Leave the 'sources' untouched.
            foreach ($attach as $key => $element) {
                $token = $element[0];
                switch ($token) {

                    case SENDMAIL_TOKEN_DOWNLOAD_FILENAME:
                        $exportFilename = substr($element, 2);
                        unset($attach[$key]);
                        break;

                    case SENDMAIL_TOKEN_DOWNLOAD_MODE:
                        $downloadMode = substr($element, 2);
                        unset($attach[$key]);
                        break;

                    case SENDMAIL_TOKEN_ATTACHMENT_FILE:
                        if ($downloadMode == '') { // Set only if not explicitly given.
                            $downloadMode = DOWNLOAD_MODE_FILE;
                        }
                        break;

                    case SENDMAIL_TOKEN_ATTACHMENT_URL:
                    case SENDMAIL_TOKEN_ATTACHMENT_URL_PARAM:
                    case SENDMAIL_TOKEN_ATTACHMENT_PAGE:
                        if ($downloadMode == '') { // Set only if not explicitly given.
                            $downloadMode = DOWNLOAD_MODE_PDF;
                        }
                        break;

                    default:
                        throw new \UserReportException('Unknown token in _sendmail: ' . $token, ERROR_UNKNOWN_TOKEN);
                        break;
                }
            }

            $vars[SIP_DOWNLOAD_PARAMETER] = implode(PARAM_DELIMITER, $attach);
            $vars[DOWNLOAD_MODE] = (count($attach) > 1) ? DOWNLOAD_MODE_PDF : $downloadMode;

            $file = $download->process($vars, OUTPUT_MODE_FILE);

            if (empty($exportFilename) && HelperFile::isQfqTemp($file)) {

                $exportFilename = DOWNLOAD_OUTPUT_FILENAME;
                if ($downloadMode == DOWNLOAD_MODE_PDF) {
                    $exportFilename .= '.pdf';
                }
            }

            // In case an exportFilename is given: move/rename it, if it is necessary.
            if (!empty($exportFilename)) {

                $exportFilename = Sanitize::safeFilename($exportFilename, true);

                $dir = HelperFile::mktempdir(); // Attachments might have the same filename - create one directory per attachment, to ensure same filenames do not conflict.
                $exportFilename = $dir . '/' . $exportFilename;

                if (HelperFile::isQfqTemp($file)) {
                    HelperFile::rename($file, $exportFilename);
                } else {
                    HelperFile::copy($file, $exportFilename);
                }
                $file = $exportFilename;
            }
            $files[] = $file;
        }

        return $files;
    }


    /**
     * Convert a token based sendMail string into an array.
     * - Each attachment (single file or multiple concatenated files) is an array in the array.
     *
     * @param string $data E.g.: 't:john@doe.com|f:jane@miller.com|s:Latest|b:Dear John ...'
     * @return array
     * @throws \UserFormException
     */
    public function parseStringToArray($data) {

        $args = array();
        $attachment = array();

        $flagConcat = false;
        $flagSource = false;
        $flagFilename = false;

        $convertToShorthandToken = [
            SENDMAIL_TOKEN_RECEIVER_LONG => SENDMAIL_TOKEN_RECEIVER,
            SENDMAIL_TOKEN_SENDER_LONG => SENDMAIL_TOKEN_SENDER,
            SENDMAIL_TOKEN_SUBJECT_LONG => SENDMAIL_TOKEN_SUBJECT,
            SENDMAIL_TOKEN_BODY_LONG => SENDMAIL_TOKEN_BODY,
            SENDMAIL_TOKEN_RECEIVER_CC_LONG => SENDMAIL_TOKEN_RECEIVER_CC,
            SENDMAIL_TOKEN_RECEIVER_BCC_LONG => SENDMAIL_TOKEN_RECEIVER_BCC,
            SENDMAIL_TOKEN_REPLY_TO_LONG => SENDMAIL_TOKEN_REPLY_TO,
            SENDMAIL_TOKEN_FLAG_AUTO_SUBMIT_LONG => SENDMAIL_TOKEN_FLAG_AUTO_SUBMIT,
            SENDMAIL_TOKEN_GR_ID_LONG => SENDMAIL_TOKEN_GR_ID,
            SENDMAIL_TOKEN_X_ID_LONG => SENDMAIL_TOKEN_X_ID,
            SENDMAIL_TOKEN_X_ID2_LONG => SENDMAIL_TOKEN_X_ID2,
            SENDMAIL_TOKEN_X_ID3_LONG => SENDMAIL_TOKEN_X_ID3,
            SENDMAIL_TOKEN_HEADER_LONG => SENDMAIL_TOKEN_HEADER,
            SENDMAIL_TOKEN_SRC_LONG => SENDMAIL_TOKEN_SRC,
            SENDMAIL_TOKEN_BODY_MODE_LONG => SENDMAIL_TOKEN_BODY_MODE,
        ];

        $param = explode(PARAM_DELIMITER, $data);

        // Iterate over all parameter: use token as key. Collect corresponding attachments arguments in separate array elements.
        foreach ($param as $line) {

            if (empty($line)) {
                continue;
            }

            $tokenAndValue = explode(PARAM_TOKEN_DELIMITER, $line, 2);
            if (count($tokenAndValue) < 2 && $tokenAndValue[0] !== SENDMAIL_TOKEN_CONCAT) {
                throw new \UserFormException('Missing token delimiter "' . PARAM_TOKEN_DELIMITER . '" in: ' . $line, ERROR_UNKNOWN_TOKEN);
            }

            $token = $tokenAndValue[0];
            // convert speaking word tokens to shorthand
            if (strlen($token) > 1) {
                $token = strtolower($token); // speaking word tokens are all lowercase
                if (isset($convertToShorthandToken[$token])) {
                    $token = $convertToShorthandToken[$token];
                } else {
                    throw new \UserFormException('Unknown token "' . $token . '" in: ' . $line, ERROR_UNKNOWN_TOKEN);
                }
            }

            // Check for deprecated token.
            if ($token == SENDMAIL_TOKEN_ATTACHMENT_FILE_DEPRECATED) {
                throw new \UserFormException('Sendmail: Option "a:" is deprecated, please use "' . SENDMAIL_TOKEN_ATTACHMENT_FILE . '" instead', ERROR_UNKNOWN_TOKEN);
            }

            switch ($token) {
                case SENDMAIL_TOKEN_CONCAT:
                    $flagConcat = true;
                    if (!empty($attachment)) {
                        $args[SENDMAIL_TOKEN_ATTACHMENT][] = $attachment;
                        $attachment = array();
                    }
                    break;

                case SENDMAIL_TOKEN_ATTACHMENT_FILE:
                case SENDMAIL_TOKEN_ATTACHMENT_URL:
                case SENDMAIL_TOKEN_ATTACHMENT_URL_PARAM:
                case SENDMAIL_TOKEN_ATTACHMENT_PAGE:
                    if ($flagSource && !$flagConcat) {
                        $args[SENDMAIL_TOKEN_ATTACHMENT][] = $attachment;
                        $attachment = array();
                        $flagFilename = false;
                    }
                    $flagSource = true;
                    $attachment[] = $line;
                    break;

                case SENDMAIL_TOKEN_DOWNLOAD_FILENAME:
                    if ($flagFilename && !$flagConcat) {
                        $args[SENDMAIL_TOKEN_ATTACHMENT][] = $attachment;
                        $attachment = array();
                        $flagSource = false;
                    }
                    $flagFilename = true;
                    $attachment[] = $line;
                    break;

                default:
                    $args[$token] = $tokenAndValue[1];
                    break;
            }
        }

        if (!empty($attachment)) {
            $args[SENDMAIL_TOKEN_ATTACHMENT][] = $attachment;
        }

        return ($args);
    }

    /**
     * Force sender email address to be $forceSmtpSender.
     * - If not given, fill SENDMAIL_TOKEN_REPLY_TO with the orginal meant 'SENDER'.
     * - $forcelSmtpSender might be in format 'john@doe.com' or 'John Doe <john@doe.com>'
     *
     * @param $forceSmtp
     * @param $mailConfig
     * @return void
     */
    private function forceSmtpSender($forceSmtp, &$mailConfig) {

        // Split all to find the most detailed realname.
        $sender = OnString::splitEmailRealname($mailConfig[SENDMAIL_TOKEN_SENDER] ?? '');
        $smtp = OnString::splitEmailRealname($forceSmtp);
        $replyto = OnString::splitEmailRealname($mailConfig[SENDMAIL_TOKEN_REPLY_TO] ?? '');

        if (!empty($replyto[NAME_REALNAME])) {
            // First choice would be a defined realname from replyto
            $realname = $replyto[NAME_REALNAME];
        } elseif (!empty($sender[NAME_REALNAME])) {
            // Second choice would be a defined realname from sender
            $realname = $sender[NAME_REALNAME];
        } elseif (!empty($smtp[NAME_REALNAME])) {
            // Third choice would be a defined realname from generalSmtp
            $realname = $smtp[NAME_REALNAME];
        } else {
            // Last option: choose the original sender email as realname.
            $realname = $sender[NAME_EMAIL];
        }

        // If there is not already a relyto defined: set it.
        if (empty($mailConfig[SENDMAIL_TOKEN_REPLY_TO])) {
            $mailConfig[SENDMAIL_TOKEN_REPLY_TO] = $sender[NAME_EMAIL];
        }

        // Force given smtp
        $mailConfig[SENDMAIL_TOKEN_SENDER] = $realname . ' <' . $smtp[NAME_EMAIL] . '>';
    }

}
