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

[FEATURE] General reusable validation implementation and middleware for validating DOMDocument #1313

Merged
merged 49 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
b1bd8d9
Initial commit of mets xml validator stack
markusweigelt Aug 2, 2024
c25d3eb
Validator implementation
markusweigelt Aug 12, 2024
beba812
Merge branch 'kitodo:master' into xml-validator
markusweigelt Aug 12, 2024
82871af
Running saxon validation
markusweigelt Aug 12, 2024
16ebded
Refactor XsdValidator to XmlSchemesValidator
markusweigelt Aug 13, 2024
9421db6
Update validation and documentation
markusweigelt Aug 14, 2024
32df88d
Add another example of mets and improve base validator
markusweigelt Aug 14, 2024
eb34d38
Add mets error
markusweigelt Aug 14, 2024
0f5e1a3
Add validation base classes
markusweigelt Aug 22, 2024
3238f1b
Remove files from repo
markusweigelt Aug 22, 2024
cecb93a
Code documentation of classes
markusweigelt Aug 22, 2024
eb13e61
Remove validator middleware
markusweigelt Aug 22, 2024
1e18bf0
Improvements validation middleware implementation
markusweigelt Aug 22, 2024
5747196
Update phpdoc
markusweigelt Aug 23, 2024
edeb37b
Update documentation
markusweigelt Aug 23, 2024
67bb62c
Add libxmltrait
markusweigelt Aug 23, 2024
d548ffa
Merge branch 'kitodo:master' into xml-validator
markusweigelt Aug 23, 2024
c942214
Update middleware
markusweigelt Aug 23, 2024
4f10961
Test validation stack
markusweigelt Aug 26, 2024
2722296
Test validation stack
markusweigelt Aug 26, 2024
4367dda
Testing XmlSchemesValidator
markusweigelt Aug 26, 2024
5bfa3ca
Merge branch 'master' into xml-validator
sebastian-meyer Aug 27, 2024
3931ad1
Add saxon validator test
markusweigelt Aug 28, 2024
b917eab
Update codacy fixes
markusweigelt Aug 28, 2024
6bdbe51
Renaming validators and improve saxon validator
markusweigelt Aug 28, 2024
7471b21
Fix phpstan error
markusweigelt Aug 28, 2024
11da71d
Update saxon validator
markusweigelt Aug 28, 2024
d98e55f
Update codacy fixes
markusweigelt Aug 28, 2024
1801bee
Update codacy fixes
markusweigelt Aug 28, 2024
a416d7d
Update codacy fixes
markusweigelt Aug 28, 2024
9323668
Update codacy fixes
markusweigelt Aug 28, 2024
2acea1b
Change file_get_contents to internal function
markusweigelt Aug 28, 2024
6221b86
Update codacy fixes
markusweigelt Aug 28, 2024
95e6744
Update codacy fixes
markusweigelt Aug 28, 2024
ae43166
Changs regarding codacy issues
markusweigelt Aug 28, 2024
ef5c95c
Update codacy fixes
markusweigelt Aug 28, 2024
a4848d8
Update codacy fixes
markusweigelt Aug 28, 2024
42cc06a
Update codacy
markusweigelt Aug 28, 2024
76cb7af
Fix tests to support logging
markusweigelt Aug 28, 2024
d52bcb1
Codacy issues
markusweigelt Aug 28, 2024
bfb9328
Improve path assignment
markusweigelt Aug 28, 2024
5ff5c49
Update codacy issue fixes
markusweigelt Aug 28, 2024
0d7a82f
Remove isset
markusweigelt Aug 28, 2024
14830a9
Change exception handling
markusweigelt Aug 28, 2024
b93cb06
Update timestamp in error code
markusweigelt Aug 28, 2024
290e313
Update validtion middleware implementation
markusweigelt Aug 29, 2024
2ee80f2
Update documentation regarding the refactoring
markusweigelt Aug 29, 2024
dfb4b69
Reset faulty removed lines
markusweigelt Sep 23, 2024
1410fc0
Merge branch 'kitodo:master' into xml-validator
markusweigelt Sep 23, 2024
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
1 change: 0 additions & 1 deletion Build/Test/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# Adopted/reduced from https://github.com/TYPO3/typo3/blob/608f238a8b7696a49a47e1e73ce8e2845455f0f5/Build/testing-docker/local/docker-compose.yml

version: "2.3"
services:
mysql:
image: docker.io/mysql:${MYSQL_VERSION}
Expand Down
116 changes: 116 additions & 0 deletions Classes/Middleware/DOMDocumentValidation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<?php

namespace Kitodo\Dlf\Middleware;

/**
* (c) Kitodo. Key to digital objects e.V. <[email protected]>
*
* This file is part of the Kitodo and TYPO3 projects.
*
* @license GNU General Public License version 3 or later.
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*/

use DOMDocument;
use InvalidArgumentException;
use Kitodo\Dlf\Validation\DOMDocumentValidationStack;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Log\LoggerAwareTrait;
use TYPO3\CMS\Core\Http\ResponseFactory;
use TYPO3\CMS\Core\Log\LogManager;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
use TYPO3\CMS\Extbase\Error\Result;

