<?php

/**
 * @file classes/mail/transport/PHPMailerTransport.php
 *
 * Copyright (c) 2014-2022 Simon Fraser University
 * Copyright (c) 2000-2022 John Willinsky
 * Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
 *
 * @class PHPMailerTransport.php
 *
 * @brief Transport/adapter to send with PHPMailer
 */

namespace PKP\mail\transport;

use APP\core\Application;
use Exception;
use PHPMailer\PHPMailer\PHPMailer;
use PKP\core\PKPString;
use ReflectionObject;
use Symfony\Component\Mailer\Envelope;
use Symfony\Component\Mailer\SentMessage;
use Symfony\Component\Mailer\Transport\TransportInterface;
use Symfony\Component\Mime\Email;
use Symfony\Component\Mime\RawMessage;

class PHPMailerTransport implements TransportInterface
{
    public const MAIL_WRAP = 76;

    /**
     * @inheritDoc
     */
    public function send(RawMessage $symfonyMessage, ?Envelope $envelope = null): ?SentMessage
    {
        if (!($symfonyMessage instanceof Email)) {
            throw new Exception('Can\'t send raw message with phpmailer');
        }

        $phpmailerMessage = $this->getPHPMailerMessage($symfonyMessage);
        try {
            $success = $phpmailerMessage->Send();
            if (!$success) {
                error_log($phpmailerMessage->ErrorInfo);
                return null;
            }
        } catch (Exception $e) {
            error_log($phpmailerMessage->ErrorInfo);
            return null;
        }

        return new SentMessage($symfonyMessage, $envelope ?? Envelope::create($symfonyMessage));
    }

    public function __toString(): string
    {
        return 'phpmailer';
    }

    /**
     * We won't send symfony message; transfer data to PHPMailer to send with mail()
     *
     * @throws Exception
     */
    protected function getPHPMailerMessage(Email $symfonyMessage): PHPMailer
    {
        $mailer = new PHPMailer();
        $mailer->isMail();
        $mailer->IsHTML();
        $mailer->Encoding = PHPMailer::ENCODING_BASE64;
        $mailer->CharSet = $symfonyMessage->getHtmlCharset() ?? 'utf-8';
        $mailer->XMailer = 'Public Knowledge Project Suite v3';
        $mailer->WordWrap = static::MAIL_WRAP;

        $request = Application::get()->getRequest();
        $fromAddresses = $symfonyMessage->getFrom();
        if (!empty($fromAddresses)) {
            $f = array_shift($fromAddresses);
            if (!empty($fromAddresses)) {
                throw new Exception('Can\'t set multiple From email field value with PHPMailer');
            }
            $mailer->setFrom($f->getAddress(), $f->getName());
        }
        // Set the envelope sender (RFC5321.MailFrom)
        if (($s = $symfonyMessage->getSender()) != null) {
            $mailer->Sender = $s->toString();
        }
        foreach ($symfonyMessage->getReplyTo() as $r) {
            $mailer->addReplyTo($r->getAddress(), $r->getName());
        }
        foreach ($symfonyMessage->getTo() as $recipientInfo) {
            $mailer->addAddress($recipientInfo->getAddress(), $recipientInfo->getName());
        }
        foreach ($symfonyMessage->getCc() as $ccInfo) {
            $mailer->addCC($ccInfo->getAddress(), $ccInfo->getName());
        }
        foreach ($symfonyMessage->getBcc() as $bccInfo) {
            $mailer->addBCC($bccInfo->getAddress(), $bccInfo->getName());
        }
        $mailer->Subject = $symfonyMessage->getSubject();
        $mailer->Body = $symfonyMessage->getHtmlBody();
        $mailer->AltBody = PKPString::html2text($symfonyMessage->getHtmlBody());

        $remoteAddr = $mailer->secureHeader($request->getRemoteAddr());
        if ($remoteAddr != '') {
            $mailer->addCustomHeader("X-Originating-IP: {$remoteAddr}");
        }

        foreach ($this->getAttachments($symfonyMessage) as $attachmentInfo) {
            $mailer->addStringAttachment(
                $attachmentInfo['body'],
                $attachmentInfo['filename'],
                PHPMailer::ENCODING_BASE64,
                $attachmentInfo['mediaType'] . '/' . $attachmentInfo['subtype']
            );
        }

        return $mailer;
    }

    /**
     * Retrieve Symfony Message's assignments
     */
    protected function getAttachments(Email $message): array
    {
        $attachments = [];
        foreach ($message->getAttachments() as $dataPart) {
            $reflection = new ReflectionObject($dataPart);
            $attachment = [];
            foreach ($reflection->getProperties() as $property) {
                switch ($property->getName()) {
                    case 'filename':
                    case 'mediaType':
                        $property->setAccessible(true);
                        $attachment[$property->getName()] = $property->getValue($dataPart);
                        break;
                }
            }

            $attachment['subtype'] = $dataPart->getMediaSubtype();
            $attachment['body'] = $dataPart->getBody();
            $attachments[] = $attachment;
        }

        return $attachments;
    }
}
