Completed
Push — master ( af129a...c7fd3e )
by Fabio
12:48
created

TReCaptcha2::raiseCallbackEvent()   B

Complexity

Conditions 6
Paths 10

Size

Total Lines 32
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 1
Metric Value
c 1
b 1
f 1
dl 0
loc 32
rs 8.439
cc 6
eloc 16
nc 10
nop 1
1
<?php
2
3
/**
4
 * TReCaptcha2 class file
5
 *
6
 * @author Cristian Camilo Naranjo Valencia
7
 * @link http://icolectiva.co
8
 * @copyright Copyright &copy; 2005-2016 The PRADO Group
9
 * @license https://github.com/pradosoft/prado/blob/master/COPYRIGHT
10
 * @package System.Web.UI.WebControls
11
 */
12
13
Prado::using('System.Web.UI.ActiveControls.TCallbackEventParameter');
14
Prado::using('System.Web.UI.ActiveControls.TActivePanel');
15
16
/**
17
 * TReCaptcha2 class.
18
 *
19
 * TReCaptcha2 displays a reCAPTCHA (a token displayed as an image) that can be used
20
 * to determine if the input is entered by a real user instead of some program. It can
21
 * also prevent multiple submits of the same form either by accident, or on purpose (ie. spamming).
22
 *
23
 * The reCAPTCHA to solve (a string consisting of two separate words) displayed is automatically
24
 * generated by the reCAPTCHA system at recaptcha.net. However, in order to use the services
25
 * of the site you will need to register and get a public and a private API key pair, and 
26
 * supply those to the reCAPTCHA control through setting the {@link setSecretKey SecretKey} 
27
 * and {@link setSiteKey SiteKey} properties. 
28
 *
29
 * Currently the reCAPTCHA API supports only one reCAPTCHA field per page, so you MUST make sure that all 
30
 * your input is protected and validated by a single reCAPTCHA control. Placing more than one reCAPTCHA
31
 * control on the page will lead to unpredictable results, and the user will most likely unable to solve 
32
 * any of them successfully.
33
 *
34
 * Upon postback, user input can be validated by calling {@link validate()}.
35
 * The {@link TReCaptcha2Validator} control can also be used to do validation, which provides
36
 * server-side validation. Calling (@link validate()) will invalidate the token supplied, so all consecutive
37
 * calls to the method - without solving a new captcha - will return false. Therefore if implementing a multi-stage
38
 * input process, you must make sure that you call validate() only once, either at the end of the input process, or 
39
 * you store the result till the end of the processing.
40
 *
41
 * The following template shows a typical use of TReCaptcha control:
42
 * <code>
43
 * <com:TReCaptcha2 ID="Captcha"
44
 *                 SiteKey="..."
45
 *                 SecretKey="..."
46
 * />
47
 * <com:TReCaptcha2Validator ControlToValidate="Captcha"
48
 *                          ErrorMessage="You are challenged!" />
49
 * </code>
50
 *
51
 * @author Cristian Camilo Naranjo Valencia
52
 * @package System.Web.UI.WebControls
53
 * @since 3.3.1
54
 */
