├── lang
├── _manifest_exclude
├── en.yml
└── fi.yml
├── _config.php
├── templates
└── SilverStripe
│ └── Recaptcha
│ ├── Recaptcha.ss
│ └── Recaptcha_NoScript.ss
├── code-of-conduct.md
├── _config
└── config.yml
├── phpunit.xml.dist
├── .travis.yml
├── composer.json
├── code
├── RecaptchaProtector.php
├── RecaptchaFieldHttpClient.php
└── RecaptchaField.php
├── tests
├── RecaptchaFieldFunctionalTestController.php
├── RecaptchaFieldTestHttpClient.php
├── RecaptchaFieldTest.php
└── RecaptchaFieldFunctionalTest.php
├── LICENSE
├── .scrutinizer.yml
└── README.md
/lang/_manifest_exclude:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/_config.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/code-of-conduct.md:
--------------------------------------------------------------------------------
1 | When having discussions about this module in issues or pull request please adhere to the [SilverStripe Community Code of Conduct](https://docs.silverstripe.org/en/contributing/code_of_conduct).
2 |
--------------------------------------------------------------------------------
/lang/en.yml:
--------------------------------------------------------------------------------
1 | en:
2 | SilverStripe\Recaptcha\RecaptchaField:
3 | EMPTY: Please answer the captcha question
4 | VALIDSOLUTION: Your answer didn't match the captcha words, please try again.
5 | NORESPONSE: The recaptcha service gave no response. Please try again later.
--------------------------------------------------------------------------------
/lang/fi.yml:
--------------------------------------------------------------------------------
1 | fi:
2 | SilverStripe\Recaptcha\RecaptchaField:
3 | EMPTY: Captcha-kenttä tulee täyttää
4 | VALIDSOLUTION: Vastauksesi ei ollut sama kuin captcha-kentässä olleet sanat. Yritä uudelleen.
5 | NORESPONSE: Tekninen virhe: Recaptcha-palvelu ei vastaa. Yritä myöhemmin uudelleen.
6 |
--------------------------------------------------------------------------------
/_config/config.yml:
--------------------------------------------------------------------------------
1 | RecaptchaField:
2 | recaptcha_noscript_url: 'https://www.google.com/recaptcha/api/fallback?k=%s'
3 | recaptcha_js_url: 'https://www.google.com/recaptcha/api.js'
4 | api_verify_server: 'https://www.google.com/recaptcha/api/siteverify'
5 | httpclient_class: 'RecaptchaField_HTTPClient'
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 | tests
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 |
3 | language: php
4 |
5 | env:
6 | global:
7 | - COMPOSER_ROOT_VERSION="4.0.x-dev"
8 |
9 | matrix:
10 | include:
11 | - php: 7.2
12 | env: DB=PGSQL
13 | - php: 7.3
14 | env: DB=MYSQL
15 |
16 | before_script:
17 | - phpenv rehash
18 | - composer validate
19 | - if [[ $DB == PGSQL ]]; then composer require --prefer-dist --no-update silverstripe/postgresql:2.0.x-dev; fi
20 | - composer install --prefer-dist --no-interaction --no-progress --no-suggest --optimize-autoloader --verbose --profile
21 |
22 | script:
23 | - vendor/bin/phpunit tests/
24 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "silverstripe/recaptcha",
3 | "description": "Provides a form field which allows form to validate for non-bot submissions by giving them a challenge to decrypt an image.",
4 | "type": "silverstripe-vendormodule",
5 | "keywords": [
6 | "silverstripe",
7 | "spamprotection"
8 | ],
9 | "authors": [
10 | {
11 | "name": "Ingo Schommer",
12 | "email": "ingo@silverstripe.com"
13 | }
14 | ],
15 | "require": {
16 | "silverstripe/spamprotection": "~3.0",
17 | "silverstripe/framework": "^4.0"
18 | },
19 | "require-dev": {
20 | "phpunit/phpunit": "^5.7"
21 | },
22 | "autoload": {
23 | "psr-4": {
24 | "SilverLeague\\Recaptcha\\": "code/",
25 | "SilverLeague\\Recaptcha\\Tests\\": "tests/"
26 | }
27 | },
28 | "extra": {
29 | "branch-alias": {
30 | "dev-master": "3.0.x-dev"
31 | }
32 | },
33 | "minimum-stability": "dev"
34 | }
35 |
--------------------------------------------------------------------------------
/code/RecaptchaProtector.php:
--------------------------------------------------------------------------------
1 | useSSL = Director::is_https();
29 |
30 | return $field;
31 | }
32 |
33 | /**
34 | * Not used by Recaptcha
35 | */
36 | public function setFieldMapping($fieldMapping)
37 | {
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/templates/SilverStripe/Recaptcha/Recaptcha_NoScript.ss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/RecaptchaFieldFunctionalTestController.php:
--------------------------------------------------------------------------------
1 | setHTTPClient(new RecaptchaFieldTestHttpClient());
37 |
38 | return $form;
39 | }
40 |
41 | public function doSubmit($data, $form)
42 | {
43 | return 'submitted';
44 | }
45 | }
--------------------------------------------------------------------------------
/code/RecaptchaFieldHttpClient.php:
--------------------------------------------------------------------------------
1 | proxy_server)) {
26 | curl_setopt($ch, CURLOPT_PROXY, $server);
27 | if (!empty($auth = RecaptchaField::config()->proxy_auth)) {
28 | curl_setopt($ch, CURLOPT_PROXYUSERPWD, $auth);
29 | }
30 | }
31 |
32 | curl_setopt($ch, CURLOPT_TIMEOUT, 10);
33 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
34 | curl_setopt($ch, CURLOPT_USERAGENT, 'reCAPTCHA/PHP');
35 | // we need application/x-www-form-urlencoded
36 | curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postVars));
37 | $response = curl_exec($ch);
38 |
39 | $responseObj = new HTTPResponse($response, 200);
40 | $responseObj->addHeader('Content-Type', 'application/json');
41 | return $responseObj;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | * Copyright (c) 2008, Silverstripe Ltd.
2 | * All rights reserved.
3 | *
4 | * Redistribution and use in source and binary forms, with or without
5 | * modification, are permitted provided that the following conditions are met:
6 | * * Redistributions of source code must retain the above copyright
7 | * notice, this list of conditions and the following disclaimer.
8 | * * Redistributions in binary form must reproduce the above copyright
9 | * notice, this list of conditions and the following disclaimer in the
10 | * documentation and/or other materials provided with the distribution.
11 | * * Neither the name of the nor the
12 | * names of its contributors may be used to endorse or promote products
13 | * derived from this software without specific prior written permission.
14 | *
15 | * THIS SOFTWARE IS PROVIDED BY Silverstripe Ltd. ``AS IS'' AND ANY
16 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 | * DISCLAIMED. IN NO EVENT SHALL Silverstripe Ltd. BE LIABLE FOR ANY
19 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/tests/RecaptchaFieldTestHttpClient.php:
--------------------------------------------------------------------------------
1 | true,
23 | 'challenge_ts' => date('c'),
24 | // timestamp of the challenge load (ISO format yyyy-MM-dd'T'HH:mm:ssZZ)
25 | 'hostname' => $_SERVER['HTTP_HOST'],
26 | // the hostname of the site where the reCAPTCHA was solved
27 | ];
28 | $response->addHeader('Content-Type', 'application/json');
29 | $response->setBody(Convert::array2json($data));
30 | return $response;
31 | }
32 |
33 | if ($postVars['response'] == 'invalid') {
34 | $response = new HTTPResponse();
35 | $data = [
36 | 'success' => false,
37 | 'challenge_ts' => date('c'),
38 | 'hostname' => $_SERVER['HTTP_HOST'],
39 | 'error-codes' => [
40 | 'invalid-input-response'
41 | ]
42 | ];
43 | $response->addHeader('Content-Type', 'application/json');
44 | $response->setBody(Convert::array2json($data));
45 | return $response;
46 | }
47 |
48 | return new HTTPResponse();
49 | }
50 | }
--------------------------------------------------------------------------------
/tests/RecaptchaFieldTest.php:
--------------------------------------------------------------------------------
1 | setHTTPClient(new RecaptchaFieldTestHttpClient());
25 | $f->setForm($form);
26 | $v = new RequiredFields();
27 |
28 | $origRequest = Controller::curr()->getRequest();
29 | $origSession = $origRequest->getSession();
30 |
31 | $request = new HTTPRequest('POST', '/', [], [
32 | 'g-recaptcha-response' => 'valid',
33 | ]);
34 | $request->setSession($origSession);
35 | Controller::curr()->setRequest($request);
36 | $this->assertTrue($f->validate($v));
37 |
38 | Controller::curr()->setRequest($origRequest);
39 | }
40 |
41 | public function testValidateWithInvalidResponse()
42 | {
43 | $form = Form::create(Controller::create(), 'Form', new FieldList(), new FieldList());
44 | $f = new RecaptchaField('MyField');
45 | $f->setHTTPClient(new RecaptchaFieldTestHttpClient());
46 | $f->setForm($form);
47 | $v = new RequiredFields();
48 |
49 | $origRequest = Controller::curr()->getRequest();
50 | $origSession = $origRequest->getSession();
51 |
52 | $request = new HTTPRequest('POST', '/', [], [
53 | 'g-recaptcha-response' => 'invalid',
54 | ]);
55 | $request->setSession($origSession);
56 | Controller::curr()->setRequest($request);
57 | $this->assertFalse($f->validate($v));
58 |
59 | Controller::curr()->setRequest($origRequest);
60 | }
61 | }
--------------------------------------------------------------------------------
/tests/RecaptchaFieldFunctionalTest.php:
--------------------------------------------------------------------------------
1 | orig['public_api_key'] = Config::inst()->get(RecaptchaField::class, 'public_api_key');
32 | $this->orig['private_api_key'] = Config::inst()->get(RecaptchaField::class, 'private_api_key');
33 | Config::modify()->set(RecaptchaField::class, 'public_api_key', 'test');
34 | Config::modify()->set(RecaptchaField::class, 'private_api_key', 'test');
35 | Config::modify()->set(RecaptchaField::class, 'noscript_enabled', true);
36 | }
37 |
38 | public function tearDown()
39 | {
40 | Config::modify()->set(RecaptchaField::class, 'public_api_key', $this->orig['public_api_key']);
41 | Config::modify()->set(RecaptchaField::class, 'private_api_key', $this->orig['private_api_key']);
42 |
43 | parent::tearDown();
44 | }
45 |
46 | public function testValidSubmission()
47 | {
48 | $this->get('RecaptchaFieldFunctionalTest_Controller');
49 | $data = [
50 | 'g-recaptcha-response' => 'valid',
51 | ];
52 | $response = $this->submitForm('Form_Form', null, $data);
53 | $this->assertEquals('submitted', $response->getBody());
54 | }
55 |
56 | public function testInvalidSubmission()
57 | {
58 | $this->get('RecaptchaFieldFunctionalTest_Controller');
59 | $data = [
60 | 'g-recaptcha-response' => 'invalid',
61 | ];
62 | $response = $this->submitForm('Form_Form', null, $data);
63 | $body = $response->getBody();
64 | $this->assertContains('Your answer didn\'t match the captcha words, please try again.', $body);
65 | }
66 |
67 | }
68 |
69 |
--------------------------------------------------------------------------------
/.scrutinizer.yml:
--------------------------------------------------------------------------------
1 | inherit: true
2 |
3 | checks:
4 | php:
5 | verify_property_names: true
6 | verify_argument_usable_as_reference: true
7 | verify_access_scope_valid: true
8 | useless_calls: true
9 | use_statement_alias_conflict: true
10 | variable_existence: true
11 | unused_variables: true
12 | unused_properties: true
13 | unused_parameters: true
14 | unused_methods: true
15 | unreachable_code: true
16 | too_many_arguments: true
17 | sql_injection_vulnerabilities: true
18 | simplify_boolean_return: true
19 | side_effects_or_types: true
20 | security_vulnerabilities: true
21 | return_doc_comments: true
22 | return_doc_comment_if_not_inferrable: true
23 | require_scope_for_properties: true
24 | require_scope_for_methods: true
25 | require_php_tag_first: true
26 | psr2_switch_declaration: true
27 | psr2_class_declaration: true
28 | property_assignments: true
29 | prefer_while_loop_over_for_loop: true
30 | precedence_mistakes: true
31 | precedence_in_conditions: true
32 | phpunit_assertions: true
33 | php5_style_constructor: true
34 | parse_doc_comments: true
35 | parameter_non_unique: true
36 | parameter_doc_comments: true
37 | param_doc_comment_if_not_inferrable: true
38 | optional_parameters_at_the_end: true
39 | one_class_per_file: true
40 | no_unnecessary_if: true
41 | no_trailing_whitespace: true
42 | no_property_on_interface: true
43 | no_non_implemented_abstract_methods: true
44 | no_error_suppression: true
45 | no_duplicate_arguments: true
46 | no_commented_out_code: true
47 | newline_at_end_of_file: true
48 | missing_arguments: true
49 | method_calls_on_non_object: true
50 | instanceof_class_exists: true
51 | foreach_traversable: true
52 | fix_line_ending: true
53 | fix_doc_comments: true
54 | duplication: true
55 | deprecated_code_usage: true
56 | deadlock_detection_in_loops: true
57 | code_rating: true
58 | closure_use_not_conflicting: true
59 | catch_class_exists: true
60 | blank_line_after_namespace_declaration: false
61 | avoid_multiple_statements_on_same_line: true
62 | avoid_duplicate_types: true
63 | avoid_conflicting_incrementers: true
64 | avoid_closing_tag: true
65 | assignment_of_null_return: true
66 | argument_type_checks: true
67 |
68 | filter:
69 | paths: [code/*, tests/*]
70 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # reCAPTCHA FormField Module
2 |
3 | [](http://travis-ci.org/chillu/silverstripe-recaptcha)
4 |
5 | *This module is no longer supported. Please use [UndefinedOffset/silverstripe-nocaptcha](https://github.com/UndefinedOffset/silverstripe-nocaptcha) instead*
6 |
7 | ## Introduction
8 |
9 | Provides a FormField which allows form to validate for non-bot submissions
10 | using Google's [reCAPTCHA v2](https://developers.google.com/recaptcha/docs/display) service.
11 |
12 | In order to use Google's new [Invisible reCAPTCHA](https://developers.google.com/recaptcha/docs/invisible) service,
13 | please use the [undefinedoffset/silverstripe-nocaptcha](https://github.com/UndefinedOffset/silverstripe-nocaptcha) module. This module is not intended to be used with [reCAPTCHA v3](https://developers.google.com/recaptcha/docs/v3) (see [rationale](https://github.com/chillu/silverstripe-recaptcha/issues/55))
14 |
15 | ## Requirements
16 |
17 | * SilverStripe Framework 4.0 or newer
18 | * curl PHP module
19 | * Requires [spamprotection](http://silverstripe.org/spam-protection-module/) module
20 |
21 | ## Installation
22 |
23 | * Copy the `recaptcha` directory into your main SilverStripe webroot
24 | * Run ?flush=1
25 |
26 | This should go in your `mysite/_config/recaptcha.yml`. You can get an free API key at [https://www.google.com/recaptcha](https://www.google.com/recaptcha/admin/create)
27 |
28 | ```
29 | SilverStripe\Recaptcha\RecaptchaField:
30 | public_api_key: "your-site-key"
31 | private_api_key: "your-secret-key"
32 | ```
33 |
34 | If using on a site requiring a proxy server for outgoing traffic then you can set these additional
35 | options in your `mysite/_config/recaptcha.yml` by adding.
36 | ```
37 | proxy_server: "proxy_address"
38 | proxy_auth: "username:password"
39 | ```
40 |
41 | To use the noscript fallback method, add the key `noscript_enabled: true` to your yml.
42 |
43 | To change the language, add it to an array of options to your yml
44 | ```
45 | options:
46 | hl: NL
47 | theme: dark
48 | type: audio
49 | size: compact
50 | ```
51 |
52 | See https://developers.google.com/recaptcha/docs/display#render_param for all available parameters
53 |
54 | ## Usage
55 |
56 | ### As a Standalone Field
57 |
58 | If you want to use reCAPTCHA field by itself, you can simply just include it as a field in your form.
59 |
60 | $recaptchaField = new RecaptchaField('MyCaptcha');
61 | $recaptchaField->options = array('theme' => 'light'); // optional
62 |
63 | See [reCAPTCHA docs](https://developers.google.com/recaptcha/docs/display#render_param) for more configuration options.
64 |
65 | ### Integration with spamprotection module
66 |
67 | This requires the [spamprotection](https://github.com/silverstripe/silverstripe-spamprotection) module to be installed, see its documentation for details. You can use this field to protect any built informs on your website, including user comments in the [[:modules:blog]] module.
68 |
69 | Configuration example in `mysite/_config/spamprotection.yml`
70 |
71 | ---
72 | name: spamprotection
73 | ---
74 | SilverStripe\SpamProtection\Extension\FormSpamProtectionExtension:
75 | default_spam_protector: SilverStripe\Recaptcha\RecaptchaProtector
76 |
77 |
78 | Then once you have setup this config you will need to include the spam protector field as per the instructions on the [spamprotection](https://github.com/silverstripe/silverstripe-spamprotection) page.
79 |
80 | ## Known issues:
81 |
82 | ### Problems with page doctype XHTML
83 |
84 | reCAPTCHA current does not work if the page doctype is XHTML. The API returns
85 | Javascript which uses "document.write", which is not supported in XHTML.
86 | A work-around is to always use the no-script version of the module (modify the
87 | relevant lines in RecaptchaField.php), or to switch your webpage's doctype to
88 | HTML 4. See: http://www.w3schools.com/tags/tag_DOCTYPE.asp
89 |
--------------------------------------------------------------------------------
/code/RecaptchaField.php:
--------------------------------------------------------------------------------
1 |
31 | * "array('theme' => 'white')
32 | *
33 | *
34 | * @see https://developers.google.com/recaptcha/docs/display
35 | * @var array
36 | */
37 | public $options = array();
38 |
39 | /**
40 | * @var RecaptchaFieldHttpClient
41 | */
42 | public $client;
43 |
44 | /**
45 | * Your public API key for a specific domain (get one at https://www.google.com/recaptcha/admin)
46 | *
47 | * @var string
48 | */
49 | private static $public_api_key = '';
50 |
51 | /**
52 | * Your private API key for a specific domain (get one at https://www.google.com/recaptcha/admin)
53 | *
54 | * @var string
55 | */
56 | private static $private_api_key = '';
57 |
58 | /**
59 | * Your proxy server details including the port
60 | *
61 | * @var string
62 | */
63 | private static $proxy_server = '';
64 |
65 | /**
66 | * Your proxy server authentication
67 | *
68 | * @var string
69 | */
70 | private static $proxy_auth = '';
71 |
72 | /**
73 | * Verify API server address (relative)
74 | *
75 | * @var string
76 | */
77 | private static $api_verify_server = 'https://www.google.com/recaptcha/api/siteverify';
78 |
79 | /**
80 | * Javascript-address which includes necessary logic from the recaptcha-server.
81 | * Your public key is automatically inserted.
82 | *
83 | * @var string
84 | */
85 | private static $recaptcha_js_url = 'https://www.google.com/recaptcha/api.js';
86 |
87 | /**
88 | * @var string
89 | */
90 | private static $recaptcha_noscript_url = 'https://www.google.com/recaptcha/api/fallback?k=%s';
91 |
92 | /**
93 | * Default the noscript option to false
94 | * @var bool
95 | */
96 | private static $noscript_enabled = false;
97 |
98 | /**
99 | * @var string
100 | */
101 | private static $httpclient_class = RecaptchaFieldHttpClient::class;
102 |
103 | public function __construct($name, $title = null, $value = null)
104 | {
105 | parent::__construct($name, $title, $value);
106 |
107 | // do not need a fallback title if none was defined.
108 | if (empty($title)) {
109 | $this->title = '';
110 | }
111 | }
112 |
113 | public function Field($properties = array())
114 | {
115 | $request = Controller::curr()->getRequest();
116 |
117 | $privateKey = self::config()->get('private_api_key');
118 | $publicKey = self::config()->get('public_api_key');
119 | if (empty($publicKey) || empty($privateKey)) {
120 | user_error('SilverStripe\Recaptcha\RecaptchaField::FieldHolder() Please specify valid Recaptcha Keys', E_USER_ERROR);
121 | }
122 |
123 | $previousError = $request->getSession()->get("FormField.{$this->form->FormName()}.{$this->getName()}.error");
124 | $request->getSession()->clear("FormField.{$this->form->FormName()}.{$this->getName()}.error");
125 |
126 | $recaptchaJsUrl = self::config()->get('recaptcha_js_url');
127 | // js (main logic)
128 | $jsURL = sprintf($recaptchaJsUrl, $publicKey);
129 | if (!empty($previousError)) {
130 | $jsURL .= "&error={$previousError}";
131 | }
132 |
133 | // turn options array into data attributes
134 | $optionString = '';
135 | $config = self::config()->get('options') ?: array();
136 | foreach ($config as $option => $value) {
137 | $optionString .= ' data-' . htmlentities($option) . '="' . htmlentities($value) . '"';
138 | }
139 |
140 | Requirements::javascript($jsURL);
141 | $fieldData = ArrayData::create(
142 | array(
143 | 'public_api_key' => self::config()->get('public_api_key'),
144 | 'name' => $this->getName(),
145 | 'options' => $optionString
146 | )
147 | );
148 | $html = $fieldData->renderWith('SilverStripe\Recaptcha\Recaptcha');
149 | if (self::config()->get('noscript_enabled')) {
150 | // noscript fallback
151 | $noscriptData = ArrayData::create(
152 | array(
153 | 'public_api_key' => self::config()->get('public_api_key')
154 | )
155 | );
156 | $resultHTML = $noscriptData->renderWith('SilverStripe\Recaptcha\Recaptcha_NoScript');
157 | $html .= $resultHTML;
158 | }
159 | return $html;
160 | }
161 |
162 | /**
163 | * Validate by submitting to external service
164 | *
165 | * @todo implement socket timeout handling (or switch to curl?)
166 | * @param Validator $validator
167 | * @return boolean
168 | */
169 | public function validate($validator)
170 | {
171 | /** @var HTTPRequest $request */
172 | $request = Controller::curr()->getRequest();
173 | $data = $request->postVars();
174 |
175 | // don't bother querying the recaptcha-service if fields were empty
176 | if (!array_key_exists('g-recaptcha-response', $data) || empty($data['g-recaptcha-response'])) {
177 | $validator->validationError(
178 | $this->name,
179 | _t(
180 | 'SilverStripe\Recaptcha\RecaptchaField.EMPTY',
181 | "Please answer the captcha question",
182 | "Recaptcha (https://www.google.com/recaptcha) protects this website "
183 | . "from spam and abuse."
184 | ),
185 | "validation",
186 | false
187 | );
188 |
189 | return false;
190 | }
191 |
192 | $response = $this->recaptchaHttpPost($data['g-recaptcha-response']);
193 |
194 | if (!$response) {
195 | $validator->validationError(
196 | $this->name,
197 | _t(
198 | 'SilverStripe\Recaptcha\RecaptchaField.NORESPONSE',
199 | 'The recaptcha service gave no response. Please try again later.',
200 | 'Recaptcha (https://www.google.com/recaptcha) protects this website '
201 | . 'from spam and abuse.'
202 | ),
203 | 'validation',
204 | false
205 | );
206 | return false;
207 | }
208 |
209 | // get the payload of the response and decode it
210 | $response = json_decode($response, true);
211 |
212 | if ($response['success'] != 'true') {
213 | // Count some errors as "user level", meaning they raise a validation error rather than a system error
214 | $userLevelErrors = array('missing-input-response', 'invalid-input-response');
215 | $error = implode(', ', $response['error-codes']);
216 | if (count(array_intersect($response['error-codes'], $userLevelErrors)) === 0) {
217 | user_error("SilverStripe\Recaptcha\RecatpchaField::validate(): Recaptcha-service error: '{$error}'", E_USER_ERROR);
218 | return false;
219 | } else {
220 | // Internal error-string returned by recaptcha, e.g. "incorrect-captcha-sol".
221 | // Used to generate the new iframe-url/js-url after form-refresh.
222 | $request->getSession()->set("FormField.{$this->form->FormName()}.{$this->getName()}.error", trim($error));
223 | $validator->validationError(
224 | $this->name,
225 | _t(
226 | 'SilverStripe\Recaptcha\RecaptchaField.VALIDSOLUTION',
227 | "Your answer didn't match",
228 | 'Recaptcha (https://www.google.com/recaptcha) protects this website '
229 | . 'from spam and abuse.'
230 | ),
231 | 'validation',
232 | false
233 | );
234 | return false;
235 | }
236 | }
237 |
238 | return true;
239 | }
240 |
241 | /**
242 | * Fires off a HTTP-POST request
243 | *
244 | * @see Based on http://recaptcha.net/plugins/php/
245 | * @param string $responseStr
246 | * @return string Raw HTTP-response
247 | */
248 | protected function recaptchaHttpPost($responseStr)
249 | {
250 | $postVars = array(
251 | 'secret' => self::config()->get('private_api_key'),
252 | 'remoteip' => $_SERVER['REMOTE_ADDR'],
253 | 'response' => $responseStr,
254 | );
255 | $client = $this->getHttpClient();
256 | $response = $client->post(self::config()->get('api_verify_server'), $postVars);
257 |
258 | return $response->getBody();
259 | }
260 |
261 | /**
262 | * @param RecaptchaField_HTTPClient
263 | * @return $this
264 | */
265 | public function setHttpClient($client)
266 | {
267 | $this->client = $client;
268 | return $this;
269 | }
270 |
271 | /**
272 | * @return RecaptchaFieldHttpClient
273 | */
274 | public function getHttpClient()
275 | {
276 | if (!$this->client) {
277 | $class = self::config()->get('httpclient_class');
278 | $this->client = new $class();
279 | }
280 |
281 | return $this->client;
282 | }
283 | }
--------------------------------------------------------------------------------