<?php

/**
 * @package     Joomla.API
 * @subpackage  com_contact
 *
 * @copyright   (C) 2019 Open Source Matters, Inc. <https://www.joomla.org>
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\Component\Contact\Api\Controller;

use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Event\Contact\SubmitContactEvent;
use Joomla\CMS\Event\Contact\ValidateContactEvent;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Mail\Exception\MailDisabledException;
use Joomla\CMS\Mail\MailTemplate;
use Joomla\CMS\MVC\Controller\ApiController;
use Joomla\CMS\MVC\Controller\Exception\SendEmail;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Router\Exception\RouteNotFoundException;
use Joomla\CMS\String\PunycodeHelper;
use Joomla\CMS\Uri\Uri;
use Joomla\CMS\User\UserFactoryAwareInterface;
use Joomla\CMS\User\UserFactoryAwareTrait;
use Joomla\Component\Fields\Administrator\Helper\FieldsHelper;
use Joomla\Registry\Registry;
use Joomla\String\Inflector;
use PHPMailer\PHPMailer\Exception as phpMailerException;
use Tobscure\JsonApi\Exception\InvalidParameterException;

// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects

/**
 * The contact controller
 *
 * @since  4.0.0
 */
class ContactController extends ApiController implements UserFactoryAwareInterface
{
    use UserFactoryAwareTrait;

    /**
     * The content type of the item.
     *
     * @var    string
     * @since  4.0.0
     */
    protected $contentType = 'contacts';

    /**
     * The default view for the display method.
     *
     * @var    string
     * @since  3.0
     */
    protected $default_view = 'contacts';

    /**
     * Method to allow extended classes to manipulate the data to be saved for an extension.
     *
     * @param   array  $data  An array of input data.
     *
     * @return  array
     *
     * @since   4.0.0
     */
    protected function preprocessSaveData(array $data): array
    {
        foreach (FieldsHelper::getFields('com_contact.contact') as $field) {
            if (isset($data[$field->name])) {
                !isset($data['com_fields']) && $data['com_fields'] = [];

                $data['com_fields'][$field->name] = $data[$field->name];
                unset($data[$field->name]);
            }
        }

        return $data;
    }

    /**
     * Submit contact form
     *
     * @param   integer  $id Leave empty if you want to retrieve data from the request
     * @return  static  A \JControllerLegacy object to support chaining.
     *
     * @since   4.0.0
     */
    public function submitForm($id = null)
    {
        if ($id === null) {
            $id = $this->input->post->get('id', 0, 'int');
        }

        $modelName = Inflector::singularize($this->contentType);

        /** @var  \Joomla\Component\Contact\Site\Model\ContactModel $model */
        $model = $this->getModel($modelName, 'Site');

        if (!$model) {
            throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_MODEL_CREATE'));
        }

        $model->setState('filter.published', 1);

        $data    = $this->input->get('data', json_decode($this->input->json->getRaw(), true), 'array');
        $contact = $model->getItem($id);

        if ($contact->id === null) {
            throw new RouteNotFoundException('Item does not exist');
        }

        $contactParams = new Registry($contact->params);

        if (!$contactParams->get('show_email_form')) {
            throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_DISPLAY_EMAIL_FORM'));
        }

        // Contact plugins
        PluginHelper::importPlugin('contact');

        Form::addFormPath(JPATH_SITE . '/components/com_contact/forms');

        // Validate the posted data.
        $form = $model->getForm();

        if (!$form) {
            throw new \RuntimeException($model->getError(), 500);
        }

        if (!$model->validate($form, $data)) {
            $errors   = $model->getErrors();
            $messages = [];

            for ($i = 0, $n = \count($errors); $i < $n && $i < 3; $i++) {
                if ($errors[$i] instanceof \Exception) {
                    $messages[] = "{$errors[$i]->getMessage()}";
                } else {
                    $messages[] = "{$errors[$i]}";
                }
            }

            throw new InvalidParameterException(implode("\n", $messages));
        }

        // Validation succeeded, continue with custom handlers
        $results = $this->getDispatcher()->dispatch('onValidateContact', new ValidateContactEvent('onValidateContact', [
            'subject' => $contact,
            'data'    => &$data, // @todo: Remove reference in Joomla 6, @deprecated: Data modification onValidateContact is not allowed, use onSubmitContact instead
        ]))->getArgument('result', []);

        foreach ($results as $result) {
            if ($result instanceof \Exception) {
                throw new InvalidParameterException($result->getMessage());
            }
        }

        // Passed Validation: Process the contact plugins to integrate with other applications
        $event = $this->getDispatcher()->dispatch('onSubmitContact', new SubmitContactEvent('onSubmitContact', [
            'subject' => $contact,
            'data'    => &$data, // @todo: Remove reference in Joomla 6, see SubmitContactEvent::__constructor()
        ]));
        // Get the final data
        $data = $event->getArgument('data', $data);

        // Send the email
        $params = ComponentHelper::getParams('com_contact');

        if (!$params->get('custom_reply') && !$this->_sendEmail($data, $contact, $params->get('show_email_copy', 0))) {
            throw new SendEmail('Error sending message');
        }

        return $this;
    }

