diff --git a/composer.json b/composer.json index 6b58eda..f6046d0 100644 --- a/composer.json +++ b/composer.json @@ -25,12 +25,13 @@ }, "require": { "php": "^7.2 | ^8.0", + "ext-intl": "*", "cakephp/cakephp": "^4.0" }, "require-dev": { "cakephp/cakephp-codesniffer": "^4.0", "php-parallel-lint/php-parallel-lint": "^1.0", - "phpro/grumphp": "^v1.0", + "phpro/grumphp": "^v0.19 | ^v1.0", "phpunit/phpunit": "^8.0 | ^9.0" }, "scripts": { diff --git a/src/View/Widget/HCaptchaWidget.php b/src/View/Widget/HCaptchaWidget.php index e88abb4..614c9c5 100644 --- a/src/View/Widget/HCaptchaWidget.php +++ b/src/View/Widget/HCaptchaWidget.php @@ -9,6 +9,8 @@ use Cake\View\StringTemplate; use Cake\View\View; use Cake\View\Widget\WidgetInterface; +use Laminas\Diactoros\Uri; +use Locale; /** * Class HCaptchaWidget @@ -27,11 +29,21 @@ class HCaptchaWidget implements WidgetInterface */ private $_view; + /** + * @var string + */ + protected $_apiUrl = 'https://hcaptcha.com/1/api.js'; + + /** + * @var string[] + */ + protected $_renderAllowedValues = ['explicit', 'onload']; + /** * HCaptchaWidget constructor. * - * @param \Cake\View\StringTemplate $templates String templates - * @param \Cake\View\View $view Cake view + * @param \Cake\View\StringTemplate $templates String templates + * @param \Cake\View\View $view Cake view */ public function __construct(StringTemplate $templates, View $view) { @@ -43,8 +55,8 @@ public function __construct(StringTemplate $templates, View $view) /** * Render HCaptcha div and append javascript call to script block * - * @param array $data The data to render. - * @param \Cake\View\Form\ContextInterface $context The current form context. + * @param array $data The data to render. + * @param \Cake\View\Form\ContextInterface $context The current form context. * @return string Generated HTML for the widget element. */ public function render(array $data, ContextInterface $context): string @@ -52,6 +64,10 @@ public function render(array $data, ContextInterface $context): string $data += [ 'fieldName' => '', 'withoutJs' => false, + 'onload' => null, + 'render' => null, + 'lang' => null, + 'recaptchacompat' => null, ]; $this->_view->Form->unlockField($data['fieldName']); @@ -59,7 +75,34 @@ public function render(array $data, ContextInterface $context): string // Append js if (!$data['withoutJs']) { - $this->_view->Html->script('https://hcaptcha.com/1/api.js', ['block' => 'script']); + $uri = new Uri($this->_apiUrl); + $queryArgs = []; + + if ($data['onload']) { + $queryArgs['onload'] = h($data['onload']); + } + if ($data['render'] && in_array($data['render'], $this->_renderAllowedValues)) { + $queryArgs['render'] = h($data['render']); + } + + if ($data['lang']) { + $locale = Locale::parseLocale((string)$data['lang']); + if (!empty($locale['language'])) { + $queryArgs['hl'] = $locale['language']; + } + } + + if ($data['recaptchacompat'] !== null) { + $queryArgs['recaptchacompat'] = in_array( + $data['recaptchacompat'], + [1, '1', 'y', 'Y', 'yes', 'on'] + ) ? 'on' : 'off'; + } + + $url = $uri->withQuery(http_build_query($queryArgs)) + ->__toString(); + + $this->_view->Html->script($url, ['block' => 'script', 'async', 'defer']); } $key = Configure::read('HCaptcha.key'); @@ -73,7 +116,7 @@ public function render(array $data, ContextInterface $context): string /** * No field should be secured * - * @param array $data The data to render. + * @param array $data The data to render. * @return string[] Array of fields to secure. */ public function secureFields(array $data): array diff --git a/tests/TestCase/View/Widget/HCaptchaWidgetTest.php b/tests/TestCase/View/Widget/HCaptchaWidgetTest.php index e84e1c5..afc39f2 100644 --- a/tests/TestCase/View/Widget/HCaptchaWidgetTest.php +++ b/tests/TestCase/View/Widget/HCaptchaWidgetTest.php @@ -89,19 +89,45 @@ public function testRenderNoKey(): void * @test * @covers ::render */ - public function testRender(): void + public function testRenderSimple(): void { Configure::write('HCaptcha.key', 'testing-site-key'); $context = new ArrayContext([]); $this->html->expects($this->once()) ->method('script') - ->with('https://hcaptcha.com/1/api.js', ['block' => 'script']); + ->with('https://hcaptcha.com/1/api.js', ['block' => 'script', 'async', 'defer']); $result = $this->widget->render(['fieldName' => 'field'], $context); $this->assertSame('
', $result); } + /** + * @test + * @covers ::render + */ + public function testRenderWithOptions(): void + { + Configure::write('HCaptcha.key', 'testing-site-key'); + $context = new ArrayContext([]); + + $this->html->expects($this->once()) + ->method('script') + ->with( + 'https://hcaptcha.com/1/api.js?onload=myFunction&render=explicit&hl=fr&recaptchacompat=off', + ['block' => 'script', 'async', 'defer'] + ); + + $result = $this->widget->render([ + 'fieldName' => 'field', + 'lang' => 'fr_FR', + 'onload' => 'myFunction', + 'render' => 'explicit', + 'recaptchacompat' => false, + ], $context); + $this->assertSame('', $result); + } + /** * @test * @covers ::secureFields