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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ Návod pro přechod: [UPGRADE-2.0.md](UPGRADE-2.0.md).
- Data k odeslání, která nejdou převést na JSON (např. neplatné UTF-8), vyhodí `EcomailFlexibeeRequestFail`. Verze 1.4 v takovém případě tiše odeslala požadavek bez platného těla. Základní URL bez schématu nebo hostitele je odmítnuta při vytvoření klienta.

### Nové
- Přílohy záznamů: `getAttachments()`, `getAttachmentContent()`, `addAttachment()` a `deleteAttachment()` (#31). `HttpClient::send()` odešle `CurlRequest` včetně binárního těla.
- `Client::findByCodes()` načte více záznamů evidence podle seznamu kódů jedním requestem (#43).
- `Client` a `CompanyClient` přijímají volitelný `HttpClient`.
- `Config::debugMode()` a `Config::withEvidence()`.
- Výchozí timeouty požadavků (5 s spojení, 25 s celkem).

### Opravy
- Odpověď se čte jako PDF jen u `GET`, jehož cesta končí na `.pdf`. Dříve stačilo, aby URL kdekoli obsahovala `.pdf`, např. nahrání přílohy `faktura.pdf` nebo filtr s `.pdf`.
- Kód záznamu s apostrofem je odmítnut výjimkou `EcomailFlexibeeInvalidRequestParameter`. Dříve mohl změnit význam filtru `(kod eq '…')`.
- Ověření TLS certifikátu se při přihlášení přes session nevypíná, pokud je `$verifySSLCertificate` zapnuté.
- Název firmy v URL pro obnovu zálohy je zakódovaný (`&` nebo `#` v názvu už nerozbije URL).
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,16 @@ $client->deleteById($id, $dryRun);
$client->deleteByCode($code, $dryRun);
```

## Přílohy
Přílohy záznamu evidence (podle id záznamu):
```
$attachments = $client->getAttachments($evidenceItemId); // pole EvidenceResult (id, nazSoub, contentType, …)
$content = $client->getAttachmentContent($evidenceItemId, $attachmentId); // binární obsah přílohy
$attachmentId = $client->addAttachment($evidenceItemId, 'faktura.pdf', $content, 'application/pdf');
$client->deleteAttachment($evidenceItemId, $attachmentId);
```
Existující přílohu FlexiBee neumí změnit, je potřeba ji smazat a nahrát znovu. Dokumentace FlexiBee: [Přílohy](https://podpora.flexibee.eu/cs/articles/4722200-prilohy).

## Generování PDF
Systém Flexibee umožňuje vrátit vygenerované faktury.
```
Expand Down
1 change: 1 addition & 0 deletions UPGRADE-2.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,6 @@ Následující změny se týkají jen kódu, který tyto třídy používá nebo
- `EcomailFlexibee\Http\UrlBuilder` už nedědí z `Purl\Url`. Zděděné metody (`setPath()`, `setQuery()`, `getUrl()` …) nejsou k dispozici a konstruktor nepřijímá `Purl\ParserInterface`.
- `EcomailFlexibee\Validator\ParameterValidator` už nedědí z `Rakit\Validation\Validator`.
- `EcomailFlexibee\Http\ResponseFactory::createFromOutput()` má navíc parametr `?string $curlInfoDebug`.
- `EcomailFlexibee\Http\HttpCurlBuilder::build()` přijímá `CurlRequest` a `Config` místo samostatných parametrů. `HttpClient::request()` zůstává beze změny.
- `EcomailFlexibee\Config` a `EcomailFlexibee\Result\EvidenceResult` jsou `readonly` třídy.
- Název firmy v `UrlBuilder::createRestoreUrl()` se v URL kóduje (`rawurlencode()`).
86 changes: 86 additions & 0 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
namespace EcomailFlexibee;

use EcomailFlexibee\Exception\EcomailFlexibeeNoEvidenceResult;
use EcomailFlexibee\Exception\EcomailFlexibeeRequestFail;
use EcomailFlexibee\Exception\EcomailFlexibeeSaveFailed;
use EcomailFlexibee\Http\CurlRequest;
use EcomailFlexibee\Http\HttpClient;
use EcomailFlexibee\Http\Method;
use EcomailFlexibee\Http\Response\FlexibeeResponse;
Expand Down Expand Up @@ -262,6 +264,90 @@ public function findByCode(string $code, array $uriParameters = []): EvidenceRes
}
}

/**
* @return array<\EcomailFlexibee\Result\EvidenceResult>
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeConnectionFail
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeForbidden
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeInvalidAuthorization
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeMethodNotAllowed
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeNotAcceptableRequest
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeRequestFail
*/
public function getAttachments(int $id): array
{
return $this->responseHydrator->convertResponseToAttachments(
$this->httpClient->request($this->queryBuilder->createAttachmentsUrl($id), Method::GET, [], [], [], $this->config),
);
}

