summaryrefslogtreecommitdiff
path: root/framework/Web/UI
diff options
context:
space:
mode:
authorFabio Bas <ctrlaltca@gmail.com>2016-03-24 11:54:39 +0100
committerFabio Bas <ctrlaltca@gmail.com>2016-03-24 11:54:39 +0100
commitc7fd3e1167b6f2fa7746edbd0fb8f8c1694c61f9 (patch)
treef67f61a6fd5a8ce4893663ab0c3a56d1df7aeae0 /framework/Web/UI
parentaf129adce0014fc7c71e335804fef4148a057290 (diff)
Added TReCaptcha2 and wrote doc; fix #560
Diffstat (limited to 'framework/Web/UI')
-rw-r--r--framework/Web/UI/WebControls/TReCaptcha2.php363
-rw-r--r--framework/Web/UI/WebControls/TReCaptcha2Validator.php110
-rw-r--r--framework/Web/UI/WebControls/TReCaptchaValidator.php2
3 files changed, 474 insertions, 1 deletions
diff --git a/framework/Web/UI/WebControls/TReCaptcha2.php b/framework/Web/UI/WebControls/TReCaptcha2.php
new file mode 100644
index 00000000..ed3f9871
--- /dev/null
+++ b/framework/Web/UI/WebControls/TReCaptcha2.php
@@ -0,0 +1,363 @@
+<?php
+
+/**
+ * TReCaptcha2 class file
+ *
+ * @author Cristian Camilo Naranjo Valencia
+ * @link http://icolectiva.co
+ * @copyright Copyright &copy; 2005-2016 The PRADO Group
+ * @license https://github.com/pradosoft/prado/blob/master/COPYRIGHT
+ * @package System.Web.UI.WebControls
+ */
+
+Prado::using('System.Web.UI.ActiveControls.TCallbackEventParameter');
+Prado::using('System.Web.UI.ActiveControls.TActivePanel');
+
+/**
+ * TReCaptcha2 class.
+ *
+ * TReCaptcha2 displays a reCAPTCHA (a token displayed as an image) that can be used
+ * to determine if the input is entered by a real user instead of some program. It can
+ * also prevent multiple submits of the same form either by accident, or on purpose (ie. spamming).
+ *
+ * The reCAPTCHA to solve (a string consisting of two separate words) displayed is automatically
+ * generated by the reCAPTCHA system at recaptcha.net. However, in order to use the services
+ * of the site you will need to register and get a public and a private API key pair, and
+ * supply those to the reCAPTCHA control through setting the {@link setSecretKey SecretKey}
+ * and {@link setSiteKey SiteKey} properties.
+ *
+ * Currently the reCAPTCHA API supports only one reCAPTCHA field per page, so you MUST make sure that all
+ * your input is protected and validated by a single reCAPTCHA control. Placing more than one reCAPTCHA
+ * control on the page will lead to unpredictable results, and the user will most likely unable to solve
+ * any of them successfully.
+ *
+ * Upon postback, user input can be validated by calling {@link validate()}.
+ * The {@link TReCaptcha2Validator} control can also be used to do validation, which provides
+ * server-side validation. Calling (@link validate()) will invalidate the token supplied, so all consecutive
+ * calls to the method - without solving a new captcha - will return false. Therefore if implementing a multi-stage
+ * input process, you must make sure that you call validate() only once, either at the end of the input process, or
+ * you store the result till the end of the processing.
+ *
+ * The following template shows a typical use of TReCaptcha control:
+ * <code>
+ * <com:TReCaptcha2 ID="Captcha"
+ * SiteKey="..."
+ * SecretKey="..."
+ * />
+ * <com:TReCaptcha2Validator ControlToValidate="Captcha"
+ * ErrorMessage="You are challenged!" />
+ * </code>
+ *
+ * @author Cristian Camilo Naranjo Valencia
+ * @package System.Web.UI.WebControls
+ * @since 3.3.1
+ */
+
+class TReCaptcha2 extends TActivePanel implements ICallbackEventHandler, IValidatable
+{
+ const ChallengeFieldName = 'g-recaptcha-response';
+ private $_widgetId=0;
+ private $_isValid=true;
+
+ public function __construct()
+ {
+ parent::__construct();
+ $this->setAdapter(new TActiveControlAdapter($this));
+ }
+ public function getActiveControl()
+ {
+ return $this->getAdapter()->getBaseActiveControl();
+ }
+ public function getClientSide()
+ {
+ return $this->getAdapter()->getBaseActiveControl()->getClientSide();
+ }
+ public function getClientClassName()
+ {
+ return 'Prado.WebUI.TReCaptcha2';
+ }
+ public function getTagName()
+ {
+ return 'div';
+ }
+ /**
+ * Returns true if this control validated successfully.
+ * Defaults to true.
+ * @return bool wether this control validated successfully.
+ */
+ public function getIsValid()
+ {
+ return $this->_isValid;
+ }
+ /**
+ * @param bool wether this control is valid.
+ */
+ public function setIsValid($value)
+ {
+ $this->_isValid=TPropertyValue::ensureBoolean($value);
+ }
+ public function getValidationPropertyValue()
+ {
+ return $this->Request[$this->getResponseFieldName()];
+ }
+ public function getResponseFieldName()
+ {
+ $captchas = $this->Page->findControlsByType('TReCaptcha2');
+ $cont = 0;
+ $responseFieldName = self::ChallengeFieldName;
+ foreach ($captchas as $captcha)
+ {
+ if ($this->getClientID() == $captcha->ClientID)
+ {
+ $responseFieldName .= ($cont > 0) ? '-'.$cont : '';
+ }
+ $cont++;
+ }
+ return $responseFieldName;
+ }
+ /**
+ * Returns your site key.
+ * @return string.
+ */
+ public function getSiteKey()
+ {
+ return $this->getViewState('SiteKey');
+ }
+ /**
+ * @param string your site key.
+ */
+ public function setSiteKey($value)
+ {
+ $this->setViewState('SiteKey', TPropertyValue::ensureString($value));
+ }
+ /**
+ * Returns your secret key.
+ * @return string.
+ */
+ public function getSecretKey()
+ {
+ return $this->getViewState('SecretKey');
+ }
+ /**
+ * @param string your secret key.
+ */
+ public function setSecretKey($value)
+ {
+ $this->setViewState('SecretKey', TPropertyValue::ensureString($value));
+ }
+ /**
+ * Returns your language.
+ * @return string.
+ */
+ public function getLanguage()
+ {
+ return $this->getViewState('Language', 'en');
+ }
+ /**
+ * @param string your language.
+ */
+ public function setLanguage($value)
+ {
+ $this->setViewState('Language', TPropertyValue::ensureString($value), 'en');
+ }
+ /**
+ * Returns the color theme of the widget.
+ * @return string.
+ */
+ public function getTheme()
+ {
+ return $this->getViewState('Theme', 'light');
+ }
+ /**
+ * The color theme of the widget.
+ * Default: light
+ * @param string the color theme of the widget.
+ */
+ public function setTheme($value)
+ {
+ $this->setViewState('Theme', TPropertyValue::ensureString($value), 'light');
+ }
+ /**
+ * Returns the type of CAPTCHA to serve.
+ * @return string.
+ */
+ public function getType()
+ {
+ return $this->getViewState('Type', 'image');
+ }
+ /**
+ * The type of CAPTCHA to serve.
+ * Default: image
+ * @param string the type of CAPTCHA to serve.
+ */
+ public function setType($value)
+ {
+ $this->setViewState('Type', TPropertyValue::ensureString($value), 'image');
+ }
+ /**
+ * Returns the size of the widget.
+ * @return string.
+ */
+ public function getSize()
+ {
+ return $this->getViewState('Size', 'normal');
+ }
+ /**
+ * The size of the widget.
+ * Default: normal
+ * @param string the size of the widget.
+ */
+ public function setSize($value)
+ {
+ $this->setViewState('Size', TPropertyValue::ensureString($value), 'normal');
+ }
+ /**
+ * Returns the tabindex of the widget and challenge.
+ * If other elements in your page use tabindex, it should be set to make user navigation easier.
+ * @return string.
+ */
+ public function getTabIndex()
+ {
+ return $this->getViewState('TabIndex', 0);
+ }
+ /**
+ * The tabindex of the widget and challenge.
+ * If other elements in your page use tabindex, it should be set to make user navigation easier.
+ * Default: 0
+ * @param string the tabindex of the widget and challenge.
+ */
+ public function setTabIndex($value)
+ {
+ $this->setViewState('TabIndex', TPropertyValue::ensureInteger($value), 0);
+ }
+ /**
+ * Resets the reCAPTCHA widget.
+ * Optional widget ID, defaults to the first widget created if unspecified.
+ */
+ public function reset()
+ {
+ $this->Page->CallbackClient->callClientFunction('grecaptcha.reset',array(array($this->WidgetId)));
+ }
+ /**
+ * Gets the response for the reCAPTCHA widget.
+ */
+ public function getResponse()
+ {
+ return $this->getViewState('Response', '');
+ }
+ public function setResponse($value)
+ {
+ $this->setViewState('Response', TPropertyValue::ensureString($value), '');
+ }
+ public function getWidgetId()
+ {
+ return $this->getViewState('WidgetId', 0);
+ }
+ public function setWidgetId($value)
+ {
+ $this->setViewState('WidgetId', TPropertyValue::ensureInteger($value), 0);
+ }
+ protected function getClientOptions()
+ {
+ $options['ID'] = $this->getClientID();
+ $options['EventTarget'] = $this->getUniqueID();
+ $options['FormID'] = $this->Page->getForm()->getClientID();
+ $options['onCallback'] = $this->hasEventHandler('OnCallback');
+ $options['onCallbackExpired'] = $this->hasEventHandler('OnCallbackExpired');
+ $options['options']['sitekey'] = $this->getSiteKey();
+ if ($theme = $this->getTheme()) $options['options']['theme'] = $theme;
+ if ($type = $this->getType()) $options['options']['type'] = $type;
+ if ($size = $this->getSize()) $options['options']['size'] = $size;
+ if ($tabIndex = $this->getTabIndex()) $options['options']['tabindex'] = $tabIndex;
+
+ return $options;
+ }
+ protected function registerClientScript()
+ {
+ $id = $this->getClientID();
+ $options = TJavaScript::encode($this->getClientOptions());
+ $className = $this->getClientClassName();
+ $cs = $this->Page->ClientScript;
+ $code = "new $className($options);";
+
+ $cs->registerPradoScript('ajax');
+ $cs->registerEndScript("grecaptcha:$id", $code);
+ }
+ public function validate()
+ {
+ if ((is_null($this->getValidationPropertyValue())) || (empty($this->getValidationPropertyValue())))
+ return false;
+
+ return true;
+ }
+ /**
+ * Checks for API keys
+ * @param mixed event parameter
+ */
+ public function onPreRender($param)
+ {
+ parent::onPreRender($param);
+
+ if("" == $this->getSiteKey())
+ throw new TConfigurationException('recaptcha_publickey_unknown');
+ if("" == $this->getSecretKey())
+ throw new TConfigurationException('recaptcha_privatekey_unknown');
+
+ // need to register captcha fields so they will be sent postback
+ $this->Page->registerRequiresPostData($this->getResponseFieldName());
+ $this->Page->ClientScript->registerHeadScriptFile('grecaptcha2', 'https://www.google.com/recaptcha/api.js?onload=TReCaptcha2_onloadCallback&render=explicit&hl=' . $this->getLanguage());
+ }
+ protected function addAttributesToRender($writer)
+ {
+ $writer->addAttribute('id',$this->getClientID());
+ parent::addAttributesToRender($writer);
+ }
+ public function raiseCallbackEvent($param)
+ {
+ $params = $param->getCallbackParameter();
+ if ($params instanceof stdClass)
+ {
+ $callback = property_exists($params, 'onCallback');
+ $callbackExpired = property_exists($params, 'onCallbackExpired');
+
+ if ($callback)
+ {
+ $this->WidgetId = $params->widgetId;
+ $this->Response = $params->response;
+ $this->Page->CallbackClient->jQuery($params->responseField, 'text',array($params->response));
+
+ if ($params->onCallback)
+ {
+ $this->onCallback($param);
+ }
+ }
+
+ if ($callbackExpired)
+ {
+ $this->Response = '';
+ $this->reset();
+
+ if ($params->onCallbackExpired)
+ {
+ $this->onCallbackExpired($param);
+ }
+ }
+ }
+ }
+
+ public function onCallback($param)
+ {
+ $this->raiseEvent('OnCallback', $this, $param);
+ }
+
+ public function onCallbackExpired($param)
+ {
+ $this->raiseEvent('OnCallbackExpired', $this, $param);
+ }
+
+ public function render($writer)
+ {
+ $this->registerClientScript();
+ parent::render($writer);
+ }
+}
diff --git a/framework/Web/UI/WebControls/TReCaptcha2Validator.php b/framework/Web/UI/WebControls/TReCaptcha2Validator.php
new file mode 100644
index 00000000..2cd4b6d1
--- /dev/null
+++ b/framework/Web/UI/WebControls/TReCaptcha2Validator.php
@@ -0,0 +1,110 @@
+<?php
+
+/**
+ * TReCaptcha2Validator class file
+ *
+ * @author Cristian Camilo Naranjo Valencia
+ * @link http://icolectiva.co
+ * @copyright Copyright &copy; 2005-2016 The PRADO Group
+ * @license https://github.com/pradosoft/prado/blob/master/COPYRIGHT
+ * @package System.Web.UI.WebControls
+ */
+
+Prado::using('System.Web.UI.WebControls.TBaseValidator');
+Prado::using('System.Web.UI.WebControls.TReCaptcha2');
+
+/**
+ * TReCaptcha2Validator class
+ *
+ * TReCaptcha2Validator validates a reCAPTCHA represented by a {@link TReCaptcha} control.
+ * The input control fails validation if th user did not pass the humanity test.
+ *
+ * To use TReCaptcha2Validator, specify the {@link setCaptchaControl CaptchaControl}
+ * to be the ID path of the {@link TReCaptcha} control.
+ *
+ * @author Cristian Camilo Naranjo Valencia
+ * @package System.Web.UI.WebControls
+ * @since 3.3.1
+ */
+
+class TReCaptcha2Validator extends TBaseValidator
+{
+ protected $_isvalid = null;
+
+ protected function getClientClassName()
+ {
+ return 'Prado.WebUI.TReCaptcha2Validator';
+ }
+ public function getEnableClientScript()
+ {
+ return true;
+ }
+ protected function getCaptchaControl()
+ {
+ $control = $this->getValidationTarget();
+ if (!$control)
+ throw new Exception('No target control specified for TReCaptcha2Validator');
+ if (!($control instanceof TReCaptcha2))
+ throw new Exception('TReCaptcha2Validator only works with TReCaptcha2 controls');
+ return $control;
+ }
+ public function getClientScriptOptions()
+ {
+ $options = parent::getClientScriptOptions();
+ $options['ResponseFieldName'] = $this->getCaptchaControl()->getResponseFieldName();
+ return $options;
+ }
+ /**
+ * This method overrides the parent's implementation.
+ * The validation succeeds if the input control has the same value
+ * as the one displayed in the corresponding RECAPTCHA control.
+ *
+ * @return boolean whether the validation succeeds
+ */
+ protected function evaluateIsValid()
+ {
+ // check validity only once (if trying to evaulate multiple times, all redundant checks would fail)
+ if (is_null($this->_isvalid))
+ {
+ $control = $this->getCaptchaControl();
+ $this->_isvalid = $control->validate();
+ }
+ return ($this->_isvalid==true);
+ }
+ public function onPreRender($param)
+ {
+ parent::onPreRender($param);
+
+ $cs = $this->Page->getClientScript();
+ $cs->registerPradoScript('validator');
+
+ // communicate validation status to the client side
+ $value = $this->_isvalid===false ? '0' : '1';
+ $cs->registerHiddenField($this->getClientID().'_1',$value);
+
+ // update validator display
+ if ($control = $this->getValidationTarget())
+ {
+ $fn = 'captchaUpdateValidatorStatus_'.$this->getClientID();
+
+ $cs->registerEndScript($this->getClientID().'::validate', implode(' ',array(
+ // this function will be used to update the validator
+ 'function '.$fn.'(valid)',
+ '{',
+ ' jQuery('.TJavaScript::quoteString('#'.$this->getClientID().'_1').').val(valid);',
+ ' Prado.Validation.validateControl('.TJavaScript::quoteString($control->ClientID).'); ',
+ '}',
+ '',
+ // update the validator to the result if we're in a callback
+ // (if we're in initial rendering or a postback then the result will be rendered directly to the page html anyway)
+ $this->Page->IsCallback ? $fn.'('.$value.');' : '',
+ '',
+ // install event handler that clears the validation error when user changes the captcha response field
+ 'jQuery("#'.$control->getClientID().'").on("change", '.TJavaScript::quoteString('#'.$control->getResponseFieldName()).', function() { ',
+ $fn.'("1");',
+ '});',
+ )));
+ }
+ }
+}
+
diff --git a/framework/Web/UI/WebControls/TReCaptchaValidator.php b/framework/Web/UI/WebControls/TReCaptchaValidator.php
index de3b42a5..9078354b 100644
--- a/framework/Web/UI/WebControls/TReCaptchaValidator.php
+++ b/framework/Web/UI/WebControls/TReCaptchaValidator.php
@@ -21,7 +21,7 @@ Prado::using('System.Web.UI.WebControls.TReCaptcha');
* is not the same as the token displayed in reCAPTCHA. Note, if the user does
* not enter any thing, it is still considered as failing the validation.
*
- * To use TReCaptchaValidator, specify the {@link setControlToValidate ControlToValidate}
+ * To use TReCaptchaValidator, specify the {@link setCaptchaControl CaptchaControl}
* to be the ID path of the {@link TReCaptcha} control.
*
* @author Bérczi Gábor <gabor.berczi@devworx.hu>