diff --git a/Classes/Domain/Model/Location.php b/Classes/Domain/Model/Location.php index db10fba..601e68b 100644 --- a/Classes/Domain/Model/Location.php +++ b/Classes/Domain/Model/Location.php @@ -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, '.', ''); + } } diff --git a/Classes/Domain/Repository/LocationRepository.php b/Classes/Domain/Repository/LocationRepository.php index 6f85ad6..911f1f6 100644 --- a/Classes/Domain/Repository/LocationRepository.php +++ b/Classes/Domain/Repository/LocationRepository.php @@ -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() + ; + } } diff --git a/Classes/Service/DestinationDataImportService/LocationAssignment.php b/Classes/Service/DestinationDataImportService/LocationAssignment.php index bcdc7fb..14093ae 100644 --- a/Classes/Service/DestinationDataImportService/LocationAssignment.php +++ b/Classes/Service/DestinationDataImportService/LocationAssignment.php @@ -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; } } diff --git a/Classes/Updates/MigrateDuplicateLocations.php b/Classes/Updates/MigrateDuplicateLocations.php new file mode 100644 index 0000000..9ef6a69 --- /dev/null +++ b/Classes/Updates/MigrateDuplicateLocations.php @@ -0,0 +1,204 @@ + + * + * 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 + */ + 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(); + } + } +} diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index 2282efb..060160b 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -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 diff --git a/Documentation/Changelog/3.4.0.rst b/Documentation/Changelog/3.4.0.rst index 468656b..4243c51 100644 --- a/Documentation/Changelog/3.4.0.rst +++ b/Documentation/Changelog/3.4.0.rst @@ -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 ----- diff --git a/Tests/Functional/Import/DestinationDataTest/Assertions/ImportsExampleAsExpected.csv b/Tests/Functional/Import/DestinationDataTest/Assertions/ImportsExampleAsExpected.csv index 116b36c..f3376db 100644 --- a/Tests/Functional/Import/DestinationDataTest/Assertions/ImportsExampleAsExpected.csv +++ b/Tests/Functional/Import/DestinationDataTest/Assertions/ImportsExampleAsExpected.csv @@ -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,,,,,,,,,,,,,,,,,,,,,,,,, diff --git a/Tests/Functional/Import/DestinationDataTest/Assertions/ImportsWithLocations.csv b/Tests/Functional/Import/DestinationDataTest/Assertions/ImportsWithLocations.csv deleted file mode 100644 index 85db457..0000000 --- a/Tests/Functional/Import/DestinationDataTest/Assertions/ImportsWithLocations.csv +++ /dev/null @@ -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" diff --git a/Tests/Functional/Import/DestinationDataTest/Assertions/ImportsWithLocations.php b/Tests/Functional/Import/DestinationDataTest/Assertions/ImportsWithLocations.php new file mode 100644 index 0000000..63feb36 --- /dev/null +++ b/Tests/Functional/Import/DestinationDataTest/Assertions/ImportsWithLocations.php @@ -0,0 +1,52 @@ + [ + [ + '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', + ], + ], +]; diff --git a/Tests/Functional/Import/DestinationDataTest/Assertions/ImportsWithSingleLocationOnDuplicates.php b/Tests/Functional/Import/DestinationDataTest/Assertions/ImportsWithSingleLocationOnDuplicates.php new file mode 100644 index 0000000..a9573ab --- /dev/null +++ b/Tests/Functional/Import/DestinationDataTest/Assertions/ImportsWithSingleLocationOnDuplicates.php @@ -0,0 +1,20 @@ + [ + [ + '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', + ], + ], +]; diff --git a/Tests/Functional/Import/DestinationDataTest/Assertions/UpdatesExistingLocation.php b/Tests/Functional/Import/DestinationDataTest/Assertions/UpdatesExistingLocation.php new file mode 100644 index 0000000..929b5b8 --- /dev/null +++ b/Tests/Functional/Import/DestinationDataTest/Assertions/UpdatesExistingLocation.php @@ -0,0 +1,21 @@ + [ + [ + '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', + ], + ], +]; diff --git a/Tests/Functional/Import/DestinationDataTest/Fixtures/Database/ExistingLocation.php b/Tests/Functional/Import/DestinationDataTest/Fixtures/Database/ExistingLocation.php new file mode 100644 index 0000000..7510c9a --- /dev/null +++ b/Tests/Functional/Import/DestinationDataTest/Fixtures/Database/ExistingLocation.php @@ -0,0 +1,21 @@ + [ + [ + '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', + ], + ], +]; diff --git a/Tests/Functional/Import/DestinationDataTest/Fixtures/ResponseWithLocationUsingDifferentLatitudeAndLongitude.json b/Tests/Functional/Import/DestinationDataTest/Fixtures/ResponseWithLocationUsingDifferentLatitudeAndLongitude.json new file mode 100644 index 0000000..7143cf0 --- /dev/null +++ b/Tests/Functional/Import/DestinationDataTest/Fixtures/ResponseWithLocationUsingDifferentLatitudeAndLongitude.json @@ -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.
Eintritt: 10 € (inkl. Gedeck mit weihnachtlicher Schillerlocke)
Um Voranmeldung unter 03672-486470 oder schillerhaus@rudolstadt.de wird gebeten.
Es gilt die 2G-PLUS-Regel. 
" + }, + { + "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.
Eintritt: 10 € (inkl. Gedeck mit weihnachtlicher Schillerlocke)
Um Voranmeldung unter 03672-486470 oder schillerhaus@rudolstadt.de wird gebeten.
Es gilt die 2G-PLUS-Regel. 
" + }, + { + "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": [] + } + ] +} diff --git a/Tests/Functional/Import/DestinationDataTest/Fixtures/ResponseWithLocationWithDifferentValues.json b/Tests/Functional/Import/DestinationDataTest/Fixtures/ResponseWithLocationWithDifferentValues.json new file mode 100644 index 0000000..b318162 --- /dev/null +++ b/Tests/Functional/Import/DestinationDataTest/Fixtures/ResponseWithLocationWithDifferentValues.json @@ -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": [] + } + ] +} diff --git a/Tests/Functional/Import/DestinationDataTest/ImportsWithLocationsTest.php b/Tests/Functional/Import/DestinationDataTest/ImportsWithLocationsTest.php index 41bff4d..38ef64f 100644 --- a/Tests/Functional/Import/DestinationDataTest/ImportsWithLocationsTest.php +++ b/Tests/Functional/Import/DestinationDataTest/ImportsWithLocationsTest.php @@ -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(); } } diff --git a/Tests/Functional/Updates/Assertions/MigrateDuplicateLocations.php b/Tests/Functional/Updates/Assertions/MigrateDuplicateLocations.php new file mode 100644 index 0000000..376aa02 --- /dev/null +++ b/Tests/Functional/Updates/Assertions/MigrateDuplicateLocations.php @@ -0,0 +1,59 @@ + [ + [ + '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, + ], + ], +]; diff --git a/Tests/Functional/Updates/Fixtures/MigrateDuplicateLocations.php b/Tests/Functional/Updates/Fixtures/MigrateDuplicateLocations.php new file mode 100644 index 0000000..5c65ab5 --- /dev/null +++ b/Tests/Functional/Updates/Fixtures/MigrateDuplicateLocations.php @@ -0,0 +1,74 @@ + [ + [ + '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, + ], + ], +]; diff --git a/Tests/Functional/Updates/Fixtures/MigrateDuplicateLocationsNoDuplicates.php b/Tests/Functional/Updates/Fixtures/MigrateDuplicateLocationsNoDuplicates.php new file mode 100644 index 0000000..4b5c399 --- /dev/null +++ b/Tests/Functional/Updates/Fixtures/MigrateDuplicateLocationsNoDuplicates.php @@ -0,0 +1,36 @@ + [ + [ + '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', + ], + ], +]; diff --git a/Tests/Functional/Updates/MigrateDuplicateLocationsTest.php b/Tests/Functional/Updates/MigrateDuplicateLocationsTest.php new file mode 100644 index 0000000..cb6d72c --- /dev/null +++ b/Tests/Functional/Updates/MigrateDuplicateLocationsTest.php @@ -0,0 +1,85 @@ + + * + * 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'); + } +} diff --git a/Tests/Unit/Domain/Model/LocationTest.php b/Tests/Unit/Domain/Model/LocationTest.php new file mode 100644 index 0000000..e829a8e --- /dev/null +++ b/Tests/Unit/Domain/Model/LocationTest.php @@ -0,0 +1,83 @@ + + * + * 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', + ], + ]; + } +} diff --git a/Tests/Unit/Service/DestinationDataImportService/LocationAssignmentTest.php b/Tests/Unit/Service/DestinationDataImportService/LocationAssignmentTest.php new file mode 100644 index 0000000..3a7d25c --- /dev/null +++ b/Tests/Unit/Service/DestinationDataImportService/LocationAssignmentTest.php @@ -0,0 +1,120 @@ + + * + * 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', + ], + ]; + } +} diff --git a/ext_localconf.php b/ext_localconf.php index f84164a..eab3554 100644 --- a/ext_localconf.php +++ b/ext_localconf.php @@ -53,4 +53,5 @@ call_user_func(function () { \Wrm\Events\Caching\PageCacheTimeout::register(); \Wrm\Events\Updates\MigrateOldLocations::register(); + \Wrm\Events\Updates\MigrateDuplicateLocations::register(); }); diff --git a/phpstan.neon b/phpstan.neon index b3e4507..186b490 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -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\\.$#"