/**
* Middleware for validation of DOMDocuments.
*
* @package TYPO3
* @subpackage dlf
* @access public
*/
class DOMDocumentValidation implements MiddlewareInterface
{
use LoggerAwareTrait;

/**
* The main method of the middleware.
*
* @access public
*
* @param ServerRequestInterface $request for processing
* @param RequestHandlerInterface $handler for processing
*
* @throws InvalidArgumentException
*
* @return ResponseInterface
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$this->logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(static::class);
$response = $handler->handle($request);
// parameters are sent by POST --> use getParsedBody() instead of getQueryParams()
$parameters = $request->getQueryParams();

// Return if not this middleware
if (!isset($parameters['middleware']) || ($parameters['middleware'] != 'dlf/domDocumentValidation')) {
return $response;
}

$urlParam = $parameters['url'];
if (!isset($urlParam)) {
throw new InvalidArgumentException('URL parameter is missing.', 1724334674);
}

/** @var ConfigurationManagerInterface $configurationManager */
$configurationManager = GeneralUtility::makeInstance(ConfigurationManagerInterface::class);
$settings = $configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_SETTINGS);

if (!array_key_exists("domDocumentValidationValidators", $settings)) {
$this->logger->error('DOMDocumentValidation is not configured correctly.');
throw new InvalidArgumentException('DOMDocumentValidation is not configured correctly.', 1724335601);
}

$validation = GeneralUtility::makeInstance(DOMDocumentValidationStack::class, $settings['domDocumentValidationValidators']);

if (!GeneralUtility::isValidUrl($urlParam)) {
$this->logger->debug('Parameter "' . $urlParam . '" is not a valid url.');
throw new InvalidArgumentException('Value of url parameter is not a valid url.', 1724852611);
}

$content = GeneralUtility::getUrl($urlParam);
if ($content === false) {
$this->logger->debug('Error while loading content of "' . $urlParam . '"');
throw new InvalidArgumentException('Error while loading content of url.', 1724420640);
}

$document = new DOMDocument();
if ($document->loadXML($content) === false) {
$this->logger->debug('Error converting content of "' . $urlParam . '" to xml.');
throw new InvalidArgumentException('Error converting content to xml.', 1724420648);
}

$result = $validation->validate($document);
return $this->getJsonResponse($result);
}

protected function getJsonResponse(Result $result): ResponseInterface
{
$resultContent = ["valid" => !$result->hasErrors()];

foreach ($result->getErrors() as $error) {
$resultContent["results"][$error->getTitle()][] = $error->getMessage();
}

/** @var ResponseFactory $responseFactory */
$responseFactory = GeneralUtility::makeInstance(ResponseFactory::class);
$response = $responseFactory->createResponse()
->withHeader('Content-Type', 'application/json; charset=utf-8');
$response->getBody()->write(json_encode($resultContent));
return $response;
}
}
129 changes: 129 additions & 0 deletions Classes/Validation/AbstractDlfValidationStack.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
<?php

declare(strict_types=1);

/**
* (c) Kitodo. Key to digital objects e.V. <[email protected]>
*
* This file is part of the Kitodo and TYPO3 projects.
*
* @license GNU General Public License version 3 or later.
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*/

namespace Kitodo\Dlf\Validation;

use InvalidArgumentException;
use Psr\Log\LoggerAwareTrait;
use TYPO3\CMS\Core\Utility\GeneralUtility;

