<?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;

jimport('joomla.filesystem.folder');
jimport('joomla.filesystem.file');

/**
 * Authenticate model class.
 *
 * @since  1.0
 */
class VPAdvancedUserModelAuthenticate extends JModelLegacy
{
	protected $hybrid_config;
	
	protected $custom_fields;
	
	protected $providers;
	
	/**
	 * Method to auto-populate the model state.
	 *
	 * Calling getState in this method will result in recursion.
	 *
	 * @return  void
	 */
	protected function populateState()
	{
		// Get the application object.
		$app = JFactory::getApplication();
		
		// Get provider
		$provider = $app->input->getCmd('provider', '');
		$this->setState('provider', $provider);
		
		// Get core users parameters
		$params = $app->getParams('com_users');

		// Load the core users parameters.
		$this->setState('params', $params);
		
		// Get own parameters
		$myparams = $app->getParams('com_vpadvanceduser');

		// Load own parameters.
		$this->setState('myparams', $myparams);
		
		// Disable force login by default
		$this->setState('force_login', false);
	}
	
	public function getProviders()
	{
		// Register frontend helper
		JLoader::register('VPAdvancedUserFrontendHelper', JPATH_ADVANCEDUSER_SITE . '/helpers/vpadvanceduser.php');
		
		return VPAdvancedUserFrontendHelper::getProviders();
	}

