Properly handle latitude and longitude during Destination One import (#32)

They sometimes use a different separator.
The code is adjusted to always use same separator and precision.

That will prevent the same location from showing up multiple times due
to different latitude and longitude values.
This commit is contained in:
Daniel Siepmann 2023-07-06 12:39:47 +02:00 committed by GitHub
parent 6348b1079d
commit 00946af6ad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 1276 additions and 15 deletions

View file

@ -75,8 +75,8 @@ class Location extends AbstractEntity
$this->district = $district;
$this->country = $country;
$this->phone = $phone;
$this->latitude = $latitude;
$this->longitude = $longitude;
$this->latitude = $this->normalizeGeocoordinate($latitude);
$this->longitude = $this->normalizeGeocoordinate($longitude);
$this->_languageUid = $languageUid;
$this->globalId = $this->generateGlobalId();
@ -132,6 +132,14 @@ class Location extends AbstractEntity
return $this->globalId;
}
public function updateFromLocation(self $location): void
{
// Only updates values not being part of global id.
$this->phone = $location->getPhone();
$this->longitude = $location->getLongitude();
$this->latitude = $location->getLatitude();
}
/**
* Validates the location.
*
@ -158,8 +166,21 @@ class Location extends AbstractEntity
$this->city,
$this->district,
$this->country,
$this->latitude,
$this->longitude,
]));
}
private function normalizeGeocoordinate(string $coordinate): string
{
$numberOfCommas = substr_count($coordinate, ',');
$numberOfPoints = substr_count($coordinate, '.');
if (
$numberOfCommas === 1
&& $numberOfPoints === 0
) {
$coordinate = str_replace(',', '.', $coordinate);
}
return number_format((float)$coordinate, 6, '.', '');
}
}

View file

@ -3,7 +3,19 @@
namespace Wrm\Events\Domain\Repository;
use TYPO3\CMS\Extbase\Persistence\Repository;
use Wrm\Events\Domain\Model\Location;
class LocationRepository extends Repository
{
public function findOneByGlobalId(string $globalId): ?Location
{
$query = $this->createQuery();
return $query
->matching($query->equals('globalId', $globalId))
->setLimit(1)
->execute()
->getFirst()
;
}
}

View file

@ -39,6 +39,12 @@ class LocationAssignment
$existingLocation = $this->repository->findOneByGlobalId($newLocation->getGlobalId());
return $existingLocation ?? $newLocation;
if ($existingLocation === null) {
return $newLocation;
}
$existingLocation->updateFromLocation($newLocation);
return $existingLocation;
}
}

View file

@ -0,0 +1,204 @@
<?php
declare(strict_types=1);
/*
* Copyright (C) 2023 Daniel Siepmann <coding@daniel-siepmann.de>
*
* 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 Wrm\Events\Updates;
use Generator;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Install\Updates\UpgradeWizardInterface;
use Wrm\Events\Domain\Model\Location;
final class MigrateDuplicateLocations implements UpgradeWizardInterface
{
/**
* @var ConnectionPool
*/
private $connectionPool;
public function __construct(
ConnectionPool $connectionPool
) {
$this->connectionPool = $connectionPool;
}
public function getIdentifier(): string
{
return self::class;
}
public function getTitle(): string
{
return 'Remove duplicate locations of EXT:event';
}
public function getDescription(): string
{
return 'Checks for duplicates and reduces them to one entry, fixing relations to events.';
}
public function updateNecessary(): bool
{
return true;
}
public function executeUpdate(): bool
{
$duplicates = [];
foreach ($this->getLocations() as $location) {
$locationObject = $this->buildObject($location);
if ($locationObject->getGlobalId() === $location['global_id']) {
continue;
}
$uid = (int)$location['uid'];
$matchingLocation = $this->getMatchingLocation(
$locationObject->getGlobalId(),
$uid
);
// Already have entries for the new id, this one is duplicate
if ($matchingLocation > 0) {
$duplicates[$uid] = $matchingLocation;
continue;
}
// No duplicates, update this one
$this->updateLocation($locationObject, $uid);
}
$this->removeDuplicates(array_keys($duplicates));
$this->updateRelations($duplicates);
return true;
}
public function getPrerequisites(): array
{
return [];
}
public static function register(): void
{
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][self::class] = self::class;
}
/**
* @return Generator<array>
*/
private function getLocations(): Generator
{
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('tx_events_domain_model_location');
$queryBuilder->select(
'name',
'street',
'zip',
'city',
'district',
'country',
'phone',
'latitude',
'longitude',
'global_id',
'uid',
'sys_language_uid'
);
$queryBuilder->from('tx_events_domain_model_location');
$queryBuilder->orderBy('uid', 'asc');
$result = $queryBuilder->execute();
foreach ($result->fetchAllAssociative() as $location) {
yield $location;
}
}
private function getMatchingLocation(
string $globalId,
int $uid
): int {
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('tx_events_domain_model_location');
$queryBuilder->select('uid');
$queryBuilder->from('tx_events_domain_model_location');
$queryBuilder->where($queryBuilder->expr()->eq('global_id', $queryBuilder->createNamedParameter($globalId)));
$queryBuilder->andWhere($queryBuilder->expr()->neq('uid', $queryBuilder->createNamedParameter($uid)));
$queryBuilder->setMaxResults(1);
$uid = $queryBuilder->execute()->fetchOne();
if (is_numeric($uid) === false) {
return 0;
}
return (int) $uid;
}
private function buildObject(array $location): Location
{
return new Location(
$location['name'],
$location['street'],
$location['zip'],
$location['city'],
$location['district'],
$location['country'],
$location['phone'],
$location['latitude'],
$location['longitude'],
(int)$location['sys_language_uid']
);
}
private function updateLocation(Location $location, int $uid): void
{
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('tx_events_domain_model_location');
$queryBuilder->update('tx_events_domain_model_location');
$queryBuilder->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($uid)));
$queryBuilder->set('global_id', $location->getGlobalId());
$queryBuilder->set('latitude', $location->getLatitude());
$queryBuilder->set('longitude', $location->getLongitude());
$queryBuilder->execute();
}
/**
* @param int[] $uids
*/
private function removeDuplicates(array $uids): void
{
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('tx_events_domain_model_location');
$queryBuilder->delete('tx_events_domain_model_location');
$queryBuilder->where($queryBuilder->expr()->in('uid', $queryBuilder->createNamedParameter($uids, Connection::PARAM_INT_ARRAY)));
$queryBuilder->execute();
}
private function updateRelations(array $migration): void
{
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('tx_events_domain_model_event');
$queryBuilder->update('tx_events_domain_model_event');
foreach ($migration as $legacyLocationUid => $newLocationUid) {
$finalBuilder = clone $queryBuilder;
$finalBuilder->where($finalBuilder->expr()->eq('location', $finalBuilder->createNamedParameter($legacyLocationUid)));
$finalBuilder->set('location', $newLocationUid);
$finalBuilder->execute();
}
}
}

