<?php
/**
 * @package    VP Advanced User
 * @author     Abhishek Das <info@virtueplanet.com>
 * @copyright  Copyright (C) 2012-2019 VirtuePlanet Services LLP. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 * @link       https://www.virtueplanet.com
 */

defined('_JEXEC') or die;

use Joomla\Registry\Registry;

// Register route helper
JLoader::register('VPAdvancedUserHelperRoute', JPATH_SITE . '/components/com_vpadvanceduser/helpers/route.php');

/**
 * Mailer helper class.
 *
 * @since  1.0
 */
class VPAdvancedUserHelperMailer extends JObject
{
	protected $db;
	
	protected $mail_to_admin;
	
	protected $templates = array();

	protected static $moustache_registered = false;
	
	protected static $instance = null;

	/**
	* Constructor.
	*
	* @param   array  $config  An optional associative array of configuration settings.
	*
	* @since   1.0
	*/
	public function __construct($config = array())
	{
		$config = (array) $config;
		
		$this->db            = isset($config['db']) ? $config['db'] : null;
		$this->mail_to_admin = isset($config['mail_to_admin']) ? $config['mail_to_admin'] : null;
		
		if(!in_array($this->mail_to_admin, array(true, false), true))
		{
			$this->mail_to_admin = JFactory::getApplication()->isSite();
		}
		
		// Register Mustache Autoloader class
		JLoader::register('Mustache_Autoloader', JPATH_SITE . '/components/com_vpadvanceduser/libraries/Mustache/Autoloader.php');
		
		// Register Data helper class
		JLoader::register('VPAdvancedUserMailerData', __DIR__ . '/mailer/data.php');
	}

	/**
	* Method to get the mailer helper instance.
	* 
	* @param  array  $config  An optional associative array of configuration settings.
	* 
	* @return object  VPAdvancedUserHelperMailer instance
	*/
	public static function getInstance($config = array())
	{
		if(self::$instance === null)
		{
			self::$instance = new self($config);
		}
		
		return self::$instance;
	}

