From 92ac717fffcd313a67142f3079b4119098babda2 Mon Sep 17 00:00:00 2001 From: Kai Rohrpasser Date: Fri, 30 Jan 2026 10:20:11 +0100 Subject: [PATCH 1/3] Update code to use the v2 api from Friendy-Captcha --- lang/de/validation.php | 15 +++++---- lang/en/validation.php | 15 +++++---- src/FriendlyCaptcha.php | 44 ++++++++++---------------- src/FriendlyCaptchaServiceProvider.php | 15 ++------- src/Rules/FriendlyCaptcha.php | 32 ++++++++----------- tests/FriendlyCaptchaTest.php | 26 ++++----------- 6 files changed, 55 insertions(+), 92 deletions(-) diff --git a/lang/de/validation.php b/lang/de/validation.php index e336be3..235b427 100644 --- a/lang/de/validation.php +++ b/lang/de/validation.php @@ -7,12 +7,13 @@ */ return [ - 'secret_missing' => 'Sie haben vergessen, den Parameter secret (=API-Schlüssel) hinzuzufügen.', - 'secret_invalid' => 'Der von Ihnen angegebene API-Schlüssel war ungültig.', - 'solution_missing' => 'Sie haben vergessen, den Parameter secret (=API-Schlüssel) hinzuzufügen.', - 'secret_missing' => 'Sie haben vergessen, den Lösungsparameter hinzuzufügen.', 'bad_request' => 'Mit Ihrer Anfrage ist etwas anderes nicht in Ordnung, z. B. ist Ihr Anfragekörper leer.', - 'solution_invalid' => 'Die von Ihnen angegebene Lösung war ungültig (vielleicht wurde versucht, das Rätsel zu manipulieren).', - 'solution_timeout_or_duplicate' => 'Das Rätsel, für das Sie die Lösung angegeben haben, ist abgelaufen oder wurde bereits verwendet.', + 'auth_required' => 'Authentifizierung erforderlich.', + 'auth_invalid' => 'Authentifizierung fehlgeschlagen.', + 'sitekey_invalid' => 'Der Sitekey ist ungültig.', + 'response_missing' => 'Die Captcha-Lösung fehlt.', + 'response_invalid' => 'Die Captcha-Lösung ist falsch.', + 'response_timeout' => 'Anfrage abgelaufen.', + 'response_duplicate' => 'Diese Lösung wurde bereits verwendet.', 'unexpected' => 'Ein unerwarteter Fehler ist aufgetreten.' -]; \ No newline at end of file +]; diff --git a/lang/en/validation.php b/lang/en/validation.php index ae73955..f0d2b49 100644 --- a/lang/en/validation.php +++ b/lang/en/validation.php @@ -7,12 +7,13 @@ */ return [ - 'secret_missing' => 'You forgot to add the secret (=API key) parameter.', - 'secret_invalid' => 'The API key you provided was invalid.', - 'solution_missing' => 'You forgot to add the secret (=API key) parameter.', - 'secret_missing' => 'You forgot to add the solution parameter.', 'bad_request' => 'Something else is wrong with your request, e.g. your request body is empty.', - 'solution_invalid' => 'The solution you provided was invalid (perhaps the user tried to tamper with the puzzle).', - 'solution_timeout_or_duplicate' => 'The puzzle that the solution was for has expired or has already been used.', + 'auth_required' => 'Authentification required.', + 'auth_invalid' => 'Authentification failed.', + 'sitekey_invalid' => 'The sitekey is invalid.', + 'response_missing' => 'The captcha solution is missing.', + 'response_invalid' => 'The captcha solution is invalid.', + 'response_timeout' => 'Connection timed out.', + 'response_duplicate' => 'This solution has already been used.', 'unexpected' => 'An unexpected error occurred.' -]; \ No newline at end of file +]; diff --git a/src/FriendlyCaptcha.php b/src/FriendlyCaptcha.php index e52b25f..ed3b41c 100755 --- a/src/FriendlyCaptcha.php +++ b/src/FriendlyCaptcha.php @@ -20,11 +20,6 @@ class FriendlyCaptcha */ protected $sitekey; - /** - * FriendlyCaptcha Puzzle endpoint - */ - protected $puzzle; - /** * FriendlyCaptcha verify endpoint */ @@ -44,27 +39,19 @@ class FriendlyCaptcha */ protected $http; - public function __construct($secret, $sitekey, $puzzle, $verify, $options = []) + public function __construct($secret, $sitekey, $verify, $options = []) { $this->secret = $secret; $this->sitekey = $sitekey; - $this->puzzle = $puzzle; $this->verify = $verify; $this->http = new Client($options); } - public function renderWidgetScripts($option = 'unpkg') + public function renderWidgetScripts(): string { - if ($option == 'unpkg') { - return << - - EOF; - } - return << - + + EOF; } @@ -83,8 +70,6 @@ public function renderWidget($attributes = []) */ protected function prepareAttributes(array $attributes) { - $attributes['data-puzzle-endpoint'] = $this->puzzle; - $attributes['data-sitekey'] = $this->sitekey; if (isset($attributes['dark-theme'])) { @@ -130,7 +115,7 @@ protected function buildAttributes(array $attributes) * * @return bool */ - public function verifyRequest($solution) + public function verifyRequest(string $solution) { return $this->verifyResponse( $solution, @@ -144,17 +129,19 @@ public function verifyRequest($solution) * * @return self */ - public function verifyResponse($solution) + public function verifyResponse(string $solution) { if (empty($solution)) { return false; } - $verifyResponse = $this->sendRequestVerify([ - 'solution' => $solution, - 'secret' => $this->secret, - 'sitekey' => $this->sitekey, - ]); + $verifyResponse = $this->sendRequestVerify( + ['X-API-Key' => $this->secret], + [ + 'response' => $solution, + 'sitekey' => $this->sitekey, + ] + ); if (isset($verifyResponse['success']) && $verifyResponse['success'] === true) { $this->isSuccess = true; @@ -178,13 +165,14 @@ public function verifyResponse($solution) /** * Send verify request. * + * @param array $headers * @param array $data - * * @return array */ - protected function sendRequestVerify(array $data = []) + protected function sendRequestVerify(array $headers = [], array $data = []): array { $response = $this->http->request('POST', $this->verify, [ + 'headers' => $headers, 'form_params' => $data, ]); diff --git a/src/FriendlyCaptchaServiceProvider.php b/src/FriendlyCaptchaServiceProvider.php index bef41b8..21691dd 100644 --- a/src/FriendlyCaptchaServiceProvider.php +++ b/src/FriendlyCaptchaServiceProvider.php @@ -49,19 +49,10 @@ protected function bootConfig() */ public function bootBladeDirectives() { - Blade::directive('friendlyCaptchaRenderWidgetScripts', function ($option) { - $option = trim($option, "'"); - - if (empty($option) || $option == 'unpkg') { - return << - - EOF; - } - + Blade::directive('friendlyCaptchaRenderWidgetScripts', function () { return << - + + EOF; }); } diff --git a/src/Rules/FriendlyCaptcha.php b/src/Rules/FriendlyCaptcha.php index 06040b8..932d9bc 100644 --- a/src/Rules/FriendlyCaptcha.php +++ b/src/Rules/FriendlyCaptcha.php @@ -45,24 +45,20 @@ public function message() protected function mapErrorCodeToMessage(string $code): string { switch ($code) { - case "secret_missing": - return __('validation.secret_missing'); - break; - case "secret_invalid": - return __('validation.secret_invalid'); - break; - case "solution_missing": - return __('validation.solution_missing'); - break; - case "bad_request": - return __('validation.bad_request'); - break; - case "solution_invalid": - return __('validation.solution_invalid'); - break; - case "solution_timeout_or_duplicate": - return __('validation.solution_timeout_or_duplicate'); - break; + case "auth_required": + return __('validation.auth_required'); + case "auth_invalid": + return __('validation.auth_invalid'); + case "sitekey_invalid": + return __('validation.sitekey_invalid'); + case "response_missing": + return __('validation.response_missing'); + case "response_invalid": + return __('validation.response_invalid'); + case "response_timeout": + return __('validation.response_timeout'); + case "response_duplicate": + return __('validation.response_duplicate'); default: return __('validation.unexpected'); } diff --git a/tests/FriendlyCaptchaTest.php b/tests/FriendlyCaptchaTest.php index f6686d6..7c29740 100644 --- a/tests/FriendlyCaptchaTest.php +++ b/tests/FriendlyCaptchaTest.php @@ -22,37 +22,23 @@ public function setUp(): void { parent::setUp(); - $this->captcha = new FriendlyCaptcha('{secret-key}', '{site-key}', 'https://api.friendlycaptcha.com/api/v1/puzzle', 'https://api.friendlycaptcha.com/api/v1/siteverify'); + $this->captcha = new FriendlyCaptcha('{secret-key}', '{site-key}', 'https://global.frcapi.com/api/v2/captcha/siteverify'); } /** * @test */ - public function it_can_render_unpkg_widget_script_correctly() + public function it_can_render_jsdelivr_widget_script_correctly() { $this->assertTrue($this->captcha instanceof FriendlyCaptcha); - $expectedScriptOne = ''; - $expectedScriptTwo = ''; + $expectedScriptOne = ''; + $expectedScriptTwo = ''; $this->assertStringContainsString($expectedScriptOne, $this->captcha->renderWidgetScripts()); $this->assertStringContainsString($expectedScriptTwo, $this->captcha->renderWidgetScripts()); } - /** - * @test - */ - public function it_can_render_jsdelivr_widget_script_correctly() - { - $this->assertTrue($this->captcha instanceof FriendlyCaptcha); - - $expectedScriptOne = ''; - $expectedScriptTwo = ''; - - $this->assertStringContainsString($expectedScriptOne, $this->captcha->renderWidgetScripts('jsdelivr')); - $this->assertStringContainsString($expectedScriptTwo, $this->captcha->renderWidgetScripts('jsdelivr')); - } - /** * @test */ @@ -60,8 +46,8 @@ public function it_can_render_widget_correctly() { $this->assertTrue($this->captcha instanceof FriendlyCaptcha); - $expectedWidget = '
'; - $expectedWidgetWithCustomAttributes = '
'; + $expectedWidget = '
'; + $expectedWidgetWithCustomAttributes = '
'; $this->assertEquals($expectedWidget, $this->captcha->renderWidget()); $this->assertEquals($expectedWidgetWithCustomAttributes, $this->captcha->renderWidget(['dark-theme' => true])); From 56acdaea2822bba911d47b87d405ba83d59471d8 Mon Sep 17 00:00:00 2001 From: Kai Rohrpasser Date: Wed, 8 Apr 2026 18:05:09 +0200 Subject: [PATCH 2/3] Update changelog; fix spelling and constructor --- CHANGELOG.md | 16 ++++++++++++++++ lang/en/validation.php | 4 ++-- src/FriendlyCaptcha.php | 6 +++++- src/FriendlyCaptchaServiceProvider.php | 1 - src/Rules/FriendlyCaptcha.php | 2 ++ src/config/friendlycaptcha.php | 3 +-- 6 files changed, 26 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa54ca2..19f47ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ All notable changes to `ossycodes/friendlycaptcha` will be documented in this file +## 2.0.0 - 2026-04.08 +- Replace the friendly-challenge scripts +```html + + +``` +with the new @friendlycaptcha/sdk scripts +```html + + +``` + +For more information visit: https://developer.friendlycaptcha.com/docs/v2/guides/upgrading-from-v1/script + ## 1.0.0 - 2021-10-21 - initial release diff --git a/lang/en/validation.php b/lang/en/validation.php index f0d2b49..0ab2631 100644 --- a/lang/en/validation.php +++ b/lang/en/validation.php @@ -8,8 +8,8 @@ return [ 'bad_request' => 'Something else is wrong with your request, e.g. your request body is empty.', - 'auth_required' => 'Authentification required.', - 'auth_invalid' => 'Authentification failed.', + 'auth_required' => 'Authentication required.', + 'auth_invalid' => 'Authentication failed.', 'sitekey_invalid' => 'The sitekey is invalid.', 'response_missing' => 'The captcha solution is missing.', 'response_invalid' => 'The captcha solution is invalid.', diff --git a/src/FriendlyCaptcha.php b/src/FriendlyCaptcha.php index ed3b41c..4c33c78 100755 --- a/src/FriendlyCaptcha.php +++ b/src/FriendlyCaptcha.php @@ -3,6 +3,7 @@ namespace Ossycodes\FriendlyCaptcha; use GuzzleHttp\Client; +use GuzzleHttp\Exception\GuzzleException; class FriendlyCaptcha { @@ -35,7 +36,7 @@ class FriendlyCaptcha public $isSuccess = false; /** - * @var \GuzzleHttp\Client + * @var Client */ protected $http; @@ -114,6 +115,7 @@ protected function buildAttributes(array $attributes) * @param string $solution * * @return bool + * @throws GuzzleException */ public function verifyRequest(string $solution) { @@ -128,6 +130,7 @@ public function verifyRequest(string $solution) * @param string $solution * * @return self + * @throws GuzzleException */ public function verifyResponse(string $solution) { @@ -168,6 +171,7 @@ public function verifyResponse(string $solution) * @param array $headers * @param array $data * @return array + * @throws GuzzleException */ protected function sendRequestVerify(array $headers = [], array $data = []): array { diff --git a/src/FriendlyCaptchaServiceProvider.php b/src/FriendlyCaptchaServiceProvider.php index 21691dd..f49238f 100644 --- a/src/FriendlyCaptchaServiceProvider.php +++ b/src/FriendlyCaptchaServiceProvider.php @@ -90,7 +90,6 @@ public function register() return new FriendlyCaptcha( $app['config']['friendlycaptcha.secret'], $app['config']['friendlycaptcha.sitekey'], - $app['config']['friendlycaptcha.puzzle_endpoint'], $app['config']['friendlycaptcha.verify_endpoint'], $app['config']['friendlycaptcha.options'] ); diff --git a/src/Rules/FriendlyCaptcha.php b/src/Rules/FriendlyCaptcha.php index 932d9bc..54e6ca0 100644 --- a/src/Rules/FriendlyCaptcha.php +++ b/src/Rules/FriendlyCaptcha.php @@ -59,6 +59,8 @@ protected function mapErrorCodeToMessage(string $code): string return __('validation.response_timeout'); case "response_duplicate": return __('validation.response_duplicate'); + case "bad_request": + return __('validation.bad_request'); default: return __('validation.unexpected'); } diff --git a/src/config/friendlycaptcha.php b/src/config/friendlycaptcha.php index 9dcce25..456d908 100644 --- a/src/config/friendlycaptcha.php +++ b/src/config/friendlycaptcha.php @@ -3,8 +3,7 @@ return [ 'secret' => env('FRIENDLY_CAPTCHA_SECRET'), 'sitekey' => env('FRIENDLY_CAPTCHA_SITEKEY'), - 'puzzle_endpoint' => env('FRIENDLY_CAPTCHA_PUZZLE_ENDPOINT', 'https://api.friendlycaptcha.com/api/v1/puzzle'), - 'verify_endpoint' => env('FRIENDLY_CAPTCHA_VERIFY_ENDPOINT', 'https://api.friendlycaptcha.com/api/v1/siteverify'), + 'verify_endpoint' => env('FRIENDLY_CAPTCHA_VERIFY_ENDPOINT', 'https://global.frcapi.com/api/v2/captcha/siteverify'), //see https://developer.friendlycaptcha.com/docs/v2/guides/upgrading-from-v1/backend-integration 'options' => [ 'timeout' => 30, 'http_errors' => false, From 473d30f1af75101645b97f5d88c35f2ba1fc6354 Mon Sep 17 00:00:00 2001 From: ossycodes Date: Wed, 27 May 2026 16:25:29 +0100 Subject: [PATCH 3/3] Fix all review issues from PR #10 review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix: verifyResponse() now always returns $this instead of false/bool, preventing fatal TypeError when captcha field is missing - Fix: rename $error to $errors to match usage throughout the class - Fix: sendRequestVerify() uses json instead of form_params for v2 JSON API - Fix: SDK version aligned to @0.2.0 across all files - Fix: bootLang() removed — translations loaded directly in boot() instead of overwriting the Rule macro registered by bootMacro() - Fix: Rules/FriendlyCaptcha now implements ValidationRule (Laravel 10+) instead of the removed Illuminate\Contracts\Validation\Rule interface - Fix: test methods renamed with test_ prefix (PHPUnit 11 removed @test) - Fix: CHANGELOG version corrected to 4.0.0 Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 2 +- src/FriendlyCaptcha.php | 14 +++++---- src/FriendlyCaptchaServiceProvider.php | 18 +++-------- src/Rules/FriendlyCaptcha.php | 43 +++++++++----------------- tests/FriendlyCaptchaTest.php | 16 +++------- 5 files changed, 33 insertions(+), 60 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19f47ed..8f4aece 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to `ossycodes/friendlycaptcha` will be documented in this file -## 2.0.0 - 2026-04.08 +## 4.0.0 - 2026-04-08 - Replace the friendly-challenge scripts ```html diff --git a/src/FriendlyCaptcha.php b/src/FriendlyCaptcha.php index 4c33c78..d0ab090 100755 --- a/src/FriendlyCaptcha.php +++ b/src/FriendlyCaptcha.php @@ -31,7 +31,7 @@ class FriendlyCaptcha * * @var array */ - protected $error = []; + protected $errors = []; public $isSuccess = false; @@ -51,8 +51,8 @@ public function __construct($secret, $sitekey, $verify, $options = []) public function renderWidgetScripts(): string { return << - + + EOF; } @@ -132,10 +132,12 @@ public function verifyRequest(string $solution) * @return self * @throws GuzzleException */ - public function verifyResponse(string $solution) + public function verifyResponse(string $solution): self { if (empty($solution)) { - return false; + $this->isSuccess = false; + $this->errors = []; + return $this; } $verifyResponse = $this->sendRequestVerify( @@ -177,7 +179,7 @@ protected function sendRequestVerify(array $headers = [], array $data = []): arr { $response = $this->http->request('POST', $this->verify, [ 'headers' => $headers, - 'form_params' => $data, + 'json' => $data, ]); return json_decode($response->getBody(), true); diff --git a/src/FriendlyCaptchaServiceProvider.php b/src/FriendlyCaptchaServiceProvider.php index f49238f..85bed0a 100644 --- a/src/FriendlyCaptchaServiceProvider.php +++ b/src/FriendlyCaptchaServiceProvider.php @@ -25,11 +25,11 @@ public function boot() $this->bootConfig(); } + $this->loadTranslationsFrom(__DIR__.'/../lang', 'friendlycaptcha'); + $this->bootBladeDirectives(); $this->bootMacro(); - - $this->bootLang(); } /** @@ -51,8 +51,8 @@ public function bootBladeDirectives() { Blade::directive('friendlyCaptchaRenderWidgetScripts', function () { return << - + + EOF; }); } @@ -67,16 +67,6 @@ public function bootMacro() }); } - /** - * boot lang - */ - public function bootLang() - { - Rule::macro('friendlycaptcha', function () { - $this->loadTranslationsFrom(__DIR__.'/../lang', 'friendlycaptcha'); - }); - } - /** * Register the application services. */ diff --git a/src/Rules/FriendlyCaptcha.php b/src/Rules/FriendlyCaptcha.php index 54e6ca0..34d20fc 100644 --- a/src/Rules/FriendlyCaptcha.php +++ b/src/Rules/FriendlyCaptcha.php @@ -2,67 +2,54 @@ namespace Ossycodes\FriendlyCaptcha\Rules; -use Illuminate\Contracts\Validation\Rule; +use Closure; +use Illuminate\Contracts\Validation\ValidationRule; use Ossycodes\FriendlyCaptcha\FriendlyCaptcha as FriendlyCaptchaClient; -class FriendlyCaptcha implements Rule +class FriendlyCaptcha implements ValidationRule { protected $friendlyCaptchaClient; - protected array $messages = []; - public function __construct( FriendlyCaptchaClient $friendlyCaptcha ) { $this->friendlyCaptchaClient = $friendlyCaptcha; } - public function passes($attribute, $value) + public function validate(string $attribute, mixed $value, Closure $fail): void { $response = $this->friendlyCaptchaClient->verifyResponse($value); if ($response->isSuccess()) { - return true; + return; } foreach ($response->getErrors() as $errorCode) { - $this->messages[] = $this->mapErrorCodeToMessage($errorCode); + $fail($this->mapErrorCodeToMessage($errorCode)); } - - return false; - } - - public function message() - { - return $this->messages; } - /** - * map FriendlyCaptcha error code to human readable validation message - * - * @var string $code - */ protected function mapErrorCodeToMessage(string $code): string { switch ($code) { case "auth_required": - return __('validation.auth_required'); + return __('friendlycaptcha::validation.auth_required'); case "auth_invalid": - return __('validation.auth_invalid'); + return __('friendlycaptcha::validation.auth_invalid'); case "sitekey_invalid": - return __('validation.sitekey_invalid'); + return __('friendlycaptcha::validation.sitekey_invalid'); case "response_missing": - return __('validation.response_missing'); + return __('friendlycaptcha::validation.response_missing'); case "response_invalid": - return __('validation.response_invalid'); + return __('friendlycaptcha::validation.response_invalid'); case "response_timeout": - return __('validation.response_timeout'); + return __('friendlycaptcha::validation.response_timeout'); case "response_duplicate": - return __('validation.response_duplicate'); + return __('friendlycaptcha::validation.response_duplicate'); case "bad_request": - return __('validation.bad_request'); + return __('friendlycaptcha::validation.bad_request'); default: - return __('validation.unexpected'); + return __('friendlycaptcha::validation.unexpected'); } } } diff --git a/tests/FriendlyCaptchaTest.php b/tests/FriendlyCaptchaTest.php index 7c29740..e87a8f7 100644 --- a/tests/FriendlyCaptchaTest.php +++ b/tests/FriendlyCaptchaTest.php @@ -14,7 +14,7 @@ protected function getPackageProviders($app) } /** - * @var FriendlyCaptchaTest + * @var FriendlyCaptcha */ private $captcha; @@ -25,24 +25,18 @@ public function setUp(): void $this->captcha = new FriendlyCaptcha('{secret-key}', '{site-key}', 'https://global.frcapi.com/api/v2/captcha/siteverify'); } - /** - * @test - */ - public function it_can_render_jsdelivr_widget_script_correctly() + public function test_it_can_render_widget_scripts_correctly() { $this->assertTrue($this->captcha instanceof FriendlyCaptcha); - $expectedScriptOne = ''; - $expectedScriptTwo = ''; + $expectedScriptOne = ''; + $expectedScriptTwo = ''; $this->assertStringContainsString($expectedScriptOne, $this->captcha->renderWidgetScripts()); $this->assertStringContainsString($expectedScriptTwo, $this->captcha->renderWidgetScripts()); } - /** - * @test - */ - public function it_can_render_widget_correctly() + public function test_it_can_render_widget_correctly() { $this->assertTrue($this->captcha instanceof FriendlyCaptcha);