<?php
/**
 * @version        4.3.0
 * @package        Joomla
 * @subpackage     helpdeskpro Pro
 * @author         Tuan Pham Ngoc
 * @copyright      Copyright (C) 2013 - 2021 Ossolution Team
 * @license        GNU/GPL, see LICENSE.php
 */

use Ddeboer\Imap\Search\Date\Since;
use Ddeboer\Imap\Search\Flag\Unanswered;
use Ddeboer\Imap\SearchExpression;
use Ddeboer\Imap\Server;
use EmailReplyParser\Parser\EmailParser;
use Joomla\CMS\Filesystem\File;
use Joomla\CMS\Plugin\CMSPlugin;
use OSSolution\HelpdeskPro\Site\Helper\Helper as HelpdeskproHelper;

defined('_JEXEC') or die;

class plgSystemHDPReplyViaEmail extends CMSPlugin
{
	/**
	 * Application object.
	 *
	 * @var    JApplicationCms
	 */
	protected $app;

	/**
	 * Database object.
	 *
	 * @var    JDatabaseDriver
	 */
	protected $db;

	/**
	 * Constructor
	 *
	 * @param   object  &$subject  The object to observe
	 * @param   array    $config
	 */
	public function __construct(&$subject, $config = array())
	{
		if (!file_exists(JPATH_ROOT . '/components/com_helpdeskpro/helpdeskpro.php'))
		{
			return;
		}

		parent::__construct($subject, $config);
	}

	/**
	 * Protect access to articles
	 *
	 * @return void
	 * @throws Exception
	 */
	public function onAfterRoute()
	{
		if (version_compare(PHP_VERSION, '7.2.0', '<'))
		{
			$this->app->enqueueMessage('Reply Via Email Only Works With PHP 7.2.0+');

			return;
		}

		if (!$this->canRun())
		{
			return;
		}


		//Store last run time
		$db = $this->db;
		$this->params->set('last_run', time());
		$params = $this->params->toString();

		$query = $db->getQuery(true)
			->update('#__extensions')
			->set('params = ' . $db->quote($params))
			->where($db->quoteName('element') . '=' . $db->quote($this->_name))
			->where($db->quoteName('folder') . '=' . $db->quote('system'));

		try
		{
			// Lock the tables to prevent multiple plugin executions causing a race condition
			$db->lockTable('#__extensions');
		}
		catch (Exception $e)
		{
			// If we can't lock the tables it's too risk continuing execution
			return;
		}

		try
		{
			// Update the plugin parameters
			$result = $db->setQuery($query)->execute();
			$this->clearCacheGroups(array('com_plugins'), array(0, 1));
		}
		catch (Exception $exc)
		{
			// If we failed to execute
			$db->unlockTables();
			$result = false;
		}

		try
		{
			// Unlock the tables after writing
			$db->unlockTables();
		}
		catch (Exception $e)
		{
			// If we can't lock the tables assume we have somehow failed
			$result = false;
		}

		// Abort on failure
		if (!$result)
		{
			return;
		}

		$this->fetNewEmails();
	}