/**
* Abstract class provides functions for implementing a validation stack.
*
* @package TYPO3
* @subpackage dlf
*
* @access public
*/
abstract class AbstractDlfValidationStack extends AbstractDlfValidator
{
use LoggerAwareTrait;

const ITEM_KEY_TITLE = "title";
const ITEM_KEY_BREAK_ON_ERROR = "breakOnError";
const ITEM_KEY_VALIDATOR = "validator";

protected array $validatorStack = [];

public function __construct(string $valueClassName)
{
parent::__construct($valueClassName);
}

/**
* Add validators by validation stack configuration to the internal validator stack.
*
* @param array $configuration The configuration of validators
*
* @throws InvalidArgumentException
*
* @return void
*/
public function addValidators(array $configuration): void
{
foreach ($configuration as $configurationItem) {
if (!class_exists($configurationItem["className"])) {
$this->logger->error('Unable to load class ' . $configurationItem["className"] . '.');
throw new InvalidArgumentException('Unable to load validator class.', 1723200537037);
}
$breakOnError = !isset($configurationItem["breakOnError"]) || $configurationItem["breakOnError"] !== "false";
$this->addValidator($configurationItem["className"], $configurationItem["title"] ?? "", $breakOnError, $configurationItem["configuration"] ?? []);
}
}

/**
* Add validator to the internal validator stack.
*
* @param string $className Class name of the validator which was derived from Kitodo\Dlf\Validation\AbstractDlfValidator
* @param string $title The title of the validator
* @param bool $breakOnError True if the execution of validator stack is interrupted when validator throws an error
* @param array|null $configuration The configuration of validator
*
* @throws InvalidArgumentException
*
* @return void
*/
protected function addValidator(string $className, string $title, bool $breakOnError = true, array $configuration = null): void
{
if ($configuration === null) {
$validator = GeneralUtility::makeInstance($className);
} else {
$validator = GeneralUtility::makeInstance($className, $configuration);
}

if (!$validator instanceof AbstractDlfValidator) {
$this->logger->error($className . ' must be an instance of AbstractDlfValidator.');
throw new InvalidArgumentException('Class must be an instance of AbstractDlfValidator.', 1723121212747);
}

$title = empty($title) ? $className : $title;

$this->validatorStack[] = [self::ITEM_KEY_TITLE => $title, self::ITEM_KEY_VALIDATOR => $validator, self::ITEM_KEY_BREAK_ON_ERROR => $breakOnError];
}

/**
* Check if value is valid across all validation classes of validation stack.
*
* @param $value The value of defined class name.
*
* @throws InvalidArgumentException
*
* @return void
*/
protected function isValid($value): void
{
if (!$value instanceof $this->valueClassName) {
$this->logger->error('Value must be an instance of ' . $this->valueClassName . '.');
throw new InvalidArgumentException('Type of value is not valid.', 1723127564821);
}

if (empty($this->validatorStack)) {
$this->logger->error('The validation stack has no validator.');
throw new InvalidArgumentException('The validation stack has no validator.', 1724662426);
}

foreach ($this->validatorStack as $validationStackItem) {
$validator = $validationStackItem[self::ITEM_KEY_VALIDATOR];
$result = $validator->validate($value);

foreach ($result->getErrors() as $error) {
$this->addError($error->getMessage(), $error->getCode(), [], $validationStackItem[self::ITEM_KEY_TITLE]);
}

if ($validationStackItem[self::ITEM_KEY_BREAK_ON_ERROR] && $result->hasErrors()) {
break;
}
}
}
}
56 changes: 56 additions & 0 deletions Classes/Validation/AbstractDlfValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

declare(strict_types=1);

/**
* (c) Kitodo. Key to digital objects e.V. <[email protected]>
*
* This file is part of the Kitodo and TYPO3 projects.
*
* @license GNU General Public License version 3 or later.
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*/

namespace Kitodo\Dlf\Validation;

use InvalidArgumentException;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use TYPO3\CMS\Core\Log\LogManager;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Validation\Validator\AbstractValidator;

/**
* Base Validator provides functionalities for using the derived validator within a validation stack.
*
* @package TYPO3
* @subpackage dlf
*
* @access public
*/
abstract class AbstractDlfValidator extends AbstractValidator
{
use LoggerAwareTrait;

protected string $valueClassName;

/**
* @param $valueClassName string The class name of the value
*/
public function __construct(string $valueClassName)
{
parent::__construct();
$this->logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(static::class);
$this->valueClassName = $valueClassName;
}

public function validate($value)
{
if (!$value instanceof $this->valueClassName) {
$this->logger->debug('Value must be an instance of ' . $this->valueClassName . '.');
throw new InvalidArgumentException('Type of value is not valid.', 1723126505626);
}
return parent::validate($value);
}
}
32 changes: 32 additions & 0 deletions Classes/Validation/DOMDocumentValidationStack.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

/**
* (c) Kitodo. Key to digital objects e.V. <[email protected]>
*
* This file is part of the Kitodo and TYPO3 projects.
*
* @license GNU General Public License version 3 or later.
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*/

namespace Kitodo\Dlf\Validation;

/**
* Implementation of AbstractDlfValidationStack for validating DOMDocument against the configured validators.
*
* @package TYPO3
* @subpackage dlf
*
* @access public
*/
class DOMDocumentValidationStack extends AbstractDlfValidationStack
{
public function __construct(array $configuration)
{
parent::__construct(\DOMDocument::class);
$this->addValidators($configuration);
}
}
44 changes: 44 additions & 0 deletions Classes/Validation/LibXmlTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

namespace Kitodo\Dlf\Validation;

/**
* (c) Kitodo. Key to digital objects e.V. <[email protected]>
*
* This file is part of the Kitodo and TYPO3 projects.
*
* @license GNU General Public License version 3 or later.
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*/

trait LibXmlTrait
{
/**
* Add the errors from the libxml error buffer as validation error.
*
* To enable user error handling, you need to use libxml_use_internal_errors(true) beforehand.
*
* @return void
*/
public function addErrorsOfBuffer(): void
{
$errors = libxml_get_errors();
foreach ($errors as $error) {
$this->addError($error->message, $error->code);
}
libxml_clear_errors();
}

public function enableErrorBuffer(): void
{
libxml_use_internal_errors(true);
}

public function disableErrorBuffer(): void
{
libxml_use_internal_errors(false);
}
}
Loading