<?php

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

namespace Joomla\Component\Contact\Site\Controller;

use Joomla\CMS\Event\Contact\SubmitContactEvent;
use Joomla\CMS\Event\Contact\ValidateContactEvent;
use Joomla\CMS\Factory;
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\FormController;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Router\Route;
use Joomla\CMS\String\PunycodeHelper;
use Joomla\CMS\Uri\Uri;
use Joomla\CMS\User\UserFactoryAwareInterface;
use Joomla\CMS\User\UserFactoryAwareTrait;
use Joomla\CMS\Versioning\VersionableControllerTrait;
use Joomla\Component\Fields\Administrator\Helper\FieldsHelper;
use Joomla\Utilities\ArrayHelper;
use PHPMailer\PHPMailer\Exception as phpMailerException;

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

/**
 * Controller for single contact view
 *
 * @since  1.5.19
 */
class ContactController extends FormController implements UserFactoryAwareInterface
{
    use UserFactoryAwareTrait;
    use VersionableControllerTrait;

    /**
     * The URL view item variable.
     *
     * @var    string
     * @since  4.0.0
     */
    protected $view_item = 'form';

    /**
     * The URL view list variable.
     *
     * @var    string
     * @since  4.0.0
     */
    protected $view_list = 'categories';

    /**
     * Method to get a model object, loading it if required.
     *
     * @param   string  $name    The model name. Optional.
     * @param   string  $prefix  The class prefix. Optional.
     * @param   array   $config  Configuration array for model. Optional.
     *
     * @return  \Joomla\CMS\MVC\Model\BaseDatabaseModel  The model.
     *
     * @since   1.6.4
     */
    public function getModel($name = 'form', $prefix = '', $config = ['ignore_request' => true])
    {
        return parent::getModel($name, $prefix, ['ignore_request' => false]);
    }

    /**
     * Method to submit the contact form and send an email.
     *
     * @return  boolean  True on success sending the email. False on failure.
     *
     * @since   1.5.19
     */
    public function submit()
    {
        // Check for request forgeries.
        $this->checkToken();

        $app    = $this->app;
        $model  = $this->getModel('contact');
        $stub   = $this->input->getString('id');
        $id     = (int) $stub;

        // Get the data from POST
        $data = $this->input->post->get('jform', [], 'array');

        // Get item
        $model->setState('filter.published', 1);
        $contact = $model->getItem($id);

        if ($contact === false) {
            $this->setMessage($model->getError(), 'error');

            return false;
        }

        // Get item params, take menu parameters into account if necessary
        $active      = $app->getMenu()->getActive();
        $stateParams = clone $model->getState()->get('params');

        // If the current view is the active item and a contact view for this contact, then the menu item params take priority
        if ($active && strpos($active->link, 'view=contact') && strpos($active->link, '&id=' . (int) $contact->id)) {
            // $item->params are the contact params, $temp are the menu item params
            // Merge so that the menu item params take priority
            $contact->params->merge($stateParams);
        } else {
            // Current view is not a single contact, so the contact params take priority here
            $stateParams->merge($contact->params);
            $contact->params = $stateParams;
        }

        // Check if the contact form is enabled
        if (!$contact->params->get('show_email_form')) {
            $this->setRedirect(Route::_('index.php?option=com_contact&view=contact&id=' . $stub . '&catid=' . $contact->catid, false));

            return false;
        }

        // Check for a valid session cookie
        if ($contact->params->get('validate_session', 0)) {
            if (Factory::getSession()->getState() !== 'active') {
                $this->app->enqueueMessage(Text::_('JLIB_ENVIRONMENT_SESSION_INVALID'), 'warning');

                // Save the data in the session.
                $this->app->setUserState('com_contact.contact.data', $data);

                // Redirect back to the contact form.
                $this->setRedirect(Route::_('index.php?option=com_contact&view=contact&id=' . $stub . '&catid=' . $contact->catid, false));

                return false;
            }
        }

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

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

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

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

            foreach ($errors as $error) {
                $errorMessage = $error;

                if ($error instanceof \Exception) {
                    $errorMessage = $error->getMessage();
                }

                $app->enqueueMessage($errorMessage, 'error');
            }

            $app->setUserState('com_contact.contact.data', $data);

            $this->setRedirect(Route::_('index.php?option=com_contact&view=contact&id=' . $stub . '&catid=' . $contact->catid, false));

            return false;
        }

        // 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', []);

        $passValidation = true;

        foreach ($results as $result) {
            if ($result instanceof \Exception) {
                $passValidation = false;
                $app->enqueueMessage($result->getMessage(), 'error');
            }
        }

        if (!$passValidation) {
            $app->setUserState('com_contact.contact.data', $data);

            $this->setRedirect(Route::_('index.php?option=com_contact&view=contact&id=' . $id . '&catid=' . $contact->catid, false));

            return false;
        }

        // 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
        $sent = false;

        if (!$contact->params->get('custom_reply')) {
            $sent = $this->_sendEmail($data, $contact, $contact->params->get('show_email_copy', 0));
        }