View file

@ -37,6 +37,9 @@ services:
identifier: 'WrmEventsAddSpecialPropertiesToDate'
event: TYPO3\CMS\Extbase\Event\Persistence\AfterObjectThawedEvent
Wrm\Events\Updates\MigrateDuplicateLocations:
public: true
Wrm\Events\Updates\MigrateOldLocations:
public: true

View file

@ -1,6 +1,13 @@
3.4.0
=====
Manual steps
------------
* Determining global_id of locations has changed.
An Update Wizard is provided in order to migrate existing data.
This is only necessary when using the import of locations from Destination One.
Breaking
--------
@ -74,6 +81,10 @@ Fixes
Those might not exist in newer systems where migration is not necessary.
The wizard now properly checks for existence before querying the data.
* Prevent duplicate location entries from Destination One import.
They seem to differ in writing of latitude and longitude.
An update wizard is provided to clean up existing duplicates.
Tasks
-----

View file

@ -31,9 +31,9 @@ Es gilt die 2G-PLUS-Regel.",,,,,,,"1","2",,"8","3",,"1",,3,"adventliche-orgelmus
,"13","2","0","0","0","0",-1,0,"0","0","0","3","1671732000","1671735600","no","0",,"adventliche-orgelmusik-orgel-kmd-frank-bettenhausen-2022-12-22t18-00-00",,,,,,,,,,,,,,,,,,
"tx_events_domain_model_location",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,"uid","pid","cruser_id","hidden","starttime","endtime","sys_language_uid","l10n_parent","t3ver_oid","t3ver_wsid","t3ver_state","name","street","district","city","zip","country","latitude","longitude","phone",,,,,,,,,,,,,,,,
,"1","2","0","0","0","0",-1,0,"0","0","0","Schillerhaus Rudolstadt","Schillerstraße 25",,"Rudolstadt","07407","Deutschland","50.720971023259","11.335229873657","+ 49 3672 / 486470",,,,,,,,,,,,,,,,
,"2","2","0","0","0","0",-1,0,"0","0","0","Stadtbibliothek Rudolstadt","Schulplatz 13",,"Rudolstadt","07407","Deutschland","50.720835175056","11.342568397522","0 36 72 - 48 64 20",,,,,,,,,,,,,,,,
,"3","2","0","0","0","0",-1,0,"0","0","0","Lutherkirche","Caspar-Schulte-Straße",,"Rudolstadt","07407","Deutschland","50.718688721183","11.327333450317","03672 - 48 96 13",,,,,,,,,,,,,,,,
,"1","2","0","0","0","0",-1,0,"0","0","0","Schillerhaus Rudolstadt","Schillerstraße 25",,"Rudolstadt","07407","Deutschland","50.720971","11.335230","+ 49 3672 / 486470",,,,,,,,,,,,,,,,
,"2","2","0","0","0","0",-1,0,"0","0","0","Stadtbibliothek Rudolstadt","Schulplatz 13",,"Rudolstadt","07407","Deutschland","50.720835","11.342568","0 36 72 - 48 64 20",,,,,,,,,,,,,,,,
,"3","2","0","0","0","0",-1,0,"0","0","0","Lutherkirche","Caspar-Schulte-Straße",,"Rudolstadt","07407","Deutschland","50.718689","11.327333","03672 - 48 96 13",,,,,,,,,,,,,,,,
"sys_category",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,"uid","pid","cruser_id","hidden","starttime","endtime","sys_language_uid","l10n_parent","title","items","parent",,,,,,,,,,,,,,,,,,,,,,,,,
,1,2,0,0,0,0,0,0,"Top Category",0,0,,,,,,,,,,,,,,,,,,,,,,,,,

1 tx_events_domain_model_organizer
31 sys_category
32 uid pid cruser_id hidden starttime endtime sys_language_uid l10n_parent title items parent
33 1 2 0 0 0 0 0 0 Top Category 0 0
34 2 2 0 0 0 0 0 0 Event Category Parent 0 1
35 3 2 0 0 0 0 0 0 Weihnachten 0 2
36 4 2 0 0 0 0 0 0 Kinder 0 2
37 5 2 0 0 0 0 0 0 Konzerte, Festivals, Show & Tanz 0 2
38 sys_category_record_mm
39 uid_local uid_foreign tablenames fieldname

