Add special opening hours (#84)

It is possible to define special opening hours, e.g. for holidays.
Those are now also imported and provided to the templates.

Relates: #10185
This commit is contained in:
Daniel Siepmann 2022-11-28 11:06:54 +01:00 committed by GitHub
parent 672f78a5dc
commit 8acbe136d4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 395 additions and 7 deletions

View file

@ -53,6 +53,11 @@ class Place extends Base
*/
protected $openingHours = [];
/**
* @var OpeningHour[]
*/
protected $specialOpeningHours = [];
/**
* @var ForeignReference[]
*/
@ -162,6 +167,20 @@ class Place extends Base
);
}
/**
* @return OpeningHour[]
*/
public function getSpecialOpeningHoursSpecification(): array
{
return GeneralUtility::makeInstance(DateBasedFilter::class)
->filterOutPreviousDates(
$this->specialOpeningHours,
function (OpeningHour $hour): ?\DateTimeImmutable {
return $hour->getValidThrough();
}
);
}
/**
* @return ForeignReference[]
*/
@ -201,6 +220,21 @@ class Place extends Base
{
}
/**
* @internal for mapping via Symfony component.
*/
public function addSpecialOpeningHoursSpecification(OpeningHour $openingHour): void
{
$this->specialOpeningHours[] = $openingHour;
}
/**
* @internal for mapping via Symfony component.
*/
public function removeSpecialOpeningHoursSpecification(OpeningHour $openingHour): void
{
}
/**
* @internal for mapping via Symfony component.
*/

View file

