Handle broken opening hours (#121)

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:
Daniel Siepmann (Codappix) 2024-09-10 07:07:24 +02:00 committed by GitHub
parent 98eb5ffafb
commit a2fa701396
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 255 additions and 10 deletions

View 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
{
}

View file

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Import\Entity\Properties; namespace WerkraumMedia\ThueCat\Domain\Import\Entity\Properties;
use DateTimeImmutable; use DateTimeImmutable;
use WerkraumMedia\ThueCat\Domain\Import\Entity\InvalidDataException;
class OpeningHour class OpeningHour
{ {
@ -31,9 +32,9 @@ class OpeningHour
protected ?DateTimeImmutable $validThrough = null; protected ?DateTimeImmutable $validThrough = null;
protected DateTimeImmutable $opens; protected ?DateTimeImmutable $opens = null;
protected DateTimeImmutable $closes; protected ?DateTimeImmutable $closes = null;
/** /**
* @var string[] * @var string[]
@ -52,11 +53,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;
} }

View file

@ -29,6 +29,7 @@ use TYPO3\CMS\Core\Log\LogManager;
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;
@ -378,6 +379,7 @@ class GeneralConverter implements Converter
$data = []; $data = [];
foreach ($openingHours as $openingHour) { foreach ($openingHours as $openingHour) {
try {
$data[] = array_filter([ $data[] = array_filter([
'opens' => $openingHour->getOpens()->format('H:i:s'), 'opens' => $openingHour->getOpens()->format('H:i:s'),
'closes' => $openingHour->getCloses()->format('H:i:s'), 'closes' => $openingHour->getCloses()->format('H:i:s'),
@ -385,6 +387,13 @@ class GeneralConverter implements Converter
'through' => $openingHour->getValidThrough() ?? '', 'through' => $openingHour->getValidThrough() ?? '',
'daysOfWeek' => $openingHour->getDaysOfWeek(), 'daysOfWeek' => $openingHour->getDaysOfWeek(),
]); ]);
} catch (InvalidDataException $e) {
$this->logger->error('Could not import opening hour due to type error: {errorMessage}', [
'errorMessage' => $e->getMessage(),
'openingHour' => var_export($openingHour, true),
]);
continue;
}
} }
return json_encode($data, JSON_THROW_ON_ERROR) ?: ''; return json_encode($data, JSON_THROW_ON_ERROR) ?: '';

View file

@ -17,6 +17,10 @@ Fixes
* Add missing dependency to `typo3/cms-install`. * Add missing dependency to `typo3/cms-install`.
As this provides the upgrade wizard feature. As this provides the upgrade wizard feature.
* 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
----- -----

View file

@ -108,7 +108,12 @@ abstract class AbstractImportTestCase extends \TYPO3\TestingFramework\Core\Funct
{ {
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';
}
} }

View file

@ -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"
}
}
]
}

View file

@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
use TYPO3\CMS\Core\Domain\Repository\PageRepository;
return [
'pages' => [
0 => [
'uid' => '1',
'pid' => '0',
'tstamp' => '1613400587',
'crdate' => '1613400558',
'doktype' => PageRepository::DOKTYPE_DEFAULT,
'title' => 'Rootpage',
'is_siteroot' => '1',
],
1 => [
'uid' => '10',
'pid' => '1',
'tstamp' => '1613400587',
'crdate' => '1613400558',
'doktype' => PageRepository::DOKTYPE_SYSFOLDER,
'title' => 'Storage folder',
],
],
'tx_thuecat_import_configuration' => [
0 => [
'uid' => '1',
'pid' => '0',
'tstamp' => '1613400587',
'crdate' => '1613400558',
'disable' => '0',
'title' => 'Tourist Attraction',
'type' => 'static',
'configuration' => '<?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>
',
],
],
];

View file

@ -359,6 +359,32 @@ class ImportTest extends AbstractImportTestCase
$this->assertPHPDataSet(__DIR__ . '/Assertions/Import/ImportsTouristAttractionWithSingleSlogan.php'); $this->assertPHPDataSet(__DIR__ . '/Assertions/Import/ImportsTouristAttractionWithSingleSlogan.php');
} }
#[Test]
public function importsWithBrokenOpeningHour(): void
{
$this->importPHPDataSet(__DIR__ . '/Fixtures/Import/ImportsWithBrokenOpeningHour.php');
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);