View file

@ -1,4 +0,0 @@
"tx_events_domain_model_location",,,,,,,,,,,,,,,,,,,,
,"uid","pid","cruser_id","hidden","starttime","endtime","sys_language_uid","l10n_parent","t3ver_oid","t3ver_wsid","t3ver_state","name","street","district","city","zip","country","latitude","longitude","phone"
,"1","2","0","0","0","0",-1,0,"0","0","0","Schillerhaus Rudolstadt","Schillerstraße 25",,"Rudolstadt","07407","Deutschland","50.720971023259","11.335229873657","+ 49 3672 / 486470"
,"2","2","0","0","0","0",-1,0,"0","0","0","Schillerhaus Rudolstadt","Other address",,"Rudolstadt","07407","Deutschland","50.720971023259","11.335229873657","+ 49 3672 / 486470"
1 tx_events_domain_model_location
2 uid pid cruser_id hidden starttime endtime sys_language_uid l10n_parent t3ver_oid t3ver_wsid t3ver_state name street district city zip country latitude longitude phone
3 1 2 0 0 0 0 -1 0 0 0 0 Schillerhaus Rudolstadt Schillerstraße 25 Rudolstadt 07407 Deutschland 50.720971023259 11.335229873657 + 49 3672 / 486470
4 2 2 0 0 0 0 -1 0 0 0 0 Schillerhaus Rudolstadt Other address Rudolstadt 07407 Deutschland 50.720971023259 11.335229873657 + 49 3672 / 486470

View file

@ -0,0 +1,52 @@
<?php
return [
'tx_events_domain_model_location' => [
[
'uid' => 1,
'pid' => 2,
'cruser_id' => 0,
'hidden' => 0,
'starttime' => 0,
'endtime' => 0,
'sys_language_uid' => -1,
'l10n_parent' => 0,
't3ver_oid' => 0,
't3ver_wsid' => 0,
't3ver_state' => 0,
'name' => 'Schillerhaus Rudolstadt',
'street' => 'Schillerstraße 25',
'district' => '',
'city' => 'Rudolstadt',
'zip' => '07407',
'country' => 'Deutschland',
'latitude' => '50.720971',
'longitude' => '11.335230',
'phone' => '+ 49 3672 / 486470',
'global_id' => '2c898a5e8f751906124a6eb6384a3b111754db37d3330b169ec022b98a0ddcbe',
],
[
'uid' => 2,
'pid' => 2,
'cruser_id' => 0,
'hidden' => 0,
'starttime' => 0,
'endtime' => 0,
'sys_language_uid' => -1,
'l10n_parent' => 0,
't3ver_oid' => 0,
't3ver_wsid' => 0,
't3ver_state' => 0,
'name' => 'Schillerhaus Rudolstadt',
'street' => 'Other address',
'district' => '',
'city' => 'Rudolstadt',
'zip' => '07407',
'country' => 'Deutschland',
'latitude' => '50.720971',
'longitude' => '11.335230',
'phone' => '+ 49 3672 / 486470',
'global_id' => 'c0df4e7901836412bf6e45289cf749f60b86cf8405aa23767b5e18890fa4cf30',
],
],
];

View file

@ -0,0 +1,20 @@
<?php
return [
'tx_events_domain_model_location' => [
[
'uid' => 1,
'pid' => 2,
'name' => 'Schillerhaus Rudolstadt',
'street' => 'Schillerstraße 25',
'district' => '',
'city' => 'Rudolstadt',
'zip' => '07407',
'country' => 'Deutschland',
'latitude' => '50.720971',
'longitude' => '11.335230',
'phone' => '+ 49 3672 / 486470',
'global_id' => '2c898a5e8f751906124a6eb6384a3b111754db37d3330b169ec022b98a0ddcbe',
],
],
];

View file

@ -0,0 +1,21 @@
<?php
return [
'tx_events_domain_model_location' => [
[
'uid' => 1,
'pid' => 2,
'sys_language_uid' => -1,
'name' => 'Schillerhaus Rudolstadt',
'street' => 'Schillerstraße 25',
'district' => '',
'city' => 'Rudolstadt',
'zip' => '07407',
'country' => 'Deutschland',
'latitude' => '50.720971',
'longitude' => '11.335230',
'phone' => '+ 49 3672 / 486470',
'global_id' => '2c898a5e8f751906124a6eb6384a3b111754db37d3330b169ec022b98a0ddcbe',
],
],
];

View file

@ -0,0 +1,21 @@
<?php
return [
'tx_events_domain_model_location' => [
[
'uid' => 1,
'pid' => 2,
'sys_language_uid' => -1,
'name' => 'Schillerhaus Rudolstadt',
'street' => 'Schillerstraße 25',
'district' => '',
'city' => 'Rudolstadt',
'zip' => '07407',
'country' => 'Deutschland',
'latitude' => '49.720971',
'longitude' => '10.335230',
'phone' => '+ 49 / 486470',
'global_id' => '2c898a5e8f751906124a6eb6384a3b111754db37d3330b169ec022b98a0ddcbe',
],
],
];

View file

