mirror of
https://github.com/werkraum-media/thuecat.git
synced 2024-12-04 19:16:13 +01:00
Handle broken opening hours (#119)
Those are skipped during import and written as error to TYPO3 logs. That way entries with broken opening hours can still be imported. Resolves: #11377
This commit is contained in:
parent
8c4e33a8d0
commit
c96768ba85
8 changed files with 262 additions and 13 deletions
30
Classes/Domain/Import/Entity/InvalidDataException.php
Normal file
30
Classes/Domain/Import/Entity/InvalidDataException.php
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2024 Daniel Siepmann <daniel.siepmann@codappix.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
|
* 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace WerkraumMedia\ThueCat\Domain\Import\Entity;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
final class InvalidDataException extends Exception
|
||||||
|
{
|
||||||
|
}
|
|
@ -23,6 +23,8 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace WerkraumMedia\ThueCat\Domain\Import\Entity\Properties;
|
namespace WerkraumMedia\ThueCat\Domain\Import\Entity\Properties;
|
||||||
|
|
||||||
|
use WerkraumMedia\ThueCat\Domain\Import\Entity\InvalidDataException;
|
||||||
|
|
||||||
class OpeningHour
|
class OpeningHour
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
@ -36,14 +38,14 @@ class OpeningHour
|
||||||
protected $validThrough = null;
|
protected $validThrough = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \DateTimeImmutable
|
* @var \DateTimeImmutable|null
|
||||||
*/
|
*/
|
||||||
protected $opens;
|
protected $opens = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \DateTimeImmutable
|
* @var \DateTimeImmutable|null
|
||||||
*/
|
*/
|
||||||
protected $closes;
|
protected $closes = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string[]
|
* @var string[]
|
||||||
|
@ -62,11 +64,19 @@ class OpeningHour
|
||||||
|
|
||||||
public function getOpens(): \DateTimeImmutable
|
public function getOpens(): \DateTimeImmutable
|
||||||
{
|
{
|
||||||
|
if ($this->opens === null) {
|
||||||
|
throw new InvalidDataException('Opens was empty for opening hour.');
|
||||||
|
}
|
||||||
|
|
||||||
return $this->opens;
|
return $this->opens;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCloses(): \DateTimeImmutable
|
public function getCloses(): \DateTimeImmutable
|
||||||
{
|
{
|
||||||
|
if ($this->closes === null) {
|
||||||
|
throw new InvalidDataException('Closes was empty for opening hour.');
|
||||||
|
}
|
||||||
|
|
||||||
return $this->closes;
|
return $this->closes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ use TYPO3\CMS\Core\Log\Logger;
|
||||||
use TYPO3\CMS\Extbase\Persistence\QueryResultInterface;
|
use TYPO3\CMS\Extbase\Persistence\QueryResultInterface;
|
||||||
use WerkraumMedia\ThueCat\Domain\Import\Entity\AccessibilitySpecification;
|
use WerkraumMedia\ThueCat\Domain\Import\Entity\AccessibilitySpecification;
|
||||||
use WerkraumMedia\ThueCat\Domain\Import\Entity\Base;
|
use WerkraumMedia\ThueCat\Domain\Import\Entity\Base;
|
||||||
|
use WerkraumMedia\ThueCat\Domain\Import\Entity\InvalidDataException;
|
||||||
use WerkraumMedia\ThueCat\Domain\Import\Entity\MapsToType;
|
use WerkraumMedia\ThueCat\Domain\Import\Entity\MapsToType;
|
||||||
use WerkraumMedia\ThueCat\Domain\Import\Entity\MediaObject;
|
use WerkraumMedia\ThueCat\Domain\Import\Entity\MediaObject;
|
||||||
use WerkraumMedia\ThueCat\Domain\Import\Entity\Minimum;
|
use WerkraumMedia\ThueCat\Domain\Import\Entity\Minimum;
|
||||||
|
@ -424,13 +425,21 @@ class GeneralConverter implements Converter
|
||||||
$data = [];
|
$data = [];
|
||||||
|
|
||||||
foreach ($openingHours as $openingHour) {
|
foreach ($openingHours as $openingHour) {
|
||||||
$data[] = array_filter([
|
try {
|
||||||
'opens' => $openingHour->getOpens()->format('H:i:s'),
|
$data[] = array_filter([
|
||||||
'closes' => $openingHour->getCloses()->format('H:i:s'),
|
'opens' => $openingHour->getOpens()->format('H:i:s'),
|
||||||
'from' => $openingHour->getValidFrom() ?? '',
|
'closes' => $openingHour->getCloses()->format('H:i:s'),
|
||||||
'through' => $openingHour->getValidThrough() ?? '',
|
'from' => $openingHour->getValidFrom() ?? '',
|
||||||
'daysOfWeek' => $openingHour->getDaysOfWeek(),
|
'through' => $openingHour->getValidThrough() ?? '',
|
||||||
]);
|
'daysOfWeek' => $openingHour->getDaysOfWeek(),
|
||||||
|
]);
|
||||||
|
} catch (InvalidDataException $e) {
|
||||||
|
$this->logger->error('Could not import opening hour due to type error: ' . $e->getMessage(), [
|
||||||
|
'errorMessage' => $e->getMessage(),
|
||||||
|
'openingHour' => var_export($openingHour, true),
|
||||||
|
]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return json_encode($data) ?: '';
|
return json_encode($data) ?: '';
|
||||||
|
|
|
@ -16,7 +16,9 @@ Features
|
||||||
Fixes
|
Fixes
|
||||||
-----
|
-----
|
||||||
|
|
||||||
Nothing
|
* Handle broken opening hours.
|
||||||
|
Those are skipped during import and written as error to TYPO3 logs.
|
||||||
|
That way entries with broken opening hours can still be imported.
|
||||||
|
|
||||||
Tasks
|
Tasks
|
||||||
-----
|
-----
|
||||||
|
|
|
@ -117,7 +117,12 @@ abstract class AbstractImportTest extends FunctionalTestCase
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
self::getInstancePath() . '/typo3temp/var/log/typo3_0493d91d8e.log',
|
self::getInstancePath() . '/typo3temp/var/log/typo3_0493d91d8e.log',
|
||||||
self::getInstancePath() . '/typo3temp/var/log/typo3_error_0493d91d8e.log',
|
$this->getErrorLogFile(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function getErrorLogFile(): string
|
||||||
|
{
|
||||||
|
return self::getInstancePath() . '/typo3temp/var/log/typo3_error_0493d91d8e.log';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
{
|
||||||
|
"@context": {
|
||||||
|
"cdb": "https://thuecat.org/ontology/cdb/1.0/",
|
||||||
|
"dachkg": "https://thuecat.org/ontology/dachkg/1.0/",
|
||||||
|
"dbo": "http://dbpedia.org/ontology/",
|
||||||
|
"dsv": "http://ontologies.sti-innsbruck.at/dsv/",
|
||||||
|
"foaf": "http://xmlns.com/foaf/0.1/",
|
||||||
|
"owl": "http://www.w3.org/2002/07/owl#",
|
||||||
|
"rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
|
||||||
|
"rdfs": "http://www.w3.org/2000/01/rdf-schema#",
|
||||||
|
"schema": "http://schema.org/",
|
||||||
|
"sh": "http://www.w3.org/ns/shacl#",
|
||||||
|
"thuecat": "https://thuecat.org/ontology/thuecat/1.0/",
|
||||||
|
"ttgds": "https://thuecat.org/ontology/ttgds/1.0/",
|
||||||
|
"xsd": "http://www.w3.org/2001/XMLSchema#"
|
||||||
|
},
|
||||||
|
"@graph": [
|
||||||
|
{
|
||||||
|
"@id": "https://thuecat.org/resources/attraction-with-broken-opening-hour",
|
||||||
|
"@type": [
|
||||||
|
"schema:Place",
|
||||||
|
"schema:Thing",
|
||||||
|
"schema:TouristAttraction",
|
||||||
|
"ttgds:PointOfInterest"
|
||||||
|
],
|
||||||
|
"schema:name": {
|
||||||
|
"@language": "de",
|
||||||
|
"@value": "One specialOpeningHoursSpecification misses opens and closes, leading to type error"
|
||||||
|
},
|
||||||
|
"schema:availableLanguage": [
|
||||||
|
{
|
||||||
|
"@type": "thuecat:Language",
|
||||||
|
"@value": "thuecat:German"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "thuecat:Language",
|
||||||
|
"@value": "thuecat:English"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"schema:specialOpeningHoursSpecification": [
|
||||||
|
{
|
||||||
|
"@id": "genid-1dccaefc0e06401c93068c8081b7ea8c294892-b11",
|
||||||
|
"@type": [
|
||||||
|
"schema:Thing",
|
||||||
|
"schema:Intangible",
|
||||||
|
"schema:StructuredValue",
|
||||||
|
"schema:OpeningHoursSpecification"
|
||||||
|
],
|
||||||
|
"schema:dayOfWeek": {
|
||||||
|
"@type": "schema:DayOfWeek",
|
||||||
|
"@value": "schema:Thursday"
|
||||||
|
},
|
||||||
|
"schema:validThrough": {
|
||||||
|
"@type": "schema:Date",
|
||||||
|
"@value": "2024-09-19"
|
||||||
|
},
|
||||||
|
"schema:validFrom": {
|
||||||
|
"@type": "schema:Date",
|
||||||
|
"@value": "2024-09-19"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@id": "genid-1dccaefc0e06401c93068c8081b7ea8c294892-b12",
|
||||||
|
"@type": [
|
||||||
|
"schema:Thing",
|
||||||
|
"schema:Intangible",
|
||||||
|
"schema:StructuredValue",
|
||||||
|
"schema:OpeningHoursSpecification"
|
||||||
|
],
|
||||||
|
"schema:opens": {
|
||||||
|
"@type": "schema:Time",
|
||||||
|
"@value": "09:30:00"
|
||||||
|
},
|
||||||
|
"schema:closes": {
|
||||||
|
"@type": "schema:Time",
|
||||||
|
"@value": "18:00:00"
|
||||||
|
},
|
||||||
|
"schema:dayOfWeek": {
|
||||||
|
"@type": "schema:DayOfWeek",
|
||||||
|
"@value": "schema:Thursday"
|
||||||
|
},
|
||||||
|
"schema:validThrough": {
|
||||||
|
"@type": "schema:Date",
|
||||||
|
"@value": "2024-09-19"
|
||||||
|
},
|
||||||
|
"schema:validFrom": {
|
||||||
|
"@type": "schema:Date",
|
||||||
|
"@value": "2024-09-19"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"thuecat:contentResponsible": {
|
||||||
|
"@id": "https://thuecat.org/resources/018132452787-ngbe"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<dataset>
|
||||||
|
<pages>
|
||||||
|
<uid>1</uid>
|
||||||
|
<pid>0</pid>
|
||||||
|
<tstamp>1613400587</tstamp>
|
||||||
|
<crdate>1613400558</crdate>
|
||||||
|
<cruser_id>1</cruser_id>
|
||||||
|
<doktype>4</doktype>
|
||||||
|
<title>Rootpage</title>
|
||||||
|
<is_siteroot>1</is_siteroot>
|
||||||
|
</pages>
|
||||||
|
<pages>
|
||||||
|
<uid>10</uid>
|
||||||
|
<pid>1</pid>
|
||||||
|
<tstamp>1613400587</tstamp>
|
||||||
|
<crdate>1613400558</crdate>
|
||||||
|
<cruser_id>1</cruser_id>
|
||||||
|
<doktype>254</doktype>
|
||||||
|
<title>Storage folder</title>
|
||||||
|
</pages>
|
||||||
|
|
||||||
|
<sys_language>
|
||||||
|
<uid>1</uid>
|
||||||
|
<pid>0</pid>
|
||||||
|
<title>English</title>
|
||||||
|
<flag>en-us-gb</flag>
|
||||||
|
<language_isocode>en</language_isocode>
|
||||||
|
</sys_language>
|
||||||
|
|
||||||
|
<tx_thuecat_import_configuration>
|
||||||
|
<uid>1</uid>
|
||||||
|
<pid>0</pid>
|
||||||
|
<tstamp>1613400587</tstamp>
|
||||||
|
<crdate>1613400558</crdate>
|
||||||
|
<cruser_id>1</cruser_id>
|
||||||
|
<disable>0</disable>
|
||||||
|
<title>Tourist Attraction</title>
|
||||||
|
<type>static</type>
|
||||||
|
<configuration><![CDATA[<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
|
||||||
|
<T3FlexForms>
|
||||||
|
<data>
|
||||||
|
<sheet index="sDEF">
|
||||||
|
<language index="lDEF">
|
||||||
|
<field index="storagePid">
|
||||||
|
<value index="vDEF">10</value>
|
||||||
|
</field>
|
||||||
|
<field index="urls">
|
||||||
|
<el index="el">
|
||||||
|
<field index="602a89f54d694654233086">
|
||||||
|
<value index="url">
|
||||||
|
<el>
|
||||||
|
<field index="url">
|
||||||
|
<value index="vDEF">https://thuecat.org/resources/attraction-with-broken-opening-hour</value>
|
||||||
|
</field>
|
||||||
|
</el>
|
||||||
|
</value>
|
||||||
|
<value index="_TOGGLE">0</value>
|
||||||
|
</field>
|
||||||
|
</el>
|
||||||
|
</field>
|
||||||
|
</language>
|
||||||
|
</sheet>
|
||||||
|
</data>
|
||||||
|
</T3FlexForms>
|
||||||
|
]]></configuration>
|
||||||
|
</tx_thuecat_import_configuration>
|
||||||
|
</dataset>
|
|
@ -422,6 +422,34 @@ class ImportTest extends AbstractImportTest
|
||||||
$this->assertPHPDataSet(__DIR__ . '/Assertions/Import/ImportsTouristAttractionWithSingleSlogan.php');
|
$this->assertPHPDataSet(__DIR__ . '/Assertions/Import/ImportsTouristAttractionWithSingleSlogan.php');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function importsWithBrokenOpeningHour(): void
|
||||||
|
{
|
||||||
|
$this->importDataSet(__DIR__ . '/Fixtures/Import/ImportsWithBrokenOpeningHour.xml');
|
||||||
|
|
||||||
|
GuzzleClientFaker::appendResponseFromFile(__DIR__ . '/Fixtures/Import/Guzzle/thuecat.org/resources/attraction-with-broken-opening-hour.json');
|
||||||
|
GuzzleClientFaker::appendResponseFromFile(__DIR__ . '/Fixtures/Import/Guzzle/thuecat.org/resources/018132452787-ngbe.json');
|
||||||
|
|
||||||
|
$this->importConfiguration();
|
||||||
|
|
||||||
|
$records = $this->getAllRecords('tx_thuecat_tourist_attraction');
|
||||||
|
self::assertCount(1, $this->getAllRecords('tx_thuecat_tourist_attraction'));
|
||||||
|
$specialOpeningHours = json_decode($records[0]['special_opening_hours'], true, 512, JSON_THROW_ON_ERROR);
|
||||||
|
self::assertIsArray($specialOpeningHours);
|
||||||
|
self::assertCount(1, $specialOpeningHours);
|
||||||
|
|
||||||
|
$this->expectErrors = true;
|
||||||
|
$loggedErrors = file_get_contents($this->getErrorLogFile());
|
||||||
|
self::assertIsString($loggedErrors);
|
||||||
|
self::assertStringContainsString(
|
||||||
|
'Could not import opening hour due to type error: Opens was empty for opening hour.',
|
||||||
|
$loggedErrors
|
||||||
|
);
|
||||||
|
self::assertStringContainsString('\'closes\' => NULL,', $loggedErrors);
|
||||||
|
}
|
||||||
|
|
||||||
private function importConfiguration(): void
|
private function importConfiguration(): void
|
||||||
{
|
{
|
||||||
$configuration = $this->get(ImportConfigurationRepository::class)->findByUid(1);
|
$configuration = $this->get(ImportConfigurationRepository::class)->findByUid(1);
|
||||||
|
|
Loading…
Reference in a new issue