From d6bc8375952078eedc32e9c4d6b840b5a8d44327 Mon Sep 17 00:00:00 2001 From: BoShurik Date: Tue, 26 Sep 2023 20:00:56 +0600 Subject: [PATCH] Fix sending files via third-party clients --- .github/workflows/tests.yaml | 2 +- composer.json | 7 ++++++ src/Http/PsrHttpClient.php | 43 ++++++++++++++++++++++++++++++++-- src/Http/SymfonyHttpClient.php | 6 +++++ 4 files changed, 55 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 8b0618cf..b666cc08 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -61,7 +61,7 @@ jobs: - name: Remove http client dependencies - run: composer remove psr/http-client psr/http-factory symfony/http-client guzzlehttp/guzzle --dev --no-update + run: composer remove psr/http-client psr/http-factory php-http/multipart-stream-builder symfony/http-client guzzlehttp/guzzle --dev --no-update - name: Install dependencies with composer diff --git a/composer.json b/composer.json index b835131b..cdffbf2a 100644 --- a/composer.json +++ b/composer.json @@ -28,12 +28,14 @@ "vimeo/psalm": "^5.9", "psr/http-client": "^1.0", "psr/http-factory": "^1.0", + "php-http/multipart-stream-builder": "^1.0", "symfony/http-client": "^4.3 | ^5.0 | ^6.0", "guzzlehttp/guzzle": "^7.0" }, "suggest": { "psr/http-client": "To use psr/http-client", "psr/http-factory": "To use psr/http-client", + "php-http/multipart-stream-builder": "To use psr/http-client", "guzzlehttp/guzzle": "To use psr/http-client", "symfony/http-client": "To use symfony/http-client" }, @@ -63,5 +65,10 @@ "branch-alias": { "dev-master": "2.6-dev" } + }, + "config": { + "allow-plugins": { + "php-http/discovery": false + } } } diff --git a/src/Http/PsrHttpClient.php b/src/Http/PsrHttpClient.php index df71e44e..0e886453 100644 --- a/src/Http/PsrHttpClient.php +++ b/src/Http/PsrHttpClient.php @@ -2,9 +2,11 @@ namespace TelegramBot\Api\Http; +use Http\Message\MultipartStream\MultipartStreamBuilder; use Psr\Http\Client\ClientExceptionInterface; use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestFactoryInterface; +use Psr\Http\Message\StreamFactoryInterface; use TelegramBot\Api\HttpException; use TelegramBot\Api\InvalidJsonException; @@ -20,10 +22,19 @@ class PsrHttpClient extends AbstractHttpClient */ private $requestFactory; - public function __construct(ClientInterface $http, RequestFactoryInterface $requestFactory) - { + /** + * @var StreamFactoryInterface + */ + private $streamFactory; + + public function __construct( + ClientInterface $http, + RequestFactoryInterface $requestFactory, + StreamFactoryInterface $streamFactory + ) { $this->http = $http; $this->requestFactory = $requestFactory; + $this->streamFactory = $streamFactory; } /** @@ -33,11 +44,39 @@ protected function doRequest($url, array $data = null) { if ($data) { $method = 'POST'; + $data = array_filter($data); } else { $method = 'GET'; } $request = $this->requestFactory->createRequest($method, $url); + if ($method === 'POST') { + $multipart = false; + + /** @var array $data */ + foreach ($data as &$value) { + if ($value instanceof \CURLFile) { + $value = fopen($value->getFilename(), 'r'); + $multipart = true; + } + } + unset($value); + + if ($multipart) { + $builder = new MultipartStreamBuilder($this->streamFactory); + foreach ($data as $name => $value) { + $builder->addResource($name, $value); + } + $stream = $builder->build(); + $request = $request->withHeader('Content-Type', "multipart/form-data; boundary={$builder->getBoundary()}"); + } else { + $stream = $this->streamFactory->createStream(http_build_query($data)); + $request = $request->withHeader('Content-Type', 'application/x-www-form-urlencoded'); + } + + $request = $request->withBody($stream); + } + try { $response = $this->http->sendRequest($request); } catch (ClientExceptionInterface $exception) { diff --git a/src/Http/SymfonyHttpClient.php b/src/Http/SymfonyHttpClient.php index 2b229576..18dd2961 100644 --- a/src/Http/SymfonyHttpClient.php +++ b/src/Http/SymfonyHttpClient.php @@ -26,6 +26,12 @@ protected function doRequest($url, array $data = null) $options = []; if ($data) { $method = 'POST'; + foreach ($data as &$value) { + if ($value instanceof \CURLFile) { + $value = fopen($value->getFilename(), 'r'); + } + } + $options['body'] = $data; } else { $method = 'GET';