@ -0,0 +1,341 @@
{
"status": "OK",
"count": 2,
"overallcount": 50,
"channels": [],
"facetGroups": [],
"items": [
{
"global_id": "e_100347853",
"id": "100347853",
"title": "Allerlei Weihnachtliches (Heute mit Johannes Geißer)",
"type": "Event",
"categories": [
"Weihnachten"
],
"texts": [
{
"rel": "details",
"type": "text/html",
"value": "Die Lichter sind entzündet, die Plätzchen duften, man rückt endlich wieder näher zusammen und lauscht den Geschichten. Vier Schauspieler*innen unseres Theaters überraschen mit ihren weihnachtlichen Texten, die sie für uns ausgewählt haben. Dazu plaudern sie über persönliche Anekdoten und erinnern sich an ihre schönsten und verrücktesten Weihnachtsfeste. Und da der Genuss in der Vorweihnachtszeit nicht fehlen darf, wird an jedem Adventssonntag eine andere weihnachtliche Spezialität serviert.<br>Eintritt: 10 € (inkl. Gedeck mit weihnachtlicher Schillerlocke)<br>Um Voranmeldung unter 03672-486470 oder&nbsp;<a data-cke-saved-href=\"mailto:schillerhaus@rudolstadt.de\" href=\"mailto:schillerhaus@rudolstadt.de\">schillerhaus@rudolstadt.de</a>&nbsp;wird gebeten. <br><strong>Es gilt die 2G-PLUS-Regel.</strong>&nbsp;<br>"
},
{
"rel": "details",
"type": "text/plain",
"value": "Die Lichter sind entzündet, die Plätzchen duften, man rückt endlich wieder näher zusammen und lauscht den Geschichten. Vier Schauspieler*innen unseres Theaters überraschen mit ihren weihnachtlichen Texten, die sie für uns ausgewählt haben. Dazu plaudern sie über persönliche Anekdoten und erinnern sich an ihre schönsten und verrücktesten Weihnachtsfeste. Und da der Genuss in der Vorweihnachtszeit nicht fehlen darf, wird an jedem Adventssonntag eine andere weihnachtliche Spezialität serviert.\nEintritt: 10 € (inkl. Gedeck mit weihnachtlicher Schillerlocke)\nUm Voranmeldung unter 03672-486470 oder schillerhaus@rudolstadt.de wird gebeten.\nEs gilt die 2G-PLUS-Regel."
},
{
"rel": "teaser",
"type": "text/html"
},
{
"rel": "teaser",
"type": "text/plain"
}
],
"areas": [
"Rudolstadt und Umgebung"
],
"name": "Schillerhaus Rudolstadt",
"street": "Schillerstraße 25",
"zip": "07407",
"city": "Rudolstadt",
"district": "",
"country": "Deutschland",
"phone": "+ 49 3672 / 486470",
"geo": {
"main": {
"latitude": 50.720971023258805,
"longitude": 11.335229873657227
},
"entry": [],
"attributes": []
},
"fax": "+ 49 3672 / 486475",
"web": "http://www.schillerhaus.rudolstadt.de/",
"email": "schillerhaus@rudolstadt.de",
"author": "support@hubermedia.de",
"ratings": [
{
"type": "eT4",
"value": 40.0
},
{
"type": "order",
"value": 99.0001
}
],
"cuisine_types": [],
"payment": [],
"media_objects": [
{
"rel": "venuewebsite",
"url": "http://schillerhaus.rudolstadt.de/",
"latitude": null,
"longitude": null,
"value": ""
}
],
"keywords": [],
"timeIntervals": [
{
"weekdays": [],
"start": "2099-12-19T15:00:00+01:00",
"end": "2099-12-19T16:30:00+01:00",
"tz": "Europe/Berlin",
"interval": 1
}
],
"kitchenTimeIntervals": [],
"deliveryTimeIntervals": [],
"numbers": [],
"attributes": [
{
"key": "VO_Id",
"value": "100050775"
},
{
"key": "VO_CategoryName",
"value": "POI"
},
{
"key": "VA_Id",
"value": "100050775"
},
{
"key": "VA_CategoryName",
"value": "POI"
},
{
"key": "interval_first_match_start",
"value": "2099-12-19T15:00:00+01"
},
{
"key": "interval_first_match_end",
"value": "2099-12-19T16:30:00+01"
},
{
"key": "interval_match_count",
"value": "1"
}
],
"features": [
"vorhandenes Feature",
"Barrierefrei",
"Zielgruppe Jugendliche",
"Karten an der Abendkasse",
"Ausreichende Lüftung",
"Beachtung der Hygienehinweise"
],
"addresses": [
{
"name": "Städtetourismus in Thüringen e.V.",
"city": "Weimar",
"zip": "99423",
"street": "UNESCO-Platz 1",
"phone": "+49 (3643) 745 314",
"web": "http://www.thueringer-staedte.de",
"email": "verein@thueringer-staedte.de",
"rel": "author"
},
{
"name": "Städtetourismus in Thüringen\" e.V.",
"web": "http://www.thueringer-staedte.de",
"email": "verein@thueringer-staedte.de",
"rel": "organisation"
},
{
"name": "Schillerhaus Rudolstadt",
"city": "Rudolstadt",
"zip": "07407",
"street": "Schillerstraße 25",
"phone": "+ 49 3672 / 486470",
"fax": "+ 49 3672 / 486475",
"web": "http://schillerhaus.rudolstadt.de",
"email": "schillerhaus@rudolstadt.de",
"rel": "organizer"
}
],
"created": "2099-10-31T12:29:00+00:00",
"changed": "2099-12-14T08:29:00+00:00",
"source": {
"url": "http://destination.one/",
"value": "destination.one"
},
"company": "",
"postoffice": "",
"phone2": "",
"seasons": [],
"subitems": [],
"hyperObjects": []
},
{
"global_id": "e_100347853",
"id": "100347853",
"title": "Allerlei Weihnachtliches (Heute mit Johannes Geißer)",
"type": "Event",
"categories": [
"Weihnachten"
],
"texts": [
{
"rel": "details",
"type": "text/html",
"value": "Die Lichter sind entzündet, die Plätzchen duften, man rückt endlich wieder näher zusammen und lauscht den Geschichten. Vier Schauspieler*innen unseres Theaters überraschen mit ihren weihnachtlichen Texten, die sie für uns ausgewählt haben. Dazu plaudern sie über persönliche Anekdoten und erinnern sich an ihre schönsten und verrücktesten Weihnachtsfeste. Und da der Genuss in der Vorweihnachtszeit nicht fehlen darf, wird an jedem Adventssonntag eine andere weihnachtliche Spezialität serviert.<br>Eintritt: 10 € (inkl. Gedeck mit weihnachtlicher Schillerlocke)<br>Um Voranmeldung unter 03672-486470 oder&nbsp;<a data-cke-saved-href=\"mailto:schillerhaus@rudolstadt.de\" href=\"mailto:schillerhaus@rudolstadt.de\">schillerhaus@rudolstadt.de</a>&nbsp;wird gebeten. <br><strong>Es gilt die 2G-PLUS-Regel.</strong>&nbsp;<br>"
},
{
"rel": "details",
"type": "text/plain",
"value": "Die Lichter sind entzündet, die Plätzchen duften, man rückt endlich wieder näher zusammen und lauscht den Geschichten. Vier Schauspieler*innen unseres Theaters überraschen mit ihren weihnachtlichen Texten, die sie für uns ausgewählt haben. Dazu plaudern sie über persönliche Anekdoten und erinnern sich an ihre schönsten und verrücktesten Weihnachtsfeste. Und da der Genuss in der Vorweihnachtszeit nicht fehlen darf, wird an jedem Adventssonntag eine andere weihnachtliche Spezialität serviert.\nEintritt: 10 € (inkl. Gedeck mit weihnachtlicher Schillerlocke)\nUm Voranmeldung unter 03672-486470 oder schillerhaus@rudolstadt.de wird gebeten.\nEs gilt die 2G-PLUS-Regel."
},
{
"rel": "teaser",
"type": "text/html"
},
{
"rel": "teaser",
"type": "text/plain"
}
],
"areas": [
"Rudolstadt und Umgebung"
],
"name": "Schillerhaus Rudolstadt",
"street": "Schillerstraße 25",
"zip": "07407",
"city": "Rudolstadt",
"district": "",
"country": "Deutschland",
"phone": "+ 49 3672 / 486470",
"geo": {
"main": {
"latitude": "50,720971023258805",
"longitude": "11,335229873657227"
},
"entry": [],
"attributes": []
},
"fax": "+ 49 3672 / 486475",
"web": "http://www.schillerhaus.rudolstadt.de/",
"email": "schillerhaus@rudolstadt.de",
"author": "support@hubermedia.de",
"ratings": [
{
"type": "eT4",
"value": 40.0
},
{
"type": "order",
"value": 99.0001
}
],
"cuisine_types": [],
"payment": [],
"media_objects": [
{
"rel": "venuewebsite",
"url": "http://schillerhaus.rudolstadt.de/",
"latitude": null,
"longitude": null,
"value": ""
}
],
"keywords": [],
"timeIntervals": [
{
"weekdays": [],
"start": "2099-12-19T15:00:00+01:00",
"end": "2099-12-19T16:30:00+01:00",
"tz": "Europe/Berlin",
"interval": 1
}
],
"kitchenTimeIntervals": [],
"deliveryTimeIntervals": [],
"numbers": [],
"attributes": [
{
"key": "VO_Id",
"value": "100050775"
},
{
"key": "VO_CategoryName",
"value": "POI"
},
{
"key": "VA_Id",
"value": "100050775"
},
{
"key": "VA_CategoryName",
"value": "POI"
},
{
"key": "interval_first_match_start",
"value": "2099-12-19T15:00:00+01"
},
{
"key": "interval_first_match_end",
"value": "2099-12-19T16:30:00+01"
},
{
"key": "interval_match_count",
"value": "1"
}
],
"features": [
"vorhandenes Feature",
"Barrierefrei",
"Zielgruppe Jugendliche",
"Karten an der Abendkasse",
"Ausreichende Lüftung",
"Beachtung der Hygienehinweise"
],
"addresses": [
{
"name": "Städtetourismus in Thüringen e.V.",
"city": "Weimar",
"zip": "99423",
"street": "UNESCO-Platz 1",
"phone": "+49 (3643) 745 314",
"web": "http://www.thueringer-staedte.de",
"email": "verein@thueringer-staedte.de",
"rel": "author"
},
{
"name": "Städtetourismus in Thüringen\" e.V.",
"web": "http://www.thueringer-staedte.de",
"email": "verein@thueringer-staedte.de",
"rel": "organisation"
},
{
"name": "Schillerhaus Rudolstadt",
"city": "Rudolstadt",
"zip": "07407",
"street": "Schillerstraße 25",
"phone": "+ 49 3672 / 486470",
"fax": "+ 49 3672 / 486475",
"web": "http://schillerhaus.rudolstadt.de",
"email": "schillerhaus@rudolstadt.de",
"rel": "organizer"
}
],
"created": "2099-10-31T12:29:00+00:00",
"changed": "2099-12-14T08:29:00+00:00",
"source": {
"url": "http://destination.one/",
"value": "destination.one"
},
"company": "",
"postoffice": "",
"phone2": "",
"seasons": [],
"subitems": [],
"hyperObjects": []
}
]
}

