Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ENH] ZugferdKositValidator can optionally call a running Validator in Daemon-Mode via HTTP #217

Merged
merged 1 commit into from
Dec 15, 2024
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
20 changes: 20 additions & 0 deletions examples/KositValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,23 @@ function showValidationResult(ZugferdKositValidator $kositValidator)
$kositValidator->setDocument($document)->disableCleanup()->validate();

showValidationResult($kositValidator);

/* ----------------------------------------------------------------------------------
- Validation of a document read by ZugferdDocumentPdfReader (Remote, using running daemon)
---------------------------------------------------------------------------------- */

$document = ZugferdDocumentPdfReader::readAndGuessFromFile(dirname(__FILE__) . "/invoice_1.pdf");
$kositValidator->setDocument($document)->enableRemoteMode()->setRemoteModeHost("127.0.0.1")->setRemoteModePort(8081)->validate();

showValidationResult($kositValidator);

/* ----------------------------------------------------------------------------------
- Validation of a document read by ZugferdDocumentReader (Remote, using running daemon)
---------------------------------------------------------------------------------- */

$document = ZugferdDocumentReader::readAndGuessFromFile(dirname(__FILE__) . "/../tests/assets/xml_en16931_5.xml");

$kositValidator = new ZugferdKositValidator($document);
$kositValidator->setDocument($document)->enableRemoteMode()->setRemoteModeHost("127.0.0.1")->setRemoteModePort(8081)->validate();

showValidationResult($kositValidator);
273 changes: 271 additions & 2 deletions src/ZugferdKositValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,27 @@ class ZugferdKositValidator
*/
private $cleanupBaseDirectoryIsDisabled = false;

/**
* Use remote validation (JAVA application is running in daemon mode on a remote host)
*
* @var boolean
*/
private $remoteModeEnabled = false;

/**
* The remote hostname or -ip
*
* @var string
*/
private $remoteModeHost = "";

/**
* The remote host port
*
* @var integer
*/
private $remoteModePort = 0;

/**
* Message Type "Internal Error"
*/
Expand Down Expand Up @@ -291,6 +312,76 @@ public function enableCleanup(): ZugferdKositValidator
return $this;
}

/**
* Disable the usage of a remote host validation
*
* @return ZugferdKositValidator
*/
public function disableRemoteMode(): ZugferdKositValidator
{
$this->remoteModeEnabled = false;

return $this;
}

/**
* Enable the usage of a remote host validation
*
* @return ZugferdKositValidator
*/
public function enableRemoteMode(): ZugferdKositValidator
{
$this->remoteModeEnabled = true;

return $this;
}

/**
* Set the hostname or the ip of the remote host where the validation application
* is running in daemon mode
*
* @param string $remoteModeHost
* @return ZugferdKositValidator
*/
public function setRemoteModeHost(string $remoteModeHost): ZugferdKositValidator
{
if (StringUtils::stringIsNullOrEmpty($remoteModeHost)) {
return $this;
}

$this->remoteModeHost = $remoteModeHost;

return $this;
}

/**
* Set the port of the remote host where the validation application
* is running in daemon mode
*
* @param integer $remoteModePort
* @return ZugferdKositValidator
*/
public function setRemoteModePort(int $remoteModePort): ZugferdKositValidator
{
if ($remoteModePort <= 0) {
return $this;
}

$this->remoteModePort = $remoteModePort;

return $this;
}

/**
* Returns the full remote mode URL
*
* @return string
*/
public function getRemoteModeUrl(): string
{
return sprintf("http://%s:%s", $this->remoteModeHost, $this->remoteModePort);
}

/**
* Perform validation
*
Expand Down Expand Up @@ -574,6 +665,24 @@ public function getProcessOutput(): array
*/
private function checkRequirements(): bool
{
if ($this->remoteModeEnabled === true) {
return $this->checkRequirementsRemote();
}

return $this->checkRequirementsLocal();
}