	/**
	* Method to send/resend registration mail.
	* 
	* @param  object   $user            JUser object
	* @param  integer  $useractivation  Optional user actication parameter i.e. 0, 1 or 2
	* @param  boolean  $sendpassword    Optional send password parameter
	* 
	* @return boolean  False in case of failure
	*/
	public function sendRegistrationMail($user, $useractivation = null, $sendpassword = null)
	{
		if(!($user instanceof JUser))
		{
			$this->setError('Mail could not be sent. Invalid user variable passed.');
			return false;
		}
		
		// Initialize some variables
		$app              = JFactory::getApplication();
		$config           = JFactory::getConfig();
		$lang             = JFactory::getLanguage();
		$filter           = JFilterInput::getInstance();
		$defaultLocale    = $lang->getTag();
		$params           = JComponentHelper::getParams('com_users');
		$useractivation   = ($useractivation === null) ? $params->get('useractivation') : $useractivation;
		$sendpassword     = ($sendpassword === null) ? $params->get('sendpassword', 1) : $sendpassword;
		$db               = $this->getDbo();
		$query            = $db->getQuery(true);
		
		// Initialize mail variables
		$emailSubject   = '';
		$emailBody      = '';
		$emailBodyAdmin = '';
		$html_mode      = false;
		
		// Compile the notification mail values.
		$data = $user->getProperties();
		$data['fromname'] = $config->get('fromname');
		$data['mailfrom'] = $config->get('mailfrom');
		$data['sitename'] = $config->get('sitename');
		$data['siteurl']  = JUri::root();
		
		if($app->isAdmin())
		{
			/**
			* Look for user language. Priority:
			*   1. User frontend language
			*   2. User backend language
			*/
			$userParams = new Registry($user->params);
			$userLocale = $userParams->get('language', $userParams->get('admin_language', $defaultLocale));

			if (!empty($userLocale) && $userLocale != $defaultLocale)
			{
				$lang->setLanguage($userLocale);
			}
			else
			{
				$userLocale = $defaultLocale;
			}
			
			// Reload site language
			$lang->load('com_users', JPATH_SITE, $userLocale, true);
			$lang->load('com_vpadvanceduser', JPATH_SITE, $userLocale, true);
		}
		
		// Handle account activation/confirmation emails.
		if ($useractivation == 2)
		{
			// Set the link to confirm the user email.
			$httpMode = $config->get('force_ssl', 0) == 2 ? 1 : (-1);
			$itemid   = VPAdvancedUserHelperRoute::getLoginRoute();
			$itemid   = ($itemid !== null) ? '&Itemid=' . $itemid : '';
			$link     = 'index.php?option=com_vpadvanceduser&task=user.activate&token=' . $data['activation'] . $itemid;
			
			$data['activate']  = JRoute::_($link, false, $httpMode);
			$link_html         = JRoute::_($link, true, $httpMode);

			// Remove administrator/ from activate url in case this method is called from admin
			if ($app->isAdmin())
			{
				$adminPos         = strrpos($data['activate'], 'administrator/');
				$data['activate'] = substr_replace($data['activate'], '', $adminPos, 14);
				
				$adminPosHtml     = strrpos($link_html, 'administrator/');
				$link_html        = substr_replace($link_html, '', $adminPosHtml, 14);
			}

			$data['link_text'] = $data['activate'];
			$data['link_html'] = '<a href="' . $link_html . '" target="_blank" class="link-html">' . $data['link_text'] . '</a>';

			// Get the email template for admin activation
			$template = $this->getTemplate('admin_activation');
			
			// If there is an error getting the template
			if($template === false)
			{
				$this->setError($this->getError());
				return false;
			}
			
			// Use the template if found
			if(!empty($template))
			{
				if(!$this->renderTemplate($template, $data, $emailSubject, $emailBody, $html_mode))
				{
					$this->setError($this->getError());
					return false;
				}
			}
			else
			{
				$emailSubject = JText::sprintf('COM_USERS_EMAIL_ACCOUNT_DETAILS', $data['name'], $data['sitename']);

				if ($sendpassword)
				{
					$emailBody = JText::sprintf('COM_USERS_EMAIL_REGISTERED_WITH_ADMIN_ACTIVATION_BODY',
					                            $data['name'], $data['sitename'], $data['activate'], $data['siteurl'],
					                            $data['username'], $data['password_clear']);
				}
				else
				{
					$emailBody = JText::sprintf('COM_USERS_EMAIL_REGISTERED_WITH_ADMIN_ACTIVATION_BODY_NOPW',
					                            $data['name'], $data['sitename'], $data['activate'],$data['siteurl'], 
					                            $data['username']);
				}
			}
		}
		elseif ($useractivation == 1)
		{
			// Set the link to confirm the user email.
			$httpMode = $config->get('force_ssl', 0) == 2 ? 1 : (-1);
			$itemid   = VPAdvancedUserHelperRoute::getLoginRoute();
			$itemid   = ($itemid !== null) ? '&Itemid=' . $itemid : '';
			$link     = 'index.php?option=com_vpadvanceduser&task=user.activate&token=' . $data['activation'] . $itemid;
			
			$data['activate']  = JRoute::_($link, false, $httpMode);
			$link_html         = JRoute::_($link, true, $httpMode);

			// Remove administrator/ from activate url in case this method is called from admin
			if ($app->isAdmin())
			{
				$adminPos         = strrpos($data['activate'], 'administrator/');
				$data['activate'] = substr_replace($data['activate'], '', $adminPos, 14);
				
				$adminPosHtml     = strrpos($link_html, 'administrator/');
				$link_html        = substr_replace($link_html, '', $adminPosHtml, 14);
			}

			$data['link_text'] = $data['activate'];
			$data['link_html'] = '<a href="' . $link_html . '" target="_blank" class="link-html">' . $data['link_text'] . '</a>';

			// Get the email template for self activation
			$template = $this->getTemplate('user_activation');
			
			// If there is an error getting the template
			if($template === false)
			{
				$this->setError($this->getError());
				return false;
			}

			// Use the template if found
			if(!empty($template))
			{
				if(!$this->renderTemplate($template, $data, $emailSubject, $emailBody, $html_mode))
				{
					$this->setError($this->getError());
					return false;
				}
			}
			else
			{
				$emailSubject = JText::sprintf('COM_USERS_EMAIL_ACCOUNT_DETAILS', $data['name'], $data['sitename']);

				if ($sendpassword)
				{
					$emailBody = JText::sprintf('COM_USERS_EMAIL_REGISTERED_WITH_ACTIVATION_BODY',
					                            $data['name'], $data['sitename'], $data['activate'],
					                            $data['siteurl'], $data['username'], $data['password_clear']);
				}
				else
				{
					$emailBody = JText::sprintf('COM_USERS_EMAIL_REGISTERED_WITH_ACTIVATION_BODY_NOPW',
					                            $data['name'], $data['sitename'], $data['activate'],
					                            $data['siteurl'], $data['username']);
				}
			}
		}
		else
		{
			// Get the email template for normal user registration
			$template = $this->getTemplate('user_registration');
			
			// If there is an error getting the template
			if($template === false)
			{
				$this->setError($this->getError());
				return false;
			}

			// Use the template if found
			if(!empty($template))
			{
				if(!$this->renderTemplate($template, $data, $emailSubject, $emailBody, $html_mode))
				{
					$this->setError($this->getError());
					return false;
				}
			}
			else
			{
				$emailSubject = JText::sprintf('COM_USERS_EMAIL_ACCOUNT_DETAILS', $data['name'], $data['sitename']);

				if ($sendpassword)
				{
					$emailBody = JText::sprintf('COM_USERS_EMAIL_REGISTERED_BODY',
					                            $data['name'], $data['sitename'], $data['siteurl'],
					                            $data['username'], $data['password_clear']);
				}
				else
				{
					$emailBody = JText::sprintf('COM_USERS_EMAIL_REGISTERED_BODY_NOPW',
					                            $data['name'], $data['sitename'], $data['siteurl']);
				}
			}
		}

		// Send the registration email.
		$return = JFactory::getMailer()->sendMail($data['mailfrom'], $data['fromname'], $data['email'], $emailSubject, $emailBody, $html_mode);
		
		// Send Notification mail to administrators
		if ($this->mail_to_admin && ($params->get('useractivation') < 2) && ($params->get('mail_to_admin') == 1))
		{
			// Get the email template for admin notification
			$template = $this->getTemplate('admin_notification');
			
			// If there is an error getting the template
			if($template === false)
			{
				$this->setError($this->getError());
				return false;
			}

			// Use the template if found
			if(!empty($template))
			{
				if(!$this->renderTemplate($template, $data, $emailSubject, $emailBodyAdmin, $html_mode))
				{
					$this->setError($this->getError());
					return false;
				}
			}
			else
			{
				$emailSubject   = JText::sprintf('COM_USERS_EMAIL_ACCOUNT_DETAILS', $data['name'], $data['sitename']);

				$emailBodyAdmin = JText::sprintf('COM_USERS_EMAIL_REGISTERED_NOTIFICATION_TO_ADMIN_BODY',
				                                 $data['name'], $data['username'], $data['siteurl']);
			}

			// Get all admin users
			$query->clear()
			      ->select($db->quoteName(array('name', 'email', 'sendEmail')))
			      ->from($db->quoteName('#__users'))
			      ->where($db->quoteName('sendEmail') . ' = ' . 1);

			$db->setQuery($query);

			try
			{
				$rows = $db->loadObjectList();
			}
			catch (RuntimeException $e)
			{
				$this->setError(JText::sprintf('COM_USERS_DATABASE_ERROR', $e->getMessage()), 500);
				return false;
			}

			// Send mail to all superadministrators id
			foreach ($rows as $row)
			{
				$return = JFactory::getMailer()->sendMail($data['mailfrom'], $data['fromname'], $row->email, $emailSubject, $emailBodyAdmin, $html_mode);

				// Check for an error.
				if ($return !== true)
				{
					$this->setError(JText::_('COM_USERS_REGISTRATION_ACTIVATION_NOTIFY_SEND_MAIL_FAILED'));
					return false;
				}
			}
		}
		
		if($app->isAdmin())
		{
			// Set application language back to default if we changed it
			$lang->setLanguage($defaultLocale);
			
			// Reload back the original admin language
			$lang->load('com_users', JPATH_ADMINISTRATOR, $defaultLocale, true);
			$lang->load('com_vpadvanceduser', JPATH_ADMINISTRATOR, $defaultLocale, true);
		}
		
		// Check for an error.
		if ($return !== true)
		{
			// If admin we do not need to send error notification to the administrator
			if($app->isAdmin())
			{
				$app->enqueueMessage(JText::_('JERROR_SENDING_EMAIL'), 'warning');
				return true;
			}
			
			$this->setError(JText::_('COM_USERS_REGISTRATION_SEND_MAIL_FAILED'));

			// Send a system message to administrators receiving system mails
			$db = $this->getDbo();
			$query->clear()
			      ->select($db->quoteName(array('name', 'email', 'sendEmail', 'id')))
			      ->from($db->quoteName('#__users'))
			      ->where($db->quoteName('block') . ' = ' . (int) 0)
			      ->where($db->quoteName('sendEmail') . ' = ' . (int) 1);
			$db->setQuery($query);

			try
			{
				$sendEmail = $db->loadColumn();
			}
			catch (RuntimeException $e)
			{
				$this->setError(JText::sprintf('COM_USERS_DATABASE_ERROR', $e->getMessage()), 500);
				return false;
			}

			if (count($sendEmail) > 0)
			{
				$jdate = new JDate;

				// Build the query to add the messages
				foreach ($sendEmail as $userid)
				{
					$values = array(
					    $db->quote($userid),
					    $db->quote($userid),
					    $db->quote($jdate->toSql()),
					    $db->quote(JText::_('COM_USERS_MAIL_SEND_FAILURE_SUBJECT')),
					    $db->quote(JText::sprintf('COM_USERS_MAIL_SEND_FAILURE_BODY', $return, $data['username']))
					);
					
					$query->clear()
					      ->insert($db->quoteName('#__messages'))
					      ->columns($db->quoteName(array('user_id_from', 'user_id_to', 'date_time', 'subject', 'message')))
					      ->values(implode(',', $values));
					$db->setQuery($query);

					try
					{
						$db->execute();
					}
					catch (RuntimeException $e)
					{
						$this->setError(JText::sprintf('COM_USERS_DATABASE_ERROR', $e->getMessage()), 500);
						return false;
					}
				}
			}

			return false;
		}
		
		return true;
	}