        $msg = '';

        // Set the success message if it was a success
        if ($sent) {
            $msg = Text::_('COM_CONTACT_EMAIL_THANKS');
        }

        // Flush the data from the session
        $this->app->setUserState('com_contact.contact.data', null);

        // Redirect if it is set in the parameters, otherwise redirect back to where we came from
        if ($contact->params->get('redirect')) {
            $this->setRedirect($contact->params->get('redirect'), $msg);
        } else {
            $this->setRedirect(Route::_('index.php?option=com_contact&view=contact&id=' . $stub . '&catid=' . $contact->catid, false), $msg);
        }

        return true;
    }

    /**
     * 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;

        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);
            $mailer->addUnsafeTags(['name', 'email', 'body']);
            $sent = $mailer->send();

            // If we are supposed to copy the sender, do so.
            if ($emailCopyToSender == true && !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);
                $mailer->addUnsafeTags(['name', 'email', 'body']);
                $sent = $mailer->send();
            }
        } catch (MailDisabledException | phpMailerException $exception) {
            try {
                Log::add(Text::_($exception->getMessage()), Log::WARNING, 'jerror');

                $sent = false;
            } catch (\RuntimeException $exception) {
                $this->app->enqueueMessage(Text::_($exception->errorMessage()), 'warning');

                $sent = false;
            }
        }

        return $sent;
    }

    /**
     * Method override to check if you can add a new record.
     *
     * @param   array  $data  An array of input data.
     *
     * @return  boolean
     *
     * @since   4.0.0
     */
    protected function allowAdd($data = [])
    {
        if ($categoryId = ArrayHelper::getValue($data, 'catid', $this->input->getInt('catid'), 'int')) {
            $user = $this->app->getIdentity();

            // If the category has been passed in the data or URL check it.
            return $user->authorise('core.create', 'com_contact.category.' . $categoryId);
        }

        // In the absence of better information, revert to the component permissions.
        return parent::allowAdd();
    }

    /**
     * Method override to check if you can edit an existing record.
     *
     * @param   array   $data  An array of input data.
     * @param   string  $key   The name of the key for the primary key; default is id.
     *
     * @return  boolean
     *
     * @since   4.0.0
     */
    protected function allowEdit($data = [], $key = 'id')
    {
        $recordId = (int) isset($data[$key]) ? $data[$key] : 0;

        if (!$recordId) {
            return false;
        }

        // Need to do a lookup from the model.
        $record     = $this->getModel()->getItem($recordId);
        $categoryId = (int) $record->catid;

        if ($categoryId) {
            $user = $this->app->getIdentity();

            // The category has been set. Check the category permissions.
            if ($user->authorise('core.edit', $this->option . '.category.' . $categoryId)) {
                return true;
            }

            // Fallback on edit.own.
            if ($user->authorise('core.edit.own', $this->option . '.category.' . $categoryId)) {
                return ($record->created_by === $user->id);
            }

            return false;
        }

        // Since there is no asset tracking, revert to the component permissions.
        return parent::allowEdit($data, $key);
    }

    /**
     * Method to cancel an edit.
     *
     * @param   string  $key  The name of the primary key of the URL variable.
     *
     * @return  boolean  True if access level checks pass, false otherwise.
     *
     * @since   4.0.0
     */
    public function cancel($key = null)
    {
        $result = parent::cancel($key);

        $this->setRedirect(Route::_($this->getReturnPage(), false));

        return $result;
    }

    /**
     * Gets the URL arguments to append to an item redirect.
     *
     * @param   integer  $recordId  The primary key id for the item.
     * @param   string   $urlVar    The name of the URL variable for the id.
     *
     * @return  string    The arguments to append to the redirect URL.
     *
     * @since   4.0.0
     */
    protected function getRedirectToItemAppend($recordId = 0, $urlVar = 'id')
    {
        // Need to override the parent method completely.
        $tmpl = $this->input->get('tmpl');

        $append = '';

        // Setup redirect info.
        if ($tmpl) {
            $append .= '&tmpl=' . $tmpl;
        }

        $append .= '&layout=edit';

        $append .= '&' . $urlVar . '=' . (int) $recordId;

        $itemId = $this->input->getInt('Itemid');
        $return = $this->getReturnPage();
        $catId  = $this->input->getInt('catid');

        if ($itemId) {
            $append .= '&Itemid=' . $itemId;
        }

        if ($catId) {
            $append .= '&catid=' . $catId;
        }

        if ($return) {
            $append .= '&return=' . base64_encode($return);
        }

        return $append;
    }

    /**
     * Get the return URL.
     *
     * If a "return" variable has been passed in the request
     *
     * @return  string    The return URL.
     *
     * @since   4.0.0
     */
    protected function getReturnPage()
    {
        $return = $this->input->get('return', null, 'base64');

        if (empty($return) || !Uri::isInternal(base64_decode($return))) {
            return Uri::base();
        }

        return base64_decode($return);
    }
}
