├── 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 | [![Build Status](https://secure.travis-ci.org/chillu/silverstripe-recaptcha.png)](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 | } --------------------------------------------------------------------------------