	/**
	* Method to send password reset mail
	* 
	* @param  onject  $user   JUser object
	* @param  string  $token  Reset url token
	* 
	* @return boolean  False in case of failure
	*/
	public function sendResetMail($user, $token)
	{
		if(!($user instanceof JUser))
		{
			$this->setError('Mail could not be sent. Invalid user variable passed.');
			return false;
		}
		
		// Initialize some variables
		$app    = JFactory::getApplication();
		$config = JFactory::getConfig();
		
		// Assemble the password reset confirmation link.
		$mode   = $config->get('force_ssl', 0) == 2 ? 1 : (-1);
		$itemid = VPAdvancedUserHelperRoute::getLoginRoute();
		$itemid = ($itemid !== null) ? '&Itemid=' . $itemid : '';
		$link   = 'index.php?option=com_vpadvanceduser&view=reset&layout=confirm&token=' . $token . $itemid;

		// Put together the email template data.
		$data = $user->getProperties();
		$data['fromname']  = $config->get('fromname');
		$data['mailfrom']  = $config->get('mailfrom');
		$data['sitename']  = $config->get('sitename');
		$data['link_text'] = JRoute::_($link, false, $mode);
		$data['link_html'] = '<a href="' . JRoute::_($link, true, $mode) . '" target="_blank" class="link-html">' . $data['link_text'] . '</a>';
		$data['token']     = $token;

		// Initialize mail variables
		$emailSubject = '';
		$emailBody    = '';
		$html_mode    = false;

		// Get the email template for admin notification
		$template = $this->getTemplate('password_reset');
		
		// If there is an error getting the template
		if($template === false)
		{
			$this->setError($this->getError());
			return false;
		}

		// Use the template if found
		if(!empty($template))
		{
			if(!$this->renderTemplate($template, $data, $emailSubject, $emailBody, $html_mode))
			{
				$this->setError($this->getError());
				return false;
			}
		}
		else
		{
			$emailSubject = JText::sprintf('COM_USERS_EMAIL_PASSWORD_RESET_SUBJECT', $data['sitename']);

			$emailBody = JText::sprintf('COM_USERS_EMAIL_PASSWORD_RESET_BODY',
			                            $data['sitename'], $data['token'], $data['link_text']);
		}
		
		// Send the password reset request email.
		$return = JFactory::getMailer()->sendMail($data['mailfrom'], $data['fromname'], $user->email, $emailSubject, $emailBody, $html_mode);

		// Check for an error.
		if ($return !== true)
		{
			$this->setError(JText::_('COM_USERS_MAIL_FAILED'));
			return false;
		}

		return true;
	}

