SDKiller/zyx-phpmailer

View on GitHub
Mailer.php

Summary

Maintainability
B
6 hrs
Test Coverage
<?php
/**
 * @link https://github.com/SDKiller/zyx-phpmailer
 * @copyright Copyright (c) 2014 Serge Postrash
 * @license BSD 3-Clause, see LICENSE.md
 */

namespace zyx\phpmailer;

use Yii;
use yii\base\InvalidConfigException;
use yii\helpers\ArrayHelper;
use yii\mail\BaseMailer;


class Mailer extends BaseMailer
{
    /**
     * @var string $messageClass - message default class name
     */
    public $messageClass = 'zyx\phpmailer\Message';
    /**
     * @var Adapter $adapter - instance extended from phpMailer class
     */
    public $adapter;
    /**
     * @var array $config - configuration array of initial phpMailer settings, defined in 'components' => 'mail' => 'config' => [...]
     */
    public $config = [];
    /**
     * @var bool $success - the result which has been passed by PHPMailer to our callback function
     * (it was designed as static property to avoid 'Using this when not in object context' error on callback)
     */
    protected static $success = false;
    /**
     * @var string $htmlView - html view file, may be set as default (we introduce it in addition to default '$htmlLayout')
     */
    public $htmlView = null;


    /**
     * For example, you may predefine in 'messageConfig' default contents of 'From' field:
     * ~~~
     * 'mail' => [
     *      ...
     *      'messageConfig'    => [
     *          'from' => ['noreply@example.com' => 'My Example Site']
     *      ],
     *      ...
     * ];
     * ~~~
     */
    public function init()
    {
        $this->adapter = new Adapter();

        if (!is_array($this->config)) {
            throw new InvalidConfigException('Mailer config should be set in terms of array');
        }

        if (!empty($this->config)) {
            //special handling of language
            $this->adapter->setLanguage(ArrayHelper::remove($this->config, 'language', Yii::$app->language));

            //special handling of callback (see definition of \PHPMailer::$action_function)
            $this->adapter->setCallback(ArrayHelper::remove($this->config, 'callback', 'zyx\phpmailer\Mailer::processResult'));

            //special hadling of charset. Note: PHPMailer in [[createBody()]] overrides charset and sets 'us-ascii' if no 8-bit chars are found!
            $this->adapter->setCharset(!empty($this->messageConfig['charset']) ? $this->messageConfig['charset'] : ArrayHelper::remove($this->config, 'charset', Yii::$app->charset));

            //special handling of our 'global' isHTML switch
            $this->adapter->isHTML((bool)ArrayHelper::remove($this->config, 'ishtml', false));

            //set other properties, compliant with PHPMailer's configuration public properties
            foreach (get_object_vars($this->adapter) as $prop => $value) {
                $key = strtolower($prop);
                if (array_key_exists($key, $this->config)) {
                    $this->adapter->$prop = $this->config[$key];
                }
            }
        }

        //Set current message date initially - a workaround for MessageDate bug in PHPMailer <= 5.2.7
        //see https://github.com/PHPMailer/PHPMailer/pull/227

        if (version_compare($this->adapter->getVersion(), '5.2.7', '<=') && $this->adapter->getMessageDate() == '') {
            $this->adapter->setMessageDate();
        }

    }

    /**
     * @inheritdoc
     */
    public function compose($view = null, array $params = [])
    {
        //attempt to override default layouts dynamically
        if (array_key_exists('htmlLayout', $params)) {
            $this->htmlLayout = $params['htmlLayout'];
            unset($params['htmlLayout']);
        }
        if (array_key_exists('textLayout', $params)) {
            $this->textLayout = $params['textLayout'];
            unset($params['textLayout']);
        }

        $message = $this->createMessage();

        if ($view !== null) {

            //Note: we cannot process plain text and html bodies at the same time!
            //If we mean 'multipart/alternative' - content in both HTML and plain text format cannot much differ
            //because of anti-spam filters - see http://wiki.apache.org/spamassassin/Rules/MPART_ALT_DIFF
            //PHPMailer autocreates alt-body - we don't need to call additionally [[setTextBody()]] like in parent method

            $params['message'] = $message;

            if (is_array($view)) {

                if (isset($view['html'])) {
                    $this->htmlView = $view['html'];
                    if (sizeof($params) > 1) {
                        //render view only if something is passed to it
                        //othervise consider we just wanted to setup view to call [[setHtmlBody()]] later
                        $html = $this->render($this->htmlView, $params, $this->htmlLayout);
                    }
                } elseif (isset($view['text'])) {
                    //keep it simple - preserve parent behavior for text rendering
                    $text = $this->render($view['text'], $params, $this->textLayout);
                    if (!empty($text)) {
                        $message->setTextBody($text);
                    }
                }

            } else {
                //string argument is considered as html view by BaseMailer - preserve that behavior
                $this->htmlView = $view;
                if (sizeof($params) > 1) {
                    //render view only if something is passed to it
                    //othervise consider we just wanted to setup view to call [[setHtmlBody()]] later
                    $html = $this->render($this->htmlView, $params, $this->htmlLayout);
                }
            }

            if (!empty($html)) {
                $message->setHtmlBody($html);
            }
        }

        return $message;
    }

