<?php if (!defined('VB_ENTRY')) die('Access denied.');
/*========================================================================*\
|| ###################################################################### ||
|| # vBulletin 5.4.1
|| # ------------------------------------------------------------------ # ||
|| # Copyright 2000-2018 vBulletin Solutions Inc. All Rights Reserved.  # ||
|| # This file may not be redistributed in whole or significant part.   # ||
|| # ----------------- VBULLETIN IS NOT FREE SOFTWARE ----------------- # ||
|| # http://www.vbulletin.com | http://www.vbulletin.com/license.html   # ||
|| ###################################################################### ||
\*========================================================================*/

/**
 * vB_Api_Hv
 * vBulletin Human Verification API
 *
 * @package vBApi
 * @access public
 */
class vB_Api_Hv extends vB_Api
{
	protected $disableWhiteList = array('fetchRequireHvcheck');

	/**
	 * @var vB_dB_Assertor
	 */
	protected $assertor;


	protected function __construct()
	{
		parent::__construct();

		$this->assertor = vB::getDbAssertor();
	}

	/**
	 * Fetch Current Hv Type
	 * Possible Values:
	 *   - Image
	 *   - Question
	 *   - Recaptcha
	 *   - Disabled
	 *
	 * @return string Hv Type
	 */
	public function fetchHvType()
	{
		$vboptions = vB::getDatastore()->getValue('options');
		return ($vboptions['hv_type'] ? $vboptions['hv_type'] : 'Disabled');
	}

	/**
	 * Generate a HV token
	 *
	 * @return array It contains one item:
	 *                    hash - Token hash
	 *               In unit tests, it may also include a second item
	 *                    answer - The correct answer
	 */
	public function generateToken()
	{
		require_once(DIR . '/includes/class_humanverify.php');
		$verify =& vB_HumanVerify::fetch_library(vB::get_registry());
		$token = $verify->generate_token();
		if (!defined('VB_UNITTEST'))
		{
			unset($token['answer']);
		}
		return $token;
	}

	/**
	 * Verify a HV token and its answer
	 *
	 * @param string $input HV answer user input ('input') and other data (for example, 'hash')
	 * @param string $action The name of the action to check. register, lostpw etc.
	 * @param bool $return Whether to return a bool value instead of throwing an Exception.
	 *
	 * @throws vB_Exception_Api
	 * @return bool Whether the input answer/hash is correct
	 */
	public function verifyToken($input, $action, $return = false)
	{
		if (!$this->fetchRequireHvcheck($action))
		{
			return true;
		}

		//If we are running in phpunit test mode we just return
		//TODO- We need a better solution. This works for now but is homely
		if (defined('VB_UNITTEST'))
		{
			//we have to find out if we are running hvtest.php
			$stacktrace = debug_backtrace();
			$inHVTest = false;
			foreach ($stacktrace as $caller)
			{
				if (isset($caller['file']) AND basename($caller['file']) == 'hvTest.php')
				{
					$inHVTest = true;
					break;
				}
			}

			if (!$inHVTest)
			{
				//We are not testing hVTest. So just take the human verify test as passed.
				return true;
			}
		}

		require_once(DIR . '/includes/class_humanverify.php');
		$verify =& vB_HumanVerify::fetch_library(vB::get_registry());
		$data = $verify->verify_token($input);
		if ($data)
		{
			return true;
		}
		else
		{
			if ($return)
			{
				return false;
			}
			else
			{
				throw new vB_Exception_Api($verify->fetch_error());
			}
		}
	}

	/**
	 * Fetch Human Verification Image Data
	 *
	 * @param $hash
	 * @return array 'type' => Image type 'data' => Image binary data
	 */
	public function fetchHvImage($hash = '')
	{
		$vboptions = vB::getDatastore()->getValue('options');

		$moveabout = true;
		if (!$hash OR $hash == 'test' OR $vboptions['hv_type'] != 'Image')
		{
			$imageinfo = array(
				'answer' => 'vBulletin',
			);

			$moveabout = $hash == 'test' ? true : false;
		}
		else if (
			!(
				$imageinfo = $this->assertor->getRow('humanverify', array(
					vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_SELECT,
					'hash' => $hash,
					'viewed' => 0,
				))
			)
		)
		{
			return array(
				'type' => 'gif',
				'data' => file_get_contents(DIR . '/clear.gif')
			);
		}
		else
		{
			$this->assertor->assertQuery('humanverify', array(
				vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_UPDATE,
				'viewed' => 1,
				vB_dB_Query::CONDITIONS_KEY => array(
					'hash' => $hash,
					'viewed' => 0,
				)
			));

			if ($this->assertor->affected_rows() == 0)
			{	// image managed to get viewed by someone else between the $imageinfo query above and now
				return array(
					'type' => 'gif',
					'data' => file_get_contents(DIR . '/clear.gif')
				);
			}

		}

		$image = vB_Image::instance();

		$imageInfo = $image->getImageFromString($imageinfo['answer'], $moveabout);

		return array(
			'type' => $imageInfo['filetype'],
			'data' => $imageInfo['filedata']
		);

	}

	/**
	 * Fetch Human Verification Question Data
	 *
	 * @param $hash
	 * @return string|bool Question or false if we failed to generate a question on init
	 */
	public function fetchHvQuestion($hash = '')
	{
		if (!$hash)
		{
			throw new vB_Exception_Api('invalid_hash');
		}

		$hv = $this->assertor->getRow('humanverify', array(
			vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_SELECT,
			'hash' => $hash,
		));

		//if we don't have a valid question, then assume that
		//Q&A is improperly configured and return false.  This should
		//be taken to mean that HV is not active and no validation will
		//occur (the documented behvaior for Q&A if no valid questions exist)
		$questionid = $hv['answer'];
		if (!$questionid)
		{
			return false;
		}

		$phrases = vB_Api::instanceInternal('phrase')->fetch(array('question' . $questionid));
		return $phrases['question' . $questionid];
	}

	/**
	 * The recaptcha languages codes don't map perfectly to what
	 * vBulletin has.  This translates from the user lang code
	 * to what recaptcha expects.
	 *
	 * @return array
	 *	'langcode' => the recaptch language code
	 */
	public function getRecaptchaLanguage()
	{
		$data = array();
		$userinfo = vB_Api::instanceInternal('user')->fetchCurrentUserinfo();
		if (preg_match('#^([a-z]{2})-?#i', $userinfo['lang_code'], $matches))
		{
			$data['langcode'] = strtolower($matches[1]);
		}

		return $data;
	}


	/**
	 * Returns whether or not the user requires a human verification test to complete the specified action
	 *
	 * @param string $action The name of the action to check. Possible values: register, post, search, contactus, lostpw
	 * @return boolean Whether a hv check is required
	 */
	public function fetchRequireHvcheck($action)
	{
		static $results = array();

		if (!empty($results[$action]))
		{
			return $results[$action];
		}

		$results[$action] = fetch_require_hvcheck($action);
		return $results[$action];
	}
}

/*=========================================================================*\
|| #######################################################################
|| # NulleD By - vBSupport.org
|| # CVS: $RCSfile$ - $Revision: 96284 $
|| #######################################################################
\*=========================================================================*/
