Файл: library/XenForo/Mail.php
Строк: 644
<?php
/**
 * Class to manage preparing and sending emails. This sends plain text
 * and HTML emails.
 *
 * @package XenForo_Mail
 */
class XenForo_Mail
{
    /**
     * A cache of previously sent emails.
     *
     * @var array Format: [email title][language id] => template code
     */
    protected static $_emailCache = array();
    /**
     * Stores whether or not the transport layer has been setup. This is setup
     * when the first mail is to be sent, but can be explicitly called if desired.
     *
     * @var boolean
     */
    protected static $_transportSetup = false;
    /**
     * List of email templates that need to be pre-cached.
     *
     * @var array Format: [email title] => true
     */
    protected static $_preCache = array('MAIL_CONTAINER' => true);
    /**
     * The title of the email to be sent by this instance.
     *
     * @var string
     */
    protected $_emailTitle = '';
    /**
     * Parameters to pass to the email template.
     *
     * @var array Key-value pairs
     */
    protected $_params = array();
    /**
     * The language ID the email should be sent in.
     *
     * @var integer
     */
    protected $_languageId = 0;
    /**
     * Controls whether the phrase value for all languages should
     * be pre-cached. This is useful when sending the same email to
     * multiple users (eg, subscription notifications).
     *
     * @var boolean
     */
    protected $_preCacheAllLanguages = false;
    /**
     * Captured exception when an email fails to send (at the transport level).
     *
     * @var Exception|null
     */
    protected $_failureException = null;
    protected static $_headerMap = array(
        'cc' => 'addCc',
        'bcc' => 'addBcc',
        'reply-to' => 'setReplyTo',
        'message-id' => 'setMessageId',
    );
    /**
     * Constructor.
     *
     * @param string $emailTitle Title of the email template
     * @param array $params Key-value params to pass to email template
     * @param integer|null $languageId Language of email; if null, uses language of current user (if setup)
     */
    public function __construct($emailTitle, array $params, $languageId = null)
    {
        if (!XenForo_Application::isRegistered('languages'))
        {
            XenForo_Application::set('languages', XenForo_Model::create('XenForo_Model_Language')->getAllLanguagesForCache());
        }
        if ($languageId === null)
        {
            $languageId = XenForo_Phrase::getLanguageId();
        }
        else if (!$languageId)
        {
            $languageId = XenForo_Application::get('options')->defaultLanguageId;
        }
        else
        {
            $languages = XenForo_Application::get('languages');
            if (!isset($languages[$languageId]))
            {
                $languageId = XenForo_Application::get('options')->defaultLanguageId;
            }
        }
        $this->_emailTitle = $emailTitle;
        $this->_params = $params;
        $this->_languageId = $languageId;
        if (!isset(self::$_emailCache[$emailTitle][$languageId]))
        {
            self::$_preCache[$emailTitle] = true;
        }
    }
    /**
     * Enables pre-caching of this email template in all languages. This will
     * only apply if the email template needs to be loaded.
     */
    public function enableAllLanguagePreCache()
    {
        $this->_preCacheAllLanguages = true;
    }
    /**
     * Sends the given email.
     *
     * @param string $toEmail The email address the email is sent to
     * @param string $toName Name of the person receiving it
     * @param array $headers List of additional headers to send
     * @param string $fromEmail Email address the email should come from; if not specified, uses board default
     * @param string $fromName Name the email should come from; if not specified, uses board default
     * @param string $returnPath The return path of the email (where bounces should go to)
     *
     * @return boolean True on success
     */
    public function send($toEmail, $toName = '', array $headers = array(), $fromEmail = '', $fromName = '', $returnPath = '')
    {
        if (!$toEmail)
        {
            return false;
        }
        $mailObj = $this->getPreparedMailHandler($toEmail, $toName, $headers, $fromEmail, $fromName, $returnPath);
        if (!$mailObj)
        {
            return false;
        }
        return $this->sendMail($mailObj);
    }
    /**
     * Sends the given mail object. The mail transport system will be setup first,
     * if necessary.
     *
     * @param Zend_Mail $mailObj Mail to send.
     *
     * @return boolean
     */
    public function sendMail(Zend_Mail $mailObj)
    {
        if (!self::$_transportSetup)
        {
            self::setupTransport();
        }
        if (!XenForo_Application::get('config')->enableMail)
        {
            return true;
        }
        try
        {
            $mailObj->send();
        }
        catch (Exception $e)
        {
            $this->_failureException = $e;
            $toEmails = implode(', ', $mailObj->getRecipients());
            XenForo_Error::logException($e, false, "Email to $toEmails failed: ");
            return false;
        }
        return true;
    }
    /**
     * Prepares an email for sending, but places it in a queue for sending later.
     *
     * @param string $toEmail The email address the email is sent to
     * @param string $toName Name of the person receiving it
     * @param array $headers List of additional headers to send
     * @param string $fromEmail Email address the email should come from; if not specified, uses board default
     * @param string $fromName Name the email should come from; if not specified, uses board default
     * @param string $returnPath The return path of the email (where bounces should go to)
     *
     * @return boolean True on success
     */
    public function queue($toEmail, $toName = '', array $headers = array(), $fromEmail = '', $fromName = '', $returnPath = '')
    {
        if (!$toEmail)
        {
            return false;
        }
        if (!XenForo_Application::get('config')->enableMail)
        {
            return true;
        }
        if (!XenForo_Application::get('config')->enableMailQueue)
        {
            return $this->send($toEmail, $toName, $headers, $fromEmail, $fromName, $returnPath);
        }
        $mailObj = $this->getPreparedMailHandler($toEmail, $toName, $headers, $fromEmail, $fromName, $returnPath);
        if (!$mailObj)
        {
            return false;
        }
        return XenForo_Model::create('XenForo_Model_MailQueue')->insertMailQueue($mailObj);
    }
    /**
     * Gets the fully prepared, internal mail object. This can be called directly
     * to allow advanced manipulation before sending
     *
     * @param string $toEmail The email address the email is sent to
     * @param string $toName Name of the person receiving it
     * @param array $headers List of additional headers to send
     * @param string $fromEmail Email address the email should come from; if not specified, uses board default
     * @param string $fromName Name the email should come from; if not specified, uses board default
     * @param string $returnPath The return path of the email (where bounces should go to)
     *
     * @return Zend_Mail|false
     */
    public function getPreparedMailHandler($toEmail, $toName = '', array $headers = array(), $fromEmail = '', $fromName = '', $returnPath = '')
    {
        if (!$toEmail)
        {
            return false;
        }
        $contents = $this->prepareMailContents();
        if (!$contents)
        {
            return false;
        }
        $contents = $this->wrapMailContainer($contents['subject'], $contents['bodyText'], $contents['bodyHtml']);
        $mailObj = new Zend_Mail('utf-8');
        $mailObj->setSubject($contents['subject'])
            ->setBodyText($contents['bodyText'])
            ->addTo($toEmail, $toName);
        if ($contents['bodyHtml'] !== '')
        {
            $mailObj->setBodyHtml($contents['bodyHtml']);
        }
        $options = XenForo_Application::get('options');
        if (!$fromName)
        {
            $fromName = ($options->emailSenderName ? $options->emailSenderName : $options->boardTitle);
        }
        if ($fromEmail)
        {
            $mailObj->setFrom($fromEmail, $fromName);
        }
        else
        {
            $mailObj->setFrom($options->defaultEmailAddress, $fromName);
        }
        if ($returnPath)
        {
            $mailObj->setReturnPath($returnPath);
        }
        else
        {
            $bounceEmailAddress = $options->bounceEmailAddress;
            if (!$bounceEmailAddress)
            {
                $bounceEmailAddress = $options->defaultEmailAddress;
            }
            $mailObj->setReturnPath($bounceEmailAddress);
        }
        foreach ($headers AS $headerName => $headerValue)
        {
            if (isset(self::$_headerMap[strtolower($headerName)])) {
                $func = self::$_headerMap[strtolower($headerName)];
                $mailObj->$func($headerValue);
            }
            else
            {
                $mailObj->addHeader($headerName, $headerValue);
            }
        }
        if (!$mailObj->getMessageId())
        {
            $mailObj->setMessageId();
        }
        return $mailObj;
    }
    /**
     * Prepares the subject, plain text body, and HTML body.
     *
     * @param string|null $emailTitle Title of email to send. If not specified, uses value from consructor.
     * @param array|null $params Params to pass to email template. If not specified, uses value from constructor.
     *
     * @return array|false False if the template can't be found; otherwise array with subject, bodyText, and bodyHtml keys
     */
    public function prepareMailContents($emailTitle = null, array $params = null)
    {
        if ($emailTitle === null)
        {
            $emailTitle = $this->_emailTitle;
        }
        if ($params === null)
        {
            $params = $this->_params;
        }
        $__template = $this->_loadEmailTemplate($emailTitle);
        if (!$__template)
        {
            return false;
        }
        $__defaultLanguage = XenForo_Template_Helper_Core::getDefaultLanguage();
        $__languages = XenForo_Application::get('languages');
        if (isset($__languages[$this->_languageId]))
        {
            XenForo_Template_Helper_Core::setDefaultLanguage($__languages[$this->_languageId]);
        }
        $xenOptions = XenForo_Application::get('options')->getOptions();
        extract($params);
        $emailLanguage = XenForo_Template_Helper_Core::getDefaultLanguage();
        $emailIsRtl = (isset($emailLanguage['text_direction']) && $emailLanguage['text_direction'] == 'RTL');
        $__oldErrors = error_reporting(E_ALL & ~E_NOTICE);
        XenForo_Application::disablePhpErrorHandler();
        // these variables come from the $__template
        $__subject = $__bodyText = $__bodyHtml = '';
        eval($__template);
        XenForo_Application::enablePhpErrorHandler();
        error_reporting($__oldErrors);
        XenForo_Template_Helper_Core::setDefaultLanguage($__defaultLanguage);
        if ($emailIsRtl)
        {
            $__bodyHtml = preg_replace_callback('/<rtlcss>(.*)</rtlcss>/sU', array($this, '_replaceRtlCss'), $__bodyHtml);
        }
        else
        {
            $__bodyHtml = preg_replace('/<rtlcss>(.*)</rtlcss>/sU', '1', $__bodyHtml);
        }
        return array(
            'subject' => $__subject,
            'bodyText' => $__bodyText,
            'bodyHtml' => $__bodyHtml
        );
    }
    protected function _replaceRtlCss(array $matches)
    {
        return XenForo_Template_Helper_RightToLeft::getRtlCss($matches[1]);
    }
    /**
     * Wraps the mail container template around a given message.
     *
     * @param string $subject
     * @param string $bodyText
     * @param string $bodyHtml
     *
     * @return array Wrapped mail; keys: subject, bodyText, bodyHtml
     */
    public function wrapMailContainer($subject, $bodyText, $bodyHtml)
    {
        $contents = $this->prepareMailContents('MAIL_CONTAINER', array(
            'subject' => $subject,
            'bodyText' => $bodyText,
            'bodyHtml' => $bodyHtml
        ));
        if ($contents)
        {
            // remove the bodyHtml so we skip an HTML email if there's nothing
            if ($bodyHtml === '')
            {
                $contents['bodyHtml'] = '';
            }
            return $contents;
        }
        else
        {
            return array(
                'subject' => $subject,
                'bodyText' => $bodyText,
                'bodyHtml' => $bodyHtml
            );
        }
    }
    /**
     * Loads the specified email template from the cache or DB.
     *
     * @param string $emailTitle
     *
     * @return string
     */
    protected function _loadEmailTemplate($emailTitle)
    {
        if (isset(self::$_emailCache[$emailTitle][$this->_languageId]))
        {
            return self::$_emailCache[$emailTitle][$this->_languageId];
        }
        self::$_preCache[$emailTitle] = true;
        self::$_emailCache[$emailTitle][$this->_languageId] = '';
        $this->_loadEmailTemplatesFromDb();
        return self::$_emailCache[$emailTitle][$this->_languageId];
    }
    /**
     * Loads all email templates that are to be pre-cached from the DB.
     * They will be placed on the local email cache.
     */
    protected function _loadEmailTemplatesFromDb()
    {
        if (!self::$_preCache)
        {
            return;
        }
        $db = XenForo_Application::getDb();
        if ($this->_preCacheAllLanguages)
        {
            $languageClause = '';
        }
        else
        {
            $languageClause = 'AND language_id = ' . $db->quote($this->_languageId);
        }
        $templateResult = $db->query('
            SELECT language_id, title, template_compiled
            FROM xf_email_template_compiled
            WHERE title IN (' . $db->quote(array_keys(self::$_preCache)) . ')
                ' . $languageClause . '
        ');
        while ($template = $templateResult->fetch())
        {
            self::$_emailCache[$template['title']][$template['language_id']] = $template['template_compiled'];
        }
        self::$_preCache = array();
    }
    /**
     * Gets the failure exception if there is one.
     *
     * @return Exception|null
     */
    public function getFailureException()
    {
        return $this->_failureException;
    }
    /**
     * Set up the default mail transport object. If no transport is given,
     * the default is selected based on board configuration.
     *
     * @param Zend_Mail_Transport_Abstract|null $transport If specified, used as default transport; otherwise, use board config
     */
    public static function setupTransport(Zend_Mail_Transport_Abstract $transport = null)
    {
        if (!$transport)
        {
            $transport = self::getDefaultTransport();
        }
        Zend_Mail::setDefaultTransport($transport);
        self::$_transportSetup = true;
    }
    /**
     * Gets the transport that will be used to send XF mails.
     * This will reflect explicit overrides that the get default method won't.
     *
     * @return Zend_Mail_Transport_Abstract
     */
    public static function getTransport()
    {
        if (!self::$_transportSetup)
        {
            self::setupTransport();
        }
        return Zend_Mail::getDefaultTransport();
    }
    /**
     * Gets the default mail transport object.
     *
     * @return Zend_Mail_Transport_Abstract
     */
    public static function getDefaultTransport()
    {
        $options = XenForo_Application::get('options');
        $transportOption = $options->get('emailTransport', false);
        if ($transportOption['emailTransport'] == 'smtp')
        {
            return self::_getDefaultSmtpTransport($transportOption);
        }
        else
        {
            return self::_getDefaultSendmailTransport($transportOption);
        }
    }
    /**
     * Get the default SMTP mail tranport object, based on the configuration in the
     * given array.
     *
     * @param array $transportOption Data from option (smtpPort, smtpAuth, etc)
     *
     * @return Zend_Mail_Transport_Smtp
     */
    protected static function _getDefaultSmtpTransport(array $transportOption)
    {
        $config = array();
        if (!empty($transportOption['smtpPort']) && intval($transportOption['smtpPort']) != 0)
        {
            $config['port'] = intval($transportOption['smtpPort']);
        }
        if (!empty($transportOption['smtpAuth']) && $transportOption['smtpAuth'] != 'none')
        {
            $config['auth'] = $transportOption['smtpAuth'];
            $config['username'] = (!empty($transportOption['smtpLoginUsername']) ? $transportOption['smtpLoginUsername'] : '');
            $config['password'] = (!empty($transportOption['smtpLoginPassword']) ? $transportOption['smtpLoginPassword'] : '');
        }
        if (!empty($transportOption['smtpEncrypt']) && $transportOption['smtpEncrypt'] != 'none')
        {
            $config['ssl'] = $transportOption['smtpEncrypt'];
        }
        return new Zend_Mail_Transport_Smtp($transportOption['smtpHost'], $config);
    }
    /**
     * Get the default sendmail (built-in PHP mail()) transport object, based on the
     * configuration in the given array.
     *
     * @param array $transportOption Deata from option (sendmailReturnPath, etc)
     *
     * @return Zend_Mail_Transport_Sendmail
     */
    protected static function _getDefaultSendmailTransport(array $transportOption)
    {
        $config = null;
        if (!empty($transportOption['sendmailReturnPath']))
        {
            $options = XenForo_Application::get('options');
            $bounceEmailAddress = $options->bounceEmailAddress;
            if (!$bounceEmailAddress)
            {
                $bounceEmailAddress = $options->defaultEmailAddress;
            }
            if (XenForo_Helper_Email::isEmailValid($bounceEmailAddress))
            {
                $config = '-f' . $bounceEmailAddress;
            }
        }
        return new Zend_Mail_Transport_Sendmail($config);
    }
    /**
     * Reset the email cache. The MAIL_CONTAINER will still be listed as
     * pre-cacheable.
     */
    public static function resetEmailCache()
    {
        self::$_emailCache = array();
        self::$_preCache['MAIL_CONTAINER'] = true;
    }
    /**
     * Sets a value in the email cache.
     *
     * @param string $emailTitle
     * @param integer $languageId
     * @param string $template
     */
    public static function setEmailCache($emailTitle, $languageId, $template)
    {
        self::$_emailCache[$emailTitle][$languageId] = $template;
    }
    /**
     * Factory.
     *
     * @param string $emailTitle Title of the email template
     * @param array $params Key-value params to pass to email template
     * @param integer|null $languageId Language of email; if null, uses language of current user (if setup)
     *
     * @return XenForo_Mail
     */
    public static function create($emailTitle, array $params, $languageId = null)
    {
        $createClass = XenForo_Application::resolveDynamicClass('XenForo_Mail', 'mail');
        return new $createClass($emailTitle, $params, $languageId);
    }
}