	/**
	* Method to render email template with Mustache.
	* 
	* @param  object   $template        Template object
	* @param  array    $data            Data array
	* @param  string   $emailSubject    Empty email subject string
	* @param  string   $emailBody       Empty email body string
	* @param  boolean  $html_mode       Empty HTML mode
	* 
	* @return boolean  False in case of failure.
	*/
	protected function renderTemplate($template, $data, &$emailSubject, &$emailBody, &$html_mode)
	{
		if(empty($template->id) || !is_array($data))
		{
			$this->setError('Email template rendering failed. Invalid template or data.');
			return false;
		}
		
		$filter = JFilterInput::getInstance();
		
		// Build mail data object from data array
		$data  = new VPAdvancedUserMailerData($data);
		
		// Are we using html template
		$html_mode = ($template->mode == 'html');
		
		// Get Mustache instance
		if(!$mustache = $this->getMustache($html_mode))
		{
			$this->setError($this->getError());
			return false;
		}
		
		// Render email subject
		$emailSubject = $mustache->render($template->subject, $data);
		$emailSubject = $filter->clean($emailSubject);
		
		// Render email body
		$emailBody    = $mustache->render($template->template, $data);
		
		if(!$html_mode)
		{
			// Ensure plain text email body
			$emailBody = $filter->clean($emailBody);
		}

		return true;
	}

