qa-recaptcha-captcha.php 5 KB
<?php
/*
	Question2Answer by Gideon Greenspan and contributors
	http://www.question2answer.org/

	File: qa-plugin/recaptcha-captcha/qa-recaptcha-captcha.php
	Description: Captcha module for reCAPTCHA


	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	More about this license: http://www.question2answer.org/license.php
*/

class qa_recaptcha_captcha
{
	private $directory;
	private $errorCodeMessages;

	public function load_module($directory, $urltoroot)
	{
		$this->directory = $directory;

		// human-readable error messages (though these are not currently displayed anywhere)
		$this->errorCodeMessages = array(
			'missing-input-secret' => 'The secret parameter is missing.',
			'invalid-input-secret' => 'The secret parameter is invalid or malformed.',
			'missing-input-response' => 'The response parameter is missing.',
			'invalid-input-response' => 'The response parameter is invalid or malformed.',
		);
	}

	public function admin_form()
	{
		$saved = false;

		if (qa_clicked('recaptcha_save_button')) {
			qa_opt('recaptcha_public_key', qa_post_text('recaptcha_public_key_field'));
			qa_opt('recaptcha_private_key', qa_post_text('recaptcha_private_key_field'));

			$saved = true;
		}

		$pub = trim(qa_opt('recaptcha_public_key'));
		$pri = trim(qa_opt('recaptcha_private_key'));

		$error = null;
		if (!strlen($pub) || !strlen($pri)) {
			require_once $this->directory.'recaptchalib.php';
			$error = 'To use reCAPTCHA, you must <a href="'.qa_html(ReCaptcha::getSignupUrl()).'" target="_blank">sign up</a> to get these keys.';
		}

		$form = array(
			'ok' => $saved ? 'reCAPTCHA settings saved' : null,

			'fields' => array(
				'public' => array(
					'label' => 'reCAPTCHA public key:',
					'value' => $pub,
					'tags' => 'name="recaptcha_public_key_field"',
				),

				'private' => array(
					'label' => 'reCAPTCHA private key:',
					'value' => $pri,
					'tags' => 'name="recaptcha_private_key_field"',
					'error' => $error,
				),
			),

			'buttons' => array(
				array(
					'label' => 'Save Changes',
					'tags' => 'name="recaptcha_save_button"',
				),
			),
		);

		return $form;
	}

	/**
	 * Only allow reCAPTCHA if the keys are set up (new reCAPTCHA has no special requirements)
	 */
	public function allow_captcha()
	{
		$pub = trim(qa_opt('recaptcha_public_key'));
		$pri = trim(qa_opt('recaptcha_private_key'));

		return strlen($pub) && strlen($pri);
	}

	/**
	 * Return HTML for reCAPTCHA, including non-JS fallback. New reCAPTCHA auto-detects the user's language.
	 */
	public function form_html(&$qa_content, $error)
	{
		$pub = qa_opt('recaptcha_public_key');

		$htmlLines = array(
			// currently we cannot add async/defer attributes via $qa_content so we insert script here
			'<script src="https://www.google.com/recaptcha/api.js" async defer></script>',
			'<div class="g-recaptcha" data-sitekey="'.$pub.'"></div>',

			// non-JS falback
			'<noscript>',
			'  <div style="width: 302px; height: 352px;">',
			'    <div style="width: 302px; height: 352px; position: relative;">',
			'      <div style="width: 302px; height: 352px; position: absolute;">',
			'        <iframe src="https://www.google.com/recaptcha/api/fallback?k='.$pub.'"',
			'                frameborder="0" scrolling="no"',
			'                style="width: 302px; height:352px; border-style: none;">',
			'        </iframe>',
			'      </div>',
			'      <div style="width: 250px; height: 80px; position: absolute; border-style: none;',
			'                  bottom: 21px; left: 25px; margin: 0px; padding: 0px; right: 25px;">',
			'        <textarea id="g-recaptcha-response" name="g-recaptcha-response"',
			'                  class="g-recaptcha-response"',
			'                  style="width: 250px; height: 80px; border: 1px solid #c1c1c1;',
			'                         margin: 0px; padding: 0px; resize: none;" value=""></textarea>',
			'      </div>',
			'    </div>',
			'  </div>',
			'</noscript>',
		);

		return implode("\n", $htmlLines);
	}

	/**
	 * Check that the CAPTCHA was entered correctly. reCAPTCHA sets a long string in 'g-recaptcha-response'
	 * when the CAPTCHA is completed; we check that with the reCAPTCHA API.
	 */
	public function validate_post(&$error)
	{
		require_once $this->directory.'recaptchalib.php';

		$recaptcha = new ReCaptcha(qa_opt('recaptcha_private_key'));
		$remoteIp = qa_remote_ip_address();
		$userResponse = qa_post_text('g-recaptcha-response');

		$recResponse = $recaptcha->verifyResponse($remoteIp, $userResponse);

		foreach ($recResponse->errorCodes as $code) {
			if (isset($this->errorCodeMessages[$code]))
				$error .= $this->errorCodeMessages[$code] . "\n";
		}

		return $recResponse->success;
	}
}