/**
* CHeck requirements for usage on a local installation
*
* @return boolean
*/
private function checkRequirementsLocal(): bool
{
if ($this->remoteModeEnabled === true) {
return true;
}

if (is_null($this->document)) {
$this->addToMessageBag("You must specify an instance of the ZugferdDocument class");
return false;
Expand All @@ -594,13 +703,68 @@ private function checkRequirements(): bool
return true;
}

/**
* CHeck requirements for usage on a remote host which is running the application
* in daemon mode
*
* @return boolean
*/
private function checkRequirementsRemote(): bool
{
if ($this->remoteModeEnabled !== true) {
return true;
}

if (!function_exists('curl_init') || !function_exists('curl_setopt') || !function_exists('curl_exec') || !function_exists('curl_getinfo') || !function_exists('curl_close')) {
$this->addToMessageBag("PHP-Curl not installed or activated");
return false;
}

if (StringUtils::stringIsNullOrEmpty($this->remoteModeHost)) {
$this->addToMessageBag("You must specify the hostname or it's IP where the Validator is running in daemon mode");
return false;
}

if ($this->remoteModePort <= 0) {
$this->addToMessageBag("You must specify the port of the host where the Validator is running in daemon mode");
return false;
}

try {
$httpConnection = curl_init($this->getRemoteModeUrl());
curl_setopt($httpConnection, CURLOPT_RETURNTRANSFER, true);
curl_setopt($httpConnection, CURLOPT_HEADER, true);
curl_setopt($httpConnection, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($httpConnection, CURLOPT_ENCODING, '');
curl_setopt($httpConnection, CURLOPT_AUTOREFERER, true);
curl_setopt($httpConnection, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($httpConnection, CURLOPT_TIMEOUT, 10);
curl_exec($httpConnection);
$retcode = curl_getinfo($httpConnection, CURLINFO_HTTP_CODE);
curl_close($httpConnection);
if ($retcode != 200) {
$this->addToMessageBag("Failed to connect to the host where the Validator is running in daemon mode");
return false;
}
} catch (Throwable $e) {
$this->addToMessageBag($e);
return false;
}

return true;
}

/**
* Download required files
*
* @return boolean
*/
private function downloadRequiredFiles(): bool
{
if ($this->remoteModeEnabled === true) {
return true;
}

if (!$this->runFileDownload($this->validatorDownloadUrl, $this->resolveAppZipFilename())) {
$this->addToMessageBag(sprintf("Unable to download from %s containing the JAVA-Application", $this->validatorDownloadUrl));
return false;
Expand All @@ -621,6 +785,10 @@ private function downloadRequiredFiles(): bool
*/
private function unpackRequiredFiles(): bool
{
if ($this->remoteModeEnabled === true) {
return true;
}

$validatorAppFile = $this->resolveAppZipFilename();
$validatorScenarioFile = $this->resolveScenatioZipFilename();

Expand All @@ -645,6 +813,10 @@ private function unpackRequiredFiles(): bool
*/
private function unpackRequiredFile(string $filename): bool
{
if ($this->remoteModeEnabled === true) {
return true;
}

$zip = new ZipArchive();

if ($zip->open($filename) !== true) {
Expand Down Expand Up @@ -684,6 +856,24 @@ private function unpackRequiredFile(string $filename): bool
*/
private function performValidation(): bool
{
if ($this->remoteModeEnabled === true) {
return $this->performValidationRemote();
}

return $this->performValidationLocal();
}

/**
* Runs the validator java application locally
*
* @return boolean
*/
private function performValidationLocal(): bool
{
if ($this->remoteModeEnabled === true) {
return true;
}

if (file_put_contents($this->resolveFileToValidateFilename(), $this->document->serializeAsXml()) === false) {
$this->addToMessageBag("Cannot create temporary file which contains the XML to validate");
return false;
Expand All @@ -701,7 +891,47 @@ private function performValidation(): bool
];

if (!$this->runValidationApplication($applicationOptions, $this->resolveBaseDirectory())) {
$this->parseValidatorXmlReport();
$this->parseValidatorXmlReportByFile();
return false;
}

return true;
}

/**
* Runs the validator java application on the remote host
*
* @return boolean
*/
private function performValidationRemote(): bool
{
if ($this->remoteModeEnabled !== true) {
return true;
}

try {
$httpConnection = curl_init($this->getRemoteModeUrl());
curl_setopt($httpConnection, CURLOPT_RETURNTRANSFER, true);
curl_setopt($httpConnection, CURLOPT_HEADER, true);
curl_setopt($httpConnection, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($httpConnection, CURLOPT_ENCODING, '');
curl_setopt($httpConnection, CURLOPT_AUTOREFERER, true);
curl_setopt($httpConnection, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($httpConnection, CURLOPT_TIMEOUT, 120);
curl_setopt($httpConnection, CURLOPT_POST, true);
curl_setopt($httpConnection, CURLOPT_POSTFIELDS, $this->document->serializeAsXml());
curl_setopt($httpConnection, CURLOPT_HTTPHEADER, ["Content-Type: application/xml"]);
$responseXml = curl_exec($httpConnection);
$retcode = curl_getinfo($httpConnection, CURLINFO_HTTP_CODE);
curl_close($httpConnection);
if ($retcode != 200) {
if (preg_match('/<\?xml.*?\?>.*<\/.+>/s', $responseXml, $matches)) {
$this->parseValidatorXmlReportByContent($matches[0]);
}
return false;
}
} catch (Throwable $e) {
$this->addToMessageBag($e);
return false;
}

Expand All @@ -714,7 +944,7 @@ private function performValidation(): bool
*
* @return void
*/
private function parseValidatorXmlReport(): void
private function parseValidatorXmlReportByFile(): void
{
$reportFilename =
PathUtils::combinePathWithFile(
Expand All @@ -729,6 +959,37 @@ private function parseValidatorXmlReport(): void
$domDocument = new DOMDocument();
$domDocument->load($reportFilename);

$this->parseValidatorXmlReportByDomDocument($domDocument);
}

/**
* Parses the XML content string containing the response from the validation app (JAVA application) and put errors
* to messagebag
*
* @param string $xmlContent
* @return void
*/
private function parseValidatorXmlReportByContent(string $xmlContent): void
{
if (StringUtils::stringIsNullOrEmpty($xmlContent)) {
return;
}

$domDocument = new DOMDocument();
$domDocument->loadXML($xmlContent);

$this->parseValidatorXmlReportByDomDocument($domDocument);
}

/**
* Parses the XML DOMDocument containing the response from the validation app (JAVA application) and put errors
* to messagebag
*
* @param DOMDocument $domDocument
* @return void
*/
private function parseValidatorXmlReportByDomDocument(DOMDocument $domDocument): void
{
$domXPath = new DOMXPath($domDocument);

$messageTypeMaps = [
Expand Down Expand Up @@ -762,6 +1023,10 @@ private function parseValidatorXmlReport(): void
*/
private function cleanupBaseDirectory(): void
{
if ($this->remoteModeEnabled === true) {
return;
}

if ($this->cleanupBaseDirectoryIsDisabled === true) {
return;
}
Expand All @@ -781,6 +1046,10 @@ private function cleanupBaseDirectory(): void
*/
private function cleanupBaseDirectoryInternal(string $directoryToRemove): void
{
if ($this->remoteModeEnabled === true) {
return;
}

if (!is_dir($directoryToRemove)) {
return;
}
Expand Down
Loading