    /**
     * Method to get a model object, loading it if required.
     *
     * @param   array      $data               The data to send in the email.
     * @param   \stdClass  $contact            The user information to send the email to
     * @param   boolean    $emailCopyToSender  True to send a copy of the email to the user.
     *
     * @return  boolean  True on success sending the email, false on failure.
     *
     * @since   1.6.4
     */
    private function _sendEmail($data, $contact, $emailCopyToSender)
    {
        $app = $this->app;

        $app->getLanguage()->load('com_contact', JPATH_SITE, $app->getLanguage()->getTag(), true);

        if ($contact->email_to == '' && $contact->user_id != 0) {
            $contact_user      = $this->getUserFactory()->loadUserById($contact->user_id);
            $contact->email_to = $contact_user->email;
        }

        $templateData = [
            'sitename'     => $app->get('sitename'),
            'name'         => $data['contact_name'],
            'contactname'  => $contact->name,
            'email'        => PunycodeHelper::emailToPunycode($data['contact_email']),
            'subject'      => $data['contact_subject'],
            'body'         => stripslashes($data['contact_message']),
            'url'          => Uri::base(),
            'customfields' => '',
        ];

        // Load the custom fields
        if (!empty($data['com_fields']) && $fields = FieldsHelper::getFields('com_contact.mail', $contact, true, $data['com_fields'])) {
            $output = FieldsHelper::render(
                'com_contact.mail',
                'fields.render',
                [
                    'context' => 'com_contact.mail',
                    'item'    => $contact,
                    'fields'  => $fields,
                ]
            );

            if ($output) {
                $templateData['customfields'] = $output;
            }
        }

        try {
            $mailer = new MailTemplate('com_contact.mail', $app->getLanguage()->getTag());
            $mailer->addRecipient($contact->email_to);
            $mailer->setReplyTo($templateData['email'], $templateData['name']);
            $mailer->addTemplateData($templateData);
            $sent = $mailer->send();

            // If we are supposed to copy the sender, do so.
            if ($emailCopyToSender && !empty($data['contact_email_copy'])) {
                $mailer = new MailTemplate('com_contact.mail.copy', $app->getLanguage()->getTag());
                $mailer->addRecipient($templateData['email']);
                $mailer->setReplyTo($templateData['email'], $templateData['name']);
                $mailer->addTemplateData($templateData);
                $sent = $mailer->send();
            }
        } catch (MailDisabledException | phpMailerException $exception) {
            try {
                Log::add(Text::_($exception->getMessage()), Log::WARNING, 'jerror');

                $sent = false;
            } catch (\RuntimeException $exception) {
                Factory::getApplication()->enqueueMessage(Text::_($exception->getMessage()), 'warning');

                $sent = false;
            }
        }

        return $sent;
    }
}
