<?php
class XenForo_Model_CaptchaQuestion extends XenForo_Model
{
/**
* Returns a single QA CAPTCHA record
*
* @param integer $captchaQuestionId
*
* @return array
*/
public function getCaptchaQuestionById($captchaQuestionId)
{
return $this->_getDb()->fetchRow('
SELECT *
FROM xf_captcha_question
WHERE captcha_question_id = ?
', $captchaQuestionId);
}
/**
* Returns a collection of QA CAPTCHAs
*
* @param array $conditions
* @param array $fetchOptions
*
* @return array
*/
public function getCaptchaQuestions(array $conditions = array(), array $fetchOptions = array())
{
return $this->fetchAllKeyed('
SELECT *
FROM xf_captcha_question
ORDER BY question
', 'captcha_question_id');
}
/**
* Returns a collection of all QA CAPTCHAs
*
* @return array
*/
public function getAllCaptchaQuestions()
{
return $this->getCaptchaQuestions();
}
/**
* Prepares a collection of QA CAPTCHAs for use
*
* @param array $captchaQuestions
*
* @return array
*/
public function prepareCaptchaQuestions(array $captchaQuestions)
{
foreach ($captchaQuestions AS &$captchaQuestion)
{
$captchaQuestion = $this->prepareCaptchaQuestion($captchaQuestion);
}
return $captchaQuestions;
}
/**
* Prepares a single QA CAPTCHA for use
*
* @param array $captchaQuestion
*
* @return array $captchaQuestion
*/
public function prepareCaptchaQuestion(array $captchaQuestion)
{
$captchaQuestion = $this->_prepareCaptchaQuestionAnswersArray($captchaQuestion);
return $captchaQuestion;
}
/**
* Creates the 'answers_array' entry from the existing 'answers'
* entry in the given QA CAPTCHA record
*
* @param array $captchaQuestion
*
* @return array
*/
protected function _prepareCaptchaQuestionAnswersArray(array $captchaQuestion)
{
$answersArray = XenForo_Helper_Php::safeUnserialize($captchaQuestion['answers']);
if (empty($answersArray))
{
$answersArray = array('');
}
$captchaQuestion['answersArray'] = $answersArray;
return $captchaQuestion;
}
/**
* Checks that the provided answer matches one of the acceptable answers from the QA CAPTCHA.
* Answers are case-insensitive.
*
* @param string $answer
* @param integer $captchaQuestionId
*
* @return boolean
*/
public function checkAnswer($answer, $captchaQuestionId)
{
if ($answer !== '')
{
$captchaQuestion = $this->getCaptchaQuestionById($captchaQuestionId);
if ($captchaQuestion)
{
$captchaQuestion = $this->prepareCaptchaQuestion($captchaQuestion);
if (in_array(strtolower($answer), array_map('strtolower', $captchaQuestion['answersArray'])))
{
return true;
}
}
}
return false;
}
/**
* Fetches and prepares a random QA CAPTCHA
*
* @return array|false
*/
public function getRandomCaptchaQuestion()
{
$captchaQuestion = $this->_getDb()->fetchRow('
SELECT *
FROM xf_captcha_question
WHERE active = 1
ORDER BY RAND()
');
if (!$captchaQuestion)
{
return false;
}
return $this->prepareCaptchaQuestion($captchaQuestion);
}
/**
* Removes all CAPTCHAs that are older than the specified expiry length.
*
* @param integer $expiry Delete CAPTCHAs older than this (in seconds)
*/
public function deleteOldCaptchas($expiry = 86400)
{
$this->_getDb()->delete('xf_captcha_log', 'captcha_date < ' . (XenForo_Application::$time - $expiry));
}
public function getTextCaptchaEntry()
{
$extraKeys = XenForo_Application::getOptions()->extraCaptchaKeys;
$apiKey = !empty($extraKeys['textCaptchaApiKey']) ? $extraKeys['textCaptchaApiKey'] : null;
$result = $apiKey ? $this->_makeTextCaptchaRequest($apiKey) : null;
if (!$result)
{
$result = array(
'question' => '',
'answers' => array('failed')
);
}
$result['hash'] = md5($result['question'] . uniqid() . memory_get_usage());
$this->_getDb()->insert('xf_captcha_log', array(
'hash' => $result['hash'],
'captcha_type' => 'TextCaptcha',
'captcha_data' => implode(',', $result['answers']),
'captcha_date' => XenForo_Application::$time
));
return $result;
}
protected function _makeTextCaptchaRequest($apiKey)
{
$url = "http://api.textcaptcha.com/$apiKey";
try
{
$client = XenForo_Helper_Http::getClient($url);
$result = $client->request('GET')->getBody();
}
catch (Exception $e)
{
XenForo_Error::logException($e, false, 'Error fetching textCAPTCHA: ');
return null;
}
// skip the overhead of XML parsing; it's a very simple format
if (preg_match('#<question>(.*)</question>#siU', $result, $questionMatch)
&& preg_match_all('#<answer>(.*)</answer>#siU', $result, $answerMatch))
{
$question = html_entity_decode($questionMatch[1]);
$question = str_replace(''', "'", $question);
return array(
'question' => $question,
'answers' => $answerMatch[1]
);
}
return null;
}
public function verifyTextCaptcha($hash, $answer)
{
$db = $this->_getDb();
$log = $db->fetchRow('SELECT * FROM xf_captcha_log WHERE hash = ?', $hash);
if ($log && $log['captcha_type'] == 'TextCaptcha')
{
if (!$db->delete('xf_captcha_log', 'hash = ' . $db->quote($hash)))
{
return false;
}
if ($log['captcha_data'] == 'failed')
{
// request failed, we need to pass this every time
return true;
}
$answerMd5 = md5(strtolower(trim($answer)));
$correct = explode(',', $log['captcha_data']);
return in_array($answerMd5, $correct, true);
}
return false;
}
/**
* Inserts a hash into the xf_captcha_log table,
* and adds the hash to the returned $captcha array.
*
* @param integer
*
* @return string The inserted hash
*/
protected function _insertHash($questionId)
{
$hash = sha1(
'Question'
. $questionId
. XenForo_Application::get('config')->globalSalt
. uniqid(microtime(), true)
);
$this->_getDb()->insert('xf_captcha_log', array(
'hash' => $hash,
'captcha_type' => 'Question',
'captcha_data' => $questionId,
'captcha_date' => XenForo_Application::$time
));
return $hash;
}
/**
* Static version of checkAnswer()
*
* @see XenForo_Model_CaptchaQuestion::checkAnswer()
*/
public static function isCorrect($answer, $questionHash)
{
$model = XenForo_Model::create(__CLASS__);
$db = $model->_getDb();
if ($log = $db->fetchRow('SELECT * FROM xf_captcha_log WHERE hash = ?', $questionHash))
{
if ($db->delete('xf_captcha_log', 'hash = ' . $db->quote($questionHash)))
{
if (strval($log['captcha_data']) === '0')
{
// we didn't have a question, so always pass
return true;
}
return $model->checkAnswer($answer, $log['captcha_data']);
}
}
return false;
}
/**
* Gets the a random question and inserts it into the captcha log.
*
* @return array
*/
public static function getQuestion()
{
$model = XenForo_Model::create(__CLASS__);
$question = $model->getRandomCaptchaQuestion();
if (!$question)
{
$question = array(
'captcha_question_id' => 0,
'question' => ''
);
}
$question['hash'] = $model->_insertHash($question['captcha_question_id']);
return $question;
}
}