Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,22 @@ $this->loadComponent('Recaptcha.Recaptcha', [
'secret' => 'your_secret',
'type' => 'image', // image/audio
'theme' => 'light', // light/dark
'lang' => 'vi', // default en
'lang' => 'en', // default 'en'
'size' => 'normal' // normal/compact
'callback' => null, // `callback` data attribute for the recaptcha div, default `null`
'scriptBlock' => true // Value for `block` option for HtmlHelper::script() call
]);
```

Override default configure from app config file:
Override default config from app config file:

```php
// file: config/app.php

/**
* Recaptcha configuration.
*
*/
'Recaptcha' => [
'enable' => true,
'sitekey' => 'your_site_key',
'secret' => 'your_secret',
'type' => 'image',
Expand All @@ -62,7 +62,7 @@ Override default configure from app config file:
Override default configure from recaptcha config file:

```php
// ffile: config/recaptcha.php
// file: config/recaptcha.php

return [
/**
Expand Down Expand Up @@ -96,12 +96,12 @@ Config preference:

## Usage

Display recaptcha in your view:
Display recaptcha in your template:

```php
<?= $this->Form->create() ?>
<?= $this->Form->control('email') ?>
// Display recaptcha box in your view, if configure has enable = false, nothing will be displayed
// Display recaptcha box in your template, if configure has enable = false, nothing will be displayed
<?= $this->Recaptcha->display() ?>
<?= $this->Form->button() ?>
<?= $this->Form->end() ?>
Expand Down
41 changes: 34 additions & 7 deletions src/Controller/Component/RecaptchaComponent.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,19 @@

use Cake\Controller\Component;
use Cake\Core\Configure;
use Cake\Event\EventInterface;
use Cake\Http\Client;
use Cake\I18n\I18n;
use Exception;
use Locale;

/**
* Recaptcha component
*/
class RecaptchaComponent extends Component
{
public const VERIFY_ENDPOINT = 'https://www.google.com/recaptcha/api/siteverify';

/**
* Default config
*
Expand All @@ -25,10 +31,12 @@ class RecaptchaComponent extends Component
'secret' => '6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe',
'theme' => 'light',
'type' => 'image',
'callback' => null,
'enable' => true,
'lang' => 'en',
'lang' => null,
'size' => 'normal',
'httpClientOptions' => [],
'scriptBlock' => true,
];

/**
Expand All @@ -39,11 +47,26 @@ class RecaptchaComponent extends Component
*/
public function initialize(array $config = []): void
{
if (empty($config)) {
$this->setConfig(Configure::read('Recaptcha', []));
$config += Configure::read('Recaptcha', []);
$this->setConfig($config);

if (!$this->getConfig('lang')) {
$this->setConfig('lang', Locale::getPrimaryLanguage(I18n::getLocale()));
}
}

/**
* beforeRender
*
* @param \Cake\Event\EventInterface $event Controller.beforeRender event
* @return void
*/
public function beforeRender(EventInterface $event): void
{
$config = $this->getConfig();
unset($config['secret'], $config['httpClientOptions']);

$this->getController()->viewBuilder()->addHelpers(['Recaptcha.Recaptcha' => $this->_config]);
$this->getController()->viewBuilder()->addHelpers(['Recaptcha.Recaptcha' => $config]);
}

/**
Expand All @@ -53,13 +76,17 @@ public function initialize(array $config = []): void
*/
public function verify(): bool
{
if (!$this->_config['enable']) {
if (!(bool)$this->_config['enable']) {
return true;
}

$controller = $this->_registry->getController();
if ($controller->getRequest()->getData('g-recaptcha-response')) {
$response = json_decode($this->apiCall());
try {
$response = json_decode($this->apiCall(), flags: JSON_THROW_ON_ERROR);
} catch (Exception $e) {
return false;
}

if (isset($response->success)) {
return (bool)$response->success;
Expand All @@ -85,6 +112,6 @@ protected function apiCall(): string
'remoteip' => $controller->getRequest()->clientIp(),
];

return (string)$client->post('https://www.google.com/recaptcha/api/siteverify', $data)->getBody();
return (string)$client->post(static::VERIFY_ENDPOINT, $data)->getBody();
}
}
31 changes: 29 additions & 2 deletions src/RecaptchaPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,43 @@ class RecaptchaPlugin extends BasePlugin
{
/**
* Do bootstrapping or not
*
* @var bool
*/
protected bool $bootstrapEnabled = false;

/**
* Console middleware
*
* @var bool
*/
protected bool $consoleEnabled = false;

/**
* Enable middleware
*
* @var bool
*/
protected bool $middlewareEnabled = false;

/**
* Register container services
*
* @var bool
*/
protected bool $servicesEnabled = false;

/**
* Load routes or not
*
* @var bool
*/
protected bool $routesEnabled = false;

/**
* Console middleware
* Load events or not
*
* @var bool
*/
protected bool $consoleEnabled = false;
protected bool $eventsEnabled = false;
}
52 changes: 49 additions & 3 deletions src/View/Helper/RecaptchaHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,68 @@

namespace Recaptcha\View\Helper;

use Cake\Core\Configure;
use Cake\I18n\I18n;
use Cake\View\Helper;
use Locale;

/**
* Recaptcha helper
*/
class RecaptchaHelper extends Helper
{
/**
* Default config
*
* These are merged with user-provided config when the component is used.
*
* @var array<string, mixed>
*/
protected array $_defaultConfig = [
// This is test only key
'sitekey' => '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI',
'theme' => 'light',
'type' => 'image',
'callback' => null,
'enable' => true,
'lang' => null,
'size' => 'normal',
'scriptBlock' => true,
];

/**
* Helpers
*
* @var array
*/
protected array $helpers = ['Form'];

/**
* initialize
*
* @param array $config config
* @return void
*/
public function initialize(array $config = []): void
{
$config += Configure::read('Recaptcha', []);
$this->setConfig($config);

if (!$this->getConfig('lang')) {
$this->setConfig('lang', Locale::getPrimaryLanguage(I18n::getLocale()));
}
}

/**
* Display recaptcha function
*
* @param array $config Config
* @return string
*/
public function display()
public function display(array $config = []): string
{
$recaptcha = $this->getConfig();
if (!$recaptcha['enable']) {
$recaptcha = $config + $this->getConfig();
if (!(bool)$recaptcha['enable']) {
return '';
}

Expand Down
32 changes: 23 additions & 9 deletions templates/element/recaptcha.php
Original file line number Diff line number Diff line change
@@ -1,19 +1,33 @@
<?= $this->Html->script('https://www.google.com/recaptcha/api.js?hl=' . $recaptcha['lang'] . '&onload=CaptchaCallback&render=explicit') ?>
<script type="text/javascript">
var CaptchaCallback = function() {
var el = document.getElementsByClassName('g-recaptcha');
for (var i = 0; i < el.length; i++)
grecaptcha.render(el[i], {'sitekey' : '<?= $recaptcha['sitekey'] ?>'});
};
</script>
<?php
/**
* @var \Cake\View\View $this
*/
use Cake\Core\Exception\CakeException;

try {
$this->Form->unlockField('g-recaptcha-response');
} catch (CakeException) {
// If FormProtectorComponent is not loaded, an exception in thrown in older CakePHP versions.
}
?>
<?= $this->Html->script(
'https://www.google.com/recaptcha/api.js?hl=' . $recaptcha['lang'],
[
'block' => $recaptcha['scriptBlock'],
'async' => true,
'defer' => true,
]
) ?>
<div
class="g-recaptcha"
data-sitekey="<?= $recaptcha['sitekey'] ?>"
data-theme="<?= $recaptcha['theme'] ?>"
data-type="<?= $recaptcha['type'] ?>"
data-size="<?= $recaptcha['size'] ?>"
async defer>
<?php if ($recaptcha['callback']) : ?>
data-callback="<?= $recaptcha['callback'] ?>"
<?php endif ?>
>
</div>
<noscript>
<div>
Expand Down
22 changes: 21 additions & 1 deletion tests/TestCase/Controller/Component/RecaptchaComponentTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

use Cake\Controller\ComponentRegistry;
use Cake\Controller\Controller;
use Cake\Event\Event;
use Cake\Http\ServerRequest as Request;
use Cake\TestSuite\TestCase;
use Recaptcha\Controller\Component\RecaptchaComponent;
Expand Down Expand Up @@ -32,12 +33,31 @@ public function setUp(): void
'sitekey' => 'sitekey',
'theme' => 'theme',
'type' => 'type',
'lang' => 'lang',
],
])
->getMock();
}

public function testInitialize()
{
$this->Recaptcha->initialize();
$this->assertEquals('en', $this->Recaptcha->getConfig('lang'));

$this->Recaptcha->setConfig('lang', 'ja');
$this->Recaptcha->initialize();
$this->assertEquals('ja', $this->Recaptcha->getConfig('lang'));
}

public function testBeforeRender(): void
{
$this->Recaptcha->beforeRender(new Event('Controller.beforeRender'));
$helpers = $this->controller->viewBuilder()->getHelpers();
$this->assertArrayHasKey('Recaptcha', $helpers);

$this->assertArrayHasKey('sitekey', $helpers['Recaptcha']);
$this->assertArrayNotHasKey('secret', $helpers['Recaptcha']);
}

public function testVerifyFalse(): void
{
$this->assertFalse($this->Recaptcha->verify());
Expand Down
15 changes: 12 additions & 3 deletions tests/TestCase/View/Helper/RecaptchaHelperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,30 @@ public function setUp(): void
$this->Recaptcha = new RecaptchaHelper(
$this->View,
[
'enable' => true,
'sitekey' => 'sitekey',
'theme' => 'theme',
'type' => 'type',
'lang' => 'lang',
'size' => 'size',
'callback' => 'callback',
]
);
}

public function testDisplay(): void
{
$result = $this->Recaptcha->display();
$this->assertTrue(is_string($result));
$result = $this->Recaptcha->display(['scriptBlock' => null]);
$this->assertStringContainsString('class="g-recaptcha"', $result);
$this->assertStringContainsString('data-sitekey="sitekey"', $result);
$this->assertStringContainsString('data-theme="theme"', $result);
$this->assertStringContainsString('data-type="type"', $result);
$this->assertStringContainsString('data-size="size"', $result);
$this->assertStringContainsString('data-callback="callback"', $result);

$this->assertStringContainsString('https://www.google.com/recaptcha/api.js?hl=lang', $result);

$result = $this->Recaptcha->display();
$this->assertStringNotContainsString('https://www.google.com/recaptcha/api.js', $result);

$this->Recaptcha->setConfig('enable', false);
$this->assertEmpty($this->Recaptcha->display());
Expand Down