	public function getHybridConfig()
	{
		if(!is_array($this->hybrid_config))
		{
			$provider_map = $this->getProviders();
			
			if($provider_map === false)
			{
				$this->setError($this->getError());
				return false;
			}
			
			$provider = $this->getState('provider', '');

			if(empty($provider) || !isset($provider_map[strtolower($provider)]))
			{
				$this->setError(JText::_('COM_VPADVANCEDUSER_MSG_INVALID_PROVIDER'));
				return false;
			}
			
			// Reset with provider name and state
			$provider = $provider_map[$provider];
			$this->setState('provider', $provider);
			
			// Prepare some required variables
			$app        = JFactory::getApplication();
			$jconfig    = JFactory::getConfig();
			$params     = $this->getState('myparams');
			$debug      = $params->get('debug', 0);
			$debug      = is_numeric($debug) ? (bool) $debug : (string) $debug;
			$debug_file = JPath::clean($jconfig->get('log_path') . '/com_vpadvanceduser.php');
			
			// Build the debug file if does not exist
			if($debug && !JFile::exists($debug_file))
			{
				$buffer = "\n" . '#' . "\n" . '#<?php die("Forbidden."); ?>' . "\n";
				
				if(!JFile::write($debug_file, $buffer))
				{
					// We cant log debug.
					$debug = false;
					$app->enqueueMessage('Log folder ' . $jconfig->get('log_path') . ' is not writable.', 'warning');
				}
			}
			
			// Get all active custom fields
			$custom_fields = $this->getCustomFields();
			
			// Build Facebook scope
			$fb_scope = array('email', 'public_profile');
			
			if($params->get('extended_facebook_scope', 0))
			{
				if(in_array('dob', $custom_fields))
				{
					$fb_scope[] = 'user_birthday';
				}
				
				if(in_array('city', $custom_fields))
				{
					$fb_scope[] = 'user_hometown';
					$fb_scope[] = 'user_location';
				}
			}
			
			$google_redirect_uri = $this->getRedirectUri('google');
			
			if($google_redirect_uri === false)
			{
				$this->setError($this->getError());
				return false;
			}
			
			// Build providers configuration array
			$providers = array(
			    'Facebook' => array(
			        'enabled' => (int) $params->get('enable_facebook', 0),
			        'keys'    => array('id' => (string) $params->get('facebook_id', ''), 'secret' => (string) $params->get('facebook_secret', '')),
			        'display' => ($params->get('use_popup') ? 'popup' : 'page'),
			        'scope'   => $fb_scope
			    ),
			    'Twitter' => array(
			        'enabled'      => (int) $params->get('enable_twitter', 0),
			        'keys'         => array('key' => (string) $params->get('twitter_key', ''), 'secret' => (string) $params->get('twitter_secret', '')),
			        'includeEmail' => true
			        // Need elevated persmission to fetch email from Twitter. Apply here - https://support.twitter.com/forms/platform
			    ),
			    'Google' => array(
			        'enabled'      => (int) $params->get('enable_google', 0),
			        'keys'         => array('id' => (string) $params->get('google_id', ''), 'secret' => (string) $params->get('google_secret', '')),
			        'scope'        => 'https://www.googleapis.com/auth/userinfo.profile ' .
			                          'https://www.googleapis.com/auth/userinfo.email',
			        'redirect_uri' => $google_redirect_uri
			    ),
			    'Live' => array(
			        'enabled'      => (int) $params->get('enable_live', 0),
			        'keys'         => array('id' => (string) $params->get('live_id', ''), 'secret' => (string) $params->get('live_secret', ''))
			    ),
			    'Yahoo' => array(
			        'enabled'      => (int) $params->get('enable_yahoo', 0),
			        'keys'         => array('id' => (string) $params->get('yahoo_key', ''), 'secret' => (string) $params->get('yahoo_secret', ''))
			    ),
			    'LinkedIn' => array(
			        'enabled'      => (int) $params->get('enable_linkedin', 0),
			        'keys'         => array('id' => (string) $params->get('linkedin_key', ''), 'secret' => (string) $params->get('linkedin_secret', ''))
			    ),
			    'Foursquare' => array(
			        'enabled'      => (int) $params->get('enable_foursquare', 0),
			        'keys'         => array('id' => (string) $params->get('foursquare_id', ''), 'secret' => (string) $params->get('foursquare_secret', ''))
			    ),
			    // GitHub create new application link - https://github.com/settings/applications/new
			    'GitHub' => array(
			        'enabled'      => (int) $params->get('enable_github', 0),
			        'keys'         => array('id' => (string) $params->get('github_id', ''), 'secret' => (string) $params->get('github_secret', '')),
			        'scope'        => 'user:email'
			    ),
			    // Yadex documentation - https://tech.yandex.com/oauth/
			    // Need Yandex.Passport API. https://oauth.yandex.com/
			    'Yandex' => array(
			        'enabled'      => (int) $params->get('enable_yandex', 0),
			        'keys'         => array('id' => (string) $params->get('yandex_id', ''), 'secret' => (string) $params->get('yandex_secret', ''))
			    ),
			    // PayPal documentation - https://developer.paypal.com/docs/integration/direct/identity/attributes/
			    // Create PayPal App - https://developer.paypal.com/docs/api/overview/#create-a-paypal-app
			    'Paypal' => array(
			        'enabled'      => (int) $params->get('enable_paypal', 0),
			        'keys'         => array('id' => (string) $params->get('paypal_id', ''), 'secret' => (string) $params->get('paypal_secret', '')),
			        'scope'        => array('openid', 'profile', 'email')
			    ),
			    'OpenID' => array(
			        'enabled'      => (int) $params->get('enable_openid', 0)
			    )
			);
			
			$base_url = $this->getRedirectUri('base_url');
			
			if($base_url === false)
			{
				$this->setError($this->getError());
				return false;
			}

			// Build Hybrid configuration array
			$this->hybrid_config = array(
			    'path_libraries' => JPATH_ADVANCEDUSER_SITE . DIRECTORY_SEPARATOR . 'libraries',
			    'base_url'       => $base_url,
			    'enableRegister' => true,
			    'debug_mode'     => $debug,
			    'debug_file'     => $debug_file,
			    'providers'      => $providers
			);
		}

		return $this->hybrid_config;
	}
	