View file

@ -0,0 +1,56 @@
{
"status": "OK",
"count": 1,
"overallcount": 50,
"channels": [],
"facetGroups": [],
"items": [
{
"global_id": "e_100347853",
"id": "100347853",
"title": "Allerlei Weihnachtliches (Heute mit Johannes Geißer)",
"type": "Event",
"categories": [ ],
"name": "Schillerhaus Rudolstadt",
"street": "Schillerstraße 25",
"zip": "07407",
"city": "Rudolstadt",
"district": "",
"country": "Deutschland",
"phone": "+ 49 3672 / 486470",
"geo": {
"main": {
"latitude": 50.720971023258805,
"longitude": 11.335229873657227
}
},
"fax": "+ 49 3672 / 486475",
"web": "http://www.schillerhaus.rudolstadt.de/",
"email": "schillerhaus@rudolstadt.de",
"author": "support@hubermedia.de",
"cuisine_types": [],
"payment": [],
"media_objects": [ ],
"keywords": [],
"timeIntervals": [ ],
"kitchenTimeIntervals": [],
"deliveryTimeIntervals": [],
"numbers": [],
"attributes": [ ],
"features": [ ],
"addresses": [ ],
"created": "2099-10-31T12:29:00+00:00",
"changed": "2099-12-14T08:29:00+00:00",
"source": {
"url": "http://destination.one/",
"value": "destination.one"
},
"company": "",
"postoffice": "",
"phone2": "",
"seasons": [],
"subitems": [],
"hyperObjects": []
}
]
}