/**
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeConnectionFail
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeForbidden
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeInvalidAuthorization
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeMethodNotAllowed
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeNoEvidenceResult
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeNotAcceptableRequest
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeRequestFail
*/
public function getAttachmentContent(int $id, int $attachmentId): string
{
$content = $this->httpClient->request(
$this->queryBuilder->createAttachmentContentUrl($id, $attachmentId),
Method::GET,
[],
[],
[],
$this->config,
)->getData()['attachmentContent'] ?? null;

if (!is_string($content)) {
throw new EcomailFlexibeeNoEvidenceResult();
}

return $content;
}

/**
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeConnectionFail
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeForbidden
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeInvalidAuthorization
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeMethodNotAllowed
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeNotAcceptableRequest
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeRequestFail
*/
public function addAttachment(int $id, string $fileName, string $content, string $contentType): int
{
$response = $this->httpClient->send(
new CurlRequest(
$this->queryBuilder->createNewAttachmentUrl($id, $fileName),
Method::PUT,
headers: [sprintf('Content-Type: %s', $contentType), 'Accept: application/json'],
body: $content,
),
$this->config,
);
$result = $response->getData()[0] ?? null;

if (is_array($result) && isset($result['id']) && is_numeric($result['id'])) {
return (int) $result['id'];
}

throw new EcomailFlexibeeRequestFail('FlexiBee did not return the ID of the new attachment.');
}

/**
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeConnectionFail
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeForbidden
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeInvalidAuthorization
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeMethodNotAllowed
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeNotAcceptableRequest
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeRequestFail
*/
public function deleteAttachment(int $id, int $attachmentId): void
{
$this->httpClient->request($this->queryBuilder->createAttachmentUrl($id, $attachmentId), Method::DELETE, [], [], [], $this->config);
}