	public function getRedirectUri($provider = null)
	{
		$app          = JFactory::getApplication();
		$lang         = JFactory::getLanguage();
		$multiLingual = JLanguageMultilang::isEnabled();
		$languages    = JLanguageHelper::getLanguages('lang_code');
		$langTag      = $lang->getTag();
		$root         = JUri::root();

		// Get list of valid providers
		$providers = $this->getProviders();
			
		if($providers === false)
		{
			$this->setError($this->getError());
			return false;
		}
		
		$uri = $root . 'index.php?option=com_vpadvanceduser&task=authenticate.endpoint';
		
		if($provider === null)
		{
			$list = array();
			
			foreach($providers as $key => $value)
			{
				$URIs = array();
				
				if($key == 'twitter')
				{
					$URIs[] = $root . 'index.php';
				}
				elseif($key == 'github')
				{
					$URIs[] = $uri . '&hauth.done=' . $value;
				}
				elseif($key == 'yahoo')
				{
					$parts = @parse_url($root);
					
					if(empty($parts['scheme']) || empty($parts['host']))
					{
						$URIs[] = $root;
					}
					else
					{
						$URIs[] = $parts['scheme'] . '://' . $parts['host'];
					}
				}
				elseif($key == 'live')
				{
					$URIs[] = $root;
					$URIs[] = $root . 'index.php';
				}
				else
				{
					$URIs[] = $uri . '&hauth_done=' . $key;
					$URIs[] = $uri . '&hauth_done=' . $value;
					$URIs[] = $uri . '&hauth.done=' . $key;
					$URIs[] = $uri . '&hauth.done=' . $value;
				}
				
				$temp = new stdClass;
				$temp->name = $value;
				$temp->uris = $URIs;
				
				$list[$key] = $temp;
			}
			
			return $list;
		}
		
		$provider = strtolower($provider);

		if($multiLingual && isset($languages[$langTag]))
		{
			$app->setUserState('com_vpadvanceduser.authentication.lang', $languages[$langTag]->sef);
		}

		if(isset($providers[$provider]))
		{
			$uri .= '&hauth_done=' . $provider;
		}
		
		return $uri;
	}