	/**
	* Method to get a new Mustache engine instance.
	* 
	* @param boolean $html_mode True if html mode
	* 
	* @return mixed (object/boolean) False in case of failure
	*/
	protected function getMustache($html_mode = false)
	{
		// If Mustache Autoloader class is not found throw an error
		if(!class_exists('Mustache_Autoloader'))
		{
			$this->setError('Mustache library not loaded.');
			return false;
		}
		
		// Register Mustache autoloader if not registered yet
		if(!self::$moustache_registered)
		{
			Mustache_Autoloader::register();
			self::$moustache_registered = true;
		}
		
		// Build a new Mustache engine instance
		$mustache = new Mustache_Engine(array(
		    'escape' => function($value) {
		        return $value;
		    },
		    'charset' => 'UTF-8',
		    'strict_callables' => false
		));
		
		// Register helper classes
		JLoader::register('VPAdvancedUserMailerJText', __DIR__ . '/mailer/jtext.php');
		JLoader::register('VPAdvancedUserMailerDo', __DIR__ . '/mailer/do.php');
		
		// Add Mustache JText helper
		$JTextHelper = new VPAdvancedUserMailerJText(array('html' => $html_mode));
		$mustache->addHelper('JText', $JTextHelper);
		
		// Add Mustache Do helper
		$DoHelper = new VPAdvancedUserMailerDo();
		$mustache->addHelper('do', $DoHelper);

		return $mustache;
	}
	
	/**
	* Method to get template object for a purpose.
	* 
	* @param string  $purpose         Purpose of the template
	* 
	* @return mixed  (object/boolean) False in case of failure
	*/
	protected function getTemplate($purpose)
	{
		if(!isset($this->templates[$purpose]))
		{
			$db    = $this->getDbo();
			$query = $db->getQuery(true)
			            ->select($db->quoteName('id'))
			            ->select($db->quoteName('mode'))
			            ->select($db->quoteName('name'))
			            ->select($db->quoteName('subject'))
			            ->select($db->quoteName('template'))
			            ->from($db->quoteName('#__vpadvanceduser_emailtemplates'))
			            ->where($db->quoteName('state') . ' = 1')
			            ->where($db->quoteName('is_default') . ' = 1')
			            ->where($db->quoteName('purpose') . ' = ' . $db->quote($purpose));
			
			try
			{
				$db->setQuery($query);
				$template = $db->loadObject();
			}
			catch(RuntimeException $e)
			{
				$this->setError($e->getMessage());
				return false;
			}

			$this->templates[$purpose] = !empty($template) ? $template : null;
		}
		
		return $this->templates[$purpose];
	}

	/**
	* Method to get Joomla database instance.
	* 
	* @return object JDatabase object
	*/
	protected function getDbo()
	{
		if(!is_object($this->db))
		{
			$this->db = JFactory::getDbo();
		}
		
		return $this->db;
	}
}