55
56
class TReCaptcha2 extends TActivePanel implements ICallbackEventHandler, IValidatable
57
{
58
    const ChallengeFieldName = 'g-recaptcha-response';
59
    private $_widgetId=0;
0 ignored issues
show
Unused Code introduced by
The property $_widgetId is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
60
    private $_isValid=true;
61
62
    public function __construct()
63
    {
64
        parent::__construct();
65
        $this->setAdapter(new TActiveControlAdapter($this));
66
    }
67
    public function getActiveControl()
68
    {
69
        return $this->getAdapter()->getBaseActiveControl();
70
    }
71
    public function getClientSide()
72
    {
73
        return $this->getAdapter()->getBaseActiveControl()->getClientSide();
74
    }
75
    public function getClientClassName()
76
    {
77
        return 'Prado.WebUI.TReCaptcha2';
78
    }
79
    public function getTagName()
80
    {
81
        return 'div';
82
    }
83
    /**
84
     * Returns true if this control validated successfully. 
85
     * Defaults to true.
86
     * @return bool wether this control validated successfully.
87
     */
88
    public function getIsValid()
89
    {
90
        return $this->_isValid;
91
    }
92
    /**
93
     * @param bool wether this control is valid.
94
     */
95
    public function setIsValid($value)
96
    {
97
        $this->_isValid=TPropertyValue::ensureBoolean($value);
98
    }
99
    public function getValidationPropertyValue()
100
    {
101
        return $this->Request[$this->getResponseFieldName()];
102
    }
103
    public function getResponseFieldName()
104
    {
105
        $captchas = $this->Page->findControlsByType('TReCaptcha2');
106
        $cont = 0;
107
        $responseFieldName = self::ChallengeFieldName;
108
        foreach ($captchas as $captcha)
109
        {
110
            if ($this->getClientID() == $captcha->ClientID)
111
            {
112
                $responseFieldName .= ($cont > 0) ? '-'.$cont : '';
113
            }
114
            $cont++;
115
        }
116
        return $responseFieldName;
117
    }
118
    /**
119
     * Returns your site key. 
120
     * @return string.
0 ignored issues
show
Documentation introduced by
The doc-type string. could not be parsed: Unknown type name "string." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
121
     */
122
    public function getSiteKey()
123
    {
124
        return $this->getViewState('SiteKey');
125
    }
126
    /**
127
     * @param string your site key.
128
     */
129
    public function setSiteKey($value)
130
    {
131
        $this->setViewState('SiteKey', TPropertyValue::ensureString($value));
132
    }
133
    /**
134
     * Returns your secret key. 
135
     * @return string.
0 ignored issues
show
Documentation introduced by
The doc-type string. could not be parsed: Unknown type name "string." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
136
     */
137
    public function getSecretKey()
138
    {
139
        return $this->getViewState('SecretKey');
140
    }
141
    /**
142
     * @param string your secret key.
143
     */
144
    public function setSecretKey($value)
145
    {
146
        $this->setViewState('SecretKey', TPropertyValue::ensureString($value));
147
    }
148
    /**
149
     * Returns your language.
150
     * @return string.
0 ignored issues
show
Documentation introduced by
The doc-type string. could not be parsed: Unknown type name "string." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
151
     */
152
    public function getLanguage()
153
    {
154
        return $this->getViewState('Language', 'en');
155
    }
156
    /**
157
     * @param string your language.
158
     */
159
    public function setLanguage($value)
160
    {
161
        $this->setViewState('Language', TPropertyValue::ensureString($value), 'en');
162
    }
163
    /**
164
     * Returns the color theme of the widget. 
165
     * @return string.
0 ignored issues
show
Documentation introduced by
The doc-type string. could not be parsed: Unknown type name "string." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
166
     */
167
    public function getTheme()
168
    {
169
        return $this->getViewState('Theme', 'light');
170
    }
171
    /**
172
     * The color theme of the widget.
173
     * Default: light
174
     * @param string the color theme of the widget.
175
     */
176
    public function setTheme($value)
177
    {
178
        $this->setViewState('Theme', TPropertyValue::ensureString($value), 'light');
179
    }
180
    /**
181
     * Returns the type of CAPTCHA to serve.
182
     * @return string.
0 ignored issues
show
Documentation introduced by
The doc-type string. could not be parsed: Unknown type name "string." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
183
     */
184
    public function getType()
185
    {
186
        return $this->getViewState('Type', 'image');
187
    }
188
    /**
189
     * The type of CAPTCHA to serve.
190
     * Default: image
191
     * @param string the type of CAPTCHA to serve.
192
     */
193
    public function setType($value)
194
    {
195
        $this->setViewState('Type', TPropertyValue::ensureString($value), 'image');
196
    }
197
    /**
198
     * Returns the size of the widget.
199
     * @return string.
0 ignored issues
show
Documentation introduced by
The doc-type string. could not be parsed: Unknown type name "string." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
200
     */
201
    public function getSize()
202
    {
203
        return $this->getViewState('Size', 'normal');
204
    }
205
    /**
206
     * The size of the widget.
207
     * Default: normal
208
     * @param string the size of the widget.
209
     */
210
    public function setSize($value)
211
    {
212
        $this->setViewState('Size', TPropertyValue::ensureString($value), 'normal');
213
    }
214
    /**
215
     * Returns the tabindex of the widget and challenge.
216
     * If other elements in your page use tabindex, it should be set to make user navigation easier.
217
     * @return string.
0 ignored issues
show
Documentation introduced by
The doc-type string. could not be parsed: Unknown type name "string." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
218
     */
219
    public function getTabIndex()
220
    {
221
        return $this->getViewState('TabIndex', 0);
222
    }
223
    /**
224
     * The tabindex of the widget and challenge.
225
     * If other elements in your page use tabindex, it should be set to make user navigation easier.
226
     * Default: 0
227
     * @param string the tabindex of the widget and challenge.
228
     */
229
    public function setTabIndex($value)
230
    {
231
        $this->setViewState('TabIndex', TPropertyValue::ensureInteger($value), 0);
232
    }
233
    /**
234
     * Resets the reCAPTCHA widget.
235
     * Optional widget ID, defaults to the first widget created if unspecified.
236
     */
237
    public function reset()
238
    {
239
        $this->Page->CallbackClient->callClientFunction('grecaptcha.reset',array(array($this->WidgetId)));
0 ignored issues
show
Bug introduced by
The property WidgetId does not seem to exist. Did you mean _widgetId?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
240
    }
241
    /**
242
     * Gets the response for the reCAPTCHA widget.
243
     */
244
    public function getResponse()
245
    {
246
        return $this->getViewState('Response', '');
247
    }
248
    public function setResponse($value)
249
    {
250
        $this->setViewState('Response', TPropertyValue::ensureString($value), '');
251
    }
252
    public function getWidgetId()
253
    {
254
        return $this->getViewState('WidgetId', 0);
255
    }
256
    public function setWidgetId($value)
257
    {
258
        $this->setViewState('WidgetId', TPropertyValue::ensureInteger($value), 0);
259
    }
260
    protected function getClientOptions()
261
    {
262
        $options['ID'] = $this->getClientID();
0 ignored issues
show
Coding Style Comprehensibility introduced by
$options was never initialized. Although not strictly required by PHP, it is generally a good practice to add $options = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
263
        $options['EventTarget'] = $this->getUniqueID();
264
        $options['FormID'] = $this->Page->getForm()->getClientID();
265
        $options['onCallback'] = $this->hasEventHandler('OnCallback');
266
        $options['onCallbackExpired'] = $this->hasEventHandler('OnCallbackExpired');
267
        $options['options']['sitekey'] = $this->getSiteKey();
268
        if ($theme = $this->getTheme()) $options['options']['theme'] = $theme;
269
        if ($type = $this->getType()) $options['options']['type'] = $type;
270
        if ($size = $this->getSize()) $options['options']['size'] = $size;
271
        if ($tabIndex = $this->getTabIndex()) $options['options']['tabindex'] = $tabIndex;
272
273
        return $options;
274
    }
275
    protected function registerClientScript()
276
    {
277
        $id         = $this->getClientID();
278
        $options    = TJavaScript::encode($this->getClientOptions());
279
        $className  = $this->getClientClassName();
280
        $cs         = $this->Page->ClientScript;
281
        $code       = "new $className($options);";
282
283
        $cs->registerPradoScript('ajax');
284
        $cs->registerEndScript("grecaptcha:$id", $code);
285
    }
286
    public function validate()
287
    {
288
        if ((is_null($this->getValidationPropertyValue())) || (empty($this->getValidationPropertyValue())))
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return !(is_null($this->...ationPropertyValue()));.
Loading history...
289
            return false;
290
291
        return true;
292
    }
293
    /**
294
     * Checks for API keys
295
     * @param mixed event parameter
296
     */
297
    public function onPreRender($param)
298
    {
299
        parent::onPreRender($param);
300
301
        if("" == $this->getSiteKey())
302
            throw new TConfigurationException('recaptcha_publickey_unknown');
303
        if("" == $this->getSecretKey())
304
            throw new TConfigurationException('recaptcha_privatekey_unknown');
305
306
        // need to register captcha fields so they will be sent postback
307
        $this->Page->registerRequiresPostData($this->getResponseFieldName());
308
        $this->Page->ClientScript->registerHeadScriptFile('grecaptcha2', 'https://www.google.com/recaptcha/api.js?onload=TReCaptcha2_onloadCallback&render=explicit&hl=' . $this->getLanguage());
309
    }
310
    protected function addAttributesToRender($writer)
311
    {
312
        $writer->addAttribute('id',$this->getClientID());
313
        parent::addAttributesToRender($writer);
314
    }
315
    public function raiseCallbackEvent($param)
316
    {
317
        $params = $param->getCallbackParameter();
318
        if ($params instanceof stdClass)
319
        {
320
            $callback = property_exists($params, 'onCallback');
321
            $callbackExpired = property_exists($params, 'onCallbackExpired');
322
323
            if ($callback)
324
            {
325
                $this->WidgetId = $params->widgetId;
0 ignored issues
show
Bug introduced by
The property WidgetId does not seem to exist. Did you mean _widgetId?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
326
                $this->Response = $params->response;
327
                $this->Page->CallbackClient->jQuery($params->responseField, 'text',array($params->response));
328
329
                if ($params->onCallback)
330
                {
331
                    $this->onCallback($param);
332
                }
333
            }
334
335
            if ($callbackExpired)
336
            {
337
                $this->Response = '';
338
                $this->reset();
339
340
                if ($params->onCallbackExpired)
341
                {
342
                    $this->onCallbackExpired($param);
343
                }
344
            }
345
        }
346
    }
347
348
    public function onCallback($param)
349
    {
350
        $this->raiseEvent('OnCallback', $this, $param);
351
    }
352
353
    public function onCallbackExpired($param)
354
    {
355
        $this->raiseEvent('OnCallbackExpired', $this, $param);
356
    }
357
358
    public function render($writer)
359
    {
360
        $this->registerClientScript();
361
        parent::render($writer);
362
    }
363
}
364