	/**
	* Method to get login or registration status.
	* When called this will also try to login or register user based on configuration
	* 
	* @return boolean (true/false) False in case of error or failure
	*/
	public function getStatus()
	{
		JLoader::register('Hybrid_Auth', JPATH_ADVANCEDUSER_SITE . '/libraries/Hybrid/Auth.php');
		
		if(!JFactory::getUser()->get('guest'))
		{
			$this->setState('message', JText::_('COM_VPADVANCEDUSER_MSG_ALREADY_LOGGED_IN'));
			$this->setState('reload', true);
			return false;
		}

		$app         = JFactory::getApplication();
		$config      = $this->getHybridConfig();
		$params      = $app->getParams('com_vpadvanceduser');
		$debug       = $params->get('debug', 0);
		$debug       = is_numeric($debug) ? (bool) $debug : (string) $debug;
		$user_params = $app->getParams('com_users');

		if(!$config)
		{
			$this->setState('message', $this->getError());
			return false;
		}
		
		try
		{
			$hybridauth = new Hybrid_Auth($config);
			$provider   = $this->getState('provider');
			
			if(strtolower($provider) == 'openid')
			{
				// Need OpenID indentifier
				$openid_identifier = $this->getState('openid_identifier');
				
				if(empty($openid_identifier))
				{
					$this->setError(JText::_('COM_VPADVANCEDUSER_ERROR_INVALID_OPENID_IDENTIFIER'));
					return false;
				}
				
				$adapter = $hybridauth->authenticate($provider, array('openid_identifier' => $openid_identifier));
			}
			else
			{
				$adapter = $hybridauth->authenticate($provider);
			}
			
			$user_profile = $adapter->getUserProfile();
		}
		catch(Exception $e)
		{
			$errorCode = $e->getCode();
			
			if($debug || !in_array($errorCode, array(0, 1, 2, 3, 4, 5, 6, 7, 8)))
			{
				$this->setError($e->getMessage());
			}
			else
			{
				$this->setError(JText::_('COM_VPADVANCEDUSER_HYBRIDAUTH_ERROR_' . $errorCode));
			}
			
			return false;
		}

		if(!is_object($user_profile) || empty($user_profile->identifier))
		{
			$this->setState('message', JText::sprintf('COM_VPADVANCEDUSER_MSG_FAILED_TO_RETRIEVE_PROFILE', $provider));
			return false;
		}
		
		// Check if the user is already registered
		$user_id = $this->getExistingUserID($user_profile->identifier, $provider);
		
		// If the user is already registered
		if($user_id > 0)
		{
			if(!$this->loginUser($user_id))
			{
				if($message = $this->getError())
				{
					$this->setState('message', $message);
				}
				else
				{
					$this->setState('message', JText::sprintf('COM_VPADVANCEDUSER_MSG_FAILED_TO_AUTO_LOGIN', $provider));
				}
				
				return false;
			}
			else
			{
				if(!$this->getState('message'))
				{
					$this->setState('message', JText::sprintf('COM_VPADVANCEDUSER_MSG_LOGIN_SUCCESS', $provider));
				}
				
				$this->setState('reload', true);
				return true;
			}
		}
		elseif(!$user_params->get('allowUserRegistration'))
		{
			$this->setState('message', JText::_('COM_VPADVANCEDUSER_ERROR_NO_ACCOUNT'));
			return false;
		}
		// If the user is not registered and auto registration is enabled
		elseif($params->get('auto_register', 1))
		{
			$result = $this->registerUser($user_profile, $provider);
			
			// If registration fails
			if(!$result)
			{
				if($message = $this->getError())
				{
					$this->setState('message', $message);
				}
				else
				{
					$this->setState('message', JText::sprintf('COM_VPADVANCEDUSER_MSG_FAILED_TO_AUTO_REGISTER', $provider));
				}
				
				return false;
			}
			// If registration is completed and no activation is required
			elseif(is_numeric($result) && $result > 0)
			{
				$user_id = $result;
				$userNow = JFactory::getUser();
				
				if(($params->get('auto_login', 1) || $this->getState('force_login')) && $userNow->guest)
				{
					if(!$this->loginUser($user_id))
					{
						if($message = $this->getError())
						{
							$this->setState('message', $message);
						}
						else
						{
							$this->setState('message', JText::sprintf('COM_VPADVANCEDUSER_MSG_FAILED_TO_AUTO_LOGIN', $provider));
						}
						
						return false;
					}
					else
					{
						if(!$this->getState('message'))
						{
							$this->setState('message', JText::sprintf('COM_VPADVANCEDUSER_MSG_LOGIN_SUCCESS', $provider));
						}
						
						$this->setState('reload', true);
						return true;
					}
				}
				
				// If auto login is disabled ask the user to login manually.
				$this->setState('message', JText::sprintf('COM_VPADVANCEDUSER_MSG_REGISTRATION_SUCCESS', $provider));
				return true;
			}
			// If registration is completed but email activation is required
			elseif($result == 'useractivate')
			{
				$this->setState('message', JText::_('COM_USERS_REGISTRATION_COMPLETE_ACTIVATE'));
				return true;
			}
			// If registration is completed but admin activation is required
			elseif($result == 'adminactivate')
			{
				$this->setState('message', JText::_('COM_USERS_REGISTRATION_COMPLETE_VERIFY'));
				return true;
			}
		}
		// If the user is not registered and auto registration is disabled
		else
		{
			// If registration fails
			if(!$user_id = $this->registerUserManually($user_profile, $provider))
			{
				if($message = $this->getError())
				{
					$this->setState('message', $message);
				}
				else
				{
					$this->setState('message', JText::sprintf('COM_VPADVANCEDUSER_MSG_FAILED_TO_REGISTER', $provider));
				}
				
				return false;
			}
			elseif(is_numeric($user_id) && $user_id > 0)
			{
				$userNow = JFactory::getUser();
				
				if(($params->get('auto_login', 1) || $this->getState('force_login')) && $userNow->guest)
				{
					if(!$this->loginUser($user_id))
					{
						if($message = $this->getError())
						{
							$this->setState('message', $message);
						}
						else
						{
							$this->setState('message', JText::sprintf('COM_VPADVANCEDUSER_MSG_FAILED_TO_AUTO_LOGIN', $provider));
						}
						
						return false;
					}
					else
					{
						if(!$this->getState('message'))
						{
							$this->setState('message', JText::sprintf('COM_VPADVANCEDUSER_MSG_LOGIN_SUCCESS', $provider));
						}
						
						$this->setState('reload', true);
						return true;
					}
				}
			}
			
			// Ask the user to complete registration manually.
			$this->setState('message', JText::_('COM_VPADVANCEDUSER_MSG_COMPLETE_REGISTRATION'));
			return true;
		}
		
		return true;
	}

	/**
	* Method to get existing Joomla user id from Hybrid identifier and provider
	* 
	* @param  string    $identifier  Hybrid identifier
	* @param  string    $provider    Name of the provider
	* 
	* @return integer  Zero if not found
	*/
	protected function getExistingUserID($identifier, $provider)
	{
		$db       = $this->getDbo();
		$query    = $db->getQuery(true)
		               ->select('a.' . $db->quoteName('user_id'))
		               ->from($db->quoteName('#__vpadvanceduser_users') . ' AS a')
		               ->join('INNER', $db->quoteName('#__users') . ' AS b ON b.id = a.user_id')
		               ->where('a.' . $db->quoteName('identifier') . ' = ' . $db->quote($identifier))
		               ->where('a.' . $db->quoteName('provider') . ' = ' . $db->quote(strtolower($provider)));
		$db->setQuery($query);
		$user_id = $db->loadResult();
		
		return (int) $user_id;
	}