/**
* @param list<string> $codes
* @param array<string> $uriParameters
Expand Down
24 changes: 19 additions & 5 deletions src/Http/CurlOptionsFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,10 @@ public function create(CurlRequest $request, Config $config): array
CURLOPT_USERAGENT => self::USER_AGENT,
] + $this->createAuthenticationOptions($config);

if (count($request->postFields) > 0) {
$options[CURLOPT_POSTFIELDS] = $this->encodePostFields($request->postFields);
}
$postFields = $this->createPostFields($request);

if (count($request->queryParameters) > 0) {
$options[CURLOPT_POSTFIELDS] = http_build_query($request->queryParameters);
if ($postFields !== null) {
$options[CURLOPT_POSTFIELDS] = $postFields;
}

$headers = $request->headers;
Expand Down Expand Up @@ -112,4 +110,20 @@ private function encodePostFields(array $postFields): string
}
}

/**
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeRequestFail
*/
private function createPostFields(CurlRequest $request): ?string
{
if (count($request->queryParameters) > 0) {
return http_build_query($request->queryParameters);
}

if ($request->body !== null) {
return $request->body;
}

return count($request->postFields) > 0 ? $this->encodePostFields($request->postFields) : null;
}

}
1 change: 1 addition & 0 deletions src/Http/CurlRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public function __construct(
public array $postFields = [],
public array $queryParameters = [],
public array $headers = [],
public ?string $body = null,
)
{
}
Expand Down
18 changes: 15 additions & 3 deletions src/Http/HttpClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,20 @@ public function __construct() {
*/
public function request(string $url, Method $httpMethod, array $postFields, array $queryParameters, array $headers, Config $config): FlexibeeResponse
{
return $this->send(new CurlRequest($url, $httpMethod, $postFields, $queryParameters, $headers), $config);
}

$ch = $this->httpCurlBuilder->build($url, $httpMethod, $postFields, $queryParameters, $headers, $config);
/**
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeConnectionFail
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeForbidden
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeInvalidAuthorization
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeMethodNotAllowed
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeNotAcceptableRequest
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeRequestFail
*/
public function send(CurlRequest $request, Config $config): FlexibeeResponse
{
$ch = $this->httpCurlBuilder->build($request, $config);

$output = curl_exec($ch);
$output = is_string($output) ? $output : null;
Expand All @@ -52,8 +64,8 @@ public function request(string $url, Method $httpMethod, array $postFields, arra
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

return ResponseFactory::createFromOutput(
$url,
$httpMethod,
$request->url,
$request->httpMethod,
$output,
$statusCode,
curl_errno($ch),
Expand Down
8 changes: 3 additions & 5 deletions src/Http/HttpCurlBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,12 @@ public function __construct()
}

/**
* @param array<mixed> $postFields
* @param array<mixed> $queryParameters
* @param array<int, string> $headers
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeRequestFail
*/
public function build(string $url, Method $httpMethod, array $postFields, array $queryParameters, array $headers, Config $config): CurlHandle
public function build(CurlRequest $request, Config $config): CurlHandle
{
$ch = curl_init();
curl_setopt_array($ch, $this->curlOptionsFactory->create(new CurlRequest($url, $httpMethod, $postFields, $queryParameters, $headers), $config));
curl_setopt_array($ch, $this->curlOptionsFactory->create($request, $config));

return $ch;
}
Expand Down
17 changes: 17 additions & 0 deletions src/Http/Response/FlexibeeAttachmentResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types = 1);

namespace EcomailFlexibee\Http\Response;

class FlexibeeAttachmentResponse extends FlexibeeResponse
{

public function __construct(string $attachmentContent)
{
parent::__construct(200, version: null, success: true, rowCount: 0, data: [
'attachmentContent' => $attachmentContent,
]);
}

}
21 changes: 17 additions & 4 deletions src/Http/ResponseFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use EcomailFlexibee\Exception\EcomailFlexibeeMethodNotAllowed;
use EcomailFlexibee\Exception\EcomailFlexibeeNotAcceptableRequest;
use EcomailFlexibee\Exception\EcomailFlexibeeRequestFail;
use EcomailFlexibee\Http\Response\FlexibeeAttachmentResponse;
use EcomailFlexibee\Http\Response\FlexibeeBackupResponse;
use EcomailFlexibee\Http\Response\FlexibeePdfResponse;
use EcomailFlexibee\Http\Response\FlexibeeResponse;
Expand All @@ -22,11 +23,13 @@
use function is_string;
use function json_decode;
use function mb_stripos;
use function mb_strpos;
use function parse_url;
use function reset;
use function sprintf;
use function str_ends_with;

use const CURLE_OK;
use const PHP_URL_PATH;

final class ResponseFactory
{
Expand Down Expand Up @@ -56,15 +59,25 @@ public static function createFromOutput(
throw new EcomailFlexibeeConnectionFail(sprintf('cURL error (%s): %s', $errorNumber, $errorMessage));
}

if (mb_strpos($url, '.pdf') !== false) {
return self::createBinaryResponse($url, $httpMethod, $responseContent, $statusCode)
?? self::createFromJson($responseContent, $statusCode, $curlInfoDebug);
}

private static function createBinaryResponse(string $url, Method $httpMethod, string $responseContent, int $statusCode): ?FlexibeeResponse
{
if ($httpMethod !== Method::GET) {
return null;
}

if (str_ends_with((string) parse_url($url, PHP_URL_PATH), '.pdf')) {
return new FlexibeePdfResponse($responseContent);
}

if ($httpMethod === Method::GET && mb_stripos($url, '/backup') !== false) {
if (mb_stripos($url, '/backup') !== false) {
return new FlexibeeBackupResponse($responseContent);
}

return self::createFromJson($responseContent, $statusCode, $curlInfoDebug);
return $statusCode === 200 && str_ends_with($url, '/content') ? new FlexibeeAttachmentResponse($responseContent) : null;
}

/**
Expand Down
13 changes: 13 additions & 0 deletions src/Http/ResponseHydrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
class ResponseHydrator
{

private const ATTACHMENTS_KEY = 'priloha';

public function __construct(private readonly Config $config)
{
}
Expand Down Expand Up @@ -54,6 +56,17 @@ public function convertResponseToPaginatedEvidenceResults(Response $response): a
];
}

/**
* @return array<\EcomailFlexibee\Result\EvidenceResult>
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeRequestFail
*/
public function convertResponseToAttachments(Response $response): array
{
$attachments = $this->getDataOrThrowRequestFail($response)[self::ATTACHMENTS_KEY] ?? [];

return is_array($attachments) ? $this->createEvidenceResults($attachments) : [];
}

public function convertResponseToEvidenceResult(Response $response, bool $throwException): EvidenceResult
{
$data = $response->getData();
Expand Down
25 changes: 25 additions & 0 deletions src/Http/UrlBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,26 @@ public function createUriByCodeOnly(string $code, array $uriParameters): string
return $this->createUrl($this->buildPathWithIdOrFilter(sprintf('(kod eq \'%s\')', $code)), $uriParameters);
}

public function createAttachmentsUrl(int $id): string
{
return $this->createUrl(sprintf('%s.json', $this->buildAttachmentsPath($id)));
}

public function createAttachmentUrl(int $id, int $attachmentId): string
{
return $this->createUrl(sprintf('%s/%d.json', $this->buildAttachmentsPath($id), $attachmentId));
}

public function createAttachmentContentUrl(int $id, int $attachmentId): string
{
return $this->createUrl(sprintf('%s/%d/content', $this->buildAttachmentsPath($id), $attachmentId));
}

public function createNewAttachmentUrl(int $id, string $fileName): string
{
return $this->createUrl(sprintf('%s/new/%s', $this->buildAttachmentsPath($id), rawurlencode($fileName)));
}

/**
* @param list<string> $codes
* @param array<mixed> $uriParameters
Expand Down Expand Up @@ -183,6 +203,11 @@ private function buildPathWithIdOrFilter(string|int $filterQueryOrId, string $fo
return sprintf('c/%s/%s/%s.%s', $this->company, $this->evidence, $filterQueryOrId, $format);
}

private function buildAttachmentsPath(int $id): string
{
return sprintf('c/%s/%s/%d/prilohy', $this->company, $this->evidence, $id);
}

private function buildPathForOnlyEvidence(): string
{
return sprintf('c/%s/%s.json', $this->company, $this->evidence);
Expand Down
Loading
Loading