	/**
	 * Fetch emails and create ticket
	 *
	 * @throws Exception
	 */
	private function fetNewEmails()
	{
		require_once JPATH_ROOT . '/plugins/system/hdpreplyviaemail/lib/vendor/autoload.php';

		$host     = $this->params->get('host');
		$port     = $this->params->get('port', 993);
		$username = $this->params->get('username');
		$password = $this->params->get('password');

		$requiredParams = [$host, $username, $password];

		// Make sure all required parameters are provided before processing it further
		foreach ($requiredParams as $param)
		{
			if (!strlen(trim($param)))
			{
				return;
			}
		}

		// Attempt to connect to the mailbox
		try
		{
			// $server = new Server('mail.joomdonation.com', 993, '/imap/ssl/novalidate-cert');
			$server = new Server($host, $port, $this->buildFlags());
			// $connection is instance of \Ddeboer\Imap\Connection
			// $connection = $server->authenticate('tickets@joomdonation.com', '#$$A%u^tet*d*');
			$connection = $server->authenticate($username, $password);
		}
		catch (Exception $e)
		{
			$this->logData($e->getMessage());

			// Log the error here
			return;
		}

		$mailbox = $connection->getMailbox('INBOX');

		// Search for emails from yesterday only

		$today     = new DateTimeImmutable();
		$yesterday = $today->sub(new DateInterval('P10D'));

		$date = JFactory::getDate('Now', JFactory::getConfig()->get('offset'));
		$date->modify('-1 day');

		$search = new SearchExpression();
		$search->addCondition(new Unanswered());
		$search->addCondition(new Since($yesterday));

		$messages = $mailbox->getMessages($search, \SORTDATE);

		// Bootstrap the component
		require_once JPATH_ADMINISTRATOR . '/components/com_helpdeskpro/init.php';

		// Get component config data
		$config = require JPATH_ADMINISTRATOR . '/components/com_helpdeskpro/config.php';

		// Creating component container
		$container = \OSL\Container\Container::getInstance('com_helpdeskpro', $config);
		$db        = $container->db;
		$query     = $db->getQuery(true);

		$config = HelpdeskproHelper::getConfig();

		$allowedFileTypes = explode('|', $config->allowed_file_types);

		for ($i = 0, $n = count($allowedFileTypes); $i < $n; $i++)
		{
			$allowedFileTypes[$i] = trim(strtoupper($allowedFileTypes[$i]));
		}

		$ticketIdRegex = '/#(\d+)/';

		/** @var  \OSSolution\HelpdeskPro\Admin\Model\Ticket $model */

		$model = $container->factory->createModel('Ticket', [], 'admin');

		foreach ($messages as $message)
		{
			$subject   = $message->getSubject();
			$body      = $message->getBodyText();
			$fromName  = $message->getFrom()->getName();
			$fromEmail = $message->getFrom()->getAddress();

			$email = (new EmailParser())->parse($body);

			$body = $email->getVisibleText() ?: $body;

			if (preg_match($ticketIdRegex, $subject, $matches))
			{
				$ticketId = (int) $matches[1];
			}
			else
			{
				$ticketId = 0;
			}

			$attachmentsPath = JPATH_ROOT . '/media/com_helpdeskpro/attachments';
			$attachments     = [];

			foreach ($message->getAttachments() as $attachment)
			{
				$filename                        = $attachment->getFilename();
				$attachments['original_names'][] = $filename;
				$filename                        = File::makeSafe($filename);
				$fileExt                         = strtoupper(File::getExt($filename));

				if (in_array($fileExt, $allowedFileTypes))
				{
					if (File::exists($attachmentsPath . '/' . $filename))
					{
						$filename = File::stripExt($filename) . '_' . uniqid() . '.' . $fileExt;
					}

					file_put_contents($attachmentsPath . '/' . $filename, $attachment->getDecodedContent());

					$attachments['names'][] = $filename;
				}
			}

			$data = [];

			if ($ticketId)
			{
				// Add comment
				$data['ticket_id'] = $ticketId;
				$data['message']   = $body;

				// Try to get user id from email
				$query->clear()
					->select('id')
					->from('#__users')
					->where('email = ' . $db->quote($fromEmail));
				$db->setQuery($query);
				$data['user_id'] = (int) $db->loadResult();

				$model->addTicketComment($data, $attachments);
			}
			elseif ($this->params->get('new_ticket_category_id'))
			{
				// Add a new ticket
				$data['name']        = $fromName;
				$data['email']       = $fromEmail;
				$data['subject']     = $subject;
				$data['message']     = $body;
				$data['category_id'] = $this->params->get('new_ticket_category_id');

				$model->addNewTicket($data, $attachments);
			}

			// Mark the message as ANSWERED so that it won't be processed next time
			$message->setFlag('\\ANSWERED');
		}
	}

	/**
	 * Build the flags used for imap connection from plugin parameters
	 *
	 * @return string
	 */
	private function buildFlags()
	{
		$encryption          = $this->params->get('encryption', 'ssl');
		$validateCertificate = $this->params->get('validate_certificate', 1);

		$flags = ['imap'];

		if ($encryption)
		{
			$flags[] = $encryption;
		}

		if ($validateCertificate)
		{
			$flags[] = 'validate-cert';
		}
		else
		{
			$flags[] = 'novalidate-cert';
		}

		return '/' . implode('/', $flags);
	}

	/**
	 * Clears cache groups. We use it to clear the plugins cache after we update the last run timestamp.
	 *
	 * @param   array  $clearGroups   The cache groups to clean
	 * @param   array  $cacheClients  The cache clients (site, admin) to clean
	 *
	 * @return  void
	 *
	 * @since   2.0.4
	 */
	private function clearCacheGroups(array $clearGroups, array $cacheClients = array(0, 1))
	{
		$conf = JFactory::getConfig();

		foreach ($clearGroups as $group)
		{
			foreach ($cacheClients as $client_id)
			{
				try
				{
					$options = array(
						'defaultgroup' => $group,
						'cachebase'    => ($client_id) ? JPATH_ADMINISTRATOR . '/cache' :
							$conf->get('cache_path', JPATH_SITE . '/cache'),
					);
					$cache   = JCache::getInstance('callback', $options);
					$cache->clean();
				}
				catch (Exception $e)
				{
					// Ignore it
				}
			}
		}
	}

	/**
	 * Helper method to write data to a log file, for debuging purpose
	 *
	 * @param   string  $logFile
	 * @param   array   $data
	 * @param   string  $message
	 */
	private function logData($data = [], $message = null)
	{
		$text = '[' . gmdate('m/d/Y g:i A') . '] - ';

		foreach ($data as $key => $value)
		{
			$text .= "$key=$value, ";
		}

		$text .= $message;

		$fp = fopen(__DIR__ . '/logs.txt', 'a');
		fwrite($fp, $text . "\n\n");
		fclose($fp);
	}

	/**
	 * Method to check if the plugin could be run
	 *
	 * @return bool
	 */
	protected function canRun()
	{
		// If trigger secret_code is set, usually from cron-job request, we will process email-queues immediately
		if (trim($this->params->get('secret_code')))
		{
			if ($this->params->get('secret_code') == $this->app->input->getString('secret_code'))
			{
				return true;
			}

			return false;
		}

		$lastRun   = (int) $this->params->get('last_run', 0);
		$now       = time();
		$cacheTime = 1200; // Every 20 minutes

		if (($now - $lastRun) < $cacheTime)
		{
			return false;
		}

		return true;
	}
}