	/**
	* Method to auto login a user
	* 
	* @param  integer  $user_id  Joomla User ID
	* 
	* @return boolean  False in case of error or failure
	*/
	public function loginUser($user_id)
	{
		$user   = JFactory::getUser();
		$app    = JFactory::getApplication();
		$params = $app->getParams('com_vpadvanceduser');
		$silent = (int) $params->get('silent_login', 1);
		
		if(!$user->get('guest'))
		{
			$this->setError(JText::_('COM_VPADVANCEDUSER_MSG_ALREADY_LOGGED_IN'));
			return false;
		}
		
		// Create an empty JUser object
		$userObject = new JUser();
		
		// Force reload the user records
		$userObject->load($user_id);
		
		if(empty($userObject->id))
		{
			$this->setError(JText::_('COM_VPADVANCEDUSER_MSG_USER_NOT_FOUND'));
			return false;
		}

		// Get the global JAuthentication object.
		jimport('joomla.user.authentication');
		
		// Get the global JAuthenticationResponse object
		JLoader::register('JAuthenticationResponse', JPATH_LIBRARIES . '/joomla/user/authentication.php');
		
		// Import the user plugin group.
		JPluginHelper::importPlugin('user');
		
		// Create authentication response
		$response = new JAuthenticationResponse;
		
		// Set response type
		$response->type     = 'vpadvanceduser';
		$response->status   = JAuthentication::STATUS_SUCCESS;
		$response->email    = $userObject->email;
		$response->fullname = $userObject->name;
		$response->language = $userObject->getParam('language');
		
		// Build a dummy login options array for user plugins
		$options = array();
		$options['entry_url'] = JUri::base() . 'index.php?option=com_vpadvanceduser&view=login';
		$options['remember']  = (bool) $params->get('remember_user', 0);
		$options['return']    = 'index.php?option=com_vpadvanceduser&view=login';

		if($userObject->block)
		{
			if(!$silent)
			{
				$response->status = JAuthentication::STATUS_FAILURE;
				$app->triggerEvent('onUserLoginFailure', array((array) $response));
			}
			
			$this->setError(JText::_('COM_VPADVANCEDUSER_MSG_USER_ACCOUNT_BLOCKED'));
			return false;
		}

		// Just making sure
		$userObject->block = 0;
		
		// Mark the user as logged in
		$userObject->set('guest', 0);
		
		// Register the needed session variables
		$session = JFactory::getSession();
		$session->set('user', $userObject);
		
		// Check to see the the session already exists.
		$app->checkSession();
		
		// Update the user related fields for the Joomla sessions table.
		$db    = $this->getDbo();
		$query = $db->getQuery(true)
		            ->update($db->quoteName('#__session'))
		            ->set($db->quoteName('guest') . ' = ' . $db->quote($userObject->guest))
		            ->set($db->quoteName('username') . ' = ' . $db->quote($userObject->username))
		            ->set($db->quoteName('userid') . ' = ' . (int) $userObject->id)
		            ->where($db->quoteName('session_id') . ' = ' . $db->quote($session->getId()));
		try
		{
			$db->setQuery($query)->execute();
		}
		catch (RuntimeException $e)
		{
			return false;
		}

		// Hit the user last visit field
		$userObject->setLastVisit();

		if(!$silent)
		{
			$options['user']         = $userObject;
			$options['responseType'] = $response->type;
			
			// The user is successfully logged in. Run the after login events
			$app->triggerEvent('onUserAfterLogin', array($options));
		}
		
		// Do we need to save custom fields
		$socialData = $app->getUserState('com_vpdvanceduser.sociallogin.customdata', null);
		
		if($params->get('social_show_custom_fields', 0) && is_array($socialData))
		{
			// Hacks to support System - Privacy Consent plugin
			VPAdvancedUserFrontendHelper::handlePrivacyConsentPlugin(true, $socialData);
			
			// Bind the data.
			if (!$userObject->bind($socialData))
			{
				$this->setError(JText::sprintf('COM_USERS_PROFILE_BIND_FAILED', $userObject->getError()));

				return false;
			}
			
			// Load the users plugin group.
			JPluginHelper::importPlugin('user');

			// Null the user groups so they don't get overwritten
			$userObject->groups = null;

			// Store the data.
			if (!$userObject->save())
			{
				$this->setError($userObject->getError());

				return false;
			}
			
			// Reset hacks to support System - Privacy Consent plugin
			VPAdvancedUserFrontendHelper::handlePrivacyConsentPlugin(false);
			
			// Clear registration form user state
			$app->setUserState('com_vpdvanceduser.sociallogin.customdata', null);
		}
		
		return true;
	}
	
