<?php
/**
 * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
 * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 *
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
 * @since         3.1.0
 * @license       https://opensource.org/licenses/mit-license.php MIT License
 */
namespace Cake\Mailer;

use Cake\Datasource\ModelAwareTrait;
use Cake\Event\EventListenerInterface;
use Cake\Mailer\Exception\MissingActionException;

/**
 * Mailer base class.
 *
 * Mailer classes let you encapsulate related Email logic into a reusable
 * and testable class.
 *
 * ## Defining Messages
 *
 * Mailers make it easy for you to define methods that handle email formatting
 * logic. For example:
 *
 * ```
 * class UserMailer extends Mailer
 * {
 *     public function resetPassword($user)
 *     {
 *         $this
 *             ->setSubject('Reset Password')
 *             ->setTo($user->email)
 *             ->set(['token' => $user->token]);
 *     }
 * }
 * ```
 *
 * Is a trivial example but shows how a mailer could be declared.
 *
 * ## Sending Messages
 *
 * After you have defined some messages you will want to send them:
 *
 * ```
 * $mailer = new UserMailer();
 * $mailer->send('resetPassword', $user);
 * ```
 *
 * ## Event Listener
 *
 * Mailers can also subscribe to application event allowing you to
 * decouple email delivery from your application code. By re-declaring the
 * `implementedEvents()` method you can define event handlers that can
 * convert events into email. For example, if your application had a user
 * registration event:
 *
 * ```
 * public function implementedEvents()
 * {
 *     return [
 *         'Model.afterSave' => 'onRegistration',
 *     ];
 * }
 *
 * public function onRegistration(Event $event, Entity $entity, ArrayObject $options)
 * {
 *     if ($entity->isNew()) {
 *          $this->send('welcome', [$entity]);
 *     }
 * }
 * ```
 *
 * The onRegistration method converts the application event into a mailer method.
 * Our mailer could either be registered in the application bootstrap, or
 * in the Table class' initialize() hook.
 *
 * @method \Cake\Mailer\Mailer setTo($email, $name = null)
 * @method array getTo()
 * @method \Cake\Mailer\Mailer to($email = null, $name = null)
 * @method \Cake\Mailer\Mailer setFrom($email, $name = null)
 * @method array getFrom()
 * @method \Cake\Mailer\Mailer from($email = null, $name = null)
 * @method \Cake\Mailer\Mailer setSender($email, $name = null)
 * @method array getSender()
 * @method \Cake\Mailer\Mailer sender($email = null, $name = null)
 * @method \Cake\Mailer\Mailer setReplyTo($email, $name = null)
 * @method array getReplyTo()
 * @method \Cake\Mailer\Mailer replyTo($email = null, $name = null)
 * @method \Cake\Mailer\Mailer setReadReceipt($email, $name = null)
 * @method array getReadReceipt()
 * @method \Cake\Mailer\Mailer readReceipt($email = null, $name = null)
 * @method \Cake\Mailer\Mailer setReturnPath($email, $name = null)
 * @method array getReturnPath()
 * @method \Cake\Mailer\Mailer returnPath($email = null, $name = null)
 * @method \Cake\Mailer\Mailer addTo($email, $name = null)
 * @method \Cake\Mailer\Mailer setCc($email, $name = null)
 * @method array getCc()
 * @method \Cake\Mailer\Mailer cc($email = null, $name = null)
 * @method \Cake\Mailer\Mailer addCc($email, $name = null)
 * @method \Cake\Mailer\Mailer setBcc($email, $name = null)
 * @method array getBcc()
 * @method \Cake\Mailer\Mailer bcc($email = null, $name = null)
 * @method \Cake\Mailer\Mailer addBcc($email, $name = null)
 * @method \Cake\Mailer\Mailer setCharset($charset)
 * @method string getCharset()
 * @method \Cake\Mailer\Mailer charset($charset = null)
 * @method \Cake\Mailer\Mailer setHeaderCharset($charset)
 * @method string getHeaderCharset()
 * @method \Cake\Mailer\Mailer headerCharset($charset = null)
 * @method \Cake\Mailer\Mailer setSubject($subject)
 * @method string getSubject()
 * @method \Cake\Mailer\Mailer subject($subject = null)
 * @method \Cake\Mailer\Mailer setHeaders(array $headers)
 * @method \Cake\Mailer\Mailer addHeaders(array $headers)
 * @method \Cake\Mailer\Mailer getHeaders(array $include = [])
 * @method \Cake\Mailer\Mailer setTemplate($template)
 * @method string getTemplate()
 * @method \Cake\Mailer\Mailer setLayout($layout)
 * @method string getLayout()
 * @method \Cake\Mailer\Mailer template($template = false, $layout = false)
 * @method \Cake\Mailer\Mailer setViewRenderer($viewClass)
 * @method string getViewRenderer()
 * @method \Cake\Mailer\Mailer viewRender($viewClass = null)
 * @method \Cake\Mailer\Mailer setViewVars($viewVars)
 * @method array getViewVars()
 * @method \Cake\Mailer\Mailer viewVars($viewVars = null)
 * @method \Cake\Mailer\Mailer setTheme($theme)
 * @method string getTheme()
 * @method \Cake\Mailer\Mailer theme($theme = null)
 * @method \Cake\Mailer\Mailer setHelpers(array $helpers)
 * @method array getHelpers()
 * @method \Cake\Mailer\Mailer helpers($helpers = null)
 * @method \Cake\Mailer\Mailer setEmailFormat($format)
 * @method string getEmailFormat()
 * @method \Cake\Mailer\Mailer emailFormat($format = null)
 * @method \Cake\Mailer\Mailer setTransport($name)
 * @method \Cake\Mailer\AbstractTransport getTransport()
 * @method \Cake\Mailer\Mailer transport($name = null)
 * @method \Cake\Mailer\Mailer setMessageId($message)
 * @method bool|string getMessageId()
 * @method \Cake\Mailer\Mailer messageId($message = null)
 * @method \Cake\Mailer\Mailer setDomain($domain)
 * @method string getDomain()
 * @method \Cake\Mailer\Mailer domain($domain = null)
 * @method \Cake\Mailer\Mailer setAttachments($attachments)
 * @method array getAttachments()
 * @method \Cake\Mailer\Mailer attachments($attachments = null)
 * @method \Cake\Mailer\Mailer addAttachments($attachments)
 * @method \Cake\Mailer\Mailer message($type = null)
 * @method \Cake\Mailer\Mailer setProfile($config)
 * @method string|array getProfile()
 * @method \Cake\Mailer\Mailer profile($config = null)
 * @method \Cake\Mailer\Mailer setEmailPattern($regex)
 * @method string getEmailPattern()
 * @method \Cake\Mailer\Mailer emailPattern($regex = null)
 */
