<?php

/**
 * @package         MailChimp Auto-Subscribe
 * @version         5.0.3 Pro
 *
 * @author          Tassos Marinos <info@tassos.gr>
 * @link            http://www.tassos.gr
 * @copyright       Copyright © 2021 Tassos Marinos All Rights Reserved
 * @license         GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
 */

// No direct access
defined('_JEXEC') or die;

use NRFramework\SmartTags;

/**
 *  Auto-Subscribe Class
 */
class AutoSubscribe extends JPlugin
{
	/**
	 *  Joomla Application Object
	 *
	 *  @var  object
	 */
	protected $app;

    /**
     *  Joomla Database Object
     *
     *  @var  object
     */
    protected $db;

	/**
	 *  Service API Key
	 *
	 *  @var  string
	 */
	protected $key;

	/**
	 *  Service list ID
	 *
	 *  @var  string
	 */
	protected $list;

	/**
	 *  Current User
	 *
	 *  @var  object
	 */
	protected $user;

	/**
	 *  Auto loads the language file
	 *
	 *  @var  boolean
	 */
	protected $autoloadLanguage = true;

	/**
	 *  Listen to the event onUserAfterSave
	 *
	 *  @param   JUser  $user      User's object
	 *  @param   bool   $isnew     Indicates if the user is a new user
	 *
	 *  @return  bool
	 */
	public function onUserAfterSave($user, $isnew)
	{
		$this->user = $user;
		$this->user['isnew'] = $isnew;

		// Try to subscribe user
		$this->subscribe();

		return true;
	}

	/**
	 * Initialize essentials plugin parameters and logger
	 *
	 * @return void
	 */
	protected function init()
	{
		$this->key  = $this->params->get('apikey');
		$this->list = $this->params->get('list');

		if ($this->params->get('multilist', 0) == 'perlang')
		{
			$this->list = $this->getListByLanguage();
		}

		// Init Log File
		JLog::addLogger(
			array('text_file' => $this->service . 'AutoSubscribe.php'),
			JLog::ALL, 
			array(ucfirst($this->service))
		);
	}

	/**
	 *  Subscribe User to Service
	 *
	 *  @return  bool
	 */
	private function subscribe()
	{
		$this->init();

		// Validate Service Credentials
		if (!$this->validateCredentials())
		{
			$this->log('Invalid service credentials. Check your API Key or your list ID.');
			return;
		}

		// Validate User
		if (!$this->proceed())
		{
			$this->log($this->user['email'] . ' not processed.');
			return;
		}

		// Conditional Subscription
		if (!$this->passConsentField())
		{
			$this->log('Invalid consent field value.');
			return;
		}

		// Get Data
		$this->fields = $this->getFields();
		if (!is_array($this->fields))
		{
			$this->log('Invalid fields object.');
			return;
		}

		// Call Service Method
		$method = 'subscribe' . ucfirst($this->service);
		if (!method_exists($this, $method))
		{
			$this->log('Method not found: ' . $method);
			return;
		}

		// Cool. Let's try to subscibe the user.
		try
		{
			$logData = [
				'Fields'  => $this->fields,
				'List ID' => $this->list
			];

			$this->log('Subscribing user.. ' . print_r($logData, true));
			$this->$method();

			// Consider updating user's lastvisitDate with a current timestamp, to avoid future syncs if the user hasn't logged in yet.

		} catch (Exception $e)
		{
			$this->log($e->getMessage());
			return;
		}

		$this->log('Successfully subscribed to ' . ucfirst($this->service));
	}

	/**
	 *  Validate the Consent Field
	 *
	 *  @return  boolean
	 */
	private function passConsentField()
	{
		$consentField = $this->params->get('consent_field');

		// If we have no consent field selected the check passes
		if (empty($consentField))
		{
			return true;
		}

		// Get user's custom fields. If no fields found, fail.
		if (!$fields = $this->getUserCustomFields())
		{
			return false;
		}

		foreach ($fields as $key => $field)
		{
			if ($field->name != $consentField)
			{
				continue;
			}

			// Consent field found! Let's validate its value
			if ($field->rawvalue == 'subscribe')
			{
				return true;
			}
		}

		return false;
	}

	/**
	 * Load current user custom fields
	 *
	 * @return mixed Array on succeses, Boolean on failure
	 */
	private function getUserCustomFields()
	{
		JLoader::register('FieldsHelper', JPATH_ADMINISTRATOR . '/components/com_fields/helpers/fields.php');

		if (!class_exists('FieldsHelper'))
		{
			return false;
		}

		$fields = FieldsHelper::getFields('com_users.user', $this->user, true);

		return $fields;
	}