@ -35,6 +35,7 @@ use WerkraumMedia\ThueCat\Domain\Import\Entity\Organisation;
use WerkraumMedia\ThueCat\Domain\Import\Entity\ParkingFacility;
use WerkraumMedia\ThueCat\Domain\Import\Entity\Place;
use WerkraumMedia\ThueCat\Domain\Import\Entity\Properties\ForeignReference;
use WerkraumMedia\ThueCat\Domain\Import\Entity\Properties\OpeningHour;
use WerkraumMedia\ThueCat\Domain\Import\Entity\Properties\PriceSpecification;
use WerkraumMedia\ThueCat\Domain\Import\Entity\TouristAttraction;
use WerkraumMedia\ThueCat\Domain\Import\Entity\TouristInformation;
@ -228,7 +229,8 @@ class GeneralConverter implements Converter, LoggerAwareInterface
'parking_facility_near_by' => $entity instanceof Base ? implode(',', $this->getParkingFacilitiesNearByUids($entity)) : '',
'opening_hours' => $entity instanceof Place ? $this->getOpeningHours($entity) : '',
'opening_hours' => $entity instanceof Place ? $this->getOpeningHours($entity->getOpeningHoursSpecification()) : '',
'special_opening_hours' => $entity instanceof Place ? $this->getOpeningHours($entity->getSpecialOpeningHoursSpecification()) : '',
'address' => $entity instanceof Place ? $this->getAddress($entity) : '',
'offers' => $entity instanceof Place ? $this->getOffers($entity) : '',
'other_service' => method_exists($entity, 'getOtherServices') ? implode(',', $entity->getOtherServices()) : '',
@ -404,11 +406,14 @@ class GeneralConverter implements Converter, LoggerAwareInterface
];
}
private function getOpeningHours(Place $entity): string
/**
* @param OpeningHour[] $openingHours
*/
private function getOpeningHours(array $openingHours): string
{
$data = [];
foreach ($entity->getOpeningHoursSpecification() as $openingHour) {
foreach ($openingHours as $openingHour) {
$data[] = array_filter([
'opens' => $openingHour->getOpens()->format('H:i:s'),
'closes' => $openingHour->getCloses()->format('H:i:s'),

View file

@ -38,6 +38,11 @@ abstract class Place extends Base
*/
protected $openingHours = null;
/**
* @var OpeningHours|null
*/
protected $specialOpeningHours = null;
/**
* @var ObjectStorage<ParkingFacility>
*/
@ -88,6 +93,11 @@ abstract class Place extends Base
return $this->openingHours;
}
public function getSpecialOpeningHours(): ?OpeningHours
{
return $this->specialOpeningHours;
}
public function getParkingFacilitiesNearBy(): ObjectStorage
{
return $this->parkingFacilityNearBy;

View file

@ -125,6 +125,14 @@ return (static function (string $extensionKey, string $tableName) {
'readOnly' => true,
],
],
'special_opening_hours' => [
'label' => $languagePath . '.special_opening_hours',
'l10n_mode' => 'exclude',
'config' => [
'type' => 'text',
'readOnly' => true,
],
],
'address' => [
'label' => $languagePath . '.address',
'l10n_mode' => 'exclude',
@ -200,7 +208,7 @@ return (static function (string $extensionKey, string $tableName) {
],
'types' => [
'0' => [
'showitem' => '--palette--;;language, title, description, sanitation, other_service, traffic_infrastructure, payment_accepted, distance_to_public_transport, opening_hours, offers, address, media, remote_id, --div--;' . $languagePath . '.tab.relations, town, managed_by',
'showitem' => '--palette--;;language, title, description, sanitation, other_service, traffic_infrastructure, payment_accepted, distance_to_public_transport, opening_hours, special_opening_hours, offers, address, media, remote_id, --div--;' . $languagePath . '.tab.relations, town, managed_by',
],
],
];

View file

@ -205,6 +205,14 @@ return (static function (string $extensionKey, string $tableName) {
'readOnly' => true,
],
],
'special_opening_hours' => [
'label' => $languagePath . '.special_opening_hours',
'l10n_mode' => 'exclude',
'config' => [
'type' => 'text',
'readOnly' => true,
],
],
'address' => [
'label' => $languagePath . '.address',
'l10n_mode' => 'exclude',
@ -303,7 +311,7 @@ return (static function (string $extensionKey, string $tableName) {
],
'types' => [
'0' => [
'showitem' => '--palette--;;language, title, description, slogan, start_of_construction, sanitation, other_service, museum_service, architectural_style, traffic_infrastructure, payment_accepted, digital_offer, photography, pets_allowed, is_accessible_for_free, public_access, available_languages, distance_to_public_transport, opening_hours, offers, accessibility_specification, address, media, remote_id, --div--;' . $languagePath . '.tab.relations, town, managed_by, parking_facility_near_by',
'showitem' => '--palette--;;language, title, description, slogan, start_of_construction, sanitation, other_service, museum_service, architectural_style, traffic_infrastructure, payment_accepted, digital_offer, photography, pets_allowed, is_accessible_for_free, public_access, available_languages, distance_to_public_transport, opening_hours, special_opening_hours, offers, accessibility_specification, address, media, remote_id, --div--;' . $languagePath . '.tab.relations, town, managed_by, parking_facility_near_by',
],
],
];

View file

@ -22,6 +22,10 @@ Features
This should improve the UX of website visitors.
It is not possible yet to sort opening hours by hand within the thuecat backend.
* Support special opening hours.
It is possible to define special opening hours, e.g. for holidays.
Those are now also imported and provided to the templates.
Fixes
-----

View file

@ -99,6 +99,10 @@
<source>Address</source>
<target>Anschrift</target>
</trans-unit>
<trans-unit id="content.specialOpeningHours" xml:space="preserve">
<source>Special Opening hours</source>
<target>Sonderöffnungszeiten</target>
</trans-unit>
<trans-unit id="content.openingHours" xml:space="preserve">
<source>Opening hours</source>
<target>Öffnungszeiten</target>

View file

@ -149,6 +149,9 @@
<trans-unit id="content.address" xml:space="preserve">
<source>Address</source>
</trans-unit>
<trans-unit id="content.specialOpeningHours" xml:space="preserve">
<source>Special Opening hours</source>
</trans-unit>
<trans-unit id="content.openingHours" xml:space="preserve">
<source>Opening hours</source>
</trans-unit>

View file

@ -141,6 +141,9 @@
<trans-unit id="tx_thuecat_tourist_attraction.opening_hours" xml:space="preserve">
<source>Opening Hours</source>
</trans-unit>
<trans-unit id="tx_thuecat_tourist_attraction.special_opening_hours" xml:space="preserve">
<source>Special Opening Hours</source>
</trans-unit>
<trans-unit id="tx_thuecat_tourist_attraction.address" xml:space="preserve">
<source>Address</source>
</trans-unit>
@ -193,6 +196,9 @@
<trans-unit id="tx_thuecat_parking_facility.opening_hours" xml:space="preserve">
<source>Opening Hours</source>
</trans-unit>
<trans-unit id="tx_thuecat_parking_facility.special_opening_hours" xml:space="preserve">
<source>Special Opening Hours</source>
</trans-unit>
<trans-unit id="tx_thuecat_parking_facility.address" xml:space="preserve">
<source>Address</source>
</trans-unit>

View file

@ -137,6 +137,12 @@
{f:render(partial: 'Opening', arguments: {openingHours: entity.openingHours})}
</div>
</f:if>
<f:if condition="{entity.specialOpeningHours}">
<div class="col-md-6">
<h2>{f:translate(id: 'content.specialOpeningHours', extensionName: 'Thuecat')}</h2>
{f:render(partial: 'Opening', arguments: {openingHours: entity.specialOpeningHours})}
</div>
</f:if>
</div>
<div class="thuecat__offers">

View file

@ -0,0 +1,120 @@
{
"@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/835224016581-dara",
"@type": [
"schema:Place",
"schema:Thing",
"schema:TouristAttraction",
"ttgds:PointOfInterest",
"thuecat:Building",
"thuecat:ReligiousBuilding",
"thuecat:Cathedral",
"thuecat:CatholicChurch",
"thuecat:Dome"
],
"thuecat:contentResponsible": {
"@id": "https://thuecat.org/resources/018132452787-ngbe"
},
"schema:availableLanguage": [
{
"@type": "thuecat:Language",
"@value": "thuecat:German"
},
{
"@type": "thuecat:Language",
"@value": "thuecat:English"
}
],
"schema:name": [
{
"@language": "de",
"@value": "Dom St. Marien"
},
{
"@language": "en",
"@value": "Cathedral of St. Mary"
}
],
"schema:specialOpeningHoursSpecification": [
{
"@id": "genid-6b61946f64bc4b518eac7d499bb5375f-b10",
"@type": [
"schema:Thing",
"schema:Intangible",
"schema:StructuredValue",
"schema:OpeningHoursSpecification"
],
"schema:closes": {
"@type": "schema:Time",
"@value": "14:00:00"
},
"schema:dayOfWeek": {
"@type": "schema:DayOfWeek",
"@value": "schema:Saturday"
},
"schema:opens": {
"@type": "schema:Time",
"@value": "10:00:00"
},
"schema:validFrom": {
"@type": "schema:Date",
"@value": "2050-12-31"
},
"schema:validThrough": {
"@type": "schema:Date",
"@value": "2050-12-31"
}
},
{
"@id": "genid-6b61946f64bc4b518eac7d499bb5375f-b11",
"@type": [
"schema:Thing",
"schema:Intangible",
"schema:StructuredValue",
"schema:OpeningHoursSpecification"
],
"schema:closes": {
"@type": "schema:Time",
"@value": "14:00:00"
},
"schema:dayOfWeek": {
"@type": "schema:DayOfWeek",
"@value": "schema:Saturday"
},
"schema:opens": {
"@type": "schema:Time",
"@value": "10:00:00"
},
"schema:validFrom": {
"@type": "schema:Date",
"@value": "2021-12-31"
},
"schema:validThrough": {
"@type": "schema:Date",
"@value": "2021-12-31"
}
}
],
"schema:url": {
"@type": "schema:URL",
"@value": "http://www.dom-erfurt.de"
}
}
]
}

View file

@ -0,0 +1,76 @@
<?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>
<sys_language>
<uid>2</uid>
<pid>0</pid>
<title>French</title>
<flag>fr</flag>
<language_isocode>fr</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>The 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/special-opening-hours</value>
</field>
</el>
</value>
<value index="_TOGGLE">0</value>
</field>
</el>
</field>
</language>
</sheet>
</data>
</T3FlexForms>
]]></configuration>
</tx_thuecat_import_configuration>
</dataset>

View file

@ -0,0 +1,4 @@
"tx_thuecat_tourist_attraction",,,,,,,,
,"uid","pid","sys_language_uid","l18n_parent","l10n_source","remote_id","title","special_opening_hours"
,1,10,0,0,0,"https://thuecat.org/resources/835224016581-dara","Dom St. Marien","[{""opens"":""10:00:00"",""closes"":""14:00:00"",""from"":{""date"":""2050-12-31 00:00:00.000000"",""timezone_type"":3,""timezone"":""UTC""},""through"":{""date"":""2050-12-31 00:00:00.000000"",""timezone_type"":3,""timezone"":""UTC""},""daysOfWeek"":[""Saturday""]}]"
,2,10,1,1,1,"https://thuecat.org/resources/835224016581-dara","Cathedral of St. Mary","[{""opens"":""10:00:00"",""closes"":""14:00:00"",""from"":{""date"":""2050-12-31 00:00:00.000000"",""timezone_type"":3,""timezone"":""UTC""},""through"":{""date"":""2050-12-31 00:00:00.000000"",""timezone_type"":3,""timezone"":""UTC""},""daysOfWeek"":[""Saturday""]}]"
1 tx_thuecat_tourist_attraction
2 uid pid sys_language_uid l18n_parent l10n_source remote_id title special_opening_hours
3 1 10 0 0 0 https://thuecat.org/resources/835224016581-dara Dom St. Marien [{"opens":"10:00:00","closes":"14:00:00","from":{"date":"2050-12-31 00:00:00.000000","timezone_type":3,"timezone":"UTC"},"through":{"date":"2050-12-31 00:00:00.000000","timezone_type":3,"timezone":"UTC"},"daysOfWeek":["Saturday"]}]
4 2 10 1 1 1 https://thuecat.org/resources/835224016581-dara Cathedral of St. Mary [{"opens":"10:00:00","closes":"14:00:00","from":{"date":"2050-12-31 00:00:00.000000","timezone_type":3,"timezone":"UTC"},"through":{"date":"2050-12-31 00:00:00.000000","timezone_type":3,"timezone":"UTC"},"daysOfWeek":["Saturday"]}]

View file

@ -561,4 +561,87 @@ class FrontendTest extends FunctionalTestCase
self::assertLessThan($positionThirdHour, $positionSecondHour, 'Third hour does not come after second hour.');
self::assertLessThan($positionSecondHour, $positionFirstHour, 'Second hour does not come after first hour.');
}
/**
* @test
*/
public function specialOpeningHoursAreRendered(): void
{
$this->importDataSet('EXT:thuecat/Tests/Functional/Fixtures/Frontend/TouristAttractionsOpeningHours.xml');
$hidden = new \DateTimeImmutable('yesterday');
$available = new \DateTimeImmutable('tomorrow');
$available2 = new \DateTimeImmutable('+3 days');
$this->getConnectionPool()
->getConnectionForTable('tx_thuecat_tourist_attraction')
->update(
'tx_thuecat_tourist_attraction',
['special_opening_hours' => json_encode([
[
'closes' => '17:00:00',
'opens' => '13:00:00',
'daysOfWeek' => ['Sunday'],
'from' => [
'date' => $hidden->modify('-1 day')->format('Y-m-d') . ' 00:00:00.000000',
'timezone' => 'UTC',
'timezone_type' => 3,
],
'through' => [
'date' => $hidden->format('Y-m-d') . ' 00:00:00.000000',
'timezone' => 'UTC',
'timezone_type' => 3,
],
],
[
'closes' => '17:00:00',
'opens' => '13:00:00',
'daysOfWeek' => ['Sunday'],
'from' => [
'date' => $available2->modify('-1 day')->format('Y-m-d') . ' 00:00:00.000000',
'timezone' => 'UTC',
'timezone_type' => 3,
],
'through' => [
'date' => $available2->format('Y-m-d') . ' 00:00:00.000000',
'timezone' => 'UTC',
'timezone_type' => 3,
],
],
[
'closes' => '17:00:00',
'opens' => '13:00:00',
'daysOfWeek' => ['Sunday'],
'from' => [
'date' => $available->modify('-1 day')->format('Y-m-d') . ' 00:00:00.000000',
'timezone' => 'UTC',
'timezone_type' => 3,
],
'through' => [
'date' => $available->format('Y-m-d') . ' 00:00:00.000000',
'timezone' => 'UTC',
'timezone_type' => 3,
],
],
])],
['uid' => 1]
);
$request = new InternalRequest();
$request = $request->withPageId(2);
$result = (string)$this->executeFrontendRequest($request)->getBody();
self::assertStringNotContainsString($hidden->modify('-1 day')->format('d.m.Y'), $result, 'Filtered date is shown');
self::assertStringNotContainsString($hidden->format('d.m.Y'), $result, 'Filtered date is shown');
self::assertStringContainsString($available->modify('-1 day')->format('d.m.Y'), $result, 'First special opening hour is missing');
self::assertStringContainsString($available->format('d.m.Y'), $result, 'First special opening hour is missing');
self::assertStringContainsString($available2->modify('-1 day')->format('d.m.Y'), $result, 'Second special opening hour is missing');
self::assertStringContainsString($available2->format('d.m.Y'), $result, 'Second special opening hour is missing');
$positionFirstHour = mb_strpos($result, $available->format('d.m.Y'));
$positionSecondHour = mb_strpos($result, $available2->format('d.m.Y'));
self::assertLessThan($positionSecondHour, $positionFirstHour, 'Second hour does not come after first hour.');
}
}

View file

@ -230,6 +230,21 @@ class ImportTest extends TestCase
$this->assertCSVDataSet('EXT:thuecat/Tests/Functional/Fixtures/Import/ImportsTouristAttractionsWithFilteredOpeningHours.csv');
}
/**
* @test
*/
public function importsTouristAttractionsWithSpecialOpeningHours(): void
{
$this->importDataSet(__DIR__ . '/Fixtures/Import/ImportsTouristAttractionWithSpecialOpeningHours.xml');
GuzzleClientFaker::appendResponseFromFile(__DIR__ . '/Fixtures/Import/Guzzle/thuecat.org/resources/special-opening-hours.json');
GuzzleClientFaker::appendResponseFromFile(__DIR__ . '/Fixtures/Import/Guzzle/thuecat.org/resources/018132452787-ngbe.json');
$configuration = $this->get(ImportConfigurationRepository::class)->findByUid(1);
$this->get(Importer::class)->importConfiguration($configuration);
$this->assertCSVDataSet('EXT:thuecat/Tests/Functional/Fixtures/Import/ImportsTouristAttractionsWithSpecialOpeningHours.csv');
}
/**
* @test
*/

View file

@ -55,6 +55,7 @@ CREATE TABLE tx_thuecat_tourist_attraction (
title varchar(255) DEFAULT '' NOT NULL,
description text,
opening_hours text,
special_opening_hours text,
address text,
media text,
offers text,
@ -83,6 +84,7 @@ CREATE TABLE tx_thuecat_parking_facility (
title varchar(255) DEFAULT '' NOT NULL,
description text,
opening_hours text,
special_opening_hours text,
address text,
media text,
offers text,

View file

@ -351,11 +351,11 @@ parameters:
-
message: "#^Cannot call method findByUid\\(\\) on mixed\\.$#"
count: 9
count: 10
path: Tests/Functional/ImportTest.php
-
message: "#^Cannot call method importConfiguration\\(\\) on mixed\\.$#"
count: 9
count: 10
path: Tests/Functional/ImportTest.php