diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index ad3ff11..d31c148 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -68,7 +68,7 @@ jobs: if: contains(matrix.symfony, '@dev') - run: composer update --no-interaction --no-progress --ansi ${{ matrix.composer_option }} - run: | - sed -ri 's/"symfony\/(.+)": "(.+)"/"symfony\/\1": "'${{ matrix.symfony }}'"/' composer.json; + sed -ri 's/"symfony\/(config|dependency-injection|form|http-kernel|validator)": "(.+)"/"symfony\/\1": "'${{ matrix.symfony }}'"/' composer.json; if: matrix.symfony - run: composer update --no-interaction --no-progress --ansi ${{ matrix.composer_option }} - name: Run tests diff --git a/composer.json b/composer.json index 5368778..39c49fd 100644 --- a/composer.json +++ b/composer.json @@ -26,6 +26,7 @@ }, "require-dev": { "phpunit/phpunit": "^9.6", + "symfony/http-client-contracts": "^3.5", "symfony/phpunit-bridge": "^7.2" }, "suggest": { diff --git a/config/services.xml b/config/services.xml index 7350148..ae280fa 100644 --- a/config/services.xml +++ b/config/services.xml @@ -10,6 +10,11 @@ %beelab_recaptcha2.secret% + + + + + %beelab_recaptcha2.site_key% diff --git a/docs/index.md b/docs/index.md index 16dc829..82d4ba1 100644 --- a/docs/index.md +++ b/docs/index.md @@ -40,18 +40,22 @@ beelab_recaptcha2: If your PHP environment has restrictions about `file_get_contents()` making HTTP requests, you can use another `RequestMethod` from Google's Recaptcha library. -Currently, this bundle supports the default `Post` and `CurlPost` methods. -You can use the latter by adding in your `config.yml`: +Currently, this bundle supports the default `Post` and `CurlPost` methods, and an additional +method leveraging the [Symfony HTTP Client][1]. +You can define it by adding in your configuration: ```yaml # config/packages/beelab_recaptcha2.yaml beelab_recaptcha2: - request_method: curl_post + request_method: curl_post # or http_client ``` Otherwise, the default value `post` will be used. +If you want to use the `http_client` request method, you need to require `symfony/http-client`. + + ## 3. Usage In your form, use `Beelab\Recaptcha2Bundle\Form\Type\RecaptchaType` form type, as any other Symfony form type. @@ -125,3 +129,4 @@ You can add to your `_form_theme.html.twig` file the following lines: {%- endblock %} ``` +[1]: https://symfony.com/doc/current/http_client.html diff --git a/src/DependencyInjection/BeelabRecaptcha2Extension.php b/src/DependencyInjection/BeelabRecaptcha2Extension.php index 2760b58..7f7d1ee 100644 --- a/src/DependencyInjection/BeelabRecaptcha2Extension.php +++ b/src/DependencyInjection/BeelabRecaptcha2Extension.php @@ -2,6 +2,7 @@ namespace Beelab\Recaptcha2Bundle\DependencyInjection; +use Beelab\Recaptcha2Bundle\Recaptcha\SymfonyClientRequestMethod; use ReCaptcha\RequestMethod\CurlPost; use ReCaptcha\RequestMethod\Post; use Symfony\Component\Config\FileLocator; @@ -30,6 +31,7 @@ private function getRequestMethod(string $requestMethod): string { return match ($requestMethod) { 'curl_post' => CurlPost::class, + 'http_client' => SymfonyClientRequestMethod::class, default => Post::class, }; } diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 8d0dafd..714e24e 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -14,7 +14,7 @@ public function getConfigTreeBuilder(): TreeBuilder $rootNode ->children() ->enumNode('request_method') - ->values(['curl_post', 'post']) + ->values(['curl_post', 'post', 'http_client']) ->defaultValue('post') ->end() ->scalarNode('site_key') diff --git a/src/Recaptcha/SymfonyClientRequestMethod.php b/src/Recaptcha/SymfonyClientRequestMethod.php new file mode 100644 index 0000000..62e74ed --- /dev/null +++ b/src/Recaptcha/SymfonyClientRequestMethod.php @@ -0,0 +1,43 @@ +client = $client; + } + + public function submit(RequestParameters $params): string + { + if (null === $this->client) { + throw new \UnexpectedValueException('Needed service is not injected.'); + } + + $response = $this->client->request('POST', $this->siteVerifyUrl, [ + 'body' => $params->toQueryString(), + 'headers' => [ + 'Content-Type' => 'application/x-www-form-urlencoded', + ], + ]); + + if (200 !== $response->getStatusCode()) { + return '{"success": false, "error-codes": ["'.ReCaptcha::E_CONNECTION_FAILED.'"]}'; + } + + return $response->getContent(); + } +} diff --git a/tests/Recaptcha/SymfonyClientRequestMethodTest.php b/tests/Recaptcha/SymfonyClientRequestMethodTest.php new file mode 100644 index 0000000..9b40f8a --- /dev/null +++ b/tests/Recaptcha/SymfonyClientRequestMethodTest.php @@ -0,0 +1,51 @@ +client = $this->createMock(HttpClientInterface::class); + } + + public function testServiceNotInjected(): void + { + $method = new SymfonyClientRequestMethod(); + $this->expectException(\UnexpectedValueException::class); + + $method->submit(new RequestParameters('', '')); + } + + public function testRequestFailure(): void + { + $method = new SymfonyClientRequestMethod(); + $method->setClient($this->client); + $response = $this->createMock(ResponseInterface::class); + $response->expects($this->once())->method('getStatusCode')->willReturn(404); + $this->client->expects($this->once())->method('request')->willReturn($response); + $content = $method->submit(new RequestParameters('', '')); + self::assertEquals('{"success": false, "error-codes": ["connection-failed"]}', $content); + } + + public function testRequestSuccess(): void + { + $method = new SymfonyClientRequestMethod(); + $method->setClient($this->client); + $response = $this->createMock(ResponseInterface::class); + $response->expects($this->once())->method('getStatusCode')->willReturn(200); + $response->expects($this->once())->method('getContent')->willReturn('"OK"'); + $this->client->expects($this->once())->method('request')->willReturn($response); + $content = $method->submit(new RequestParameters('', '')); + self::assertEquals('"OK"', $content); + } +}