Initial xentral_oss_20.3.c9ffacf

This commit is contained in:
Alex
2021-05-21 08:49:41 +02:00
parent 5406e4a551
commit 34e5ac43d9
18884 changed files with 2109867 additions and 0 deletions
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Xentral\Components\ScanbotApi;
final class Bootstrap
{
/**
* @return array
*/
public static function registerServices()
{
return [
'ScanbotApiClientFactory' => 'onInitScanbotApiClientFactory',
];
}
/**
* @return ScanBotApiClientFactory
*/
public static function onInitScanbotApiClientFactory()
{
return new ScanbotApiClientFactory();
}
}
@@ -0,0 +1,155 @@
<?php
namespace Xentral\Components\ScanbotApi\Client;
class CurlHttpClient
{
protected $url;
protected $method;
protected $header;
protected $post;
// Nachfolgende Properties sind erst nach dem Absenden gefüllt
protected $errorCode;
protected $errorMessage;
protected $responseContent;
protected $responseDebugInfo;
protected $responseStatusCode;
protected $responseContentType;
protected $isSent = false;
protected $hasError = false;
/**
* @param string $method [GET|POST|PUT]
* @param string $url
* @param array $header HTTP-Header
* @param array|null $post Nutzdaten für POST-/PUT-Requests (GET-Parameter in URL übergeben)
*/
public function __construct($method, $url, array $header = [], $post = null)
{
$this->url = $url;
$this->method = strtoupper($method);
$this->header = $header;
$this->post = $post;
}
/**
* @return string
*/
public function GetContent()
{
if (!$this->IsSent()) {
$this->Send();
}
return $this->responseContent;
}
/**
* @return bool
*/
protected function IsSent()
{
return $this->isSent;
}
/**
* Request abschicken
*
* @return void
*/
protected function Send()
{
$this->isSent = true;
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $this->url);
if ($this->method === 'POST') {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $this->post);
}
if ($this->method === 'PUT') {
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_POSTFIELDS, $this->post);
}
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $this->header);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_VERBOSE, false);
$this->responseContent = curl_exec($ch);
$this->responseStatusCode = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$this->responseContentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
$this->responseDebugInfo = curl_getinfo($ch);
if ($this->responseContent === false) {
$this->hasError = true;
$this->errorCode = curl_errno($ch);
$this->errorMessage = curl_error($ch);
}
curl_close($ch);
}
/**
* @return int
*/
public function GetStatusCode()
{
if (!$this->IsSent()) {
$this->Send();
}
return (int)$this->responseStatusCode;
}
/**
* @return array
*/
public function GetDebugInfo()
{
if (!$this->IsSent()) {
$this->Send();
}
return $this->responseDebugInfo;
}
/**
* @return bool
*/
public function HasError()
{
if (!$this->IsSent()) {
$this->Send();
}
return $this->hasError;
}
/**
* @return int
*/
public function GetErrorCode()
{
if (!$this->IsSent()) {
$this->Send();
}
return $this->errorCode;
}
/**
* @return string
*/
public function GetErrorMessage()
{
if (!$this->IsSent()) {
$this->Send();
}
return $this->errorMessage;
}
}
@@ -0,0 +1,241 @@
<?php
declare(strict_types=1);
namespace Xentral\Components\ScanbotApi\Client;
use Xentral\Components\ScanbotApi\Exception\RuntimeException;
class ScanbotApiOcrClient
{
/** @var string $apiUrl */
private $apiUrl;
/** @var string $apiKey */
private $apiKey;
/** @var array $result */
private $result;
/**
* @var string $resultHandle
* Referenz-ID zum Invoice-Recognition-Task
* Unter dem Handle lässt sich das Ergebnis der OCR-Erkennung abrufen.
* Das Handle wird auch benötigt um die korrigierten Daten zurückzumelden.
*/
private $resultHandle;
/**
* @param string $apiUrl
* @param string $apiKey
*/
public function __construct(string $apiUrl, string $apiKey)
{
if (empty($apiUrl)) {
throw new RuntimeException('Api-URL can not be empty.');
}
if (empty($apiKey)) {
throw new RuntimeException('Api-Key can not be empty.');
}
$this->apiUrl = $apiUrl;
$this->apiKey = $apiKey;
}
/**
* @param string $filePath Absoluter Dateipfad
* @param string $fileMimeType [image/jpeg|application/pdf]
*
* @throws RuntimeException
*
* @return void
*/
public function fetchApi(string $filePath, string $fileMimeType): void
{
// Datei hochladen + FileHandle abholen
$fileHandle = $this->fetchFileHandle($filePath, $fileMimeType);
// Handle zum Abfragen es Ergebnisses abholen
$this->resultHandle = $this->fetchResultHandle($fileHandle);
// Anhand des ResultHandles das Ergebnis abholen
$this->pollResult($this->resultHandle);
}
/**
* API-Ergebnis als Array
*
* Das Ergebnis hat folgende Struktur:
* [
* 'IBAN' => array|null,
* 'invoiceDate' => array|null,
* 'invoiceNumber' => array|null,
* 'orderId' => array|null,
* 'totalAmount' => array|null,
* 'totalTax' => array|null,
* 'hocrOutput' => string,
* ]
*
* @throws RuntimeException
*
* @return array
*/
public function getResult(): array
{
return $this->result;
}
/**
* @return string
*/
public function getResultHandle(): string
{
return $this->resultHandle;
}
/**
* @param string $filePath
* @param string $fileMimeType
*
* @return string
*/
private function fetchFileHandle(string $filePath, string $fileMimeType): string
{
if (!is_file($filePath)) {
throw new RuntimeException(sprintf('Datei "%s" nicht gefunden.', $filePath));
}
$url = $this->apiUrl . '/file';
$header = [
'Content-Type: ' . $fileMimeType,
'x-api-key: ' . $this->apiKey,
];
$curlFile = curl_file_create($filePath, $fileMimeType);
$client = new CurlHttpClient('PUT', $url, $header, [$curlFile]);
if ($client->HasError()) {
throw new RuntimeException(sprintf('Curl-Fehler: %s', $client->GetErrorMessage()));
}
$result = $client->GetContent();
$arrayResult = json_decode($result, true);
if (json_last_error() > 0) {
throw new RuntimeException(sprintf('JSON-Fehler: %s', json_last_error_msg()));
}
if (!empty($arrayResult['message'])) {
throw new RuntimeException(sprintf('API-Fehler: %s', $arrayResult['message']));
}
return $arrayResult['handle'];
}
/**
* @param string $fileHandle
*
* @return string
*/
private function fetchResultHandle(string $fileHandle): string
{
$url = $this->apiUrl . '/invoice/' . $fileHandle;
$header = [
'Accept: */*',
'x-api-key: ' . $this->apiKey,
];
$client = new CurlHttpClient('POST', $url, $header);
if ($client->HasError()) {
throw new RuntimeException(sprintf('Curl-Fehler: %s', $client->GetErrorMessage()));
}
$result = $client->GetContent();
$arrayResult = json_decode($result, true);
if (json_last_error() > 0) {
throw new RuntimeException(sprintf('JSON-Fehler: %s', json_last_error_msg()));
}
if (!empty($arrayResult['message'])) {
throw new RuntimeException(sprintf('API-Fehler: %s', $arrayResult['message']));
}
return $arrayResult['handle'];
}
/**
* @param string $resultHandle
*
* @throws RuntimeException
*
* @return string
*/
private function pollResult(string $resultHandle)
{
for ($try = 1; $try < 7; $try++) {
$statusCode = $this->sendPollRequest($resultHandle);
if ($statusCode === 200) {
// Beim HTTP-Status 200 ist entweder ein Ergebnis zurückgekommen, oder ein Fehler > Schleife beenden
break;
}
// Beim HTTP-Staus 404 ist noch kein Ergebnis verfügbar > Kurze Pause und weiter pollen...
sleep(5);
}
// Schleife ist ergebnislos durchgelaufen
if ($this->result === null) {
throw new RuntimeException('Timeout: Kein Ergebnis von der API.');
}
// Ergebnis ist da > Versuchen JSON zu lesen
$arrayResult = json_decode($this->result, true);
if (json_last_error() > 0) {
throw new RuntimeException(sprintf('JSON-Fehler: %s', json_last_error_msg()));
}
if (isset($arrayResult['message'])) {
throw new RuntimeException(sprintf('API-Meldung: %s', $arrayResult['message']));
}
if (isset($arrayResult['errorCode']) && isset($arrayResult['error'])) {
throw new RuntimeException(
sprintf('API-Fehler: Code #%s %s', $arrayResult['errorCode'], $arrayResult['error'])
);
}
// Wenn Programm bis hierhin durchgelaufen ist,
// dann ist $this->result mit API-Ergebnis gefüllt
$this->result = $arrayResult;
}
/**
* @param string $resultHandle
*
* @return int HTTP-Statuscode
*/
private function sendPollRequest(string $resultHandle): int
{
$url = $this->apiUrl . '/file/' . $resultHandle;
$header = [
'Accept: */*',
'x-api-key: ' . $this->apiKey,
];
$client = new CurlHttpClient('GET', $url, $header);
$content = $client->GetContent();
$httpCode = $client->GetStatusCode();
if ($client->HasError()) {
throw new RuntimeException(sprintf('Curl-Fehler: %s', $client->GetErrorMessage()));
}
// Beim HTTP-Status 200 ist entweder ein Ergebnis zurückgekommen, oder ein Fehler; aber immer als JSON
// Beim HTTP-Staus 404 ist noch kein Ergebnis verfügbar > nochmal pollen...
if ($httpCode === 200) {
$this->result = $content;
}
return $httpCode;
}
}
@@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
namespace Xentral\Components\ScanbotApi\Client;
use Xentral\Components\ScanbotApi\Exception\RuntimeException;
class ScanbotApiRegistrationClient
{
/** @var string $apiUrl */
private $apiUrl;
/** @var string $clientId ClientID von Xentral bei freigeist (ist gleich für alle Xentral-Kunden) */
private $clientId;
/**
* @param string $apiUrl
* @param string $clientId
*/
public function __construct(string $apiUrl, string $clientId)
{
if (empty($apiUrl)) {
throw new RuntimeException('ApiURL can not be empty.');
}
if (empty($clientId)) {
throw new RuntimeException('ClientID can not be empty.');
}
$this->apiUrl = $apiUrl;
$this->clientId = $clientId;
}
/**
* @param string $companyMail Pro Mailadresse kann nur ein ApiKey registriert werden
* @param string $companyName
*
* @throws RuntimeException
*
* @return array
*/
public function register(string $companyMail, string $companyName): array
{
if (empty($companyMail)) {
throw new RuntimeException('Mail parameter can not be empty.');
}
if (empty($companyName)) {
throw new RuntimeException('Name parameter can not be empty.');
}
$url = $this->apiUrl . '/createApiKey?' . sprintf(
'email=%s&company=%s&client_id=%s',
rawurlencode($companyMail),
rawurlencode($companyName),
$this->clientId
);
$client = new CurlHttpClient('POST', $url);
if ($client->HasError()) {
throw new RuntimeException(sprintf('Curl-Fehler: %s', $client->GetErrorMessage()));
}
$result = $client->GetContent();
$arrayResult = json_decode($result, true);
if (json_last_error() > 0) {
throw new RuntimeException(sprintf('JSON-Fehler: %s', json_last_error_msg()));
}
if (isset($arrayResult['error'])) {
throw new RuntimeException(sprintf('API-Fehler: %s', $arrayResult['error']));
}
return $arrayResult;
}
}
@@ -0,0 +1,120 @@
<?php
namespace Xentral\Components\ScanbotApi\Data;
use DateTimeImmutable;
class ScanbotApiResultData
{
/** @var string|null $iban IBAN-Nummer */
protected $iban;
/** @var DateTimeImmutable|null $invoiceDate Rechnungsdatum */
protected $invoiceDate;
/** @var string|null $invoiceNumber Rechnungsnummer */
protected $invoiceNumber;
/** @var float|null $totalAmount Rechnungsbetrag */
protected $totalAmount;
/** @var float|null $totalTax Mehrwertsteuerbetrag */
protected $totalTax;
/** @var string|null $currency Währung; dreistelliger ISO-Code */
protected $currency;
/** @var string $resultHandle Referenz-ID zum Invoice-Recognition-Task */
protected $resultHandle;
/**
* @return array
*/
public function toArray()
{
return [
'currency' => $this->currency,
'iban' => $this->iban,
'invoice_date' => $this->invoiceDate !== null ? $this->invoiceDate->format('d.m.Y') : null,
'invoice_number' => $this->invoiceNumber,
'result_handle' => $this->resultHandle,
'total_amount' => $this->totalAmount !== null ? number_format($this->totalAmount, 2, ',', '') : null,
'total_tax' => $this->totalTax !== null ? number_format($this->totalTax, 2, ',', '') : null,
];
}
/**
* @param array $data
*/
public function SetDataFromScanbotApi(array $data)
{
// ID zum Zurückmelden der Ergebnisse
if (isset($data['resultHandle']) && $data['resultHandle'] !== null) {
$this->resultHandle = $data['resultHandle'];
}
// IBAN-Nummer
if (isset($data['IBAN']) && $data['IBAN'] !== null) {
$this->iban = (string)$data['IBAN']['value'];
}
// Rechnungsdatum
if (isset($data['invoiceDate']) && $data['invoiceDate'] !== null) {
$this->invoiceDate = new DateTimeImmutable($data['invoiceDate']['value']);
}
// Rechnungsnummer
if (isset($data['invoiceNumber']) && $data['invoiceNumber'] !== null) {
$this->invoiceNumber = (string)$data['invoiceNumber']['value'];
}
// Gesamtbetrag
if (isset($data['totalAmount']) && $data['totalAmount'] !== null) {
$this->totalAmount = (float)$data['totalAmount']['value'];
}
// Mehrwertsteuerbetrag
if (isset($data['totalTax']) && $data['totalTax'] !== null) {
$this->totalTax = (float)$data['totalTax']['value'];
}
}
/**
* @param array $data
*/
public function SetDataFromHocrResult(array $data)
{
// Währung
if ($this->currency === null && isset($data['currency']) && $data['currency'] !== null) {
$this->currency = (string)$data['currency'];
}
// Rechnungsnummer
if ($this->invoiceNumber === null && isset($data['invoice_number']) && $data['invoice_number'] !== null) {
$this->invoiceNumber = (string)$data['invoice_number'];
}
// Rechnungsdatum
if ($this->invoiceDate === null && isset($data['invoice_date']) && $data['invoice_date'] !== null) {
$this->invoiceDate = new DateTimeImmutable($data['invoice_date']);
}
// Gesamtbetrag
if ($this->totalAmount === null && isset($data['total_gross']) && $data['total_gross'] !== null) {
$total = $data['total_gross'];
$lastDotPos = (int)strrpos($total, '.');
$lastCommaPos = (int)strrpos($total, ',');
if ($lastCommaPos > $lastDotPos) {
// Komma ist Dezimaltrenner
$total = str_replace('.', '', $total);
$total = str_replace(',', '.', $total);
$this->totalAmount = (float)$total;
} else {
// Punkt ist Dezimaltrenner
$total = str_replace(',', '', $total);
$this->totalAmount = (float)$total;
}
}
}
}
@@ -0,0 +1,9 @@
<?php
namespace Xentral\Components\ScanbotApi\Exception;
use RuntimeException as splRuntimeException;
class RuntimeException extends splRuntimeException implements ScanbotApiExceptionInterface
{
}
@@ -0,0 +1,9 @@
<?php
namespace Xentral\Components\ScanbotApi\Exception;
use Xentral\Core\Exception\ComponentExceptionInterface;
interface ScanbotApiExceptionInterface extends ComponentExceptionInterface
{
}
@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Xentral\Components\ScanbotApi;
use Xentral\Components\ScanbotApi\Client\ScanbotApiOcrClient;
use Xentral\Components\ScanbotApi\Client\ScanbotApiRegistrationClient;
class ScanbotApiClientFactory
{
/**
* @param string $url
* @param string $clientId
*
* @return ScanbotApiRegistrationClient
*/
public function createRegistrationClient(string $url, string $clientId): ScanbotApiRegistrationClient
{
return new ScanbotApiRegistrationClient($url, $clientId);
}
/**
* @param string $url
* @param string $apikey
*
* @return ScanbotApiOcrClient
*/
public function createOcrClient(string $url, string $apikey): ScanbotApiOcrClient
{
return new ScanbotApiOcrClient($url, $apikey);
}
}