	protected function registerUser($user_profile, $provider)
	{
		if(empty($user_profile->email))
		{
			// TODO: Call manual registration if email is not available
			
			$this->setError(JText::sprintf('COM_VPADVANCEDUSER_MSG_ERROR_NO_EMAIL', $provider));
			return false;
		}
		
		$app    = JFactory::getApplication();
		$params = $this->getState('myparams');
		$data   = array();
		
		$data['email1']    = !empty($user_profile->emailVerified) && filter_var($user_profile->emailVerified, FILTER_VALIDATE_EMAIL) ? $user_profile->emailVerified : $user_profile->email;
		$data['email2']    = $data['email1'];
		$data['password1'] = $this->generatePassword();
		$data['password2'] = $data['password1'];

		// Check email is already registered
		if($user_id = $this->emailExists($data['email1']))
		{
			return $this->updateUser($user_id, $user_profile, $provider);
		}

		if(empty($user_profile->displayName))
		{
			$parts        = explode('@', $data['email1']);
			$data['name'] = ucfirst($parts[0]);
		}
		else
		{
			$data['name'] = $user_profile->displayName;
		}
		
		$data['username'] = $this->createUsername($user_profile, $provider);

		$data['vpadvanceduserdata'] = array();
		
		// Get all custom fields
		$custom_fields = $this->getCustomFields();
		
		if(!empty($user_profile->birthDay) && !empty($user_profile->birthMonth) && !empty($user_profile->birthYear))
		{
			$dob  = $user_profile->birthYear . '-' . $user_profile->birthMonth . '-' . $user_profile->birthDay . ' 00:00:00';
			$date = JFactory::getDate($dob);
			
			$user_profile->dob = $date->toSql();
		}
		
		foreach($user_profile as $field => $value)
		{
			$field = strtolower($field);
			
			if(in_array($field, $custom_fields))
			{
				$data['vpadvanceduserdata'][$field] = $value;
			}
		}
		
		// Do we need to save custom fields
		$socialData = $app->getUserState('com_vpdvanceduser.sociallogin.customdata', null);
		
		if($params->get('auto_register', 1) && $params->get('social_show_custom_fields', 1) && is_array($socialData))
		{
			foreach($socialData as $key => $value)
			{
				$data[$key] = $value;
			}
		}
		
		// We need to save of available custom fields during registration
		$app->setUserState('com_vpadvanceduser.registration.allfields', true);
		
		// Get registration model
		$model = JModelLegacy::getInstance('Registration', 'VPAdvancedUserModel', array('ignore_request' => true));
		
		// Disable user activation process
		$disable_useractivation = (int) $params->get('disable_useractivation', 1);
		
		if($disable_useractivation == 1 || ($disable_useractivation == 2 && !empty($user_profile->emailVerified)))
		{
			$model->setState('disable_useractivation', true);
		}
		
		// Send password as it is being auto generated
		$model->setState('sendpassword', true);
		
		// Prepare and save identifier and provider information to session
		$mapdata = array(
		    'identifier' => $user_profile->identifier,
		    'provider'   => $provider
		);
		
		$app->setUserState('com_vpadvanceduser.registration.mapdata', $mapdata);

		// Try to register the user
		$result = $model->register($data);

		if($result === false)
		{
			// Do not show all fields in case of failure
			$app->setUserState('com_vpadvanceduser.registration.allfields', false);
			
			$this->setError($model->getError());
			return false;
		}
		
		// Clear registration form user state
		$app->setUserState('com_vpdvanceduser.sociallogin.customdata', null);
		
		return $result;
	}
	
