Repository: biscolab/laravel-recaptcha
Branch: master
Commit: 440fc617cba9
Files: 34
Total size: 101.0 KB
Directory structure:
gitextract_5ohrg6fm/
├── .editorconfig
├── .gitattributes
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ └── bug_report.md
│ ├── PULL_REQUEST_TEMPLATE/
│ │ └── pull_request_template.md
│ └── workflows/
│ └── ci.yml
├── .gitignore
├── .scrutinizer.yml
├── .travis.yml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── composer.json
├── config/
│ └── recaptcha.php
├── phpunit.xml.dist
├── src/
│ ├── Controllers/
│ │ └── ReCaptchaController.php
│ ├── Exceptions/
│ │ └── InvalidConfigurationException.php
│ ├── Facades/
│ │ └── ReCaptcha.php
│ ├── ReCaptchaBuilder.php
│ ├── ReCaptchaBuilderInvisible.php
│ ├── ReCaptchaBuilderV2.php
│ ├── ReCaptchaBuilderV3.php
│ ├── ReCaptchaServiceProvider.php
│ └── helpers.php
└── tests/
├── ReCaptchaConfigurationTest.php
├── ReCaptchaCustomApiDomainTest.php
├── ReCaptchaHelpersInvisibleTest.php
├── ReCaptchaHelpersV2ExplicitTest.php
├── ReCaptchaHelpersV2Test.php
├── ReCaptchaInvalidConfigurationTest.php
├── ReCaptchaLangTest.php
├── ReCaptchaTest.php
├── ReCaptchaV3Test.php
└── TestCase.php
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
[*]
charset=utf-8
end_of_line=lf
insert_final_newline=false
indent_style=space
indent_size=4
[{*.yml,*.yaml}]
indent_style=space
indent_size=2
================================================
FILE: .gitattributes
================================================
/tests export-ignore
/.github export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
/.scrutinizer.yml export-ignore
/.travis.yml export-ignore
/.editorconfig export-ignore
/phpunit.xml export-ignore
/changelog.md export-ignore
/README.md export-ignore
/CONTRIBUTING.md export-ignore
/CODE_OF_CONDUCT.md export-ignore
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Environment:**
- OS and server: [e.g. Ubuntu 16.04 / Nginx] (If you are using a VM on Windows DON'T "say" Windows)
- PHP version [7.1.x]
- Laravel version [e.g. 5.5]
- Package version [e.g. 3.4.1]
**Additional context**
Add any other context about the problem here, like code snippets.
================================================
FILE: .github/PULL_REQUEST_TEMPLATE/pull_request_template.md
================================================
Bug Fixes **ONLY**. NO NEW FEATURE ACCEPTED!
## Description
## Related Issue
## Motivation and Context
## How Has This Been Tested?
## Screenshots (if appropriate):
================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
- push
- pull_request
jobs:
run:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
php: [7.3, 7.4, '8.0', 8.1, '8.2']
laravel: [7, 8, 9, 10, '11']
exclude:
- php: 7.3
laravel: 10
- php: 7.4
laravel: 10
- php: 8.0
laravel: 10
- php: 7.3
laravel: 9
- php: 7.4
laravel: 9
- laravel: '11'
php: 7.3
- laravel: '11'
php: 7.4
- laravel: '11'
php: '8.0'
- laravel: '11'
php: 8.1
name: PHP ${{ matrix.php }} Test on ${{ matrix.os }} – Laravel ${{ matrix.laravel }}
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{matrix.php}}
- name: Install dependencies
uses: php-actions/composer@v5
with:
args: --prefer-source --no-interaction
- name: Run composer test script
uses: php-actions/composer@v5
with:
command: test
================================================
FILE: .gitignore
================================================
vendor/
/.idea
# Rocketeer PHP task runner and deployment package. https://github.com/rocketeers/rocketeer
.rocketeer/
# composer
/composer.lock
/composer.local.*
/composer.phar
# coverage reports
/coverage.clover
coverage.xml
clover.xml
report/
# testing
.phpunit.result.cache
phpunit.xml.dist.bak
================================================
FILE: .scrutinizer.yml
================================================
build:
nodes:
coverage:
tests:
override:
- command: vendor/bin/phpunit --coverage-clover=clover.xml
coverage:
file: clover.xml
format: clover
tools:
php_code_sniffer:
config:
standard: PSR2
================================================
FILE: .travis.yml
================================================
language: php
php:
- 7.3
- 7.4
- 8.0
# PHP 8.1 isn't currently support by Travis CI
# - 8.1
env:
- ILLUMINATE_VERSION=^7.0 TESTBENCH_VERSION=5.*
- ILLUMINATE_VERSION=^8.0 TESTBENCH_VERSION=6.*
- ILLUMINATE_VERSION=^9.0 TESTBENCH_VERSION=^7.0
matrix:
exclude:
# Don't test Laravel 9 on PHP 7.3 or PHP 7.4, as the mininmum
# required PHP version for this Laravel version is 8.0.2
- php: 7.3
env: ILLUMINATE_VERSION=^9.0 TESTBENCH_VERSION=^7.0
- php: 7.4
env: ILLUMINATE_VERSION=^9.0 TESTBENCH_VERSION=^7.0
before_install:
- composer require "illuminate/routing:${ILLUMINATE_VERSION}" --no-update --prefer-dist
- composer require "illuminate/support:${ILLUMINATE_VERSION}" --no-update --prefer-dist
- composer require "orchestra/testbench:${TESTBENCH_VERSION}" --no-update --prefer-dist
install: travis_retry composer install --prefer-source --no-interaction
script:
- composer test
fast_finish: true
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at roby.belotti@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing
Contributions are **welcome** and will be fully **credited**.
We accept contributions via Pull Requests on [Github](https://github.com/biscolab/laravel-recaptcha).
## Pull Requests
- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer).
- **Add tests!** - Your patch won't be accepted if it doesn't have tests.
- **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date.
- **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option.
- **Create feature branches** - Don't ask us to pull from your master branch.
- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.
- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting.
## Running Tests
``` bash
$ composer test
```
**Happy coding**!
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2017 - present Roberto Belotti
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
**Laravel ReCAPTCHA** is a very simply-to-use Laravel 5 package to embed Google reCAPTCHA in your application.
[](https://travis-ci.org/biscolab/laravel-recaptcha)
[](https://scrutinizer-ci.com/g/biscolab/laravel-recaptcha/?branch=master)
[](https://scrutinizer-ci.com/g/biscolab/laravel-recaptcha/?branch=master)
[](https://packagist.org/packages/biscolab/laravel-recaptcha)
[](https://packagist.org/packages/biscolab/laravel-recaptcha/stats)
[](https://github.com/biscolab/laravel-recaptcha/blob/master/LICENSE)
## What is reCAPTCHA?
Google developers says: "reCAPTCHA protects you against spam and other types of automated abuse. Here, we explain how to add reCAPTCHA to your site or application."
You can find further info at Google reCAPTCHA Developer's Guide
## reCAPTCHA available versions
At this moment there are 3 versions available (for web applications):
- **v3**, the latest (reCAPTCHA v3)
- **v2 checkbox** or simply reCAPTCHA v2 (reCAPTCHA v2)
- **v2 invisible** (Invisible reCAPTCHA)
## Get your key first!
First of all you have to create your own API keys here
Follow the instructions and at the end of the process you will find **Site key** and **Secret key**. Keep them close..you will need soon!
## System requirements
| Package version | reCaptcha version | PHP version | Laravel version |
| --------------- | ----------------------------- | --------------------- | ----------------------- |
| 6.1 | v3, v2 Invisible, v2 Checkbox | 7.3 or greater | 7, 8, 9, 10, 11 |
| 6.0 | v3, v2 Invisible, v2 Checkbox | 7.3 or greater | 7, 8, 9, 10 |
| 5.x | v3, v2 Invisible, v2 Checkbox | 7.3 or greater | 7, 8, 9 |
| 4.2.x to 4.4.x | v3, v2 Invisible, v2 Checkbox | 7.1 or greater | 5.5 or greater, 6, 7, 8 |
| 4.1.x | v3, v2 Invisible, v2 Checkbox | 7.1 or greater | 5.5 or greater, 6, 7 |
| 4.0.x | v3, v2 Invisible, v2 Checkbox | 7.1 or greater | 5.5 or greater, 6 |
| 3.x | v3, v2 Invisible, v2 Checkbox | 7.1 or greater | 5.5 or greater, 6 (\*) |
| 2.x | v2 Invisible, v2 Checkbox | 5.5.9, 7.0 or greater | 5.0 or greater |
> (\*) Version 3.6.1 is Laravel 6 ready
## Composer
You can install the package via composer:
```sh
$ composer require biscolab/laravel-recaptcha
```
Laravel 5.5 (or greater) uses package auto-discovery, so doesn't require you to manually add the Service Provider, but if you don't use auto-discovery `ReCaptchaServiceProvider` must be registered in `config/app.php`:
```php
'providers' => [
...
Biscolab\ReCaptcha\ReCaptchaServiceProvider::class,
];
```
You can use the facade for shorter code. Add `ReCaptcha` to your aliases:
```php
'aliases' => [
...
'ReCaptcha' => Biscolab\ReCaptcha\Facades\ReCaptcha::class,
];
```
## Publish package
Create `config/recaptcha.php` configuration file using the following artisan command:
```sh
$ php artisan vendor:publish --provider="Biscolab\ReCaptcha\ReCaptchaServiceProvider"
```
## Set the environment
### Add your API Keys
Open `.env` file and set `RECAPTCHA_SITE_KEY` and `RECAPTCHA_SECRET_KEY`:
```php
# in your .env file
RECAPTCHA_SITE_KEY=
RECAPTCHA_SECRET_KEY=
RECAPTCHA_SKIP_IP=
```
`RECAPTCHA_SKIP_IP` (since v5.2.0, not required, CSV format ) allows you to add a list of IP/CIDR (netmask included).
It will be the value of `skip_ip`
> **The following environment variables have been removed!!!**
> Now only sensitive informations as API keys are allowed as environment variables, that means you have to set configuration values in `config/recaptcha.php`
- ~~RECAPTCHA_DEFAULT_VERSION~~
- ~~RECAPTCHA_CURL_TIMEOUT~~
- ~~RECAPTCHA_DEFAULT_VALIDATION_ROUTE~~
- ~~RECAPTCHA_DEFAULT_TOKEN_PARAMETER_NAME~~
- ~~RECAPTCHA_DEFAULT_LANGUAGE~~
### Complete configuration
Open `config/recaptcha.php` configuration file and set `version`:
```php
return [
'api_site_key' => env('RECAPTCHA_SITE_KEY', ''),
'api_secret_key' => env('RECAPTCHA_SECRET_KEY', ''),
// changed in v4.0.0
'version' => 'v2', // supported: "v3"|"v2"|"invisible"
// @since v3.4.3 changed in v4.0.0
'curl_timeout' => 10,
'skip_ip' => env('RECAPTCHA_SKIP_IP', []), // array of IP addresses - String: dotted quad format e.g.: "127.0.0.1", IP/CIDR netmask eg. 127.0.0.0/24, also 127.0.0.1 is accepted and /32 assumed
// @since v3.2.0 changed in v4.0.0
'default_validation_route' => 'biscolab-recaptcha/validate',
// @since v3.2.0 changed in v4.0.0
'default_token_parameter_name' => 'token',
// @since v3.6.0 changed in v4.0.0
'default_language' => null,
// @since v4.0.0
'default_form_id' => 'biscolab-recaptcha-invisible-form', // Only for "invisible" reCAPTCHA
// @since v4.0.0
'explicit' => false, // true|false
// @since v4.3.0
'api_domain' => "www.google.com", // default value is "www.google.com"
// @since v5.1.0
'empty_message' => false,
// @since v5.1.0
'error_message_key' => 'validation.recaptcha',
// @since v4.0.0
'tag_attributes' => [
'theme' => 'light', // "light"|"dark"
'size' => 'normal', // "normal"|"compact"
'tabindex' => 0,
'callback' => null, // DO NOT SET "biscolabOnloadCallback"
'expired-callback' => null, // DO NOT SET "biscolabOnloadCallback"
'error-callback' => null, // DO NOT SET "biscolabOnloadCallback"
]
];
```
| Key | Type | Description | Default |
| ----------------------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- |
| `api_site_key` and `api_secret_key` | `string` | reCAPTCHA keys you have to create in order to perform Google API authentication. For more information about Site Key and Secret Key please visit [Google reCAPTCHA developer documentation](https://developers.google.com/recaptcha/docs/start) | `''` |
| `version` | `string` | indicates the reCAPTCHA version (supported: v3|v2|invisible). Get more info about reCAPTCHA version at https://developers.google.com/recaptcha/docs/versions | `'v2'` |
| `curl_timeout` | `int` | the maximum number of seconds to allow cURL functions to execute | `10` |
| `skip_ip` | `array` | `string` | a whitelist of IP addresses (array or CSV) that, if recognized, disable the reCAPTCHA validation (return always true) and if you embed JS code in blade (view) file **NO validation call will be performed** | `[]` |
| `default_validation_route` | `string` | the route called via javascript built-in validation script (v3 only) | `'biscolab-recaptcha/validate'` |
| `default_token_parameter_name` | `string` | the name of "token" GET parameter sent to `default_validation_route` to be validated (v3 only) | `'token'` |
| `default_language` | `string` | the default language code. It has no effect with v3. See [https://developers.google.com/recaptcha/docs/language](https://developers.google.com/recaptcha/docs/language) for further information | `null` |
| `default_form_id` | `string` | the default form ID. Only for "invisible" reCAPTCHA | `'biscolab-recaptcha-invisible-form'` |
| `explicit` | `bool` | deferring the render can be achieved by specifying your onload callback function and adding parameters to the JavaScript resource. It has no effect with v3 and invisible (supported values: true|false) | `false` |
| `api_domain` | `string` | customize API domain. Default value is `'www.google.com'`, but, if not accessible you ca set that value to `'www.recaptcha.net'`. More info about [Can I use reCAPTCHA globally?](https://developers.google.com/recaptcha/docs/faq#can-i-use-recaptcha-globally) | `'www.google.com'` |
| `empty_message` | `bool` | set default error message to `null` | `false` |
| `error_message_key` | `string` | set default error message translation key | `'validation.recaptcha'` |
#### (array) tag_attributes
| Key | Type | Description | Default |
| --------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
| `tag_attributes.theme` | `string` | the color theme of the widget. (supported values: "light"|"dark") | `'light'` |
| `tag_attributes.size` | `string` | the size of the widget. (supported values: "normal"|"compact") | `'normal'` |
| `tag_attributes.tabindex` | `int` | the tabindex of the widget and challenge | `0` |
| `tag_attributes.callback` | `string` | the name of your callback function, executed when the user submits a successful response. The g-recaptcha-response token is passed to your callback | `null` |
| `tag_attributes.expired-callback` | `string` | the name of your callback function, executed when the reCAPTCHA response expires and the user needs to re-verify | `null` |
| `tag_attributes.error-callback` | `string` | the name of your callback function, executed when reCAPTCHA encounters an error (usually network connectivity) and cannot continue until connectivity is restored. If you specify a function here, you are responsible for informing the user that they should retry | `null` |
> DO NOT SET `tag_attributes.callback`, `tag_attributes.expired-callback`, `tag_attributes.error-callback` to `biscolabOnloadCallback`. `biscolabOnloadCallback` is the default JavaScript callback function called when **explicit** is set to `true` and widget `onload` event is fired.
Here you can find further details about `tag_attributes.*` [https://developers.google.com/recaptcha/docs/display#render_param](https://developers.google.com/recaptcha/docs/display#render_param)
### Reload config cache file
> **!!! IMPORTANT !!!** Every time you change some configuration run the following shell command:
```sh
$ php artisan config:cache
```
## Have you updated?
If you are migrating from an older version check your `config/recaptcha.php` configuration file and compare it with https://github.com/biscolab/laravel-recaptcha/blob/master/config/recaptcha.php.
> Make sure `config/recaptcha.php` is updated
## Customize error message
Just for v2 and invisible users.
Before starting please add the validation message to `resources/lang/[LANG]/validation.php` file
```php
return [
...
'recaptcha' => 'Hey!!! :attribute is wrong!',
];
```
## Embed in Blade
Insert `htmlScriptTagJsApi()` helper before closing `` tag.
You can also use `ReCaptcha::htmlScriptTagJsApi()`.
```blade
...
{!! htmlScriptTagJsApi($configuration) !!}
```
#### htmlScriptTagJsApi
`htmlScriptTagJsApi` function accepts `$configuration` argument. `$configuration` has different keys depending on which ReCAPTCHA you are using:
- [Checkbox](#recaptcha-v2-checkbox)
- [Invisible](#recaptcha-v2-invisible)
### ReCAPTCHA v2 Checkbox
#### htmlScriptTagJsApi(\$configuration)
`$configuration` argument can have following keys:
- `lang` set reCAPTCHA language. This will override `default_language` in `config/recaptcha.php`. Here you ca find the complete list of availeble languages [https://developers.google.com/recaptcha/docs/language](https://developers.google.com/recaptcha/docs/language)
#### Form set-up
After you have to insert `htmlFormSnippet()` helper inside the form where you want to use the field `g-recaptcha-response`.
You can also use `ReCaptcha::htmlFormSnippet()` .
```blade
```
> DO NOT forget `@csrf` blade directive
#### htmlFormSnippet([, array \$attributes = [] ])
`htmlFormSnippet()` function does not require attributes but you can override default config `data-` attributes:
```php
{!! htmlFormSnippet([
"theme" => "light",
"size" => "normal",
"tabindex" => "3",
"callback" => "callbackFunction",
"expired-callback" => "expiredCallbackFunction",
"error-callback" => "errorCallbackFunction",
]) !!}
```
`htmlFormSnippet` methos allows are only folowing attribute names:
- theme
- size
- tabindex
- callback
- expired-callback
- error-callback
> Any different attribute name will be rejected
#### Customization
In `config/recaptcha.php` you can customize reCAPTCHA widget setting `tag_attributes` array values. Take a look to `tag_attributes` section in [Complete configuration](configuration.md#complete-configuration)
### ReCAPTCHA v2 Invisible
#### htmlScriptTagJsApi(\$configuration)
`$configuration` argument can have following keys:
- `form_id` set reCAPTCHA form ID. This will override `default_form_id` in `config/recaptcha.php`. This value will be returned by `getFormId()` function in order to set the form tag `id` property.
#### Form set-up
After you have to insert `htmlFormButton($button_label, $properties)` helper inside the form where you want to use reCAPTCHA.
This function creates submit button therefore you don't have to insert `` or similar.
You can also use `ReCaptcha::htmlFormButton($button_label, $properties)` .
`$button_label` is what you want to write on the submit button
```html
```
> DO NOT forget `@csrf` blade directive
#### getFormId()
`getFormId` function returns the default form ID value. This is the value of either `default_form_id` in `config/recaptcha.php` or `$configuration['form_id']` previously set as arguments of `htmlScriptTagJsApi` helper.
> `$configuration['form_id']` overrides default settings.
#### htmlFormButton()
`htmlFormButton` function accepts 2 arguments:
- `$button_label`: (string: optional) the button lable. For example: `Subscribe!`;
- `$properties`: (array: optional) the HTML button properties. For example:
```php
// $properties =
[
'class' => 'btn btn-info',
'data-foo' => 'bar'
]
```
> If `data-sitekey` and `data-callback` properties are set, they will be overwritten
> If `class` property is set the value `g-recaptcha` will be appended
## Verify submitted data
Add `recaptcha` to your rules
```php
$validator = Validator::make(request()->all(), [
...
'g-recaptcha-response' => 'recaptcha',
// OR since v4.0.0
recaptchaFieldName() => recaptchaRuleName()
]);
// check if validator fails
if($validator->fails()) {
...
$errors = $validator->errors();
}
```
## Embed in Blade
Insert `htmlScriptTagJsApi($config)` helper before closing `` tag.
```html
...
{!! htmlScriptTagJsApi([
'action' => 'homepage',
'callback_then' => 'callbackThen',
'callback_catch' => 'callbackCatch'
]) !!}
{!! htmlScriptTagJsApi([
'action' => 'homepage',
'custom_validation' => 'myCustomValidation'
]) !!}
```
`$config` is required and is an associative array containing configuration parameters required for the JavaScript validation handling.
The keys are:
| Key | Required | Description | Default value |
|-------------------|-----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------|
| `action` | no | is the `action` parameter required by reCAPTCHA v3 API (further info) | `homepage` |
| `custom_validation` | no | is the name of your custom callback javascript function who will override the built-in javascript validation system of this package | empty string |
| `callback_then` | no | (overlooked if `custom_validation`is set) is the name of your custom callback javascript function called by the built-in javascript validation system of this package in case of response success | empty string |
| `callback_catch` | no | (overlooked if `custom_validation`is set) is the name of your custom callback javascript function called by the built-in javascript validation system in this package in case of response fault | empty string |
## Built-in javascript validation system
As callback of `grecaptcha.execute` an ajax call to `config('recaptcha.default_validation_route')` will be performed using `fetch` function. In case of successful response a Promise object will be received and passed as parameter to the `callback_then` function you have set. In not set, no actions will be performed.
Same will happen with `callback_catch`. `callback_catch` will be called in event of response errors and errors will pass as parameter et that function. If not set, no actions will be performed.
Please, go to Using Fetch for further information on `fetch` javascript function.
> **Warning!!! Check browser compatibility**
`fetch` function has compatibility issues with some browser like IE. Please create a custom validation function and set `custom_validation` with its name. That function has to accept as argument the `token`received from Google reCAPTCHA API.
>
> Fetch browser compatibility
### Validation Laravel route
Default validation route is `config('recaptcha.default_validation_route', 'biscolab-recaptcha/validate')`.
Route and relative Controller are built-in in the package. The route if filtered and protected by Laravel `web` Middleware, that's why is important you embed `csrf-token` HTML meta tag and send `X-Requested-Wit` and `X-CSRF-TOKEN` headers.
You can also change the validation end-point changing `default_validation_route` value in `recaptcha.php` config file.
```html
...
```
### Validation response object
The output will be a JSON containing following data:
* **Default output without errors**
```json
{
"action":"homepage",
"challenge_ts":"2019-01-29T00:42:08Z",
"hostname":"www.yourdomain.ext",
"score":0.9,
"success":true
}
```
* **Output when calling IP is included in "skip_ip" config whitelist**
```json
{
"skip_by_ip":true,
"score":0.9,
"success":true
}
```
> If you embed code in your blade file using `htmlScriptTagJsApiV3` helper no validation call will be performed!
>
> More info at Configuration page
* **Output with an empty response from Google reCAPTCHA API**
```json
{
"error":"cURL response empty",
"score":0.1,
"success":false
}
```
In the next paragraph you can learn how handle Validation promise object
### "callback_then" and "callback_catch"
After built-in validation you should do something. How? Using `callback_then` and `callback_catch` functions.
What you have to do is just create functions and set parameters with their names.
* `callback_then` must receive one argument of type `Promise`.
* `callback_catch` must receive one argument of type `string`
The result should be something like that:
```html
...
...
...
{!! htmlScriptTagJsApiV3([
'action' => 'homepage',
'callback_then' => 'callbackThen',
'callback_catch' => 'callbackCatch'
]) !!}
```
### "custom_validation" function
As just said you can handle validation with your own function. To do that you have to write your function and set `custom_validation` parameter with its name.
The result should be something like that:
```html
...
...
...
{!! htmlScriptTagJsApiV3([
'action' => 'homepage',
'custom_validation' => 'myCustomValidation'
]) !!}
```
================================================
FILE: composer.json
================================================
{
"name": "biscolab/laravel-recaptcha",
"description": "Simple and painless Google reCAPTCHA package for Laravel framework",
"license": "MIT",
"type": "library",
"keywords": [
"recaptcha",
"captcha",
"laravel",
"validation"
],
"homepage": "https://biscolab.com/laravel-recaptcha",
"authors": [
{
"name": "Roberto Belotti",
"email": "roby.belotti@gmail.com",
"homepage": "https://biscolab.com",
"role": "Developer"
}
],
"require": {
"php": "^7.3|^8.0",
"illuminate/routing": "^7.0|^8.0|^9.0|^10.0|^11.0",
"illuminate/support": "^7.0|^8.0|^9.0|^10.0|^11.0"
},
"require-dev": {
"orchestra/testbench": "5.*|6.*|^7.0|^8.0|^9.0",
"phpunit/phpunit": "^9.1|^10.5"
},
"autoload": {
"psr-4": {
"Biscolab\\ReCaptcha\\": "src/"
},
"files": [
"src/helpers.php"
]
},
"autoload-dev": {
"psr-4": {
"Biscolab\\ReCaptcha\\Tests\\": "tests/"
}
},
"scripts": {
"test": "vendor/bin/phpunit --colors=always"
},
"suggest": {
"biscolab/laravel-authlog": "It allows to handle logged-in users and force log-out if needed"
},
"extra": {
"laravel": {
"providers": [
"Biscolab\\ReCaptcha\\ReCaptchaServiceProvider"
],
"aliases": {
"ReCaptcha": "Biscolab\\ReCaptcha\\Facades\\ReCaptcha"
}
}
}
}
================================================
FILE: config/recaptcha.php
================================================
env('RECAPTCHA_SITE_KEY', ''),
/**
*
* The secret key
* get secret key @ www.google.com/recaptcha/admin
*
*/
'api_secret_key' => env('RECAPTCHA_SECRET_KEY', ''),
/**
*
* ReCATCHA version
* Supported: "v2", "invisible", "v3",
*
* get more info @ https://developers.google.com/recaptcha/docs/versions
*
*/
'version' => 'v2',
/**
*
* The curl timout in seconds to validate a recaptcha token
* @since v3.5.0
*
*/
'curl_timeout' => 10,
/**
*
* IP addresses for which validation will be skipped
* IP/CIDR netmask eg. 127.0.0.0/24, also 127.0.0.1 is accepted and /32 assumed
*
*/
'skip_ip' => env('RECAPTCHA_SKIP_IP', []),
/**
*
* Default route called to check the Google reCAPTCHA token
* @since v3.2.0
*
*/
'default_validation_route' => 'biscolab-recaptcha/validate',
/**
*
* The name of the parameter used to send Google reCAPTCHA token to verify route
* @since v3.2.0
*
*/
'default_token_parameter_name' => 'token',
/**
*
* The default Google reCAPTCHA language code
* It has no effect with v3
* @see https://developers.google.com/recaptcha/docs/language
* @since v3.6.0
*
*/
'default_language' => null,
/**
*
* The default form ID. Only for "invisible" reCAPTCHA
* @since v4.0.0
*
*/
'default_form_id' => 'biscolab-recaptcha-invisible-form',
/**
*
* Deferring the render can be achieved by specifying your onload callback function and adding parameters to the JavaScript resource.
* It has no effect with v3 and invisible
* @see https://developers.google.com/recaptcha/docs/display#explicit_render
* @since v4.0.0
* Supported true, false
*
*/
'explicit' => false,
/**
*
* Set API domain. You can use "www.recaptcha.net" in case "www.google.com" is not accessible.
* (no check will be made on the entered value)
* @see https://developers.google.com/recaptcha/docs/faq#can-i-use-recaptcha-globally
* @since v4.3.0
* Default 'www.google.com' (ReCaptchaBuilder::DEFAULT_RECAPTCHA_API_DOMAIN)
*
*/
'api_domain' => 'www.google.com',
/**
*
* Set `true` when the error message must be null
* @since v5.1.0
* Default false
*
*/
'empty_message' => false,
/**
*
* Set either the error message or the errom message translation key
* @since v5.1.0
* Default 'validation.recaptcha'
*
*/
'error_message_key' => 'validation.recaptcha',
/**
*
* g-recaptcha tag attributes and grecaptcha.render parameters (v2 only)
* @see https://developers.google.com/recaptcha/docs/display#render_param
* @since v4.0.0
*/
'tag_attributes' => [
/**
* The color theme of the widget.
* Supported "light", "dark"
*/
'theme' => 'light',
/**
* The size of the widget.
* Supported "normal", "compact"
*/
'size' => 'normal',
/**
* The tabindex of the widget and challenge.
* If other elements in your page use tabindex, it should be set to make user navigation easier.
*/
'tabindex' => 0,
/**
* The name of your callback function, executed when the user submits a successful response.
* The g-recaptcha-response token is passed to your callback.
* DO NOT SET "biscolabOnloadCallback"
*/
'callback' => null,
/**
* The name of your callback function, executed when the reCAPTCHA response expires and the user needs to re-verify.
* DO NOT SET "biscolabOnloadCallback"
*/
'expired-callback' => null,
/**
* The name of your callback function, executed when reCAPTCHA encounters an error (usually network connectivity) and cannot continue until connectivity is restored.
* If you specify a function here, you are responsible for informing the user that they should retry.
* DO NOT SET "biscolabOnloadCallback"
*/
'error-callback' => null,
]
];
================================================
FILE: phpunit.xml.dist
================================================
./src./vendor./config./tests
================================================
FILE: src/Controllers/ReCaptchaController.php
================================================
input(config('recaptcha.default_token_parameter_name', 'token'), '');
return recaptcha()->validate($token);
}
}
================================================
FILE: src/Exceptions/InvalidConfigurationException.php
================================================
setApiSiteKey($api_site_key);
$this->setApiSecretKey($api_secret_key);
$this->setVersion($version);
$this->setSkipByIp($this->skipByIp());
$this->setApiDomain();
$this->setApiUrls();
}
/**
* @param string $api_site_key
*
* @return ReCaptchaBuilder
*/
public function setApiSiteKey(string $api_site_key): ReCaptchaBuilder
{
$this->api_site_key = $api_site_key;
return $this;
}
/**
* @param string $api_secret_key
*
* @return ReCaptchaBuilder
*/
public function setApiSecretKey(string $api_secret_key): ReCaptchaBuilder
{
$this->api_secret_key = $api_secret_key;
return $this;
}
/**
* @return int
*/
public function getCurlTimeout(): int
{
return config('recaptcha.curl_timeout', self::DEFAULT_CURL_TIMEOUT);
}
/**
* @param string $version
*
* @return ReCaptchaBuilder
*/
public function setVersion(string $version): ReCaptchaBuilder
{
$this->version = $version;
return $this;
}
/**
* @return string
*/
public function getVersion(): string
{
return $this->version;
}
/**
* @param bool $skip_by_ip
*
* @return ReCaptchaBuilder
*/
public function setSkipByIp(bool $skip_by_ip): ReCaptchaBuilder
{
$this->skip_by_ip = $skip_by_ip;
return $this;
}
/**
* @param null|string $api_domain
*
* @return ReCaptchaBuilder
*/
public function setApiDomain(?string $api_domain = null): ReCaptchaBuilder
{
$this->api_domain = $api_domain ?? config('recaptcha.api_domain', self::DEFAULT_RECAPTCHA_API_DOMAIN);
return $this;
}
/**
* @return string
*/
public function getApiDomain(): string
{
return $this->api_domain;
}
/**
* @return ReCaptchaBuilder
*/
public function setApiUrls(): ReCaptchaBuilder
{
$this->api_url = 'https://' . $this->api_domain . '/recaptcha/api/siteverify';
$this->api_js_url = 'https://' . $this->api_domain . '/recaptcha/api.js';
return $this;
}
/**
* @return array|mixed
*/
public function getIpWhitelist()
{
$whitelist = config('recaptcha.skip_ip', []);
if (!is_array($whitelist)) {
$whitelist = explode(',', $whitelist);
}
$whitelist = array_map(function ($item) {
return trim($item);
}, $whitelist);
return $whitelist;
}
/**
* Checks whether the user IP address is among IPs "to be skipped"
*
* @return boolean
*/
public function skipByIp(): bool
{
return IpUtils::checkIp(request()->ip(), $this->getIpWhitelist());
}
/**
* Write script HTML tag in you HTML code
* Insert before tag
*
* @param array|null $configuration
*
* @return string
* @throws \Exception
*/
public function htmlScriptTagJsApi(?array $configuration = []): string
{
$query = [];
$html = '';
// Language: "hl" parameter
// resources $configuration parameter overrides default language
$language = Arr::get($configuration, 'lang');
if (!$language) {
$language = config('recaptcha.default_language', null);
}
if ($language) {
Arr::set($query, 'hl', $language);
}
// Onload JS callback function: "onload" parameter
// "render" parameter set to "explicit"
if (config('recaptcha.explicit', null) && $this->version === 'v2') {
Arr::set($query, 'render', 'explicit');
Arr::set($query, 'onload', self::DEFAULT_ONLOAD_JS_FUNCTION);
/** @scrutinizer ignore-call */
$html = $this->getOnLoadCallback();
}
// Create query string
$query = ($query) ? '?' . http_build_query($query) : "";
$html .= "";
return $html;
}
/**
* Call out to reCAPTCHA and process the response
*
* @param string $response
*
* @return boolean|array
*/
public function validate($response)
{
if ($this->skip_by_ip) {
if ($this->returnArray()) {
// Add 'skip_by_ip' field to response
return [
'skip_by_ip' => true,
'score' => 0.9,
'success' => true
];
}
return true;
}
$params = http_build_query([
'secret' => $this->api_secret_key,
'remoteip' => request()->getClientIp(),
'response' => $response,
]);
$url = $this->api_url . '?' . $params;
if (function_exists('curl_version')) {
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_TIMEOUT, $this->getCurlTimeout());
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
$curl_response = curl_exec($curl);
} else {
$curl_response = file_get_contents($url);
}
if (is_null($curl_response) || empty($curl_response)) {
if ($this->returnArray()) {
// Add 'error' field to response
return [
'error' => 'cURL response empty',
'score' => 0.1,
'success' => false
];
}
return false;
}
$response = json_decode(trim($curl_response), true);
if ($this->returnArray()) {
return $response;
}
return $response['success'];
}
/**
* @return string
*/
public function getApiSiteKey(): string
{
return $this->api_site_key;
}
/**
* @return string
*/
public function getApiSecretKey(): string
{
return $this->api_secret_key;
}
/**
* @return bool
*/
protected function returnArray(): bool
{
return ($this->version == 'v3');
}
/**
* @return string
*/
public function getOnLoadCallback(): string
{
return "";
}
}
================================================
FILE: src/ReCaptchaBuilderInvisible.php
================================================
tag in your HTML code
* Insert before tag
*
* @param string $button_label
* @param array|null $properties
*
* @return string
*/
public function htmlFormButton($button_label = 'Submit', ?array $properties = []): string
{
$tag_properties = '';
$properties = array_merge([
'data-callback' => 'biscolabLaravelReCaptcha',
], $properties,
[
'data-sitekey' => $this->api_site_key
]);
if (empty($properties['class'])) {
$properties['class'] = 'g-recaptcha';
} else {
$properties['class'] .= ' g-recaptcha';
}
ksort($properties);
if ($properties) {
// $tag_properties = str_replace("=", '="',
// http_build_query($properties, null, '" ', PHP_QUERY_RFC3986)) . '"';
$temp_properties = [];
foreach ($properties as $k => $v) {
$temp_properties[] = $k . '="' . $v . '"';
}
$tag_properties = implode(" ", $temp_properties);
}
return ($this->version == 'invisible') ? '' : '';
}
/**
* Write script HTML tag in you HTML code
* Insert before tag
*
* @param array|null $configuration
*
* @return string
* @throws \Exception
*/
public function htmlScriptTagJsApi(?array $configuration = []): string
{
$html = parent::htmlScriptTagJsApi();
$form_id = Arr::get($configuration, 'form_id');
if (!$form_id) {
$form_id = $this->getFormId();
} else {
$this->form_id = $form_id;
}
$html .= '';
return $html;
}
/**
* @return string
* @throws \Exception
*/
public function getFormId(): string
{
if (!$this->form_id) {
$form_id = config('recaptcha.default_form_id');
} else {
$form_id = $this->form_id;
}
if (!$form_id) {
throw new InvalidConfigurationException("formId required");
}
return $form_id;
}
}
================================================
FILE: src/ReCaptchaBuilderV2.php
================================================
tag
*
* @param null|array $attributes
* @return string
*/
public function htmlFormSnippet(?array $attributes = []): string
{
$data_attributes = [];
$config_data_attributes = array_merge($this->getTagAttributes(), self::cleanAttributes($attributes));
ksort($config_data_attributes);
foreach ($config_data_attributes as $k => $v) {
if ($v) {
$data_attributes[] = 'data-' . $k . '="' . $v . '"';
}
}
$html = '';
return $html;
}
/**
* @return array
* @throws InvalidConfigurationException
*/
public function getTagAttributes(): array
{
$tag_attributes = [
'sitekey' => $this->api_site_key
];
$tag_attributes = array_merge($tag_attributes, config('recaptcha.tag_attributes', []));
if (Arr::get($tag_attributes, 'callback') === ReCaptchaBuilder::DEFAULT_ONLOAD_JS_FUNCTION) {
throw new InvalidConfigurationException('Property "callback" ("data-callback") must be different from "' . ReCaptchaBuilder::DEFAULT_ONLOAD_JS_FUNCTION . '"');
}
if (Arr::get($tag_attributes, 'expired-callback') === ReCaptchaBuilder::DEFAULT_ONLOAD_JS_FUNCTION) {
throw new InvalidConfigurationException('Property "expired-callback" ("data-expired-callback") must be different from "' . ReCaptchaBuilder::DEFAULT_ONLOAD_JS_FUNCTION . '"');
}
if (Arr::get($tag_attributes, 'error-callback') === ReCaptchaBuilder::DEFAULT_ONLOAD_JS_FUNCTION) {
throw new InvalidConfigurationException('Property "error-callback" ("data-error-callback") must be different from "' . ReCaptchaBuilder::DEFAULT_ONLOAD_JS_FUNCTION . '"');
}
return $tag_attributes;
}
/**
* @return string
*/
public function getOnLoadCallback(): string
{
$attributes = $this->getTagAttributes();
return "";
}
/**
* Compare given attributes with allowed attributes
*
* @param array|null $attributes
* @return array
*/
public static function cleanAttributes(?array $attributes = []): array
{
return array_filter($attributes, function ($k) {
return in_array($k, self::$allowed_data_attribute);
}, ARRAY_FILTER_USE_KEY);
}
}
================================================
FILE: src/ReCaptchaBuilderV3.php
================================================
getValidationUrl(),
$this->getTokenParameterName()
]
);
}
/**
* Write script HTML tag in you HTML code
* Insert before tag
*
* @param array|null $configuration
*
* @return string
*/
public function htmlScriptTagJsApi(?array $configuration = []): string
{
if ($this->skip_by_ip) {
return '';
}
$html = "";
$action = Arr::get($configuration, 'action', 'homepage');
$js_custom_validation = Arr::get($configuration, 'custom_validation', '');
// Check if set custom_validation. That function will override default fetch validation function
if ($js_custom_validation) {
$validate_function = ($js_custom_validation) ? "{$js_custom_validation}(token);" : '';
} else {
$js_then_callback = Arr::get($configuration, 'callback_then', '');
$js_callback_catch = Arr::get($configuration, 'callback_catch', '');
$js_then_callback = ($js_then_callback) ? "{$js_then_callback}(response)" : '';
$js_callback_catch = ($js_callback_catch) ? "{$js_callback_catch}(err)" : '';
$validate_function = "
fetch('" . $this->getValidationUrlWithToken() . "=' + token, {
headers: {
\"X-Requested-With\": \"XMLHttpRequest\",
\"X-CSRF-TOKEN\": csrfToken.content
}
})
.then(function(response) {
{$js_then_callback}
})
.catch(function(err) {
{$js_callback_catch}
});";
}
$html .= "";
return $html;
}
}
================================================
FILE: src/ReCaptchaServiceProvider.php
================================================
addValidationRule();
$this->registerRoutes();
$this->publishes([
__DIR__ . '/../config/recaptcha.php' => config_path('recaptcha.php'),
], 'config');
}
/**
* Extends Validator to include a recaptcha type
*/
public function addValidationRule()
{
$message = null;
if (!config('recaptcha.empty_message')) {
$message = trans(config('recaptcha.error_message_key'));
}
Validator::extendImplicit(recaptchaRuleName(), function ($attribute, $value) {
return app('recaptcha')->validate($value);
}, $message);
}
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->mergeConfigFrom(
__DIR__ . '/../config/recaptcha.php',
'recaptcha'
);
$this->registerReCaptchaBuilder();
}
/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides(): array
{
return ['recaptcha'];
}
/**
* @return ReCaptchaServiceProvider
*
* @since v3.4.1
*/
protected function registerRoutes(): ReCaptchaServiceProvider
{
Route::get(
config('recaptcha.default_validation_route', 'biscolab-recaptcha/validate'),
['uses' => 'Biscolab\ReCaptcha\Controllers\ReCaptchaController@validateV3']
)->middleware('web');
return $this;
}
/**
* Register the HTML builder instance.
*
* @return void
*/
protected function registerReCaptchaBuilder()
{
$this->app->singleton('recaptcha', function ($app) {
$recaptcha_class = '';
switch (config('recaptcha.version')) {
case 'v3':
$recaptcha_class = ReCaptchaBuilderV3::class;
break;
case 'v2':
$recaptcha_class = ReCaptchaBuilderV2::class;
break;
case 'invisible':
$recaptcha_class = ReCaptchaBuilderInvisible::class;
break;
}
return new $recaptcha_class(config('recaptcha.api_site_key'), config('recaptcha.api_secret_key'));
});
}
}
================================================
FILE: src/helpers.php
================================================
tag
*
* @param $config ['form_id'] required if you are using invisible ReCaptcha
*/
if (!function_exists('htmlScriptTagJsApi')) {
/**
* @param array|null $config
*
* @return string
*/
function htmlScriptTagJsApi(?array $config = []): string
{
return ReCaptcha::htmlScriptTagJsApi($config);
}
}
/**
* call ReCaptcha::htmlFormButton()
* Write HTML