From c5d13ea77d9d7477c6bb763bc504f3eb7c8a0ce5 Mon Sep 17 00:00:00 2001 From: Markus Weigelt Date: Mon, 23 Sep 2024 16:55:39 +0200 Subject: [PATCH] [FEATURE] General reusable validation implementation and middleware for validating DOMDocument (#1313) Co-authored-by: Sebastian Meyer --- Build/Test/docker-compose.yml | 1 - Classes/Middleware/DOMDocumentValidation.php | 116 +++++++++++++++ .../Validation/AbstractDlfValidationStack.php | 129 +++++++++++++++++ Classes/Validation/AbstractDlfValidator.php | 56 ++++++++ .../Validation/DOMDocumentValidationStack.php | 32 +++++ Classes/Validation/LibXmlTrait.php | 44 ++++++ .../Validation/SaxonXslToSvrlValidator.php | 92 ++++++++++++ Classes/Validation/XmlSchemesValidator.php | 63 +++++++++ Configuration/RequestMiddlewares.php | 6 + Documentation/Developers/Index.rst | 1 + Documentation/Developers/Validation.rst | 132 ++++++++++++++++++ .../DOMDocumentValidationStackTest.php | 83 +++++++++++ .../SaxonXslToSvrlValidatorTest.php | 81 +++++++++++ .../Validation/XmlSchemesValidatorTest.php | 106 ++++++++++++++ composer.json | 3 +- 15 files changed, 943 insertions(+), 2 deletions(-) create mode 100644 Classes/Middleware/DOMDocumentValidation.php create mode 100644 Classes/Validation/AbstractDlfValidationStack.php create mode 100644 Classes/Validation/AbstractDlfValidator.php create mode 100644 Classes/Validation/DOMDocumentValidationStack.php create mode 100644 Classes/Validation/LibXmlTrait.php create mode 100644 Classes/Validation/SaxonXslToSvrlValidator.php create mode 100644 Classes/Validation/XmlSchemesValidator.php create mode 100644 Documentation/Developers/Validation.rst create mode 100644 Tests/Unit/Validation/DOMDocumentValidationStackTest.php create mode 100644 Tests/Unit/Validation/SaxonXslToSvrlValidatorTest.php create mode 100644 Tests/Unit/Validation/XmlSchemesValidatorTest.php diff --git a/Build/Test/docker-compose.yml b/Build/Test/docker-compose.yml index 30719293a..908ff2af3 100644 --- a/Build/Test/docker-compose.yml +++ b/Build/Test/docker-compose.yml @@ -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} diff --git a/Classes/Middleware/DOMDocumentValidation.php b/Classes/Middleware/DOMDocumentValidation.php new file mode 100644 index 000000000..90d4b6144 --- /dev/null +++ b/Classes/Middleware/DOMDocumentValidation.php @@ -0,0 +1,116 @@ + + * + * 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; + } +} diff --git a/Classes/Validation/AbstractDlfValidationStack.php b/Classes/Validation/AbstractDlfValidationStack.php new file mode 100644 index 000000000..36ca9c964 --- /dev/null +++ b/Classes/Validation/AbstractDlfValidationStack.php @@ -0,0 +1,129 @@ + + * + * 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; + } + } + } +} diff --git a/Classes/Validation/AbstractDlfValidator.php b/Classes/Validation/AbstractDlfValidator.php new file mode 100644 index 000000000..4dbb39761 --- /dev/null +++ b/Classes/Validation/AbstractDlfValidator.php @@ -0,0 +1,56 @@ + + * + * 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); + } +} diff --git a/Classes/Validation/DOMDocumentValidationStack.php b/Classes/Validation/DOMDocumentValidationStack.php new file mode 100644 index 000000000..c173d786e --- /dev/null +++ b/Classes/Validation/DOMDocumentValidationStack.php @@ -0,0 +1,32 @@ + + * + * 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); + } +} diff --git a/Classes/Validation/LibXmlTrait.php b/Classes/Validation/LibXmlTrait.php new file mode 100644 index 000000000..ea7f73ddc --- /dev/null +++ b/Classes/Validation/LibXmlTrait.php @@ -0,0 +1,44 @@ + + * + * 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); + } +} diff --git a/Classes/Validation/SaxonXslToSvrlValidator.php b/Classes/Validation/SaxonXslToSvrlValidator.php new file mode 100644 index 000000000..4971df1c8 --- /dev/null +++ b/Classes/Validation/SaxonXslToSvrlValidator.php @@ -0,0 +1,92 @@ + + * + * 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 DOMDocument; +use Exception; +use InvalidArgumentException; +use Psr\Log\LoggerAwareInterface; +use SimpleXMLElement; +use Symfony\Component\Process\Exception\ProcessFailedException; +use Symfony\Component\Process\Process; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +/** + * The validator validates the DOMDocument against an XSL Schematron and converts error output to validation errors. + * + * @package TYPO3 + * @subpackage dlf + * + * @access public + */ +class SaxonXslToSvrlValidator extends AbstractDlfValidator implements LoggerAwareInterface +{ + private string $jar; + + private string $xsl; + + public function __construct(array $configuration) + { + parent::__construct(DOMDocument::class); + $this->jar = GeneralUtility::getFileAbsFileName($configuration["jar"] ?? ''); + $this->xsl = GeneralUtility::getFileAbsFileName($configuration["xsl"] ?? ''); + if (empty($this->jar)) { + $this->logger->error('Saxon JAR file not found.'); + throw new InvalidArgumentException('Saxon JAR file not found.', 1723121212747); + } + if (empty($this->xsl)) { + $this->logger->error('XSL Schematron file not found.'); + throw new InvalidArgumentException('XSL Schematron file not found.', 1723121212747); + } + } + + protected function isValid($value) + { + $svrl = $this->process($value); + $this->addErrorsOfSvrl($svrl); + } + + protected function process(mixed $value): string + { + // using source from standard input + $process = new Process(['java', '-jar', $this->jar, '-xsl:' . $this->xsl, '-s:-'], null, null, $value->saveXML()); + $process->run(); + // executes after the command finish + if (!$process->isSuccessful()) { + $this->logger->error('Processing exits with code "' . $process->getExitCode() . '"'); + throw new InvalidArgumentException('Processing was not successful.', 1724862680); + } + return $process->getOutput(); + } + + /** + * Add errors of schematron output. + * + * @throws InvalidArgumentException + */ + private function addErrorsOfSvrl(string $svrl): void + { + try { + $xml = new SimpleXMLElement($svrl); + $results = $xml->xpath("/svrl:schematron-output/svrl:failed-assert/svrl:text"); + + foreach ($results as $error) { + $this->addError($error->__toString(), 1724405095); + } + } catch (Exception $e) { + throw new InvalidArgumentException('Schematron output XML could not be parsed.', 1724754882); + } + } +} diff --git a/Classes/Validation/XmlSchemesValidator.php b/Classes/Validation/XmlSchemesValidator.php new file mode 100644 index 000000000..216ae505f --- /dev/null +++ b/Classes/Validation/XmlSchemesValidator.php @@ -0,0 +1,63 @@ + + * + * 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 DOMDocument; + +/** + * The validator combines the configured schemes into one schema and validates the provided DOMDocument against this. + * + * @package TYPO3 + * @subpackage dlf + * + * @access public + */ +class XmlSchemesValidator extends AbstractDlfValidator +{ + use LibXmlTrait; + + private array $schemes; + + public function __construct(array $configuration) + { + parent::__construct(\DOMDocument::class); + $this->schemes = $configuration; + } + + /** + * Combines the schemes to one schema and validates the DOMDocument against this. + * + * @param $value DOMDocument The value to validate + * @return bool True if is valid + */ + protected function isSchemeValid(DOMDocument $value): bool + { + $xsd = ''; + foreach ($this->schemes as $scheme) { + $xsd .= ''; + } + $xsd .= ''; + return $value->schemaValidateSource($xsd); + } + + protected function isValid($value): void + { + $this->enableErrorBuffer(); + if (!$this->isSchemeValid($value)) { + $this->addErrorsOfBuffer(); + } + $this->disableErrorBuffer(); + } +} diff --git a/Configuration/RequestMiddlewares.php b/Configuration/RequestMiddlewares.php index 748fba273..f328daa0f 100644 --- a/Configuration/RequestMiddlewares.php +++ b/Configuration/RequestMiddlewares.php @@ -29,6 +29,12 @@ 'after' => [ 'typo3/cms-frontend/prepare-tsfe-rendering' ] + ], + 'dlf/domDocumentValidation' => [ + 'target' => \Kitodo\Dlf\Middleware\DOMDocumentValidation::class, + 'after' => [ + 'typo3/cms-frontend/prepare-tsfe-rendering' + ] ] ], ]; diff --git a/Documentation/Developers/Index.rst b/Documentation/Developers/Index.rst index 6b20688c7..3639619a4 100644 --- a/Documentation/Developers/Index.rst +++ b/Documentation/Developers/Index.rst @@ -8,4 +8,5 @@ These pages are aimed at developers working on Kitodo.Presentation. Metadata Database + Validation Embedded3DViewer diff --git a/Documentation/Developers/Validation.rst b/Documentation/Developers/Validation.rst new file mode 100644 index 000000000..f1b386708 --- /dev/null +++ b/Documentation/Developers/Validation.rst @@ -0,0 +1,132 @@ +=============== +Validation +=============== + +.. contents:: + :local: + :depth: 2 + +Validators +======= + +DOMDocumentValidationStack +-------------------------- + +``Kitodo\Dlf\Validation\DOMDocumentValidationStack`` implementation of ``Kitodo\Dlf\Validation\AbstractDlfValidationStack`` for validating DOMDocument against the configurable validators. + +The configuration is an array validator configurations each with following entries: + +.. t3-field-list-table:: + :header-rows: 1 + + - :field: Key + :description: Description + + - :field: title + :description: Title of the validator + + - :field: className + :description: Fully qualified class name of validator class derived from ``Kitodo\Dlf\Validation\AbstractDlfValidator`` + + - :field: breakOnError + :description: Indicates whether the validation of the validation stack should be interrupted in case of errors. + + - :field: configuration + :description: Specific configuration of validator + + +XmlSchemesValidator +-------------------------- + +``Kitodo\Dlf\Validation\XmlSchemesValidator`` combines the configured schemes into one schema and validates the provided DOMDocument against this. + +The configuration is an array validator configurations each with following entries: + +.. t3-field-list-table:: + :header-rows: 1 + + - :field: Key + :description: Description + + - :field: namespace + :description: Specifies the URI of the namespace to import + + - :field: schemaLocation + :description: Specifies the URI to the schema for the imported namespace + + +SaxonXslToSvrlValidator +-------------------------- + +``Kitodo\Dlf\Validation\SaxonXslToSvrlValidator`` validates the DOMDocument against an XSL Schematron and converts error output to validation errors. + +To use the validator, the XSL Schematron must be available alongside the XSL processor as a JAR file, and the required Java version of the processor must be installed. + +.. t3-field-list-table:: + :header-rows: 1 + + - :field: Key + :description: Description + + - :field: jar + :description: Absolute path to the Saxon JAR file + + - :field: xsl + :description: Absolute path to the XSL Schematron + +DOMDocumentValidation Middleware +======= + +``Kitodo\Dlf\Validation\DOMDocumentValidation`` middleware can be used via the parameter ``middleware`` with the value ``dlf/domDocumentValidation`` and the parameter ``url`` with the URL to the ``DOMDocument`` content to validate. + +Configuration +-------------------------- + +The validation middleware can be configured through the plugin settings in TypoScript with the block called ``domDocumentValidationValidators``. + + .. code-block:: + + plugin.tx_dlf { + settings { + domDocumentValidationValidators { + validator { + ... + }, + validatorStack { + ... + }, + ... + +Validators derived from ``Kitodo\Dlf\Validation\AbstractDlfValidator`` can be configured here. This also includes the use of validation stack implementations derived from ``Kitodo\Dlf\Validation\AbstractDlfValidationStack``, which use ``DOMDocument`` as the ``valueClassName`` for validation. This allows for multiple levels of nesting. + +In the background of the middleware, the ``Kitodo\Dlf\Validation\DOMDocumentValidationStack`` is used, to which the configured validators are assigned. + +TypoScript Example +^^^^^^^^^^^^^^^^^^^^^^^^^ + + .. code-block:: + + plugin.tx_dlf { + settings { + storagePid = {$config.storagePid} + domDocumentValidationValidators { + 10 { + title = XML-Schemes Validator + className = Kitodo\Dlf\Validation\XmlSchemesValidator + breakOnError = false + configuration { + oai { + namespace = http://www.openarchives.org/OAI/2.0/ + schemaLocation = https://www.openarchives.org/OAI/2.0/OAI-PMH.xsd + } + mets { + namespace = http://www.loc.gov/METS/ + schemaLocation = http://www.loc.gov/standards/mets/mets.xsd + } + mods { + namespace = http://www.loc.gov/mods/v3 + schemaLocation = http://www.loc.gov/standards/mods/mods.xsd + } + } + }, + ... diff --git a/Tests/Unit/Validation/DOMDocumentValidationStackTest.php b/Tests/Unit/Validation/DOMDocumentValidationStackTest.php new file mode 100644 index 000000000..34092cc83 --- /dev/null +++ b/Tests/Unit/Validation/DOMDocumentValidationStackTest.php @@ -0,0 +1,83 @@ + + * + * 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\Tests\Unit\Validation; + +use DOMDocument; +use InvalidArgumentException; +use Kitodo\Dlf\Validation\DOMDocumentValidationStack; +use Kitodo\Dlf\Validation\XmlSchemesValidator; +use TYPO3\CMS\Extbase\Validation\Validator\UrlValidator; +use TYPO3\TestingFramework\Core\Unit\UnitTestCase; + +/** + * Testing AbstractDlfValidatorStack with implementation of DOMDocumentValidationStack. + * + * @package TYPO3 + * @subpackage dlf + * + * @access public + */ +class DOMDocumentValidationStackTest extends UnitTestCase +{ + public function setUp(): void + { + parent::setUp(); + $this->resetSingletonInstances = true; + } + + public function testValueTypeException(): void + { + $domDocumentValidationStack = new DOMDocumentValidationStack([]); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage("Type of value is not valid."); + $domDocumentValidationStack->validate(""); + } + + public function testEmptyValidationStack(): void + { + $domDocumentValidationStack = new DOMDocumentValidationStack([]); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage("The validation stack has no validator."); + $domDocumentValidationStack->validate(new DOMDocument()); + } + + public function testValidatorClassNotExists(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage("Unable to load validator class."); + new DOMDocumentValidationStack([["className" => "Kitodo\Tests\TestValidator"]]); + } + + public function testValidatorDerivation(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage("Class must be an instance of AbstractDlfValidator."); + new DOMDocumentValidationStack([["className" => UrlValidator::class]]); + } + + public function testBreakOnError(): void + { + $xmlSchemesValidatorConfig = ["className" => XmlSchemesValidator::class, "configuration" => [["namespace" => "http://www.openarchives.org/OAI/2.0/", "schemaLocation" => "https://www.openarchives.org/OAI/2.0/OAI-PMH.xsd"]]]; + $domDocumentValidationStack = new DOMDocumentValidationStack([$xmlSchemesValidatorConfig, $xmlSchemesValidatorConfig]); + $result = $domDocumentValidationStack->validate(new DOMDocument()); + self::assertCount(1, $result->getErrors()); + + // disable breaking on error + $xmlSchemesValidatorConfig["breakOnError"] = "false"; + $domDocumentValidationStack = new DOMDocumentValidationStack([$xmlSchemesValidatorConfig, $xmlSchemesValidatorConfig]); + $result = $domDocumentValidationStack->validate(new DOMDocument()); + self::assertCount(2, $result->getErrors()); + } +} diff --git a/Tests/Unit/Validation/SaxonXslToSvrlValidatorTest.php b/Tests/Unit/Validation/SaxonXslToSvrlValidatorTest.php new file mode 100644 index 000000000..d18d404fc --- /dev/null +++ b/Tests/Unit/Validation/SaxonXslToSvrlValidatorTest.php @@ -0,0 +1,81 @@ + + * + * 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\Tests\Unit\Validation; + +use InvalidArgumentException; +use Kitodo\Dlf\Validation\SaxonXslToSvrlValidator; +use ReflectionClass; +use TYPO3\CMS\Extbase\Error\Result; +use TYPO3\TestingFramework\Core\Unit\UnitTestCase; + +/** + * Testing the SaxonXslToSvrlValidator class. + * + * @package TYPO3 + * @subpackage dlf + * + * @access public + */ +class SaxonXslToSvrlValidatorTest extends UnitTestCase +{ + const SVRL = << + + The year must be greater than 1900. + + + SVRL; + + public function setUp(): void + { + parent::setUp(); + $this->resetSingletonInstances = true; + } + + public function testJarFileNotFound(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage("Saxon JAR file not found."); + new SaxonXslToSvrlValidator([]); + } + + public function testXslFileNotFound(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage("XSL Schematron file not found."); + // It only checks if a file exists at the specified path, so we can use one of the test files. + new SaxonXslToSvrlValidator(["jar" => 'EXT:dlf/Tests/Fixtures/Format/alto.xml']); + } + + public function testValidation(): void + { + $saxonXslToSvrlValidator = new SaxonXslToSvrlValidator(["jar" => 'EXT:dlf/Tests/Fixtures/Format/alto.xml', "xsl" => 'EXT:dlf/Tests/Fixtures/Format/alto.xml']); + $reflection = new ReflectionClass($saxonXslToSvrlValidator); + + $result = $reflection->getProperty("result"); + $result->setAccessible(true); + $result->setValue($saxonXslToSvrlValidator, new Result()); + + $method = $reflection->getMethod("addErrorsOfSvrl"); + $method->setAccessible(true); + $method->invoke($saxonXslToSvrlValidator, self::SVRL); + + self::assertTrue($result->getValue($saxonXslToSvrlValidator)->hasErrors()); + self::assertEquals("The year must be greater than 1900.", $result->getValue($saxonXslToSvrlValidator)->getErrors()[0]->getMessage()); + } +} diff --git a/Tests/Unit/Validation/XmlSchemesValidatorTest.php b/Tests/Unit/Validation/XmlSchemesValidatorTest.php new file mode 100644 index 000000000..339dd92f2 --- /dev/null +++ b/Tests/Unit/Validation/XmlSchemesValidatorTest.php @@ -0,0 +1,106 @@ + + * + * 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\Tests\Unit\Validation; + +use DOMDocument; +use Kitodo\Dlf\Validation\XmlSchemesValidator; +use TYPO3\TestingFramework\Core\Unit\UnitTestCase; + +/** + * Testing the XmlSchemesValidator class. + * + * @package TYPO3 + * @subpackage dlf + * + * @access public + */ +class XmlSchemesValidatorTest extends UnitTestCase +{ + const METS = << + + + + + + + + + + METS; + + const METS_MODS = << + + + + + + + + + + + + + + + + + + + METS_MODS; + + public function setUp(): void + { + parent::setUp(); + $this->resetSingletonInstances = true; + } + + public function testValidation(): void + { + $xmlSchemesValidator = new XmlSchemesValidator( + [["namespace" => "http://www.loc.gov/METS/", "schemaLocation" => "http://www.loc.gov/standards/mets/mets.xsd"], ["namespace" => "http://www.loc.gov/mods/v3", "schemaLocation" => "http://www.loc.gov/standards/mods/mods.xsd"]] + ); + + $domDocument = new DOMDocument(); + // Test with empty document + $result = $xmlSchemesValidator->validate($domDocument); + self::assertCount(1, $result->getErrors()); + + $domDocument->loadXML(self::METS); + $result = $xmlSchemesValidator->validate($domDocument); + self::assertFalse($result->hasErrors()); + + $domDocument->loadXML(self::METS_MODS); + $result = $xmlSchemesValidator->validate($domDocument); + self::assertFalse($result->hasErrors()); + + // Test with wrong mets element + $domDocument->loadXML(str_replace("mets:metsHdr", "mets:Hdr", self::METS)); + $result = $xmlSchemesValidator->validate($domDocument); + self::assertTrue($result->hasErrors()); + + // Test with wrong mods element + $domDocument->loadXML(str_replace("mods:titleInfo", "mods:title", self::METS_MODS)); + + $result = $xmlSchemesValidator->validate($domDocument); + self::assertTrue($result->hasErrors()); + } +} diff --git a/composer.json b/composer.json index f74baad88..7b3d42fcb 100644 --- a/composer.json +++ b/composer.json @@ -39,7 +39,8 @@ "caseyamcl/phpoaipmh": "^3.3.1", "slub/php-mods-reader": "^0.4.0", "ubl/php-iiif-prezi-reader": "0.3.0", - "solarium/solarium": "5.2 - 6.3" + "solarium/solarium": "5.2 - 6.3", + "symfony/process": "^5.4.40" }, "require-dev": { "phpstan/phpstan": "^1.11.10",