    /**
     * @inheritdoc
     */
    protected function createMessage()
    {
        $config = array();

        //we have to put 'mailer' at the beginning of configuration array!!!
        $config['mailer'] = $this;

        if (!array_key_exists('class', $this->messageConfig)) {
            $config['class'] = $this->messageClass;
        }
        foreach ($this->messageConfig as $key => $val) {
            $config[$key] = $val;
        }

        return Yii::createObject($config);
    }

    /**
     * @inheritdoc
     */
    public function send($message)
    {
        if (!$this->beforeSend($message)) {
            return false;
        }

        if ($this->useFileTransport) {
            $isSuccessful = $this->saveMessage($message);
        } else {
            $isSuccessful = $this->sendMessage($message);
        }

        $this->afterSend($message, $isSuccessful);

        $this->adapter->resetMailer();

        return $isSuccessful;
    }

    /**
     * @inheritdoc
     */
    protected function sendMessage($message)
    {
        //Actually PHPMailer returns 'false' on error or exception when composing message on [[preSend()]] stage,
        //after that [[send()]] will return 'true', actual result of sending message is returned to callback function

        $result = $this->adapter->send();

        if ($result === false) {
            //Failure on the very first step, callback function will NOT be called by PHPMailer
            self::$success = $result;
            if (YII_DEBUG) {
                Yii::info('FAILED - Sending email ' . implode(';', array_keys($message->getTo())) . ' "' . $message->getSubject() . '"', 'application');
            }
        }

        //If not - static property will be modified by PHPMailer via our [[processResult()]] callback

        return self::$success;
    }

    /**
     * @inheritdoc
     */
    public function beforeSend($message)
    {
        //actually nothing to do here
        //parent '$event->isValid' implementation makes no sense for us if PHPMailer returns 'false' on [[send()]]

        return true;
    }

    /**
     * This method is invoked right after mail was send.
     * You may override this method to do some postprocessing or logging based on mail send status.
     * If you override this method, please make sure you call the parent implementation first.
     *
     * Note: as of Yii 2.0.0-beta - hardcoded swiftmailer methods result in UnknownMethodException in debug MailPanel,
     * for patch @see https://github.com/yiisoft/yii2/commit/3838b501babc1f5b9e6b0ba452512fba136d3102
     *
     * @param \yii\mail\MessageInterface $message
     * @param boolean                    $isSuccessful
     */
    public function afterSend($message, $isSuccessful)
    {
        parent::afterSend($message, $isSuccessful);
    }

    /**
     * @inheritdoc
     */
    protected function saveMessage($message)
    {
        //we have to generate message first - or [[toString()]] returns empty value
        $this->adapter->preSend();

        return parent::saveMessage($message);
    }

    /**
     * This is a callback function to retrieve result returned by PHPMailer
     *
     * @var bool    $result        result of the send action
     * @var string  $to            email address of the recipient
     * @var string  $cc            cc email addresses
     * @var string  $bcc           bcc email addresses
     * @var string  $subject       the subject
     * @var string  $body          the email body
     * @var string  $from          email address of sender
     */
    public static function processResult($result, $to = '', $cc = '', $bcc = '', $subject = '', $body = '', $from = '')
    {
        self::$success = $result;

        if (YII_DEBUG) {

            $msg = ' - Sending email. ';
            //native PHPMailer's way to pass results to [[doCallback()]] function is a little bit strange
            $msg .= (!empty($to)) ? ('To: ' . (is_array($to) ? implode(';', $to) : $to) . '.') : '';
            $msg .= (!empty($cc)) ? ('Cc: ' . (is_array($cc) ? implode(';', $cc) : $cc) . '.') : '';
            $msg .= (!empty($bcc)) ? ('Bcc: ' . (is_array($bcc) ? implode(';', $bcc) : $bcc) . '.') : '';

            $msg .= ' Subject: "' . $subject . '"';
            if ($result) {
                Yii::info('OK' . $msg, __METHOD__);
            } else {
                Yii::warning('FAILED' . $msg, __METHOD__);
            }
        }
    }

}