	/**
	 *  Validate service API key and List ID
	 *
	 *  @return  bool
	 */
	protected function validateCredentials()
	{
		if (empty($this->key) || empty($this->list))
		{
			return;
		}

		return true;
	}

	/**
	 *  Get fields array map
	 *
	 *  @return  array
	 */
	protected function getFields()
	{
		if (!$fields = $this->params->get('fields'))
		{
			return;
		}

		// add tags
		if ($tags = $this->params->get('tags'))
		{
			$fields->tags = (object) [
				'key' => 'tags',
				'value' => $tags
			];
		}
		
		$fields = json_encode($this->params->get('fields'));
		$this->replaceSmartTags($fields);

		$data = array();

		// Validate Fields
		foreach ($fields as $key => $field)
		{
			// Skip invalid fields
			if (!$field->key || !$field->value || empty($field->value))
			{
				continue;
			}

			// This replacement is likely to be needed only by MailChimp.
			$key_ = str_replace(['*, |'], '', trim($field->key));

			$data[$key_] = trim($field->value);
		}

		return $data;
	}

	/**
	 *  Replace SmartTags in the object
	 *
	 *  @param   array  $object   The object to search for SmartTags
	 *
	 *  @return  void
	 */
	private function replaceSmartTags(&$object)
	{
		// Initialize SmartTags with the active User
		$tags = new SmartTags(['user' => $this->user['id']]);

		// Add user's group names to tags list
		if (strpos($object, 'user.groups.names') !== false)
		{
		    $tags->add([
		    	'user.groups.names' => $this->userGroupsNames()
			]);
		}

		// Add Custom Fields to tags list
		// Consider adding the code block to framework
		$customFields = $this->getUserCustomFields();
		$hasCustomFieldsSelected = strpos($object, 'cf.') !== false ? true : false;

		if (is_array($customFields) && !empty($customFields) && $hasCustomFieldsSelected)
		{
			foreach ($customFields as $key => $customField)
			{
				$value = $customField->value;

				if ($customField->rawvalue && $customField->value != $customField->rawvalue)
				{
					$value = $customField->rawvalue;
				}
	
				$value = is_array($value) ? implode(', ', $value) : $value;

				$field_key = $customField->name;
				$customFields['cf.' . strtolower($field_key)] = $value;
				unset($customFields[$field_key]);
			}

			$tags->add($customFields);
		}

		// Replace Tags
		$object = json_decode($tags->replace($object));
	}

	/**
	 *  Detects whether the sync process should procceed or not.
	 *
	 *  @return  bool
	 */
	protected function proceed()
	{
		// Subscribe new user without waiting for account confirmation.
		if ($this->params->get('forceadd', false) && (bool) $this->user['isnew'])
		{
			return true;
		}

		// We are using the lastvisitDate parameter to detect whether the user has logged in before.
		$visitedDate = $this->user['lastvisitDate'];

		return 
		(
			(is_null($visitedDate) || empty($visitedDate) || $visitedDate == '0000-00-00 00:00:00') && // This line help us skip the Profile Edit Page.
			!$this->user['block']
		);
	}

	/**
	 *  Returns user group names
	 *  Consider adding this method to framework (SmartTags)
	 *
	 *  @return  string              Comma separated user groups name
	 */
	private function userGroupsNames()
	{
		$db = $this->db;

		$query = $db->getQuery(true);
		$query->select($db->quoteName(array('title')))
			->from($db->quoteName('#__usergroups'))
			->where($db->quoteName('id') . ' IN (' . implode(',', $this->user['groups']) . ')')
			->order('id');

		$db->setQuery($query);

		return implode(',', $db->loadColumn());
	}

	/**
	 *  Get list ID based on front-end active language
	 *
	 *  @return  string
	 */
	private function getListByLanguage()
	{
		$data = $this->list;

		if (empty($data))
		{
			return;
		}

		// Value is expected in JSON format. Otherwise, return the original value.
		$data = json_decode($data);
		if (!is_object($data))
		{
			return $this->list;
		}

		// Get active language tag
		$langs = explode('-', JFactory::getDocument()->language);
		if (!is_array($langs))
		{
			return;
		}

		$lang = $langs[0];

		// If language tag does not exist return first indexed property
		if (!isset($data->$lang))
		{
			$firstIndex = array_values(get_object_vars($data));
			return $firstIndex[0];
		}

		// Finally return matched property
		return $data->$lang;
	}

	/**
	 *  Log messages to a log file
	 *
	 *  @param   string  $msg  The string to log as a message
	 *
	 *  @return  void
	 */
	private function log($msg)
	{
		if (!$this->params->get('logging', false))
		{
			return;
		}

		$prefix = isset($this->user['email']) ? $this->user['email'] . ': ' : '';

		JLog::add($prefix . $msg, JLog::ERROR, ucfirst($this->service));
	}
}
