diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..b576a73
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,15 @@
+# Contributing
+
+- Maintenance on this module is a shared effort of those who use it
+- To contribute improvements to the code, ensure you raise a pull request and discuss with the module maintainers
+- Please follow the Silverstripe CMS [code contribution guidelines](https://docs.silverstripe.org/en/contributing/code/) and [Module Standard](https://docs.silverstripe.org/en/developer_guides/extending/modules/#module-standard)
+- Supply documentation that follows the [GitHub Flavored Markdown](https://help.github.com/articles/markdown-basics/) conventions
+- When having discussions about this module in issues or pull request please adhere to the [Silverstripe CMS Community Code of Conduct](https://docs.silverstripe.org/en/project_governance/code_of_conduct/)
+
+## Contributor license agreement
+
+By supplying code to this module in patches, tickets and pull requests, you agree to assign copyright
+of that code to MODULE_COPYRIGHT_HOLDER_HERE., on the condition that these code changes are released under the
+same BSD license as the original module. We ask for this so that the ownership in the license is clear
+and unambiguous. By releasing this code under a permissive license such as BSD, this copyright assignment
+won't prevent you from using the code in any way you see fit.
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..1886837
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,12 @@
+Copyright (c)
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..3232f8b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,143 @@
+Turnstile Smart CAPTCHA
+=================
+(Please note this is a initial work, Have not done any Test yet not ideal for production environment)
+Adds a "spam protection" field to SilverStripe userforms using Cloudflare's
+[smart CAPTCHA](https://developers.cloudflare.com/turnstile) service.
+
+## Requirements
+* SilverStripe 5.x
+* [SilverStripe Spam Protection
+ 3.x](https://github.com/silverstripe/silverstripe-spamprotection/)
+* PHP CURL
+
+## Installation
+```
+composer require silverstripe-terraformers/turnstile-captcha
+```
+
+After installing the module via composer or manual install you must set the spam
+protector to NocaptchaProtector, this needs to be set in your site's config file
+normally this is mysite/\_config/config.yml.
+```yml
+SilverStripe\SpamProtection\Extension\FormSpamProtectionExtension:
+ default_spam_protector: Terraformers\TurnstileCaptcha\Forms\TurnstileCaptchaProtector
+```
+
+Finally, add the "spam protection" field to your form by calling
+``enableSpamProtection()`` on the form object.
+```php
+$form->enableSpamProtection();
+```
+
+## Configuration
+There are multiple configuration options for the field, you must set the
+site_key and the secret_key which you can get from the [reCAPTCHA
+page](https://www.google.com/recaptcha). These configuration options must be
+added to your site's yaml config typically this is mysite/\_config/config.yml.
+```yml
+Terraformers\TurnstileCaptcha\Forms\TurnstileCaptchaField:
+ site_key: "YOUR_SITE_KEY" #Your site key (required)
+ secret_key: "YOUR_SECRET_KEY" #Your secret key (required)
+ verify_ssl: true #Allows you to disable php-curl's SSL peer verification by setting this to false (optional, defaults to true)
+ default_theme: "light" #Default theme color (optional, light or dark, defaults to light)
+ default_handle_submit: true #Default setting for whether nocaptcha should handle form submission. See "Handling form submission" below.
+ proxy_server: "" #Your proxy server address (optional)
+ proxy_port: "" #Your proxy server address port (optional)
+ proxy_auth: "" #Your proxy server authentication information (optional)
+```
+
+## Adding field labels
+
+If you want to add a field label or help text to the Captcha field you can do so
+like this:
+
+```php
+$form->enableSpamProtection()
+ ->fields()->fieldByName('Captcha')
+ ->setTitle("Spam protection")
+ ->setDescription("Please tick the box to prove you're a human and help us stop spam.");
+```
+
+### Commenting Module
+When your using the
+[silverstripe/comments](https://github.com/silverstripe/silverstripe-comments)
+module you must add the following (per their documentation) to your \_config.php
+in order to use Terraformers\TurnstileCaptcha on comment forms.
+
+```php
+CommentingController::add_extension('CommentSpamProtection');
+```
+
+## Retrieving the Verify Response
+
+If you wish to manually retrieve the Site Verify response in you form action use
+the `getVerifyResponse()` method
+
+```php
+function doSubmit($data, $form) {
+ $captchaResponse = $form->Fields()->fieldByName('Captcha')->getVerifyResponse();
+
+ // $captchaResponse = array (size=5) [
+ // 'success' => boolean true
+ // 'challenge_ts' => string '2020-09-08T20:48:34Z' (length=20)
+ // 'hostname' => string 'localhost' (length=9)
+ // 'score' => float 0.9
+ // 'action' => string 'submit' (length=6)
+ // ];
+}
+```
+
+## Handling form submission
+By default, the javascript included with this module will add a submit event handler to your form.
+
+If you need to handle form submissions in a special way (for example to support front-end validation),
+you can choose to handle form submit events yourself.
+
+This can be configured site-wide using the Config API
+```yml
+Terraformers\TurnstileCaptcha\Forms\TurnstileCaptchaField:
+ default_handle_submit: false
+```
+
+Or on a per form basis:
+```php
+$captchaField = $form->Fields()->fieldByName('Captcha');
+$captchaField->setHandleSubmitEvents(false);
+```
+
+With this configuration no event handlers will be added by this module to your form. Instead, a
+function will be provided called `nocaptcha_handleCaptcha` which you can call from your code
+when you're ready to submit your form. It has the following signature:
+```js
+function nocaptcha_handleCaptcha(form, callback)
+```
+`form` must be the form element, and `callback` should be a function that finally submits the form,
+though it is optional.
+
+In the simplest case, you can use it like this:
+```js
+document.addEventListener("DOMContentLoaded", function(event) {
+ // where formID is the element ID for your form
+ const form = document.getElementById(formID);
+ const submitListener = function(event) {
+ event.preventDefault();
+ let valid = true;
+ /* Your validation logic here */
+ if (valid) {
+ nocaptcha_handleCaptcha(form, form.submit.bind(form));
+ }
+ };
+ form.addEventListener('submit', submitListener);
+});
+```
+
+## Reporting an issue
+
+When you're reporting an issue please ensure you specify what version of
+SilverStripe you are using i.e. 3.1.3, 3.2beta, master etc. Also be sure to
+include any JavaScript or PHP errors you receive, for PHP errors please ensure
+you include the full stack trace. Also please include how you produced the
+issue. You may also be asked to provide some of the classes to aid in
+re-producing the issue. Stick with the issue, remember that you seen the issue
+not the maintainer of the module so it may take allot of questions to arrive at
+a fix or answer.
diff --git a/_config.php b/_config.php
new file mode 100644
index 0000000..4293057
--- /dev/null
+++ b/_config.php
@@ -0,0 +1,4 @@
+
+
+ CodeSniffer ruleset for Silverstripe coding conventions.
+
+ src
+ tests
+
+
+
+
+
+
+
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
new file mode 100644
index 0000000..3801dc3
--- /dev/null
+++ b/phpunit.xml.dist
@@ -0,0 +1,8 @@
+
+
+
+
+ tests/php
+
+
+
diff --git a/src/Forms/TurnstileCaptchaField.php b/src/Forms/TurnstileCaptchaField.php
new file mode 100644
index 0000000..2c5c19d
--- /dev/null
+++ b/src/Forms/TurnstileCaptchaField.php
@@ -0,0 +1,356 @@
+title = $title;
+
+ $this->_captchaTheme = self::config()->default_theme;
+ $this->handleSubmitEvents = self::config()->default_handle_submit;
+ }
+
+ /**
+ * Adds in the requirements for the field
+ * @param array $properties Array of properties for the form element (not used)
+ * @return string Rendered field template
+ */
+ public function Field($properties = array())
+ {
+ $siteKey = $this->getSiteKey();
+ $secretKey = $this->_secretKey ? $this->_secretKey : self::config()->secret_key;
+
+ if (empty($siteKey) || empty($secretKey)) {
+ user_error('You must configure Nocaptcha.site_key and Nocaptcha.secret_key, you can retrieve these at https://google.com/recaptcha', E_USER_ERROR);
+ }
+
+ Requirements::javascript(
+ 'https://challenges.cloudflare.com/turnstile/v0/api.js?hl=' . Locale::getPrimaryLanguage(i18n::get_locale()) . ($this->config()->js_onload_callback ? '&onload=' . $this->config()->js_onload_callback : ''),
+ [
+ 'async' => true,
+ 'defer' => true,
+ ]
+ );
+ return parent::Field($properties);
+ }
+
+
+ /**
+ * Validates the captcha against the Recaptcha API
+ *
+ * @param Validator $validator Validator to send errors to
+ * @return bool Returns boolean true if valid false if not
+ */
+ public function validate($validator)
+ {
+
+ $request = Controller::curr()->getRequest();
+ $recaptchaResponse = $request->requestVar('cf-turnstile-response');
+
+ if (!isset($recaptchaResponse)) {
+ $validator->validationError($this->name, _t(
+ 'Terraformers\\TurnstileCaptcha\\Forms\\CaptchaField.EMPTY',
+ 'if you do not see the captcha you must enable JavaScript'),
+ 'validation');
+ return false;
+ }
+
+ if (!function_exists('curl_init')) {
+ user_error('You must enable php-curl to use this field', E_USER_ERROR);
+ return false;
+ }
+
+ $secret_key = $this->_secretKey ?: self::config()->secret_key;
+ $url = 'https://challenges.cloudflare.com/turnstile/v0/siteverify';
+ $ch = curl_init($url);
+ $proxy_server = $this->_proxyServer ?: self::config()->proxy_server;
+ if (!empty($proxy_server)) {
+ curl_setopt($ch, CURLOPT_PROXY, $proxy_server);
+
+ $proxy_auth = $this->_proxyAuth ?: self::config()->proxy_auth;
+ if (!empty($proxy_auth)) {
+ curl_setopt($ch, CURLOPT_PROXYUSERPWD, $proxy_auth);
+ }
+
+ $proxy_port = $this->_proxyPort ?: self::config()->proxy_port;
+ if (!empty($proxy_port)) {
+ curl_setopt($ch, CURLOPT_PROXYPORT, $proxy_port);
+ }
+ }
+
+ curl_setopt($ch, CURLOPT_TIMEOUT, 10);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, self::config()->verify_ssl);
+ curl_setopt($ch, CURLOPT_POST, true);
+ curl_setopt($ch, CURLOPT_USERAGENT, str_replace(',', '/', 'SilverStripe'));
+ curl_setopt(
+ $ch,
+ CURLOPT_POSTFIELDS,
+ http_build_query([
+ 'secret' => $secret_key,
+ 'response' => $recaptchaResponse,
+ 'remoteip' => $request->getIP(),
+ ])
+ );
+ $response = json_decode(curl_exec($ch), true);
+
+ if (is_array($response)) {
+ $this->verifyResponse = $response;
+
+ if (!array_key_exists('success', $response) || !$response['success']) {
+ $validator->validationError($this->name, _t(
+ 'Terraformers\\TurnstileCaptcha\\Forms\\CaptchaField.EMPTY',
+ '_Please answer the captcha,
+ if you do not see the captcha you must enable JavaScript'),
+ 'validation');
+ return false;
+ }
+
+ } else {
+ $validator->validationError($this->name, _t(
+ 'Terraformers\\TurnstileCaptcha\\Forms\\CaptchaField.VALIDATE_ERROR',
+ '_Captcha could not be validated'),
+ 'validation');
+ $logger = Injector::inst()->get(LoggerInterface::class);
+ $logger->error(
+ 'Captcha validation failed as request was not successful.'
+ );
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Sets whether form submit events are handled directly by this module.
+ *
+ * @param boolean $value
+ * @return TurnstileCaptchaField
+ */
+ public function setHandleSubmitEvents(bool $value): TurnstileCaptchaField
+ {
+ $this->handleSubmitEvents = $value;
+ return $this;
+ }
+
+ /**
+ * Get whether form submit events are handled directly by this module.
+ *
+ * @return boolean
+ */
+ public function getHandleSubmitEvents(): bool
+ {
+ return $this->handleSubmitEvents;
+ }
+
+ /**
+ * Sets the theme for this captcha
+ * @param string $value Theme to set it to, currently the api supports light and dark
+ * @return TurnstileCaptchaField
+ */
+ public function setTheme(string $value): TurnstileCaptchaField
+ {
+ $this->_captchaTheme = $value;
+
+ return $this;
+ }
+
+ /**
+ * Gets the theme for this captcha
+ * @return string
+ */
+ public function getCaptchaTheme(): string
+ {
+ return $this->_captchaTheme;
+ }
+
+ /**
+ * Gets the site key configured via NocaptchaField.site_key this is used in the template
+ * @return string
+ */
+ public function getSiteKey(): string
+ {
+ return $this->_sitekey ? $this->_sitekey : self::config()->site_key;
+ }
+
+ /**
+ * Setter for _siteKey to allow injector config to override the value
+ */
+ public function setSiteKey($key)
+ {
+ $this->_sitekey = $key;
+ }
+
+ /**
+ * Setter for _secretKey to allow injector config to override the value
+ */
+ public function setSecretKey($key)
+ {
+ $this->_secretKey = $key;
+ }
+
+ /**
+ * Setter for _proxyServer to allow injector config to override the value
+ */
+ public function setProxyServer($server)
+ {
+ $this->_proxyServer = $server;
+ }
+
+ /**
+ * Setter for _proxyAuth to allow injector config to override the value
+ */
+ public function setProxyAuth($auth)
+ {
+ $this->_proxyAuth = $auth;
+ }
+
+ /**
+ * Setter for _proxyPort to allow injector config to override the value
+ */
+ public function setProxyPort($port)
+ {
+ $this->_proxyPort = $port;
+ }
+
+ /**
+ * Gets the form's id
+ * @return ?string
+ */
+ public function getFormID(): ?string
+ {
+ return ($this->form ? $this->getTemplateHelper()->generateFormID($this->form) : null);
+ }
+
+ /**
+ * @return array
+ */
+ public function getVerifyResponse(): array
+ {
+ return $this->verifyResponse;
+ }
+
+}
diff --git a/src/Forms/TurnstileCaptchaProtector.php b/src/Forms/TurnstileCaptchaProtector.php
new file mode 100644
index 0000000..469c507
--- /dev/null
+++ b/src/Forms/TurnstileCaptchaProtector.php
@@ -0,0 +1,24 @@
+
+
+
+
+
diff --git a/tests/README.md b/tests/README.md
new file mode 100644
index 0000000..e0e6f88
--- /dev/null
+++ b/tests/README.md
@@ -0,0 +1,5 @@
+# Tests
+
+Look over the [testing documentation](https://docs.silverstripe.org/en/developer_guides/testing/)
+
+Make sure to remove this readme in your actual module!
diff --git a/tests/endtoend/README.md b/tests/endtoend/README.md
new file mode 100644
index 0000000..8e25944
--- /dev/null
+++ b/tests/endtoend/README.md
@@ -0,0 +1,5 @@
+# End-to-end tests
+
+The supported modules use behat for end-to-end testing, but other options such as Cypress also work well.
+
+Make sure to remove this readme in your actual module!
diff --git a/tests/php/README.md b/tests/php/README.md
new file mode 100644
index 0000000..946fcba
--- /dev/null
+++ b/tests/php/README.md
@@ -0,0 +1,5 @@
+# Unit and functional tests
+
+Look over the [unit testing](https://docs.silverstripe.org/en/developer_guides/testing/unit_testing/) and [functional testing](https://docs.silverstripe.org/en/developer_guides/testing/functional_testing/) documentation.
+
+Make sure to remove this readme in your actual module!