View file

@ -7,7 +7,7 @@ use GuzzleHttp\Psr7\Response;
/**
* @testdox DestinationData import
*/
class ImportsWithLocationsTest extends AbstractTest
final class ImportsWithLocationsTest extends AbstractTest
{
protected function setUp(): void
{
@ -29,13 +29,48 @@ class ImportsWithLocationsTest extends AbstractTest
*/
public function importsWithLocations(): void
{
$requests = &$this->setUpResponses([
$this->setUpResponses([
new Response(200, [], file_get_contents(__DIR__ . '/Fixtures/ResponseWithLocations.json') ?: ''),
]);
$tester = $this->executeCommand();
self::assertSame(0, $tester->getStatusCode());
$this->assertCSVDataSet('EXT:events/Tests/Functional/Import/DestinationDataTest/Assertions/ImportsWithLocations.csv');
$this->assertPHPDataSet(__DIR__ . '/Assertions/ImportsWithLocations.php');
$this->assertEmptyLog();
}
/**
* A single location might be available with different ways to write latitude an longitude ("," and ".").
* This test ensures this situation is properly handled by streamlining the values.
*
* @test
*/
public function importsWithSingleLocationOnDuplicates(): void
{
$this->setUpResponses([
new Response(200, [], file_get_contents(__DIR__ . '/Fixtures/ResponseWithLocationUsingDifferentLatitudeAndLongitude.json') ?: ''),
]);
$tester = $this->executeCommand();
self::assertSame(0, $tester->getStatusCode());
$this->assertPHPDataSet(__DIR__ . '/Assertions/ImportsWithSingleLocationOnDuplicates.php');
$this->assertEmptyLog();
}
/**
* @test
*/
public function updatesExistingLocation(): void
{
$this->importPHPDataSet(__DIR__ . '/Fixtures/Database/ExistingLocation.php');
$this->setUpResponses([
new Response(200, [], file_get_contents(__DIR__ . '/Fixtures/ResponseWithLocationWithDifferentValues.json') ?: ''),
]);
$tester = $this->executeCommand();
self::assertSame(0, $tester->getStatusCode());
$this->assertPHPDataSet(__DIR__ . '/Assertions/UpdatesExistingLocation.php');
$this->assertEmptyLog();
}
}

View file

@ -0,0 +1,59 @@
<?php
return [
'tx_events_domain_model_location' => [
[
'uid' => 1,
'pid' => 0,
'sys_language_uid' => -1,
'global_id' => 'a91656ec76732f2b7b72987d11d81d926fa67ea3b2eb4cc6fd75bb2b748da21d',
'name' => 'Domplatz',
'street' => '',
'city' => 'Erfurt',
'zip' => '99084',
'district' => 'Altstadt',
'country' => 'Deutschland',
'phone' => '',
'latitude' => '50.977089',
'longitude' => '11.024878',
],
[
'uid' => 3,
'pid' => 0,
'sys_language_uid' => -1,
'global_id' => '95ca076b77e478cc8eb831f48aacaa608a640034e31da2e11b42da9758c84aaf',
'name' => 'Wenigemarkt',
'street' => '',
'city' => 'Erfurt',
'zip' => '99084',
'district' => 'Altstadt',
'country' => 'Deutschland',
'phone' => '',
'latitude' => '50.978500',
'longitude' => '11.031589',
],
],
'tx_events_domain_model_event' => [
[
'uid' => 1,
'pid' => 0,
'title' => 'Abendmahlsgottesdienst',
'global_id' => 'e_100171396',
'location' => 1,
],
[
'uid' => 2,
'pid' => 0,
'title' => 'Travestie-Revue "Pretty Wo(man)"',
'global_id' => 'e_100172162',
'location' => 1,
],
[
'uid' => 3,
'pid' => 0,
'title' => 'Abendgebet in englischer Sprache',
'global_id' => 'e_100172275',
'location' => 3,
],
],
];

View file

@ -0,0 +1,74 @@
<?php
return [
'tx_events_domain_model_location' => [
[
'uid' => 1,
'pid' => 0,
'sys_language_uid' => -1,
'global_id' => '21e0561cb967c2b3c7977c367615b97b4176e26302dd77fadb33296cd37fb4b0',
'name' => 'Domplatz',
'street' => '',
'city' => 'Erfurt',
'zip' => '99084',
'district' => 'Altstadt',
'country' => 'Deutschland',
'phone' => '',
'latitude' => '50,977089',
'longitude' => '11,024878',
],
[
'uid' => 2,
'pid' => 0,
'sys_language_uid' => -1,
'global_id' => '1cc57820faf4a3bf6bf8326f2821068f0619f9fc8bcbebd8c6e4c496e38471c7',
'name' => 'Domplatz',
'street' => '',
'city' => 'Erfurt',
'zip' => '99084',
'district' => 'Altstadt',
'country' => 'Deutschland',
'phone' => '',
'latitude' => '50.977089',
'longitude' => '11.024878',
],
[
'uid' => 3,
'pid' => 0,
'sys_language_uid' => -1,
'global_id' => '64d0def98fe304c32c79e6926cac40c8501797158e0e43990c36f7b1fb50c17e',
'name' => 'Wenigemarkt',
'street' => '',
'city' => 'Erfurt',
'zip' => '99084',
'district' => 'Altstadt',
'country' => 'Deutschland',
'phone' => '',
'latitude' => '50.9785',
'longitude' => '11.031589',
],
],
'tx_events_domain_model_event' => [
[
'uid' => 1,
'pid' => 0,
'title' => 'Abendmahlsgottesdienst',
'global_id' => 'e_100171396',
'location' => 1,
],
[
'uid' => 2,
'pid' => 0,
'title' => 'Travestie-Revue "Pretty Wo(man)"',
'global_id' => 'e_100172162',
'location' => 2,
],
[
'uid' => 3,
'pid' => 0,
'title' => 'Abendgebet in englischer Sprache',
'global_id' => 'e_100172275',
'location' => 3,
],
],
];

View file

@ -0,0 +1,36 @@
<?php
return [
'tx_events_domain_model_location' => [
[
'uid' => 1,
'pid' => 0,
'sys_language_uid' => -1,
'global_id' => '1cc57820faf4a3bf6bf8326f2821068f0619f9fc8bcbebd8c6e4c496e38471c7',
'name' => 'Domplatz',
'street' => '',
'city' => 'Erfurt',
'zip' => '99084',
'district' => 'Altstadt',
'country' => 'Deutschland',
'phone' => '',
'latitude' => '50.977089',
'longitude' => '11.024878',
],
[
'uid' => 2,
'pid' => 0,
'sys_language_uid' => -1,
'global_id' => '64d0def98fe304c32c79e6926cac40c8501797158e0e43990c36f7b1fb50c17e',
'name' => 'Wenigemarkt',
'street' => '',
'city' => 'Erfurt',
'zip' => '99084',
'district' => 'Altstadt',
'country' => 'Deutschland',
'phone' => '',
'latitude' => '50.9785',
'longitude' => '11.031589',
],
],
];

View file

@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
/*
* Copyright (C) 2023 Daniel Siepmann <coding@daniel-siepmann.de>
*
* 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 Wrm\Events\Tests\Functional\Updates;
use Wrm\Events\Tests\Functional\AbstractFunctionalTestCase;
use Wrm\Events\Updates\MigrateDuplicateLocations;
/**
* @testdox The update wizard to migrate duplicate locations
*/
final class MigrateDuplicateLocationsTest extends AbstractFunctionalTestCase
{
/**
* @test
*/
public function canBeCreated(): void
{
$subject = $this->get(MigrateDuplicateLocations::class);
self::assertInstanceOf(MigrateDuplicateLocations::class, $subject);
}
/**
* @test
*/
public function isRegistered(): void
{
self::assertArrayHasKey(
MigrateDuplicateLocations::class,
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']
);
}
/**
* @test
*/
public function keepsDataIfNothingToDo(): void
{
$this->importPHPDataSet(__DIR__ . '/Fixtures/MigrateDuplicateLocationsNoDuplicates.php');
$subject = $this->get(MigrateDuplicateLocations::class);
self::assertInstanceOf(MigrateDuplicateLocations::class, $subject);
self::assertTrue($subject->updateNecessary());
$this->assertPHPDataSet(__DIR__ . '/Fixtures/MigrateDuplicateLocationsNoDuplicates.php');
}
/**
* @test
*/
public function migratesDuplicateEntries(): void
{
$this->importPHPDataSet(__DIR__ . '/Fixtures/MigrateDuplicateLocations.php');
$subject = $this->get(MigrateDuplicateLocations::class);
self::assertInstanceOf(MigrateDuplicateLocations::class, $subject);
self::assertTrue($subject->updateNecessary());
$subject->executeUpdate();
$this->assertPHPDataSet(__DIR__ . '/Assertions/MigrateDuplicateLocations.php');
}
}

View file

@ -0,0 +1,83 @@
<?php
declare(strict_types=1);
/*
* Copyright (C) 2023 Daniel Siepmann <coding@daniel-siepmann.de>
*
* 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 Wrm\Events\Tests\Unit\Domain\Model;
use PHPUnit\Framework\TestCase;
use Wrm\Events\Domain\Model\Location;
/**
* @covers \Wrm\Events\Domain\Model\Location
*/
final class LocationTest extends TestCase
{
/**
* @test
*
* @dataProvider possibleLatitudeAndLongitude
*/
public function normalizesLatitudeAndLongitude(
string $latitude,
string $longitude
): void {
$subject = new Location(
'Name',
'Street',
'Zip',
'City',
'District',
'Country',
'Phone',
$latitude,
$longitude,
1
);
self::assertSame(
'50.720971',
$subject->getLatitude()
);
self::assertSame(
'11.335230',
$subject->getLongitude()
);
}
public static function possibleLatitudeAndLongitude(): array
{
return [
'Using float numbers' => [
'latitude' => (string)50.720971023258805,
'longitude' => (string)11.335229873657227,
],
'Using ","' => [
'latitude' => '50,720971023258805',
'longitude' => '11,335229873657227',
],
'Using "."' => [
'latitude' => '50.720971023258805',
'longitude' => '11.335229873657227',
],
];
}
}

View file

@ -0,0 +1,120 @@
<?php
declare(strict_types=1);
/*
* Copyright (C) 2023 Daniel Siepmann <coding@daniel-siepmann.de>
*
* 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 Wrm\Events\Tests\Unit\Service\DestinationDataImportService;
use PHPUnit\Framework\TestCase;
use Wrm\Events\Domain\Model\Location;
use Wrm\Events\Domain\Repository\LocationRepository;
use Wrm\Events\Service\DestinationDataImportService\LocationAssignment;
/**
* @covers \Wrm\Events\Service\DestinationDataImportService\LocationAssignment
*/
final class LocationAssignmentTest extends TestCase
{
/**
* @test
*/
public function canBeCreated(): void
{
$repository = $this->createStub(LocationRepository::class);
$subject = new LocationAssignment(
$repository
);
self::assertInstanceOf(
LocationAssignment::class,
$subject
);
}
/**
* @test
*
* @dataProvider possibleLatitudeAndLongitude
*
* @param string|float $latitude
* @param string|float $longitude
*/
public function normalizesLatitudeAndLongitude(
$latitude,
$longitude
): void {
$repository = $this->createStub(LocationRepository::class);
$repository->method('findOneByGlobalId')->willReturn(null);
$subject = new LocationAssignment(
$repository
);
$result = $subject->getLocation([
'name' => 'Name',
'street' => 'Street',
'zip' => 'Zip',
'city' => 'City',
'district' => 'District',
'country' => 'Country',
'phone' => 'Phone',
'geo' => [
'main' => [
'latitude' => $latitude,
'longitude' => $longitude,
],
],
]);
self::assertInstanceOf(
Location::class,
$result
);
self::assertSame(
'50.720971',
$result->getLatitude(),
'Latitude returns unexpected value.'
);
self::assertSame(
'11.335230',
$result->getLongitude(),
'Longitude returns unexpected value.'
);
}
public static function possibleLatitudeAndLongitude(): array
{
return [
'Using float numbers' => [
'latitude' => 50.720971023258805,
'longitude' => 11.335229873657227,
],
'Using ","' => [
'latitude' => '50,720971023258805',
'longitude' => '11,335229873657227',
],
'Using "."' => [
'latitude' => '50.720971023258805',
'longitude' => '11.335229873657227',
],
];
}
}

View file

@ -53,4 +53,5 @@ call_user_func(function () {
\Wrm\Events\Caching\PageCacheTimeout::register();
\Wrm\Events\Updates\MigrateOldLocations::register();
\Wrm\Events\Updates\MigrateDuplicateLocations::register();
});

View file

@ -12,8 +12,12 @@ parameters:
- "#^Call to an undefined method Doctrine\\\\DBAL\\\\Result\\:\\:fetch\\(\\)\\.$#"
- "#^Cannot call method fetchAll\\(\\) on Doctrine\\\\DBAL\\\\Driver\\\\ResultStatement\\|int\\.$#"
- "#^Cannot call method fetchAll\\(\\) on Doctrine\\\\DBAL\\\\Result\\|int\\.$#"
- "#^Cannot call method fetchAllAssociative\\(\\) on Doctrine\\\\DBAL\\\\Driver\\\\ResultStatement\\|int\\.$#"
- "#^Cannot call method fetchAllAssociative\\(\\) on Doctrine\\\\DBAL\\\\Result\\|int\\.$#"
- "#^Cannot call method fetchColumn\\(\\) on Doctrine\\\\DBAL\\\\Driver\\\\ResultStatement\\|int\\.$#"
- "#^Cannot call method fetchColumn\\(\\) on Doctrine\\\\DBAL\\\\Result\\|int\\.$#"
- "#^Cannot call method fetchFirstColumn\\(\\) on Doctrine\\\\DBAL\\\\Driver\\\\ResultStatement\\|int\\.$#"
- "#^Cannot call method fetchFirstColumn\\(\\) on Doctrine\\\\DBAL\\\\Result\\|int\\.$#"
- "#^Cannot call method fetch\\(\\) on Doctrine\\\\DBAL\\\\Driver\\\\ResultStatement\\|int\\.$#"
- "#^Cannot call method fetch\\(\\) on Doctrine\\\\DBAL\\\\Result\\|int\\.$#"
- "#^Cannot call method fetchOne\\(\\) on Doctrine\\\\DBAL\\\\Driver\\\\ResultStatement\\|int\\.$#"