	protected function updateUser($user_id, $user_profile, $provider)
	{
		// Get registration model
		$model = JModelLegacy::getInstance('Registration', 'VPAdvancedUserModel', array('ignore_request' => true));
		
		// Map the new provider with existing user
		if(!$model->saveUserIdentifier($user_profile->identifier, $provider, $user_id))
		{
			// Throw a warning as provider mapping was not saved.
			$this->setError(JText::sprintf('COM_VPADVANCEDUSER_MSG_FAILED_TO_SAVE_IDENTIFIER', $model->getError()));
			return false;
		}
		
		return $user_id;
	}
	
	protected function registerUserManually($user_profile, $provider)
	{
		$app    = JFactory::getApplication();
		$params = $this->getState('myparams');
		$data   = array();
		
		if(!empty($user_profile->email))
		{
			// Check email is already registered
			if($user_id = $this->emailExists($user_profile->email))
			{
				return $this->updateUser($user_id, $user_profile, $provider);
				
				// $this->setError(JText::sprintf('COM_VPADVANCEDUSER_MSG_EMAIL_ALREADY_REGISTERED', $provider));
				// return false;
			}
			
			$data['email'] = $user_profile->email;
		}
		
		if(empty($user_profile->displayName) && !empty($data['email']))
		{
			$parts        = explode('@', $data['email']);
			$data['name'] = ucfirst($parts[0]);
		}
		elseif(!empty($user_profile->displayName))
		{
			$data['name'] = $user_profile->displayName;
		}
		
		if(!$params->get('manualreg_show_username', 1))
		{
			$data['username'] = $this->createUsername($user_profile, $provider, true);
		}
		
		if(!$params->get('manualreg_show_password', 1))
		{
			$data['password'] = $this->generatePassword();
		}
		
		$data['vpadvanceduserdata'] = array();
		
		// Get all custom fields
		$custom_fields = $this->getCustomFields();
		
		if(!empty($user_profile->birthDay) && !empty($user_profile->birthMonth) && !empty($user_profile->birthYear))
		{
			$dob  = $user_profile->birthYear . '-' . $user_profile->birthMonth . '-' . $user_profile->birthDay . ' 00:00:00';
			$date = JFactory::getDate($dob);
			
			$user_profile->dob = $date->toSql();
		}
		
		foreach($user_profile as $field => $value)
		{
			$field = strtolower($field);
			
			if(in_array($field, $custom_fields))
			{
				$data['vpadvanceduserdata'][$field] = $value;
			}
		}
		
		// Set a user state as a special registration is in process
		$app->setUserState('com_vpadvanceduser.registration.inprocess', true);
		
		// Save fetched data to a temporary user state
		$app->setUserState('com_vpadvanceduser.registration.tempdata', $data);
		
		// Prepare and save identifier and provider information to session
		$mapdata = array(
		    'identifier' => $user_profile->identifier,
		    'provider'   => $provider
		);
		
		$app->setUserState('com_vpadvanceduser.registration.mapdata', $mapdata);
		
		return true;
	}
	
	protected function getCustomFields()
	{
		if(!is_array($this->custom_fields))
		{
			$db    = $this->getDbo();
			$query = $db->getQuery(true)
			            ->select('name')
			            ->from('#__vpadvanceduser_fields')
			            ->where('state = 1');
			$db->setQuery($query);
			$fields = $db->loadColumn();
			
			$this->custom_fields = is_array($fields) ? $fields : array();
		}
		
		return $this->custom_fields;
	}
	