abstract class Mailer implements EventListenerInterface
{
    use ModelAwareTrait;

    /**
     * Mailer's name.
     *
     * @var string
     */
    public static $name;

    /**
     * Email instance.
     *
     * @var \Cake\Mailer\Email
     */
    protected $_email;

    /**
     * Cloned Email instance for restoring instance after email is sent by
     * mailer action.
     *
     * @var \Cake\Mailer\Email
     */
    protected $_clonedEmail;

    /**
     * Constructor.
     *
     * @param \Cake\Mailer\Email|null $email Email instance.
     */
    public function __construct(Email $email = null)
    {
        if ($email === null) {
            $email = new Email();
        }

        $this->_email = $email;
        $this->_clonedEmail = clone $email;
    }

    /**
     * Returns the mailer's name.
     *
     * @return string
     */
    public function getName()
    {
        if (!static::$name) {
            static::$name = str_replace(
                'Mailer',
                '',
                implode('', array_slice(explode('\\', get_class($this)), -1))
            );
        }

        return static::$name;
    }

    /**
     * Sets layout to use.
     *
     * @deprecated 3.4.0 Use setLayout() which sets the layout on the email class instead.
     * @param string $layout Name of the layout to use.
     * @return $this
     */
    public function layout($layout)
    {
        deprecationWarning(
            'Mailer::layout() is deprecated. Use $mailer->viewBuilder()->setLayout() instead.'
        );

        $this->_email->viewBuilder()->setLayout($layout);

        return $this;
    }

    /**
     * Get Email instance's view builder.
     *
     * @return \Cake\View\ViewBuilder
     */
    public function viewBuilder()
    {
        return $this->_email->viewBuilder();
    }

    /**
     * Magic method to forward method class to Email instance.
     *
     * @param string $method Method name.
     * @param array $args Method arguments
     * @return $this|mixed
     */
    public function __call($method, $args)
    {
        $result = $this->_email->$method(...$args);
        if (strpos($method, 'get') === 0) {
            return $result;
        }

        return $this;
    }

    /**
     * Sets email view vars.
     *
     * @param string|array $key Variable name or hash of view variables.
     * @param mixed $value View variable value.
     * @return $this
     */
    public function set($key, $value = null)
    {
        $this->_email->setViewVars(is_string($key) ? [$key => $value] : $key);

        return $this;
    }

    /**
     * Sends email.
     *
     * @param string $action The name of the mailer action to trigger.
     * @param array $args Arguments to pass to the triggered mailer action.
     * @param array $headers Headers to set.
     * @return array
     * @throws \Cake\Mailer\Exception\MissingActionException
     * @throws \BadMethodCallException
     */
    public function send($action, $args = [], $headers = [])
    {
        try {
            if (!method_exists($this, $action)) {
                throw new MissingActionException([
                    'mailer' => $this->getName() . 'Mailer',
                    'action' => $action,
                ]);
            }

            $this->_email->setHeaders($headers);
            if (!$this->_email->viewBuilder()->getTemplate()) {
                $this->_email->viewBuilder()->setTemplate($action);
            }

            $this->$action(...$args);

            $result = $this->_email->send();
        } finally {
            $this->reset();
        }

        return $result;
    }

    /**
     * Reset email instance.
     *
     * @return $this
     */
    protected function reset()
    {
        $this->_email = clone $this->_clonedEmail;

        return $this;
    }

    /**
     * Implemented events.
     *
     * @return array
     */
    public function implementedEvents()
    {
        return [];
    }
}
