Blame view

plugins/captcha/recaptcha_invisible/recaptcha_invisible.php 5.46 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
<?php
/**
 * @package     Joomla.Plugin
 * @subpackage  Captcha
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('_JEXEC') or die;

use Joomla\CMS\Captcha\Google\HttpBridgePostRequestMethod;
use Joomla\Utilities\IpHelper; 

/**
 * Invisible reCAPTCHA Plugin.
 *
 * @since  3.9.0
 */
class PlgCaptchaRecaptcha_Invisible extends \JPlugin
{
	/**
	 * Load the language file on instantiation.
	 *
	 * @var    boolean
	 * @since  3.9.0
	 */
	protected $autoloadLanguage = true;

	/**
	 * Reports the privacy related capabilities for this plugin to site administrators.
	 *
	 * @return  array
	 *
	 * @since   3.9.0
	 */
	public function onPrivacyCollectAdminCapabilities()
	{
		$this->loadLanguage();

		return array(
			JText::_('PLG_CAPTCHA_RECAPTCHA_INVISIBLE') => array(
				JText::_('PLG_RECAPTCHA_INVISIBLE_PRIVACY_CAPABILITY_IP_ADDRESS'),
			)
		);
	}

	/**
	 * Initialise the captcha
	 *
	 * @param   string  $id  The id of the field.
	 *
	 * @return  boolean	True on success, false otherwise
	 *
	 * @since   3.9.0
	 * @throws  \RuntimeException
	 */
	public function onInit($id = 'dynamic_recaptcha_invisible_1')
	{
		$pubkey = $this->params->get('public_key', '');

		if ($pubkey === '')
		{
			throw new \RuntimeException(JText::_('PLG_RECAPTCHA_INVISIBLE_ERROR_NO_PUBLIC_KEY'));
		}

		// Load callback first for browser compatibility
		\JHtml::_(
			'script',
			'plg_captcha_recaptcha_invisible/recaptcha.min.js',
			array('version' => 'auto', 'relative' => true),
			array('async' => 'async', 'defer' => 'defer')
		);

		// Load Google reCAPTCHA api js
		$file = 'https://www.google.com/recaptcha/api.js'
			. '?onload=JoomlaInitReCaptchaInvisible'
			. '&render=explicit'
			. '&hl=' . \JFactory::getLanguage()->getTag();
		\JHtml::_(
			'script',
			$file,
			array(),
			array('async' => 'async', 'defer' => 'defer')
		);

		return true;
	}

	/**
	 * Gets the challenge HTML
	 *
	 * @param   string  $name   The name of the field. Not Used.
	 * @param   string  $id     The id of the field.
	 * @param   string  $class  The class of the field.
	 *
	 * @return  string  The HTML to be embedded in the form.
	 *
	 * @since  3.9.0
	 */
	public function onDisplay($name = null, $id = 'dynamic_recaptcha_invisible_1', $class = '')
	{
		$dom = new \DOMDocument('1.0', 'UTF-8');
		$ele = $dom->createElement('div');
		$ele->setAttribute('id', $id);
		$ele->setAttribute('class', ((trim($class) == '') ? 'g-recaptcha' : ($class . ' g-recaptcha')));
		$ele->setAttribute('data-sitekey', $this->params->get('public_key', ''));
		$ele->setAttribute('data-badge', $this->params->get('badge', 'bottomright'));
		$ele->setAttribute('data-size', 'invisible');
		$ele->setAttribute('data-tabindex', $this->params->get('tabindex', '0'));
		$ele->setAttribute('data-callback', $this->params->get('callback', ''));
		$ele->setAttribute('data-expired-callback', $this->params->get('expired_callback', ''));
		$ele->setAttribute('data-error-callback', $this->params->get('error_callback', ''));
		$dom->appendChild($ele);

		return $dom->saveHTML($ele);
	}

	/**
	 * Calls an HTTP POST function to verify if the user's guess was correct
	 *
	 * @param   string  $code  Answer provided by user. Not needed for the Recaptcha implementation
	 *
	 * @return  boolean  True if the answer is correct, false otherwise
	 *
	 * @since   3.9.0
	 * @throws  \RuntimeException
	 */
	public function onCheckAnswer($code = null)
	{
		$input      = \JFactory::getApplication()->input;
		$privatekey = $this->params->get('private_key');
		$remoteip   = IpHelper::getIp();

		$response  = $input->get('g-recaptcha-response', '', 'string');

		// Check for Private Key
		if (empty($privatekey))
		{
			throw new \RuntimeException(JText::_('PLG_RECAPTCHA_INVISIBLE_ERROR_NO_PRIVATE_KEY'));
		}

		// Check for IP
		if (empty($remoteip))
		{
			throw new \RuntimeException(JText::_('PLG_RECAPTCHA_INVISIBLE_ERROR_NO_IP'));
		}

		// Discard spam submissions
		if (trim($response) == '')
		{
			throw new \RuntimeException(JText::_('PLG_RECAPTCHA_INVISIBLE_ERROR_EMPTY_SOLUTION'));
		}

		return $this->getResponse($privatekey, $remoteip, $response);
	}

	/**
	 * Method to react on the setup of a captcha field. Gives the possibility
	 * to change the field and/or the XML element for the field.
	 *
	 * @param   \Joomla\CMS\Form\Field\CaptchaField  $field    Captcha field instance
	 * @param   \SimpleXMLElement                    $element  XML form definition
	 *
	 * @return void
	 *
	 * @since 3.9.0
	 */
	public function onSetupField(\Joomla\CMS\Form\Field\CaptchaField $field, \SimpleXMLElement $element)
	{
		// Hide the label for the invisible recaptcha type
		$element['hiddenLabel'] = true;
	}

	/**
	 * Get the reCaptcha response.
	 *
	 * @param   string  $privatekey  The private key for authentication.
	 * @param   string  $remoteip    The remote IP of the visitor.
	 * @param   string  $response    The response received from Google.
	 *
	 * @return  boolean  True if response is good | False if response is bad.
	 *
	 * @since   3.9.0
	 * @throws  \RuntimeException
	 */
	private function getResponse($privatekey, $remoteip, $response)
	{
		$reCaptcha = new \ReCaptcha\ReCaptcha($privatekey, new HttpBridgePostRequestMethod);
		$response = $reCaptcha->verify($response, $remoteip);

		if (!$response->isSuccess())
		{
			foreach ($response->getErrorCodes() as $error)
			{
				throw new \RuntimeException($error);
			}

			return false;
		}

		return true;
	}
}