	/**
	* Automatically generates unique secured password based on com_users configuration
	* 
	* @return  string  Generated password
	*/
	protected function generatePassword()
	{
		$userParams = $this->getState('params');
		
		$minimum_length    = (int) $userParams->get('minimum_length', 4);
		$minimum_length    = ($minimum_length < 6) ? 6 : $minimum_length;
		$minimum_integers  = (int) $userParams->get('minimum_integers', 0);
		$minimum_symbols   = (int) $userParams->get('minimum_symbols', 0);
		$minimum_uppercase = (int) $userParams->get('minimum_uppercase', 0);
		$max_length        = max($minimum_length, $minimum_integers, $minimum_symbols, $minimum_uppercase);
		$repeat            = ($max_length > 10) ? ($max_length / 10) : 1;
		$repeat            = ceil($repeat);

		$integers  = str_repeat('1234567890', $repeat);
		$symbols   = str_repeat('!@#$%&*&+-)(', $repeat);
		$uppercase = str_repeat('ABCDEFGHIJKLMNOPQRSTUVWXYZ', $repeat);
		$letters   = $integers . $symbols . $uppercase . str_repeat('abcdefghijklmnopqrstuvwxyz', $repeat);
		
		$password  = substr(str_shuffle($letters), 0, $minimum_length);
		
		if($minimum_integers)
		{
			$password .= substr(str_shuffle($integers), 0, $minimum_integers);
		}
		
		if($minimum_symbols)
		{
			$password .= substr(str_shuffle($symbols), 0, $minimum_symbols);
		}
		
		if($minimum_uppercase)
		{
			$password .= substr(str_shuffle($uppercase), 0, $minimum_uppercase);
		}
		
		if($minimum_integers || $minimum_symbols || $minimum_uppercase)
		{
			$password = str_shuffle($password);
		}
		
		return $password;
	}
	
	/**
	* Check if email id is already registered
	* 
	* @param  string  $email  The email id to be checked
	* 
	* @return  misex (boolean/integer)  False if does not exist or user id which is registered with the email.
	*/
	protected function emailExists($email)
	{
		$email = JStringPunycode::emailToPunycode($email);
		
		$db    = $this->getDbo();
		$query = $db->getQuery(true);
		
		$query->select($db->quoteName('id'))
			->from($db->quoteName('#__users'))
			->where($db->quoteName('email') . ' = ' . $db->quote($email));

		$db->setQuery($query);
		$user_id = $db->loadResult();
		
		if(!empty($user_id))
		{
			return (int) $user_id;
		}
		
		return false;
	}
	
	/**
	* Generates username as per configuration
	* 
	* @param  object   $user_profile
	* @param  object   $provider
	* @param  boolean  $manual
	* 
	* @return  string  Username
	*/
	protected function createUsername($user_profile, $provider, $manual = false)
	{
		$filter = JFilterInput::getInstance();
		$params = $this->getState('myparams');
		$source = strtolower($provider);
		
		$username_suffix = !empty($user_profile->username) ? $user_profile->username : $user_profile->identifier;
		
		if(function_exists('filter_var') && filter_var($username_suffix, FILTER_VALIDATE_URL))
		{
			if(substr($username_suffix, 0, 7) == 'http://')
			{
				$username_suffix = substr($username_suffix, 7);
			}
			elseif(substr($username_suffix, 0, 8) == 'https://')
			{
				$username_suffix = substr($username_suffix, 8);
			}
			if(strpos($username_suffix, '#') !== false)
			{
				$username_suffix = strtok($username_suffix, '#');
			}
			if(strpos($username_suffix, '/') !== false)
			{
				$parts = explode('/', $username_suffix);
				$username_suffix = $parts[0] . '_' . end($parts);
			}
		}
		
		if($manual)
		{
			$username_as_email = $params->get('email_as_username', 0) || $params->get('set_manual_social_username_as_email', 1);
		}
		else
		{
			$username_as_email = $params->get('email_as_username', 0) || $params->get('set_social_username_as_email', 1);
		}
		
		if($username_as_email)
		{
			$username = !empty($user_profile->emailVerified) && filter_var($user_profile->emailVerified, FILTER_VALIDATE_EMAIL) ? $user_profile->emailVerified : $user_profile->email;
		}
		else
		{
			$username = $params->get($source . '_prefix', $source . '_') . $username_suffix;
			$username = $filter->clean($username, 'username');
		}
		
		// Check if the username is in use
		$db    = $this->getDbo();
		$query = $db->getQuery(true);
		
		$query->select($db->quoteName('id'))
			->from($db->quoteName('#__users'))
			->where($db->quoteName('username') . ' = ' . $db->quote($username));
		
		try
		{
			$db->setQuery($query);
			$result = $db->loadResult();
		}
		catch (RuntimeException $e)
		{
			$this->setError($e->getMessage());
			return false;
		}
		
		// If username exists
		if(!empty($result))
		{
			// Suffix some random letters after the username 
			$username .= '-' . substr(md5(microtime()), rand(0, 26), 5);
		}
		
		return $username;
	}
}
