From 9176ba0cec800d23bc1fb2860923e60efd364bfe Mon Sep 17 00:00:00 2001 From: Daniel Siepmann Date: Wed, 3 Feb 2021 17:20:01 +0100 Subject: [PATCH] Import tourist attraction only in German language Allows to import entity of type TouristAttraction. Right now only in German, as this is most important. Add output of tourist attraction via custom content element. --- Classes/Domain/Import/Converter/Converter.php | 8 +- .../Domain/Import/Converter/Organisation.php | 27 +- .../Import/Converter/TouristAttraction.php | 86 ++++ .../Import/Converter/TouristInformation.php | 37 +- Classes/Domain/Import/Converter/Town.php | 27 +- Classes/Domain/Import/Importer/SaveData.php | 73 ++- Classes/Domain/Import/JsonLD/Parser.php | 140 ++++++ .../Import/JsonLD/Parser/OpeningHours.php | 102 ++++ Classes/Domain/Import/Model/Entity.php | 6 + .../Domain/Import/Model/EntityCollection.php | 66 +++ Classes/Domain/Import/Model/GenericEntity.php | 18 + Classes/Domain/Model/Backend/ImportLog.php | 1 + .../Domain/Model/Backend/ImportLogEntry.php | 1 + Classes/Domain/Model/Frontend/OpeningHour.php | 127 +++++ .../Domain/Model/Frontend/OpeningHours.php | 75 +++ .../Model/Frontend/TouristAttraction.php | 54 +++ Classes/Domain/Model/Frontend/Town.php | 42 ++ Classes/Extension.php | 25 + .../DataProcessing/ResolveEntities.php | 85 ++++ Configuration/Extbase/Persistence/Classes.php | 6 + Configuration/Services.yaml | 4 + .../SiteConfiguration/Overrides/sites.php | 2 +- Configuration/TCA/Overrides/sys_template.php | 14 + Configuration/TCA/Overrides/tt_content.php | 18 + .../tt_content_tourist_attraction.php | 63 +++ .../TCA/tx_thuecat_tourist_attraction.php | 89 ++++ Configuration/TypoScript/Rendering.typoscript | 6 + .../Rendering/TouristAttraction.typoscript | 13 + .../TypoScript/Rendering/_base.typoscript | 6 + Configuration/TypoScript/setup.typoscript | 1 + README.md | 4 +- Resources/Private/Language/locallang.xlf | 3 + Resources/Private/Language/locallang_tca.xlf | 35 ++ .../ContentElement/TouristAttraction.html | 21 + .../Import/Converter/OrganisationTest.php | 32 +- .../Converter/TouristAttractionTest.php | 224 +++++++++ .../Converter/TouristInformationTest.php | 113 +++-- .../Unit/Domain/Import/Converter/TownTest.php | 81 +++- Tests/Unit/Domain/Import/ImporterTest.php | 43 +- .../Import/JsonLD/Parser/OpeningHoursTest.php | 310 ++++++++++++ .../Unit/Domain/Import/JsonLD/ParserTest.php | 457 ++++++++++++++++++ .../Import/Model/EntityCollectionTest.php | 138 ++++++ .../Domain/Import/Model/GenericEntityTest.php | 54 +++ composer.json | 3 +- dependency-checker.json | 1 + ext_localconf.php | 5 + ext_tables.sql | 9 + phpstan-baseline.neon | 27 ++ phpstan.neon | 18 +- 49 files changed, 2667 insertions(+), 133 deletions(-) create mode 100644 Classes/Domain/Import/Converter/TouristAttraction.php create mode 100644 Classes/Domain/Import/JsonLD/Parser.php create mode 100644 Classes/Domain/Import/JsonLD/Parser/OpeningHours.php create mode 100644 Classes/Domain/Import/Model/EntityCollection.php create mode 100644 Classes/Domain/Model/Frontend/OpeningHour.php create mode 100644 Classes/Domain/Model/Frontend/OpeningHours.php create mode 100644 Classes/Domain/Model/Frontend/TouristAttraction.php create mode 100644 Classes/Domain/Model/Frontend/Town.php create mode 100644 Classes/Frontend/DataProcessing/ResolveEntities.php create mode 100644 Configuration/TCA/Overrides/sys_template.php create mode 100644 Configuration/TCA/Overrides/tt_content.php create mode 100644 Configuration/TCA/Overrides/tt_content_tourist_attraction.php create mode 100644 Configuration/TCA/tx_thuecat_tourist_attraction.php create mode 100644 Configuration/TypoScript/Rendering.typoscript create mode 100644 Configuration/TypoScript/Rendering/TouristAttraction.typoscript create mode 100644 Configuration/TypoScript/Rendering/_base.typoscript create mode 100644 Configuration/TypoScript/setup.typoscript create mode 100644 Resources/Private/Templates/Frontend/ContentElement/TouristAttraction.html create mode 100644 Tests/Unit/Domain/Import/Converter/TouristAttractionTest.php create mode 100644 Tests/Unit/Domain/Import/JsonLD/Parser/OpeningHoursTest.php create mode 100644 Tests/Unit/Domain/Import/JsonLD/ParserTest.php create mode 100644 Tests/Unit/Domain/Import/Model/EntityCollectionTest.php create mode 100644 ext_localconf.php create mode 100644 phpstan-baseline.neon diff --git a/Classes/Domain/Import/Converter/Converter.php b/Classes/Domain/Import/Converter/Converter.php index 01ce484..d61ea5e 100644 --- a/Classes/Domain/Import/Converter/Converter.php +++ b/Classes/Domain/Import/Converter/Converter.php @@ -23,7 +23,7 @@ namespace WerkraumMedia\ThueCat\Domain\Import\Converter; * 02110-1301, USA. */ -use WerkraumMedia\ThueCat\Domain\Import\Model\Entity; +use WerkraumMedia\ThueCat\Domain\Import\Model\EntityCollection; interface Converter { @@ -33,5 +33,9 @@ interface Converter */ public function canConvert(array $type): bool; - public function convert(array $jsonIdOfEntity): Entity; + /** + * A single JSONLD entity can have multiple languages. + * That may result in multiple entities in TYPO3. + */ + public function convert(array $jsonLD): EntityCollection; } diff --git a/Classes/Domain/Import/Converter/Organisation.php b/Classes/Domain/Import/Converter/Organisation.php index acdc574..ffae345 100644 --- a/Classes/Domain/Import/Converter/Organisation.php +++ b/Classes/Domain/Import/Converter/Organisation.php @@ -23,21 +23,38 @@ namespace WerkraumMedia\ThueCat\Domain\Import\Converter; * 02110-1301, USA. */ +use TYPO3\CMS\Core\Utility\GeneralUtility; +use WerkraumMedia\ThueCat\Domain\Import\JsonLD\Parser; +use WerkraumMedia\ThueCat\Domain\Import\Model\EntityCollection; use WerkraumMedia\ThueCat\Domain\Import\Model\GenericEntity; class Organisation implements Converter { - public function convert(array $jsonIdOfEntity): GenericEntity + private Parser $parser; + + public function __construct( + Parser $parser + ) { + $this->parser = $parser; + } + + public function convert(array $jsonLD): EntityCollection { - return new GenericEntity( + $entity = GeneralUtility::makeInstance( + GenericEntity::class, 10, 'tx_thuecat_organisation', - $jsonIdOfEntity['@id'], + 0, + $this->parser->getId($jsonLD), [ - 'title' => $jsonIdOfEntity['schema:name']['@value'], - 'description' => $jsonIdOfEntity['schema:description']['@value'], + 'title' => $this->parser->getTitle($jsonLD), + 'description' => $this->parser->getDescription($jsonLD), ] ); + $entities = GeneralUtility::makeInstance(EntityCollection::class); + $entities->add($entity); + + return $entities; } public function canConvert(array $type): bool diff --git a/Classes/Domain/Import/Converter/TouristAttraction.php b/Classes/Domain/Import/Converter/TouristAttraction.php new file mode 100644 index 0000000..4e595a4 --- /dev/null +++ b/Classes/Domain/Import/Converter/TouristAttraction.php @@ -0,0 +1,86 @@ + + * + * 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. + */ + +use TYPO3\CMS\Core\Utility\GeneralUtility; +use WerkraumMedia\ThueCat\Domain\Import\JsonLD\Parser; +use WerkraumMedia\ThueCat\Domain\Import\Model\EntityCollection; +use WerkraumMedia\ThueCat\Domain\Import\Model\GenericEntity; +use WerkraumMedia\ThueCat\Domain\Repository\Backend\OrganisationRepository; +use WerkraumMedia\ThueCat\Domain\Repository\Backend\TownRepository; + +class TouristAttraction implements Converter +{ + private Parser $parser; + private OrganisationRepository $organisationRepository; + private TownRepository $townRepository; + + public function __construct( + Parser $parser, + OrganisationRepository $organisationRepository, + TownRepository $townRepository + ) { + $this->parser = $parser; + $this->organisationRepository = $organisationRepository; + $this->townRepository = $townRepository; + } + + public function convert(array $jsonLD): EntityCollection + { + $storagePid = 10; + $manager = $this->organisationRepository->findOneByRemoteId($this->parser->getManagerId($jsonLD)); + $town = $this->townRepository->findOneByRemoteIds($this->parser->getContainedInPlaceIds($jsonLD)); + $entities = GeneralUtility::makeInstance(EntityCollection::class); + + foreach ($this->parser->getLanguages($jsonLD) as $language) { + if ($language !== 'de') { + continue; + } + $systemLanguageUid = 0; + + $entity = GeneralUtility::makeInstance( + GenericEntity::class, + $storagePid, + 'tx_thuecat_tourist_attraction', + $systemLanguageUid, + $this->parser->getId($jsonLD), + [ + 'title' => $this->parser->getTitle($jsonLD, $language), + 'description' => $this->parser->getDescription($jsonLD, $language), + 'managed_by' => $manager ? $manager->getUid() : 0, + 'town' => $town ? $town->getUid() : 0, + 'opening_hours' => json_encode($this->parser->getOpeningHours($jsonLD)), + ] + ); + $entities->add($entity); + } + + return $entities; + } + + public function canConvert(array $type): bool + { + return array_search('schema:TouristAttraction', $type) !== false; + } +} diff --git a/Classes/Domain/Import/Converter/TouristInformation.php b/Classes/Domain/Import/Converter/TouristInformation.php index c4e37ec..809a357 100644 --- a/Classes/Domain/Import/Converter/TouristInformation.php +++ b/Classes/Domain/Import/Converter/TouristInformation.php @@ -23,50 +23,59 @@ namespace WerkraumMedia\ThueCat\Domain\Import\Converter; * 02110-1301, USA. */ +use TYPO3\CMS\Core\Utility\GeneralUtility; +use WerkraumMedia\ThueCat\Domain\Import\JsonLD\Parser; +use WerkraumMedia\ThueCat\Domain\Import\Model\EntityCollection; use WerkraumMedia\ThueCat\Domain\Import\Model\GenericEntity; use WerkraumMedia\ThueCat\Domain\Repository\Backend\OrganisationRepository; use WerkraumMedia\ThueCat\Domain\Repository\Backend\TownRepository; class TouristInformation implements Converter { + private Parser $parser; private OrganisationRepository $organisationRepository; private TownRepository $townRepository; public function __construct( + Parser $parser, OrganisationRepository $organisationRepository, TownRepository $townRepository ) { + $this->parser = $parser; $this->organisationRepository = $organisationRepository; $this->townRepository = $townRepository; } - public function convert(array $jsonIdOfEntity): GenericEntity + public function convert(array $jsonLD): EntityCollection { - $manager = $this->organisationRepository->findOneByRemoteId($jsonIdOfEntity['thuecat:managedBy']['@id']); - $town = $this->townRepository->findOneByRemoteIds($this->getContainedInPlaceIds($jsonIdOfEntity)); + $manager = $this->organisationRepository->findOneByRemoteId( + $this->parser->getManagerId($jsonLD) + ); + $town = $this->townRepository->findOneByRemoteIds( + $this->parser->getContainedInPlaceIds($jsonLD) + ); - return new GenericEntity( + $entity = GeneralUtility::makeInstance( + GenericEntity::class, 10, 'tx_thuecat_tourist_information', - $jsonIdOfEntity['@id'], + 0, + $this->parser->getId($jsonLD), [ - 'title' => $jsonIdOfEntity['schema:name']['@value'], - 'description' => $jsonIdOfEntity['schema:description'][0]['@value'], + 'title' => $this->parser->getTitle($jsonLD), + 'description' => $this->parser->getDescription($jsonLD), 'managed_by' => $manager ? $manager->getUid() : 0, 'town' => $town ? $town->getUid() : 0, ] ); + $entities = GeneralUtility::makeInstance(EntityCollection::class); + $entities->add($entity); + + return $entities; } public function canConvert(array $type): bool { return array_search('thuecat:TouristInformation', $type) !== false; } - - private function getContainedInPlaceIds(array $jsonIdOfEntity): array - { - return array_map(function (array $place) { - return $place['@id']; - }, $jsonIdOfEntity['schema:containedInPlace']); - } } diff --git a/Classes/Domain/Import/Converter/Town.php b/Classes/Domain/Import/Converter/Town.php index 3b332f1..61cdbdf 100644 --- a/Classes/Domain/Import/Converter/Town.php +++ b/Classes/Domain/Import/Converter/Town.php @@ -23,32 +23,47 @@ namespace WerkraumMedia\ThueCat\Domain\Import\Converter; * 02110-1301, USA. */ +use TYPO3\CMS\Core\Utility\GeneralUtility; +use WerkraumMedia\ThueCat\Domain\Import\JsonLD\Parser; +use WerkraumMedia\ThueCat\Domain\Import\Model\EntityCollection; use WerkraumMedia\ThueCat\Domain\Import\Model\GenericEntity; use WerkraumMedia\ThueCat\Domain\Repository\Backend\OrganisationRepository; class Town implements Converter { + private Parser $parser; private OrganisationRepository $organisationRepository; public function __construct( + Parser $parser, OrganisationRepository $organisationRepository ) { + $this->parser = $parser; $this->organisationRepository = $organisationRepository; } - public function convert(array $jsonIdOfEntity): GenericEntity + public function convert(array $jsonLD): EntityCollection { - $manager = $this->organisationRepository->findOneByRemoteId($jsonIdOfEntity['thuecat:managedBy']['@id']); - return new GenericEntity( + $manager = $this->organisationRepository->findOneByRemoteId( + $this->parser->getManagerId($jsonLD) + ); + + $entity = GeneralUtility::makeInstance( + GenericEntity::class, 10, 'tx_thuecat_town', - $jsonIdOfEntity['@id'], + 0, + $this->parser->getId($jsonLD), [ - 'title' => $jsonIdOfEntity['schema:name']['@value'], - 'description' => $jsonIdOfEntity['schema:description']['@value'] ?? '', + 'title' => $this->parser->getTitle($jsonLD), + 'description' => $this->parser->getDescription($jsonLD), 'managed_by' => $manager ? $manager->getUid() : 0, ] ); + $entities = GeneralUtility::makeInstance(EntityCollection::class); + $entities->add($entity); + + return $entities; } public function canConvert(array $type): bool diff --git a/Classes/Domain/Import/Importer/SaveData.php b/Classes/Domain/Import/Importer/SaveData.php index a727afb..c6539fa 100644 --- a/Classes/Domain/Import/Importer/SaveData.php +++ b/Classes/Domain/Import/Importer/SaveData.php @@ -26,6 +26,7 @@ namespace WerkraumMedia\ThueCat\Domain\Import\Importer; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\DataHandling\DataHandler; use WerkraumMedia\ThueCat\Domain\Import\Model\Entity; +use WerkraumMedia\ThueCat\Domain\Import\Model\EntityCollection; use WerkraumMedia\ThueCat\Domain\Model\Backend\ImportLog; use WerkraumMedia\ThueCat\Domain\Model\Backend\ImportLogEntry; @@ -33,6 +34,7 @@ class SaveData { private DataHandler $dataHandler; private ConnectionPool $connectionPool; + private array $errorLog; public function __construct( DataHandler $dataHandler, @@ -42,31 +44,50 @@ class SaveData $this->connectionPool = $connectionPool; } - public function import(Entity $entity, ImportLog $log): void + public function import(EntityCollection $entityCollection, ImportLog $log): void { - $dataHandler = clone $this->dataHandler; + $this->errorLog = []; - $identifier = $this->getIdentifier($entity); - $dataHandler->start([ - $entity->getTypo3DatabaseTableName() => [ - $identifier => array_merge($entity->getData(), [ - 'pid' => $entity->getTypo3StoragePid(), - 'remote_id' => $entity->getRemoteId(), - ]), - ], - ], []); - $dataHandler->process_datamap(); + $this->processSimpleDataHandlerDataMap($entityCollection); + // TODO: Insert update / insert of localization - if (isset($dataHandler->substNEWwithIDs[$identifier])) { - $entity->setImportedTypo3Uid($dataHandler->substNEWwithIDs[$identifier]); - } elseif (is_numeric($identifier)) { - $entity->setExistingTypo3Uid((int) $identifier); + foreach ($entityCollection->getEntities() as $entity) { + $log->addEntry(new ImportLogEntry($entity, $this->errorLog)); + } + } + + private function processSimpleDataHandlerDataMap(EntityCollection $collection): void + { + $dataArray = []; + $identifierMapping = []; + + foreach ($collection->getEntities() as $entity) { + $identifier = $this->getIdentifier($entity); + if (strpos($identifier, 'NEW') === 0 && $entity->isTranslation()) { + continue; + } + if (is_numeric($identifier)) { + $entity->setExistingTypo3Uid((int) $identifier); + } else { + $identifierMapping[spl_object_id($entity)] = $identifier; + } + + $dataArray[$entity->getTypo3DatabaseTableName()][$identifier] = $this->getEntityData($entity); } - $log->addEntry(new ImportLogEntry( - $entity, - $dataHandler->errorLog - )); + $dataHandler = clone $this->dataHandler; + $dataHandler->start($dataArray, []); + $dataHandler->process_datamap(); + $this->errorLog = array_merge($this->errorLog, $dataHandler->errorLog); + + foreach ($collection->getEntities() as $entity) { + if ( + isset($identifierMapping[spl_object_id($entity)]) + && isset($dataHandler->substNEWwithIDs[$identifierMapping[spl_object_id($entity)]]) + ) { + $entity->setImportedTypo3Uid($dataHandler->substNEWwithIDs[$identifierMapping[spl_object_id($entity)]]); + } + } } private function getIdentifier(Entity $entity): string @@ -77,7 +98,17 @@ class SaveData return (string) $existingUid; } - return 'NEW_1'; + $identifier = 'NEW_' . sha1($entity->getRemoteId() . $entity->getTypo3SystemLanguageUid()); + // Ensure new ID is max 30, as this is max volumn of the sys_log column + return substr($identifier, 0, 30); + } + + private function getEntityData(Entity $entity): array + { + return array_merge($entity->getData(), [ + 'pid' => $entity->getTypo3StoragePid(), + 'remote_id' => $entity->getRemoteId(), + ]); } private function getExistingUid(Entity $entity): int diff --git a/Classes/Domain/Import/JsonLD/Parser.php b/Classes/Domain/Import/JsonLD/Parser.php new file mode 100644 index 0000000..b41c72d --- /dev/null +++ b/Classes/Domain/Import/JsonLD/Parser.php @@ -0,0 +1,140 @@ + + * + * 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. + */ + +use WerkraumMedia\ThueCat\Domain\Import\JsonLD\Parser\OpeningHours; + +class Parser +{ + private OpeningHours $openingHours; + + public function __construct( + OpeningHours $openingHours + ) { + $this->openingHours = $openingHours; + } + public function getId(array $jsonLD): string + { + return $jsonLD['@id']; + } + + public function getTitle(array $jsonLD, string $language = ''): string + { + return $this->getValueForLanguage($jsonLD['schema:name'], $language); + } + + public function getDescription(array $jsonLD, string $language = ''): string + { + return $this->getValueForLanguage($jsonLD['schema:description'], $language); + } + + public function getManagerId(array $jsonLD): string + { + return $jsonLD['thuecat:contentResponsible']['@id']; + } + + /** + * @return string[] + */ + public function getContainedInPlaceIds(array $jsonLD): array + { + return array_map(function (array $place) { + return $place['@id']; + }, $jsonLD['schema:containedInPlace']); + } + + public function getOpeningHours(array $jsonLD): array + { + return $this->openingHours->get($jsonLD); + } + + /** + * @return string[] + */ + public function getLanguages(array $jsonLD): array + { + if (isset($jsonLD['schema:availableLanguage']) === false) { + return []; + } + + $languages = $jsonLD['schema:availableLanguage']; + + $languages = array_filter($languages, function (array $language) { + return isset($language['@type']) + && $language['@type'] === 'thuecat:Language' + ; + }); + + $languages = array_map(function (array $language) { + $language = $language['@value']; + + if ($language === 'thuecat:German') { + return 'de'; + } + if ($language === 'thuecat:English') { + return 'en'; + } + if ($language === 'thuecat:French') { + return 'fr'; + } + + throw new \Exception('Unsupported language "' . $language . '".', 1612367481); + }, $languages); + + return $languages; + } + + private function getValueForLanguage( + array $property, + string $language + ): string { + if ( + $this->doesLanguageMatch($property, $language) + && isset($property['@value']) + ) { + return $property['@value']; + } + + foreach ($property as $languageEntry) { + if ( + is_array($languageEntry) + && $this->doesLanguageMatch($languageEntry, $language) + ) { + return $languageEntry['@value']; + } + } + + return ''; + } + + private function doesLanguageMatch(array $property, string $language): bool + { + return isset($property['@language']) + && ( + $property['@language'] === $language + || $language === '' + ) + ; + } +} diff --git a/Classes/Domain/Import/JsonLD/Parser/OpeningHours.php b/Classes/Domain/Import/JsonLD/Parser/OpeningHours.php new file mode 100644 index 0000000..3fa43a9 --- /dev/null +++ b/Classes/Domain/Import/JsonLD/Parser/OpeningHours.php @@ -0,0 +1,102 @@ + + * + * 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. + */ + +class OpeningHours +{ + public function get(array $jsonLD): array + { + $openingHours = $jsonLD['schema:openingHoursSpecification'] ?? []; + if ($openingHours === []) { + return []; + } + + if (isset($openingHours['@id'])) { + return [$this->parseSingleEntry($openingHours)]; + } + + return array_values(array_map([$this, 'parseSingleEntry'], $openingHours)); + } + + private function parseSingleEntry(array $openingHour): array + { + return [ + 'opens' => $this->getOpens($openingHour), + 'closes' => $this->getCloses($openingHour), + 'from' => $this->getFrom($openingHour), + 'through' => $this->getThrough($openingHour), + 'daysOfWeek' => $this->getDaysOfWeek($openingHour), + ]; + } + + private function getOpens(array $openingHour): string + { + return $openingHour['schema:opens']['@value'] ?? ''; + } + + private function getCloses(array $openingHour): string + { + return $openingHour['schema:closes']['@value'] ?? ''; + } + + private function getFrom(array $openingHour): ?\DateTimeImmutable + { + if (isset($openingHour['schema:validFrom']['@value'])) { + return new \DateTimeImmutable($openingHour['schema:validFrom']['@value']); + } + + return null; + } + + private function getThrough(array $openingHour): ?\DateTimeImmutable + { + if (isset($openingHour['schema:validThrough']['@value'])) { + return new \DateTimeImmutable($openingHour['schema:validThrough']['@value']); + } + + return null; + } + + private function getDaysOfWeek(array $openingHour): array + { + if (isset($openingHour['schema:dayOfWeek']['@value'])) { + return [$this->getDayOfWeekString($openingHour['schema:dayOfWeek']['@value'])]; + } + $daysOfWeek = array_map(function ($dayOfWeek) { + return $this->getDayOfWeekString($dayOfWeek['@value']); + }, $openingHour['schema:dayOfWeek'] ?? []); + + sort($daysOfWeek); + return $daysOfWeek; + } + + private function getDayOfWeekString(string $jsonLDValue): string + { + return str_replace( + 'schema:', + '', + $jsonLDValue + ); + } +} diff --git a/Classes/Domain/Import/Model/Entity.php b/Classes/Domain/Import/Model/Entity.php index 86a7b70..45b5584 100644 --- a/Classes/Domain/Import/Model/Entity.php +++ b/Classes/Domain/Import/Model/Entity.php @@ -29,6 +29,12 @@ interface Entity public function getTypo3DatabaseTableName(): string; + public function getTypo3SystemLanguageUid(): int; + + public function isForDefaultLanguage(): bool; + + public function isTranslation(): bool; + /** * Return full remote id as delivered by remote API. */ diff --git a/Classes/Domain/Import/Model/EntityCollection.php b/Classes/Domain/Import/Model/EntityCollection.php new file mode 100644 index 0000000..cb3eea3 --- /dev/null +++ b/Classes/Domain/Import/Model/EntityCollection.php @@ -0,0 +1,66 @@ + + * + * 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. + */ + +class EntityCollection +{ + /** + * @var Entity[] + */ + private array $entities = []; + + public function add(Entity $entity): void + { + $this->entities[] = $entity; + } + + /** + * @return Entity[] + */ + public function getEntities(): array + { + return $this->entities; + } + + public function getDefaultLanguageEntity(): ?Entity + { + foreach ($this->entities as $entity) { + if ($entity->isForDefaultLanguage()) { + return $entity; + } + } + + return null; + } + + /** + * @return Entity[] + */ + public function getTranslatedEntities(): array + { + return array_filter($this->entities, function (Entity $entity) { + return $entity->isTranslation(); + }); + } +} diff --git a/Classes/Domain/Import/Model/GenericEntity.php b/Classes/Domain/Import/Model/GenericEntity.php index 2383bdf..d90ef52 100644 --- a/Classes/Domain/Import/Model/GenericEntity.php +++ b/Classes/Domain/Import/Model/GenericEntity.php @@ -27,6 +27,7 @@ class GenericEntity implements Entity { private int $typo3StoragePid; private string $typo3DatabaseTableName; + private int $typo3SystemLanguageUid; private bool $created = false; private int $typo3Uid = 0; private string $remoteId; @@ -35,11 +36,13 @@ class GenericEntity implements Entity public function __construct( int $typo3StoragePid, string $typo3DatabaseTableName, + int $typo3SystemLanguageUid, string $remoteId, array $data ) { $this->typo3StoragePid = $typo3StoragePid; $this->typo3DatabaseTableName = $typo3DatabaseTableName; + $this->typo3SystemLanguageUid = $typo3SystemLanguageUid; $this->remoteId = $remoteId; $this->data = $data; } @@ -54,6 +57,21 @@ class GenericEntity implements Entity return $this->typo3DatabaseTableName; } + public function getTypo3SystemLanguageUid(): int + { + return $this->typo3SystemLanguageUid; + } + + public function isForDefaultLanguage(): bool + { + return $this->getTypo3SystemLanguageUid() === 0; + } + + public function isTranslation(): bool + { + return $this->getTypo3SystemLanguageUid() !== 0; + } + public function getRemoteId(): string { return $this->remoteId; diff --git a/Classes/Domain/Model/Backend/ImportLog.php b/Classes/Domain/Model/Backend/ImportLog.php index 4b47387..569239d 100644 --- a/Classes/Domain/Model/Backend/ImportLog.php +++ b/Classes/Domain/Model/Backend/ImportLog.php @@ -80,6 +80,7 @@ class ImportLog extends Typo3AbstractEntity foreach ($this->getEntries() as $entry) { if ($entry->hasErrors()) { $errors = array_merge($errors, $entry->getErrors()); + $errors = array_unique($errors); } } diff --git a/Classes/Domain/Model/Backend/ImportLogEntry.php b/Classes/Domain/Model/Backend/ImportLogEntry.php index afdda84..3d550cf 100644 --- a/Classes/Domain/Model/Backend/ImportLogEntry.php +++ b/Classes/Domain/Model/Backend/ImportLogEntry.php @@ -85,6 +85,7 @@ class ImportLogEntry extends Typo3AbstractEntity { if ($this->errorsAsArray === [] && $this->errors !== '') { $this->errorsAsArray = json_decode($this->errors, true); + $this->errorsAsArray = array_unique($this->errorsAsArray); } return $this->errorsAsArray; diff --git a/Classes/Domain/Model/Frontend/OpeningHour.php b/Classes/Domain/Model/Frontend/OpeningHour.php new file mode 100644 index 0000000..ecff698 --- /dev/null +++ b/Classes/Domain/Model/Frontend/OpeningHour.php @@ -0,0 +1,127 @@ + + * + * 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. + */ + +class OpeningHour +{ + private string $opens; + private string $closes; + private array $daysOfWeek; + private ?\DateTimeImmutable $from; + private ?\DateTimeImmutable $through; + + private function __construct( + string $opens, + string $closes, + array $daysOfWeek, + ?\DateTimeImmutable $from, + ?\DateTimeImmutable $through + ) { + $this->opens = $opens; + $this->closes = $closes; + $this->daysOfWeek = $daysOfWeek; + $this->from = $from; + $this->through = $through; + } + + public static function createFromArray(array $rawData): self + { + $from = null; + if (isset($rawData['from'])) { + $timeZone = new \DateTimeZone($rawData['from']['timezone']); + $from = new \DateTimeImmutable($rawData['from']['date'], $timeZone); + } + $through = null; + if (isset($rawData['through'])) { + $timeZone = new \DateTimeZone($rawData['through']['timezone']); + $through = new \DateTimeImmutable($rawData['through']['date'], $timeZone); + } + + return new self( + $rawData['opens'] ?? '', + $rawData['closes'] ?? '', + $rawData['daysOfWeek'] ?? '', + $from, + $through + ); + } + + public function getOpens(): string + { + return $this->opens; + } + + public function getCloses(): string + { + return $this->closes; + } + + public function getDaysOfWeek(): array + { + return $this->daysOfWeek; + } + + public function getDaysOfWeekWithMondayFirstWeekDay(): array + { + return $this->sortedDaysOfWeek([ + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday', + 'Sunday', + ]); + } + + public function getFrom(): ?\DateTimeImmutable + { + return $this->from; + } + + public function getThrough(): ?\DateTimeImmutable + { + return $this->through; + } + + private function sortedDaysOfWeek(array $sorting): array + { + if ($this->daysOfWeek === []) { + return []; + } + + $days = []; + + foreach ($sorting as $weekDay) { + $position = array_search($weekDay, $this->daysOfWeek); + if ($position === false) { + continue; + } + + $days[] = $this->daysOfWeek[$position]; + } + + return $days; + } +} diff --git a/Classes/Domain/Model/Frontend/OpeningHours.php b/Classes/Domain/Model/Frontend/OpeningHours.php new file mode 100644 index 0000000..a70d476 --- /dev/null +++ b/Classes/Domain/Model/Frontend/OpeningHours.php @@ -0,0 +1,75 @@ + + * + * 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. + */ + +use TYPO3\CMS\Core\Type\TypeInterface; + +/** + * @implements \Iterator + */ +class OpeningHours implements TypeInterface, \Iterator +{ + private string $serialized = ''; + private array $array = []; + private int $position = 0; + + public function __construct(string $serialized) + { + $this->serialized = $serialized; + $this->array = array_map( + [OpeningHour::class, 'createFromArray'], + json_decode($serialized, true) + ); + } + + public function __toString(): string + { + return $this->serialized; + } + + public function current(): OpeningHour + { + return $this->array[$this->position]; + } + + public function next(): void + { + ++$this->position; + } + + public function key(): int + { + return $this->position; + } + + public function valid(): bool + { + return isset($this->array[$this->position]); + } + + public function rewind(): void + { + $this->position = 0; + } +} diff --git a/Classes/Domain/Model/Frontend/TouristAttraction.php b/Classes/Domain/Model/Frontend/TouristAttraction.php new file mode 100644 index 0000000..a69c8ee --- /dev/null +++ b/Classes/Domain/Model/Frontend/TouristAttraction.php @@ -0,0 +1,54 @@ + + * + * 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. + */ + +use TYPO3\CMS\Extbase\DomainObject\AbstractEntity; + +class TouristAttraction extends AbstractEntity +{ + protected string $title = ''; + protected string $description = ''; + protected ?OpeningHours $openingHours = null; + protected ?Town $town = null; + + public function getTitle(): string + { + return $this->title; + } + + public function getDescription(): string + { + return $this->description; + } + + public function getOpeningHours(): ?OpeningHours + { + return $this->openingHours; + } + + public function getTown(): ?Town + { + return $this->town; + } +} diff --git a/Classes/Domain/Model/Frontend/Town.php b/Classes/Domain/Model/Frontend/Town.php new file mode 100644 index 0000000..4bdd44e --- /dev/null +++ b/Classes/Domain/Model/Frontend/Town.php @@ -0,0 +1,42 @@ + + * + * 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. + */ + +use TYPO3\CMS\Extbase\DomainObject\AbstractEntity; + +class Town extends AbstractEntity +{ + protected string $title = ''; + protected string $description = ''; + + public function getTitle(): string + { + return $this->title; + } + + public function getDescription(): string + { + return $this->description; + } +} diff --git a/Classes/Extension.php b/Classes/Extension.php index c717285..293a135 100644 --- a/Classes/Extension.php +++ b/Classes/Extension.php @@ -23,6 +23,7 @@ namespace WerkraumMedia\ThueCat; * 02110-1301, USA. */ +use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; use TYPO3\CMS\Extbase\Utility\ExtensionUtility; use WerkraumMedia\ThueCat\Controller\Backend\ImportController; use WerkraumMedia\ThueCat\Controller\Backend\OverviewController; @@ -33,6 +34,8 @@ class Extension public const EXTENSION_NAME = 'Thuecat'; + public const TT_CONTENT_GROUP = 'thuecat'; + public static function getLanguagePath(): string { return 'LLL:EXT:' . self::EXTENSION_KEY . '/Resources/Private/Language/'; @@ -56,4 +59,26 @@ class Extension ] ); } + + public static function registerConfig(): void + { + $languagePath = self::getLanguagePath() . 'locallang_tca.xlf:tt_content'; + + // TODO: Add Icon + ExtensionManagementUtility::addPageTSConfig(' + mod.wizards.newContentElement.wizardItems.thuecat { + header = ' . $languagePath . '.group + show = * + elements { + thuecat_tourist_attraction{ + title = ' . $languagePath . '.thuecat_tourist_attraction + description = ' . $languagePath . '.thuecat_tourist_attraction.description + tt_content_defValues { + CType = thuecat_tourist_attraction + } + } + } + } + '); + } } diff --git a/Classes/Frontend/DataProcessing/ResolveEntities.php b/Classes/Frontend/DataProcessing/ResolveEntities.php new file mode 100644 index 0000000..47dee3b --- /dev/null +++ b/Classes/Frontend/DataProcessing/ResolveEntities.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. + */ + +use TYPO3\CMS\Core\Database\Connection; +use TYPO3\CMS\Core\Database\ConnectionPool; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper; +use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; +use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface; + +class ResolveEntities implements DataProcessorInterface +{ + private ConnectionPool $connectionPool; + private DataMapper $dataMapper; + + public function __construct( + ConnectionPool $connectionPool, + DataMapper $dataMapper + ) { + $this->connectionPool = $connectionPool; + $this->dataMapper = $dataMapper; + } + + public function process( + ContentObjectRenderer $cObj, + array $contentObjectConfiguration, + array $processorConfiguration, + array $processedData + ) { + $as = $cObj->stdWrapValue('as', $processorConfiguration, 'entities'); + $table = $cObj->stdWrapValue('table', $processorConfiguration, ''); + $uids = $cObj->stdWrapValue('uids', $processorConfiguration, ''); + + $uids = GeneralUtility::intExplode(',', $uids); + if ($uids === [] || $table === '') { + return $processedData; + } + + $processedData[$as] = $this->resolveEntities($table, $uids); + return $processedData; + } + + private function resolveEntities(string $table, array $uids): array + { + $targetType = '\WerkraumMedia\ThueCat\Domain\Model\Frontend\\' . $this->convertTableToEntity($table); + + $queryBuilder = $this->connectionPool->getQueryBuilderForTable($table); + $queryBuilder->select('*'); + $queryBuilder->from($table); + $queryBuilder->where($queryBuilder->expr()->in( + 'uid', + $queryBuilder->createNamedParameter($uids, Connection::PARAM_INT_ARRAY) + )); + + return $this->dataMapper->map($targetType, $queryBuilder->execute()->fetchAll()); + } + + private function convertTableToEntity(string $table): string + { + $entityPart = str_replace('tx_thuecat_', '', $table); + return GeneralUtility::underscoredToUpperCamelCase($entityPart); + } +} diff --git a/Configuration/Extbase/Persistence/Classes.php b/Configuration/Extbase/Persistence/Classes.php index 280fbe1..8cd3d03 100644 --- a/Configuration/Extbase/Persistence/Classes.php +++ b/Configuration/Extbase/Persistence/Classes.php @@ -19,4 +19,10 @@ return [ \WerkraumMedia\ThueCat\Domain\Model\Backend\ImportLogEntry::class => [ 'tableName' => 'tx_thuecat_import_log_entry', ], + \WerkraumMedia\ThueCat\Domain\Model\Frontend\TouristAttraction::class => [ + 'tableName' => 'tx_thuecat_tourist_attraction', + ], + \WerkraumMedia\ThueCat\Domain\Model\Frontend\Town::class => [ + 'tableName' => 'tx_thuecat_town', + ], ]; diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index 7820594..0f313d9 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -10,3 +10,7 @@ services: WerkraumMedia\ThueCat\Domain\Import\Importer\FetchData: arguments: $requestFactory: '@WerkraumMedia\ThueCat\Domain\Import\RequestFactory' + + WerkraumMedia\ThueCat\Frontend\DataProcessing\: + resource: '../Classes/Frontend/DataProcessing/*' + public: true diff --git a/Configuration/SiteConfiguration/Overrides/sites.php b/Configuration/SiteConfiguration/Overrides/sites.php index 7b392ff..2c644b4 100644 --- a/Configuration/SiteConfiguration/Overrides/sites.php +++ b/Configuration/SiteConfiguration/Overrides/sites.php @@ -3,7 +3,7 @@ defined('TYPO3') or die(); (static function (string $extensionKey, string $tableName) { - $languagePath = 'LLL:EXT:' . $extensionKey . '/Resources/Private/Language/locallang_be.xlf:' . $tableName . '.'; + $languagePath = \WerkraumMedia\ThueCat\Extension::getLanguagePath() . 'locallang_be.xlf:' . $tableName . '.'; \TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($GLOBALS['SiteConfiguration']['site'], [ 'columns' => [ diff --git a/Configuration/TCA/Overrides/sys_template.php b/Configuration/TCA/Overrides/sys_template.php new file mode 100644 index 0000000..44ca9d7 --- /dev/null +++ b/Configuration/TCA/Overrides/sys_template.php @@ -0,0 +1,14 @@ + [ + // TODO: Add Icon + // 'typeicon_classes' => [ + // $cType => '', + // ], + ], + 'types' => [ + $cType => [ + 'showitem' => + '--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:general,' + . '--palette--;;general,' + . '--palette--;;headers,' + . 'records,' + . '--div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:tabs.appearance,' + . '--palette--;;frames,' + . '--palette--;;appearanceLinks,' + . '--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language,' + . '--palette--;;language,' + . '--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access,' + . '--palette--;;hidden,' + . '--palette--;;access,' + . '--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:categories,' + . '--div--;LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_category.tabs.category,' + . 'categories,' + . '--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:notes,' + . 'rowDescription,' + . '--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:extended', + 'columnsOverrides' => [ + 'records' => [ + 'config' => [ + 'allowed' => 'tx_thuecat_tourist_attraction', + ], + ], + ], + ], + ], + ]); + + \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTcaSelectItem( + $tableName, + 'CType', + [ + $languagePath, + $cType, + // TODO: Add Icon + '', + \WerkraumMedia\ThueCat\Extension::TT_CONTENT_GROUP, + ] + ); +})( + \WerkraumMedia\ThueCat\Extension::EXTENSION_KEY, + 'tt_content', + 'thuecat_tourist_attraction' +); diff --git a/Configuration/TCA/tx_thuecat_tourist_attraction.php b/Configuration/TCA/tx_thuecat_tourist_attraction.php new file mode 100644 index 0000000..acfa0c7 --- /dev/null +++ b/Configuration/TCA/tx_thuecat_tourist_attraction.php @@ -0,0 +1,89 @@ + [ + 'label' => 'title', + 'default_sortby' => 'title', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'title' => $languagePath, + 'enablecolumns' => [ + 'disabled' => 'disable', + ], + 'searchFields' => 'title, description', + ], + 'columns' => [ + 'title' => [ + 'label' => $languagePath . '.title', + 'config' => [ + 'type' => 'input', + 'size' => 20, + 'max' => 255, + 'readOnly' => true, + ], + ], + 'description' => [ + 'label' => $languagePath . '.description', + 'config' => [ + 'type' => 'text', + 'readOnly' => true, + ], + ], + 'opening_hours' => [ + 'label' => $languagePath . '.opening_hours', + 'config' => [ + 'type' => 'text', + 'readOnly' => true, + ], + ], + 'remote_id' => [ + 'label' => $languagePath . '.remote_id', + 'config' => [ + 'type' => 'input', + 'readOnly' => true, + ], + ], + 'town' => [ + 'label' => $languagePath . '.town', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_thuecat_town', + 'items' => [ + [ + $languagePath . '.town.unkown', + 0, + ], + ], + 'readOnly' => true, + ], + ], + 'managed_by' => [ + 'label' => $languagePath . '.managed_by', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_thuecat_organisation', + 'items' => [ + [ + $languagePath . '.managed_by.unkown', + 0, + ], + ], + 'readOnly' => true, + ], + ], + ], + 'types' => [ + '0' => [ + 'showitem' => 'title, description, opening_hours, remote_id, town, managed_by', + ], + ], + ]; +})(\WerkraumMedia\ThueCat\Extension::EXTENSION_KEY, 'tx_thuecat_tourist_attraction'); diff --git a/Configuration/TypoScript/Rendering.typoscript b/Configuration/TypoScript/Rendering.typoscript new file mode 100644 index 0000000..bcc5032 --- /dev/null +++ b/Configuration/TypoScript/Rendering.typoscript @@ -0,0 +1,6 @@ +lib.thuecatContentElement =< lib.contentElement +lib.thuecatContentElement { + templateRootPaths { + 9999 = EXT:thuecat/Resources/Private/Templates/Frontend/ContentElement/ + } +} diff --git a/Configuration/TypoScript/Rendering/TouristAttraction.typoscript b/Configuration/TypoScript/Rendering/TouristAttraction.typoscript new file mode 100644 index 0000000..67be9f9 --- /dev/null +++ b/Configuration/TypoScript/Rendering/TouristAttraction.typoscript @@ -0,0 +1,13 @@ +tt_content { + thuecat_tourist_attraction =< lib.thuecatContentElement + thuecat_tourist_attraction { + templateName = TouristAttraction + dataProcessing { + 10 = WerkraumMedia\ThueCat\Frontend\DataProcessing\ResolveEntities + 10 { + table = tx_thuecat_tourist_attraction + uids.data = field:records + } + } + } +} diff --git a/Configuration/TypoScript/Rendering/_base.typoscript b/Configuration/TypoScript/Rendering/_base.typoscript new file mode 100644 index 0000000..bcc5032 --- /dev/null +++ b/Configuration/TypoScript/Rendering/_base.typoscript @@ -0,0 +1,6 @@ +lib.thuecatContentElement =< lib.contentElement +lib.thuecatContentElement { + templateRootPaths { + 9999 = EXT:thuecat/Resources/Private/Templates/Frontend/ContentElement/ + } +} diff --git a/Configuration/TypoScript/setup.typoscript b/Configuration/TypoScript/setup.typoscript new file mode 100644 index 0000000..beee004 --- /dev/null +++ b/Configuration/TypoScript/setup.typoscript @@ -0,0 +1 @@ +@import 'EXT:thuecat/Configuration/TypoScript/Rendering/*.typoscript' diff --git a/README.md b/README.md index 9b133cb..15ce77e 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,8 @@ The extension already allows: * Tourist information + * Tourist attraction + * Backend module: * To inspect current existing organisations @@ -36,8 +38,6 @@ The extension already allows: * Integrate proper icons -* Import of tourist attraction - * Content element to display tourist attraction, town, tourist information and organisation. diff --git a/Resources/Private/Language/locallang.xlf b/Resources/Private/Language/locallang.xlf index 6b2e759..7882d95 100644 --- a/Resources/Private/Language/locallang.xlf +++ b/Resources/Private/Language/locallang.xlf @@ -90,6 +90,9 @@ Tourist Information + + Tourist Attraction + Import finished diff --git a/Resources/Private/Language/locallang_tca.xlf b/Resources/Private/Language/locallang_tca.xlf index 4445cf7..3c66576 100644 --- a/Resources/Private/Language/locallang_tca.xlf +++ b/Resources/Private/Language/locallang_tca.xlf @@ -72,6 +72,31 @@ Unkown + + Tourist Attraction + + + Title + + + Description + + + Remote ID + + + Town + + + Unkown + + + Managed by + + + Unkown + + Import Configuration @@ -128,6 +153,16 @@ Created + + + ThüCAT + + + Tourist Attraction + + + Renders selected tourist attractions + diff --git a/Resources/Private/Templates/Frontend/ContentElement/TouristAttraction.html b/Resources/Private/Templates/Frontend/ContentElement/TouristAttraction.html new file mode 100644 index 0000000..e648a00 --- /dev/null +++ b/Resources/Private/Templates/Frontend/ContentElement/TouristAttraction.html @@ -0,0 +1,21 @@ + + +
+
+
{entity.title} ({entity.town.title})
+

{entity.description}

+ + +

+ + {weekday}: {openingHour.opens} - {openingHour.closes}
+
+ {openingHour.from -> f:format.date(format: 'd.m.Y')} - + {openingHour.through -> f:format.date(format: 'd.m.Y')} +

+
+
+
+
+ diff --git a/Tests/Unit/Domain/Import/Converter/OrganisationTest.php b/Tests/Unit/Domain/Import/Converter/OrganisationTest.php index 10c309c..200d8c4 100644 --- a/Tests/Unit/Domain/Import/Converter/OrganisationTest.php +++ b/Tests/Unit/Domain/Import/Converter/OrganisationTest.php @@ -24,21 +24,28 @@ namespace WerkraumMedia\ThueCat\Tests\Unit\Domain\Import\Converter; */ use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; use WerkraumMedia\ThueCat\Domain\Import\Converter\Converter; use WerkraumMedia\ThueCat\Domain\Import\Converter\Organisation; +use WerkraumMedia\ThueCat\Domain\Import\JsonLD\Parser; +use WerkraumMedia\ThueCat\Domain\Import\Model\EntityCollection; /** * @covers WerkraumMedia\ThueCat\Domain\Import\Converter\Organisation + * @uses WerkraumMedia\ThueCat\Domain\Import\Model\EntityCollection * @uses WerkraumMedia\ThueCat\Domain\Import\Model\GenericEntity */ class OrganisationTest extends TestCase { + use ProphecyTrait; + /** * @test */ public function instanceCanBeCreated(): void { - $subject = new Organisation(); + $parser = $this->prophesize(Parser::class); + $subject = new Organisation($parser->reveal()); self::assertInstanceOf(Organisation::class, $subject); } @@ -47,7 +54,8 @@ class OrganisationTest extends TestCase */ public function isInstanceOfConverter(): void { - $subject = new Organisation(); + $parser = $this->prophesize(Parser::class); + $subject = new Organisation($parser->reveal()); self::assertInstanceOf(Converter::class, $subject); } @@ -56,7 +64,8 @@ class OrganisationTest extends TestCase */ public function canConvertTouristMarketingCompany(): void { - $subject = new Organisation(); + $parser = $this->prophesize(Parser::class); + $subject = new Organisation($parser->reveal()); self::assertTrue($subject->canConvert([ 'thuecat:TouristMarketingCompany', 'schema:Thing', @@ -69,8 +78,7 @@ class OrganisationTest extends TestCase */ public function convertsJsonIdToGenericEntity(): void { - $subject = new Organisation(); - $entity = $subject->convert([ + $jsonLD = [ '@id' => 'https://example.com/resources/018132452787-ngbe', 'schema:name' => [ '@value' => 'Title', @@ -78,8 +86,20 @@ class OrganisationTest extends TestCase 'schema:description' => [ '@value' => 'Description', ], - ]); + ]; + $parser = $this->prophesize(Parser::class); + $parser->getId($jsonLD)->willReturn('https://example.com/resources/018132452787-ngbe'); + $parser->getTitle($jsonLD)->willReturn('Title'); + $parser->getDescription($jsonLD)->willReturn('Description'); + + $subject = new Organisation($parser->reveal()); + $entities = $subject->convert($jsonLD); + + self::assertInstanceOf(EntityCollection::class, $entities); + self::assertCount(1, $entities->getEntities()); + + $entity = $entities->getEntities()[0]; self::assertSame(10, $entity->getTypo3StoragePid()); self::assertSame('tx_thuecat_organisation', $entity->getTypo3DatabaseTableName()); self::assertSame('https://example.com/resources/018132452787-ngbe', $entity->getRemoteId()); diff --git a/Tests/Unit/Domain/Import/Converter/TouristAttractionTest.php b/Tests/Unit/Domain/Import/Converter/TouristAttractionTest.php new file mode 100644 index 0000000..29748c1 --- /dev/null +++ b/Tests/Unit/Domain/Import/Converter/TouristAttractionTest.php @@ -0,0 +1,224 @@ + + * + * 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. + */ + +use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; +use WerkraumMedia\ThueCat\Domain\Import\Converter\TouristAttraction; +use WerkraumMedia\ThueCat\Domain\Import\JsonLD\Parser; +use WerkraumMedia\ThueCat\Domain\Import\Model\EntityCollection; +use WerkraumMedia\ThueCat\Domain\Model\Backend\Organisation; +use WerkraumMedia\ThueCat\Domain\Model\Backend\Town; +use WerkraumMedia\ThueCat\Domain\Repository\Backend\OrganisationRepository; +use WerkraumMedia\ThueCat\Domain\Repository\Backend\TownRepository; + +/** + * @covers WerkraumMedia\ThueCat\Domain\Import\Converter\TouristAttraction + * @uses WerkraumMedia\ThueCat\Domain\Import\Model\EntityCollection + * @uses WerkraumMedia\ThueCat\Domain\Import\Model\GenericEntity + */ +class TouristAttractionTest extends TestCase +{ + use ProphecyTrait; + + /** + * @test + */ + public function canBeCreated(): void + { + $parser = $this->prophesize(Parser::class); + $organisationRepository = $this->prophesize(OrganisationRepository::class); + $townRepository = $this->prophesize(TownRepository::class); + + $subject = new TouristAttraction( + $parser->reveal(), + $organisationRepository->reveal(), + $townRepository->reveal() + ); + + self::assertInstanceOf(TouristAttraction::class, $subject); + } + + /** + * @test + */ + public function canConvert(): void + { + $parser = $this->prophesize(Parser::class); + $organisationRepository = $this->prophesize(OrganisationRepository::class); + $townRepository = $this->prophesize(TownRepository::class); + + $subject = new TouristAttraction( + $parser->reveal(), + $organisationRepository->reveal(), + $townRepository->reveal() + ); + + self::assertTrue($subject->canConvert(['schema:TouristAttraction'])); + } + + /** + * @test + */ + public function convertsWithoutRelations(): void + { + $jsonLD = [ + '@id' => 'https://example.com/resources/018132452787-ngbe', + 'thuecat:managedBy' => [ + '@id' => 'https://example.com/resources/018132452787-xxxx', + ], + 'schema:containedInPlace' => [ + [ + '@id' => 'https://example.com/resources/043064193523-jcyt', + ], + [ + '@id' => 'https://example.com/resources/573211638937-gmqb', + ], + ], + 'schema:name' => [ + '@value' => 'Title', + ], + 'schema:description' => [ + [ + '@value' => 'Description', + ], + ], + ]; + + $parser = $this->prophesize(Parser::class); + $parser->getManagerId($jsonLD)->willReturn('https://example.com/resources/018132452787-xxxx'); + $parser->getContainedInPlaceIds($jsonLD)->willReturn([ + 'https://example.com/resources/043064193523-jcyt', + 'https://example.com/resources/573211638937-gmqb', + ]); + $parser->getLanguages($jsonLD)->willReturn(['de']); + $parser->getId($jsonLD)->willReturn('https://example.com/resources/018132452787-ngbe'); + $parser->getTitle($jsonLD, 'de')->willReturn('Title'); + $parser->getDescription($jsonLD, 'de')->willReturn('Description'); + $parser->getOpeningHours($jsonLD)->willReturn([]); + + $organisationRepository = $this->prophesize(OrganisationRepository::class); + $townRepository = $this->prophesize(TownRepository::class); + + $subject = new TouristAttraction( + $parser->reveal(), + $organisationRepository->reveal(), + $townRepository->reveal() + ); + + $entities = $subject->convert($jsonLD); + + self::assertInstanceOf(EntityCollection::class, $entities); + self::assertCount(1, $entities->getEntities()); + + $entity = $entities->getEntities()[0]; + self::assertSame(10, $entity->getTypo3StoragePid()); + self::assertSame('tx_thuecat_tourist_attraction', $entity->getTypo3DatabaseTableName()); + self::assertSame('https://example.com/resources/018132452787-ngbe', $entity->getRemoteId()); + self::assertSame([ + 'title' => 'Title', + 'description' => 'Description', + 'managed_by' => 0, + 'town' => 0, + 'opening_hours' => '[]', + ], $entity->getData()); + } + + /** + * @test + */ + public function convertsWithRelations(): void + { + $jsonLD = [ + '@id' => 'https://example.com/resources/018132452787-ngbe', + 'thuecat:managedBy' => [ + '@id' => 'https://example.com/resources/018132452787-xxxx', + ], + 'schema:containedInPlace' => [ + [ + '@id' => 'https://example.com/resources/043064193523-jcyt', + ], + [ + '@id' => 'https://example.com/resources/573211638937-gmqb', + ], + ], + 'schema:name' => [ + '@value' => 'Title', + ], + 'schema:description' => [ + [ + '@value' => 'Description', + ], + ], + ]; + + $parser = $this->prophesize(Parser::class); + $parser->getManagerId($jsonLD)->willReturn('https://example.com/resources/018132452787-xxxx'); + $parser->getContainedInPlaceIds($jsonLD)->willReturn([ + 'https://example.com/resources/043064193523-jcyt', + 'https://example.com/resources/573211638937-gmqb', + ]); + $parser->getLanguages($jsonLD)->willReturn(['de']); + $parser->getId($jsonLD)->willReturn('https://example.com/resources/018132452787-ngbe'); + $parser->getTitle($jsonLD, 'de')->willReturn('Title'); + $parser->getDescription($jsonLD, 'de')->willReturn('Description'); + $parser->getOpeningHours($jsonLD)->willReturn([]); + + $organisation = $this->prophesize(Organisation::class); + $organisation->getUid()->willReturn(10); + $organisationRepository = $this->prophesize(OrganisationRepository::class); + $organisationRepository->findOneByRemoteId('https://example.com/resources/018132452787-xxxx')->willReturn($organisation->reveal()); + + $town = $this->prophesize(Town::class); + $town->getUid()->willReturn(20); + $townRepository = $this->prophesize(TownRepository::class); + $townRepository->findOneByRemoteIds([ + 'https://example.com/resources/043064193523-jcyt', + 'https://example.com/resources/573211638937-gmqb', + ])->willReturn($town->reveal()); + + $subject = new TouristAttraction( + $parser->reveal(), + $organisationRepository->reveal(), + $townRepository->reveal() + ); + + $entities = $subject->convert($jsonLD); + + self::assertInstanceOf(EntityCollection::class, $entities); + self::assertCount(1, $entities->getEntities()); + + $entity = $entities->getEntities()[0]; + self::assertSame(10, $entity->getTypo3StoragePid()); + self::assertSame('tx_thuecat_tourist_attraction', $entity->getTypo3DatabaseTableName()); + self::assertSame('https://example.com/resources/018132452787-ngbe', $entity->getRemoteId()); + self::assertSame([ + 'title' => 'Title', + 'description' => 'Description', + 'managed_by' => 10, + 'town' => 20, + 'opening_hours' => '[]', + ], $entity->getData()); + } +} diff --git a/Tests/Unit/Domain/Import/Converter/TouristInformationTest.php b/Tests/Unit/Domain/Import/Converter/TouristInformationTest.php index 5e7d5ea..7b536f3 100644 --- a/Tests/Unit/Domain/Import/Converter/TouristInformationTest.php +++ b/Tests/Unit/Domain/Import/Converter/TouristInformationTest.php @@ -27,6 +27,8 @@ use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; use WerkraumMedia\ThueCat\Domain\Import\Converter\Converter; use WerkraumMedia\ThueCat\Domain\Import\Converter\TouristInformation; +use WerkraumMedia\ThueCat\Domain\Import\JsonLD\Parser; +use WerkraumMedia\ThueCat\Domain\Import\Model\EntityCollection; use WerkraumMedia\ThueCat\Domain\Model\Backend\Organisation; use WerkraumMedia\ThueCat\Domain\Model\Backend\Town; use WerkraumMedia\ThueCat\Domain\Repository\Backend\OrganisationRepository; @@ -34,6 +36,7 @@ use WerkraumMedia\ThueCat\Domain\Repository\Backend\TownRepository; /** * @covers WerkraumMedia\ThueCat\Domain\Import\Converter\TouristInformation + * @uses WerkraumMedia\ThueCat\Domain\Import\Model\EntityCollection * @uses WerkraumMedia\ThueCat\Domain\Import\Model\GenericEntity */ class TouristInformationTest extends TestCase @@ -45,10 +48,12 @@ class TouristInformationTest extends TestCase */ public function instanceCanBeCreated(): void { + $parser = $this->prophesize(Parser::class); $organisationRepository = $this->prophesize(OrganisationRepository::class); $townRepository = $this->prophesize(TownRepository::class); $subject = new TouristInformation( + $parser->reveal(), $organisationRepository->reveal(), $townRepository->reveal() ); @@ -60,10 +65,12 @@ class TouristInformationTest extends TestCase */ public function isInstanceOfConverter(): void { + $parser = $this->prophesize(Parser::class); $organisationRepository = $this->prophesize(OrganisationRepository::class); $townRepository = $this->prophesize(TownRepository::class); $subject = new TouristInformation( + $parser->reveal(), $organisationRepository->reveal(), $townRepository->reveal() ); @@ -75,10 +82,12 @@ class TouristInformationTest extends TestCase */ public function canConvertTouristMarketingCompany(): void { + $parser = $this->prophesize(Parser::class); $organisationRepository = $this->prophesize(OrganisationRepository::class); $townRepository = $this->prophesize(TownRepository::class); $subject = new TouristInformation( + $parser->reveal(), $organisationRepository->reveal(), $townRepository->reveal() ); @@ -92,21 +101,7 @@ class TouristInformationTest extends TestCase */ public function convertsJsonIdToGenericEntityWithoutRelations(): void { - $organisationRepository = $this->prophesize(OrganisationRepository::class); - $organisationRepository->findOneByRemoteId('https://example.com/resources/018132452787-xxxx') - ->willReturn(null); - - $townRepository = $this->prophesize(TownRepository::class); - $townRepository->findOneByRemoteIds([ - 'https://example.com/resources/043064193523-jcyt', - 'https://example.com/resources/573211638937-gmqb', - ])->willReturn(null); - - $subject = new TouristInformation( - $organisationRepository->reveal(), - $townRepository->reveal() - ); - $entity = $subject->convert([ + $jsonLD = [ '@id' => 'https://example.com/resources/018132452787-ngbe', 'thuecat:managedBy' => [ '@id' => 'https://example.com/resources/018132452787-xxxx', @@ -127,7 +122,39 @@ class TouristInformationTest extends TestCase '@value' => 'Description', ], ], + ]; + + $parser = $this->prophesize(Parser::class); + $parser->getManagerId($jsonLD)->willReturn('https://example.com/resources/018132452787-xxxx'); + $parser->getContainedInPlaceIds($jsonLD)->willReturn([ + 'https://example.com/resources/043064193523-jcyt', + 'https://example.com/resources/573211638937-gmqb', ]); + $parser->getId($jsonLD)->willReturn('https://example.com/resources/018132452787-ngbe'); + $parser->getTitle($jsonLD)->willReturn('Title'); + $parser->getDescription($jsonLD)->willReturn('Description'); + + $organisationRepository = $this->prophesize(OrganisationRepository::class); + $organisationRepository->findOneByRemoteId('https://example.com/resources/018132452787-xxxx') + ->willReturn(null); + + $townRepository = $this->prophesize(TownRepository::class); + $townRepository->findOneByRemoteIds([ + 'https://example.com/resources/043064193523-jcyt', + 'https://example.com/resources/573211638937-gmqb', + ])->willReturn(null); + + $subject = new TouristInformation( + $parser->reveal(), + $organisationRepository->reveal(), + $townRepository->reveal() + ); + $entities = $subject->convert($jsonLD); + + self::assertInstanceOf(EntityCollection::class, $entities); + self::assertCount(1, $entities->getEntities()); + + $entity = $entities->getEntities()[0]; self::assertSame(10, $entity->getTypo3StoragePid()); self::assertSame('tx_thuecat_tourist_information', $entity->getTypo3DatabaseTableName()); @@ -145,25 +172,7 @@ class TouristInformationTest extends TestCase */ public function convertsJsonIdToGenericEntityWithRelations(): void { - $organisation = $this->prophesize(Organisation::class); - $organisation->getUid()->willReturn(10); - $organisationRepository = $this->prophesize(OrganisationRepository::class); - $organisationRepository->findOneByRemoteId('https://example.com/resources/018132452787-xxxx') - ->willReturn($organisation->reveal()); - - $town = $this->prophesize(Town::class); - $town->getUid()->willReturn(20); - $townRepository = $this->prophesize(TownRepository::class); - $townRepository->findOneByRemoteIds([ - 'https://example.com/resources/043064193523-jcyt', - 'https://example.com/resources/573211638937-gmqb', - ])->willReturn($town->reveal()); - - $subject = new TouristInformation( - $organisationRepository->reveal(), - $townRepository->reveal() - ); - $entity = $subject->convert([ + $jsonLD = [ '@id' => 'https://example.com/resources/018132452787-ngbe', 'thuecat:managedBy' => [ '@id' => 'https://example.com/resources/018132452787-xxxx', @@ -184,7 +193,43 @@ class TouristInformationTest extends TestCase '@value' => 'Description', ], ], + ]; + + $parser = $this->prophesize(Parser::class); + $parser->getManagerId($jsonLD)->willReturn('https://example.com/resources/018132452787-xxxx'); + $parser->getContainedInPlaceIds($jsonLD)->willReturn([ + 'https://example.com/resources/043064193523-jcyt', + 'https://example.com/resources/573211638937-gmqb', ]); + $parser->getId($jsonLD)->willReturn('https://example.com/resources/018132452787-ngbe'); + $parser->getTitle($jsonLD)->willReturn('Title'); + $parser->getDescription($jsonLD)->willReturn('Description'); + + $organisation = $this->prophesize(Organisation::class); + $organisation->getUid()->willReturn(10); + $organisationRepository = $this->prophesize(OrganisationRepository::class); + $organisationRepository->findOneByRemoteId('https://example.com/resources/018132452787-xxxx') + ->willReturn($organisation->reveal()); + + $town = $this->prophesize(Town::class); + $town->getUid()->willReturn(20); + $townRepository = $this->prophesize(TownRepository::class); + $townRepository->findOneByRemoteIds([ + 'https://example.com/resources/043064193523-jcyt', + 'https://example.com/resources/573211638937-gmqb', + ])->willReturn($town->reveal()); + + $subject = new TouristInformation( + $parser->reveal(), + $organisationRepository->reveal(), + $townRepository->reveal() + ); + $entities = $subject->convert($jsonLD); + + self::assertInstanceOf(EntityCollection::class, $entities); + self::assertCount(1, $entities->getEntities()); + + $entity = $entities->getEntities()[0]; self::assertSame(10, $entity->getTypo3StoragePid()); self::assertSame('tx_thuecat_tourist_information', $entity->getTypo3DatabaseTableName()); diff --git a/Tests/Unit/Domain/Import/Converter/TownTest.php b/Tests/Unit/Domain/Import/Converter/TownTest.php index b19e8cf..a3612ad 100644 --- a/Tests/Unit/Domain/Import/Converter/TownTest.php +++ b/Tests/Unit/Domain/Import/Converter/TownTest.php @@ -27,11 +27,14 @@ use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; use WerkraumMedia\ThueCat\Domain\Import\Converter\Converter; use WerkraumMedia\ThueCat\Domain\Import\Converter\Town; +use WerkraumMedia\ThueCat\Domain\Import\JsonLD\Parser; +use WerkraumMedia\ThueCat\Domain\Import\Model\EntityCollection; use WerkraumMedia\ThueCat\Domain\Model\Backend\Organisation; use WerkraumMedia\ThueCat\Domain\Repository\Backend\OrganisationRepository; /** * @covers WerkraumMedia\ThueCat\Domain\Import\Converter\Town + * @uses WerkraumMedia\ThueCat\Domain\Import\Model\EntityCollection * @uses WerkraumMedia\ThueCat\Domain\Import\Model\GenericEntity */ class TownTest extends TestCase @@ -43,9 +46,13 @@ class TownTest extends TestCase */ public function instanceCanBeCreated(): void { + $parser = $this->prophesize(Parser::class); $organisationRepository = $this->prophesize(OrganisationRepository::class); - $subject = new Town($organisationRepository->reveal()); + $subject = new Town( + $parser->reveal(), + $organisationRepository->reveal() + ); self::assertInstanceOf(Town::class, $subject); } @@ -54,9 +61,13 @@ class TownTest extends TestCase */ public function isInstanceOfConverter(): void { + $parser = $this->prophesize(Parser::class); $organisationRepository = $this->prophesize(OrganisationRepository::class); - $subject = new Town($organisationRepository->reveal()); + $subject = new Town( + $parser->reveal(), + $organisationRepository->reveal() + ); self::assertInstanceOf(Converter::class, $subject); } @@ -65,9 +76,13 @@ class TownTest extends TestCase */ public function canConvertTouristMarketingCompany(): void { + $parser = $this->prophesize(Parser::class); $organisationRepository = $this->prophesize(OrganisationRepository::class); - $subject = new Town($organisationRepository->reveal()); + $subject = new Town( + $parser->reveal(), + $organisationRepository->reveal() + ); self::assertTrue($subject->canConvert([ 'thuecat:Town', ])); @@ -78,11 +93,7 @@ class TownTest extends TestCase */ public function convertsJsonIdToGenericEntityWithoutOrganisation(): void { - $organisationRepository = $this->prophesize(OrganisationRepository::class); - $organisationRepository->findOneByRemoteId('https://example.com/resources/018132452787-xxxx')->willReturn(null); - - $subject = new Town($organisationRepository->reveal()); - $entity = $subject->convert([ + $jsonLD = [ '@id' => 'https://example.com/resources/018132452787-ngbe', 'thuecat:managedBy' => [ '@id' => 'https://example.com/resources/018132452787-xxxx', @@ -93,7 +104,27 @@ class TownTest extends TestCase 'schema:description' => [ '@value' => 'Description', ], - ]); + ]; + + $parser = $this->prophesize(Parser::class); + $parser->getManagerId($jsonLD)->willReturn('https://example.com/resources/018132452787-xxxx'); + $parser->getId($jsonLD)->willReturn('https://example.com/resources/018132452787-ngbe'); + $parser->getTitle($jsonLD)->willReturn('Title'); + $parser->getDescription($jsonLD)->willReturn('Description'); + + $organisationRepository = $this->prophesize(OrganisationRepository::class); + $organisationRepository->findOneByRemoteId('https://example.com/resources/018132452787-xxxx')->willReturn(null); + + $subject = new Town( + $parser->reveal(), + $organisationRepository->reveal() + ); + $entities = $subject->convert($jsonLD); + + self::assertInstanceOf(EntityCollection::class, $entities); + self::assertCount(1, $entities->getEntities()); + + $entity = $entities->getEntities()[0]; self::assertSame(10, $entity->getTypo3StoragePid()); self::assertSame('tx_thuecat_town', $entity->getTypo3DatabaseTableName()); @@ -110,13 +141,7 @@ class TownTest extends TestCase */ public function convertsJsonIdToGenericEntityWithOrganisation(): void { - $organisation = $this->prophesize(Organisation::class); - $organisation->getUid()->willReturn(10); - $organisationRepository = $this->prophesize(OrganisationRepository::class); - $organisationRepository->findOneByRemoteId('https://example.com/resources/018132452787-xxxx')->willReturn($organisation->reveal()); - - $subject = new Town($organisationRepository->reveal()); - $entity = $subject->convert([ + $jsonLD = [ '@id' => 'https://example.com/resources/018132452787-ngbe', 'thuecat:managedBy' => [ '@id' => 'https://example.com/resources/018132452787-xxxx', @@ -127,7 +152,29 @@ class TownTest extends TestCase 'schema:description' => [ '@value' => 'Description', ], - ]); + ]; + + $parser = $this->prophesize(Parser::class); + $parser->getManagerId($jsonLD)->willReturn('https://example.com/resources/018132452787-xxxx'); + $parser->getId($jsonLD)->willReturn('https://example.com/resources/018132452787-ngbe'); + $parser->getTitle($jsonLD)->willReturn('Title'); + $parser->getDescription($jsonLD)->willReturn('Description'); + + $organisation = $this->prophesize(Organisation::class); + $organisation->getUid()->willReturn(10); + $organisationRepository = $this->prophesize(OrganisationRepository::class); + $organisationRepository->findOneByRemoteId('https://example.com/resources/018132452787-xxxx')->willReturn($organisation->reveal()); + + $subject = new Town( + $parser->reveal(), + $organisationRepository->reveal() + ); + $entities = $subject->convert($jsonLD); + + self::assertInstanceOf(EntityCollection::class, $entities); + self::assertCount(1, $entities->getEntities()); + + $entity = $entities->getEntities()[0]; self::assertSame(10, $entity->getTypo3StoragePid()); self::assertSame('tx_thuecat_town', $entity->getTypo3DatabaseTableName()); diff --git a/Tests/Unit/Domain/Import/ImporterTest.php b/Tests/Unit/Domain/Import/ImporterTest.php index 31a0fca..d0ad6bb 100644 --- a/Tests/Unit/Domain/Import/ImporterTest.php +++ b/Tests/Unit/Domain/Import/ImporterTest.php @@ -31,7 +31,7 @@ use WerkraumMedia\ThueCat\Domain\Import\Converter\Registry as ConverterRegistry; use WerkraumMedia\ThueCat\Domain\Import\Importer; use WerkraumMedia\ThueCat\Domain\Import\Importer\FetchData; use WerkraumMedia\ThueCat\Domain\Import\Importer\SaveData; -use WerkraumMedia\ThueCat\Domain\Import\Model\Entity; +use WerkraumMedia\ThueCat\Domain\Import\Model\EntityCollection; use WerkraumMedia\ThueCat\Domain\Import\UrlProvider\Registry as UrlProviderRegistry; use WerkraumMedia\ThueCat\Domain\Import\UrlProvider\UrlProvider; use WerkraumMedia\ThueCat\Domain\Model\Backend\ImportConfiguration; @@ -67,6 +67,35 @@ class ImporterTest extends TestCase self::assertInstanceOf(Importer::class, $subject); } + /** + * @test + */ + public function importsNothingIfUrlProviderCouldNotBeResolved(): void + { + $urls = $this->prophesize(UrlProviderRegistry::class); + $converter = $this->prophesize(ConverterRegistry::class); + $importLogRepository = $this->prophesize(ImportLogRepository::class); + $fetchData = $this->prophesize(FetchData::class); + $saveData = $this->prophesize(SaveData::class); + $configuration = $this->prophesize(ImportConfiguration::class); + + $urls->getProviderForConfiguration($configuration->reveal())->willReturn(null); + $fetchData->jsonLDFromUrl()->shouldNotBeCalled(); + $saveData->import()->shouldNotBeCalled(); + + $subject = new Importer( + $urls->reveal(), + $converter->reveal(), + $importLogRepository->reveal(), + $fetchData->reveal(), + $saveData->reveal() + ); + $result = $subject->importConfiguration($configuration->reveal()); + + self::assertInstanceOf(ImportLog::class, $result); + self::assertCount(0, $result->getEntries()); + } + /** * @test */ @@ -109,8 +138,8 @@ class ImporterTest extends TestCase $saveData = $this->prophesize(SaveData::class); $configuration = $this->prophesize(ImportConfiguration::class); - $entity1 = $this->prophesize(Entity::class); - $entity2 = $this->prophesize(Entity::class); + $entities1 = $this->prophesize(EntityCollection::class); + $entities2 = $this->prophesize(EntityCollection::class); $urls->getProviderForConfiguration($configuration->reveal())->willReturn($urlProvider->reveal()); $urlProvider->getUrls()->willReturn([ @@ -144,13 +173,13 @@ class ImporterTest extends TestCase $concreteConverter->convert(Argument::that(function (array $jsonEntity) { return $jsonEntity['@id'] === 'https://example.com/resources/34343-ex'; - }))->willReturn($entity1->reveal()); + }))->willReturn($entities1->reveal()); $concreteConverter->convert(Argument::that(function (array $jsonEntity) { return $jsonEntity['@id'] === 'https://example.com/resources/34344-es'; - }))->willReturn($entity2->reveal()); + }))->willReturn($entities2->reveal()); - $saveData->import($entity1->reveal(), Argument::type(ImportLog::class))->shouldBeCalled(); - $saveData->import($entity2->reveal(), Argument::type(ImportLog::class))->shouldBeCalled(); + $saveData->import($entities1->reveal(), Argument::type(ImportLog::class))->shouldBeCalled(); + $saveData->import($entities2->reveal(), Argument::type(ImportLog::class))->shouldBeCalled(); $subject = new Importer( $urls->reveal(), diff --git a/Tests/Unit/Domain/Import/JsonLD/Parser/OpeningHoursTest.php b/Tests/Unit/Domain/Import/JsonLD/Parser/OpeningHoursTest.php new file mode 100644 index 0000000..0017ad6 --- /dev/null +++ b/Tests/Unit/Domain/Import/JsonLD/Parser/OpeningHoursTest.php @@ -0,0 +1,310 @@ + + * + * 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. + */ + +use PHPUnit\Framework\TestCase; +use WerkraumMedia\ThueCat\Domain\Import\JsonLD\Parser\OpeningHours; + +/** + * @covers WerkraumMedia\ThueCat\Domain\Import\JsonLD\Parser\OpeningHours + */ +class OpeningHoursTest extends TestCase +{ + /** + * @test + */ + public function canBeCreated(): void + { + $subject = new OpeningHours(); + + self::assertInstanceOf(OpeningHours::class, $subject); + } + + /** + * @test + */ + public function returnsEmptyArrayIfOpeningHoursAreMissing(): void + { + $subject = new OpeningHours(); + + $result = $subject->get([ + ]); + + self::assertSame([], $result); + } + + /** + * @test + */ + public function returnsSingleOpeningHourWrappedInArray(): void + { + $subject = new OpeningHours(); + + $result = $subject->get([ + 'schema:openingHoursSpecification' => [ + '@id' => 'genid-28b33237f71b41e3ad54a99e1da769b9-b13', + 'schema:opens' => [ + '@type' => 'schema:Time', + '@value' => '10:00:00', + ], + 'schema:closes' => [ + '@type' => 'schema:Time', + '@value' => '18:00:00', + ], + 'schema:validFrom' => [ + '@type' => 'schema:Date', + '@value' => '2021-03-01', + ], + 'schema:validThrough' => [ + '@type' => 'schema:Date', + '@value' => '2021-12-31', + ], + 'schema:dayOfWeek' => [ + 0 => [ + '@type' => 'schema:DayOfWeek', + '@value' => 'schema:Saturday', + ], + 1 => [ + '@type' => 'schema:DayOfWeek', + '@value' => 'schema:Sunday', + ], + 2 => [ + '@type' => 'schema:DayOfWeek', + '@value' => 'schema:Friday', + ], + 3 => [ + '@type' => 'schema:DayOfWeek', + '@value' => 'schema:Thursday', + ], + 4 => [ + '@type' => 'schema:DayOfWeek', + '@value' => 'schema:Tuesday', + ], + 5 => [ + '@type' => 'schema:DayOfWeek', + '@value' => 'schema:Wednesday', + ], + ], + ], + ]); + + self::assertCount(1, $result); + self::assertSame('10:00:00', $result[0]['opens']); + self::assertSame('18:00:00', $result[0]['closes']); + self::assertSame([ + 'Friday', + 'Saturday', + 'Sunday', + 'Thursday', + 'Tuesday', + 'Wednesday', + ], $result[0]['daysOfWeek']); + self::assertInstanceOf(\DateTimeImmutable::class, $result[0]['from']); + self::assertSame('2021-03-01 00:00:00', $result[0]['from']->format('Y-m-d H:i:s')); + + self::assertInstanceOf(\DateTimeImmutable::class, $result[0]['through']); + self::assertSame('2021-12-31 00:00:00', $result[0]['through']->format('Y-m-d H:i:s')); + } + + /** + * @test + */ + public function returnsSingleWeekDay(): void + { + $subject = new OpeningHours(); + + $result = $subject->get([ + 'schema:openingHoursSpecification' => [ + '@id' => 'genid-28b33237f71b41e3ad54a99e1da769b9-b13', + 'schema:dayOfWeek' => [ + '@type' => 'schema:DayOfWeek', + '@value' => 'schema:Saturday', + ], + ], + ]); + + self::assertCount(1, $result); + self::assertSame([ + 'Saturday', + ], $result[0]['daysOfWeek']); + } + + /** + * @test + */ + public function returnsMultipleOpeningHours(): void + { + $subject = new OpeningHours(); + + $result = $subject->get([ + 'schema:openingHoursSpecification' => [ + [ + '@id' => 'genid-28b33237f71b41e3ad54a99e1da769b9-b13', + 'schema:opens' => [ + '@type' => 'schema:Time', + '@value' => '10:00:00', + ], + 'schema:closes' => [ + '@type' => 'schema:Time', + '@value' => '18:00:00', + ], + 'schema:validFrom' => [ + '@type' => 'schema:Date', + '@value' => '2021-03-01', + ], + 'schema:validThrough' => [ + '@type' => 'schema:Date', + '@value' => '2021-12-31', + ], + 'schema:dayOfWeek' => [ + 0 => [ + '@type' => 'schema:DayOfWeek', + '@value' => 'schema:Saturday', + ], + 1 => [ + '@type' => 'schema:DayOfWeek', + '@value' => 'schema:Sunday', + ], + 2 => [ + '@type' => 'schema:DayOfWeek', + '@value' => 'schema:Friday', + ], + 3 => [ + '@type' => 'schema:DayOfWeek', + '@value' => 'schema:Thursday', + ], + 4 => [ + '@type' => 'schema:DayOfWeek', + '@value' => 'schema:Tuesday', + ], + 5 => [ + '@type' => 'schema:DayOfWeek', + '@value' => 'schema:Wednesday', + ], + ], + ], + [ + '@id' => 'genid-28b33237f71b41e3ad54a99e1da769b9-b13', + 'schema:opens' => [ + '@type' => 'schema:Time', + '@value' => '09:00:00', + ], + 'schema:closes' => [ + '@type' => 'schema:Time', + '@value' => '17:00:00', + ], + 'schema:validFrom' => [ + '@type' => 'schema:Date', + '@value' => '2022-03-01', + ], + 'schema:validThrough' => [ + '@type' => 'schema:Date', + '@value' => '2022-12-31', + ], + 'schema:dayOfWeek' => [ + 0 => [ + '@type' => 'schema:DayOfWeek', + '@value' => 'schema:Saturday', + ], + 1 => [ + '@type' => 'schema:DayOfWeek', + '@value' => 'schema:Sunday', + ], + 2 => [ + '@type' => 'schema:DayOfWeek', + '@value' => 'schema:Friday', + ], + 3 => [ + '@type' => 'schema:DayOfWeek', + '@value' => 'schema:Thursday', + ], + 4 => [ + '@type' => 'schema:DayOfWeek', + '@value' => 'schema:Tuesday', + ], + 5 => [ + '@type' => 'schema:DayOfWeek', + '@value' => 'schema:Wednesday', + ], + ], + ], + ], + ]); + + self::assertCount(2, $result); + + self::assertSame('10:00:00', $result[0]['opens']); + self::assertSame('18:00:00', $result[0]['closes']); + self::assertSame([ + 'Friday', + 'Saturday', + 'Sunday', + 'Thursday', + 'Tuesday', + 'Wednesday', + ], $result[0]['daysOfWeek']); + self::assertInstanceOf(\DateTimeImmutable::class, $result[0]['from']); + self::assertSame('2021-03-01 00:00:00', $result[0]['from']->format('Y-m-d H:i:s')); + + self::assertInstanceOf(\DateTimeImmutable::class, $result[0]['through']); + self::assertSame('2021-12-31 00:00:00', $result[0]['through']->format('Y-m-d H:i:s')); + + self::assertSame('09:00:00', $result[1]['opens']); + self::assertSame('17:00:00', $result[1]['closes']); + self::assertSame([ + 'Friday', + 'Saturday', + 'Sunday', + 'Thursday', + 'Tuesday', + 'Wednesday', + ], $result[1]['daysOfWeek']); + self::assertInstanceOf(\DateTimeImmutable::class, $result[1]['from']); + self::assertSame('2022-03-01 00:00:00', $result[1]['from']->format('Y-m-d H:i:s')); + + self::assertInstanceOf(\DateTimeImmutable::class, $result[1]['through']); + self::assertSame('2022-12-31 00:00:00', $result[1]['through']->format('Y-m-d H:i:s')); + } + + /** + * @test + */ + public function returnsProperDefaultsOnMissingValues(): void + { + $subject = new OpeningHours(); + + $result = $subject->get([ + 'schema:openingHoursSpecification' => [ + '@id' => 'genid-28b33237f71b41e3ad54a99e1da769b9-b13', + ], + ]); + + self::assertCount(1, $result); + self::assertSame('', $result[0]['opens']); + self::assertSame('', $result[0]['closes']); + self::assertSame([], $result[0]['daysOfWeek']); + self::assertSame(null, $result[0]['from']); + self::assertSame(null, $result[0]['through']); + } +} diff --git a/Tests/Unit/Domain/Import/JsonLD/ParserTest.php b/Tests/Unit/Domain/Import/JsonLD/ParserTest.php new file mode 100644 index 0000000..d3d7d32 --- /dev/null +++ b/Tests/Unit/Domain/Import/JsonLD/ParserTest.php @@ -0,0 +1,457 @@ + + * + * 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. + */ + +use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; +use WerkraumMedia\ThueCat\Domain\Import\JsonLD\Parser; +use WerkraumMedia\ThueCat\Domain\Import\JsonLD\Parser\OpeningHours; + +/** + * @covers WerkraumMedia\ThueCat\Domain\Import\JsonLD\Parser + */ +class ParserTest extends TestCase +{ + use ProphecyTrait; + + /** + * @test + */ + public function canBeCreated(): void + { + $openingHours = $this->prophesize(OpeningHours::class); + $subject = new Parser( + $openingHours->reveal() + ); + + self::assertInstanceOf(Parser::class, $subject); + } + + /** + * @test + */ + public function returnsId(): void + { + $openingHours = $this->prophesize(OpeningHours::class); + $subject = new Parser( + $openingHours->reveal() + ); + $result = $subject->getId([ + '@id' => 'https://example.com/resources/165868194223-id', + ]); + + self::assertSame('https://example.com/resources/165868194223-id', $result); + } + + /** + * @test + */ + public function returnsManagerId(): void + { + $openingHours = $this->prophesize(OpeningHours::class); + $subject = new Parser( + $openingHours->reveal() + ); + $result = $subject->getManagerId([ + 'thuecat:contentResponsible' => [ + '@id' => 'https://example.com/resources/165868194223-manager', + ], + ]); + + self::assertSame('https://example.com/resources/165868194223-manager', $result); + } + + /** + * @test + * @dataProvider titles + */ + public function returnsTitle(array $jsonLD, string $language, string $expected): void + { + $openingHours = $this->prophesize(OpeningHours::class); + $subject = new Parser( + $openingHours->reveal() + ); + $result = $subject->getTitle($jsonLD, $language); + self::assertSame($expected, $result); + } + + public function titles(): array + { + return [ + 'json has multiple lanugages, one matches' => [ + 'jsonLD' => [ + 'schema:name' => [ + [ + '@language' => 'de', + '@value' => 'DE Title', + ], + [ + '@language' => 'fr', + '@value' => 'FR Title', + ], + ], + ], + 'language' => 'de', + 'expected' => 'DE Title', + ], + 'json has multiple lanugages, no language specified' => [ + 'jsonLD' => [ + 'schema:name' => [ + [ + '@language' => 'de', + '@value' => 'DE Title', + ], + [ + '@language' => 'fr', + '@value' => 'FR Title', + ], + ], + ], + 'language' => '', + 'expected' => 'DE Title', + ], + 'json has multiple languages, none matches' => [ + 'jsonLD' => [ + 'schema:name' => [ + [ + '@language' => 'de', + '@value' => 'DE Title', + ], + [ + '@language' => 'fr', + '@value' => 'FR Title', + ], + ], + ], + 'language' => 'en', + 'expected' => '', + ], + 'json has multiple languages, missing @language key' => [ + 'jsonLD' => [ + 'schema:name' => [ + [ + '@value' => 'DE Title', + ], + [ + '@value' => 'FR Title', + ], + ], + ], + 'language' => 'en', + 'expected' => '', + ], + 'json has single language, that one matches' => [ + 'jsonLD' => [ + 'schema:name' => [ + '@language' => 'de', + '@value' => 'DE Title', + ], + ], + 'language' => 'de', + 'expected' => 'DE Title', + ], + 'json contains single language, but another is requested' => [ + 'jsonLD' => [ + 'schema:name' => [ + '@language' => 'de', + '@value' => 'DE Title', + ], + ], + 'language' => 'en', + 'expected' => '', + ], + 'json contains single language, no language specified' => [ + 'jsonLD' => [ + 'schema:name' => [ + '@language' => 'de', + '@value' => 'DE Title', + ], + ], + 'language' => '', + 'expected' => 'DE Title', + ], + 'json contains single language, missing @language key' => [ + 'jsonLD' => [ + 'schema:name' => [ + '@value' => 'DE Title', + ], + ], + 'language' => '', + 'expected' => '', + ], + ]; + } + + /** + * @test + * @dataProvider descriptions + */ + public function returnsDescription(array $jsonLD, string $language, string $expected): void + { + $openingHours = $this->prophesize(OpeningHours::class); + $subject = new Parser( + $openingHours->reveal() + ); + $result = $subject->getDescription($jsonLD, $language); + self::assertSame($expected, $result); + } + + public function descriptions(): array + { + return [ + 'json has multiple lanugages, one matches' => [ + 'jsonLD' => [ + 'schema:description' => [ + [ + '@language' => 'de', + '@value' => 'DE Description', + ], + [ + '@language' => 'fr', + '@value' => 'FR Description', + ], + ], + ], + 'language' => 'de', + 'expected' => 'DE Description', + ], + 'json has multiple lanugages, no language specified' => [ + 'jsonLD' => [ + 'schema:description' => [ + [ + '@language' => 'de', + '@value' => 'DE Description', + ], + [ + '@language' => 'fr', + '@value' => 'FR Description', + ], + ], + ], + 'language' => '', + 'expected' => 'DE Description', + ], + 'json has multiple languages, none matches' => [ + 'jsonLD' => [ + 'schema:description' => [ + [ + '@language' => 'de', + '@value' => 'DE Description', + ], + [ + '@language' => 'fr', + '@value' => 'FR Description', + ], + ], + ], + 'language' => 'en', + 'expected' => '', + ], + 'json has multiple languages, missing @language key' => [ + 'jsonLD' => [ + 'schema:description' => [ + [ + '@value' => 'DE Description', + ], + [ + '@value' => 'FR Description', + ], + ], + ], + 'language' => 'en', + 'expected' => '', + ], + 'json has single language, that one matches' => [ + 'jsonLD' => [ + 'schema:description' => [ + '@language' => 'de', + '@value' => 'DE Description', + ], + ], + 'language' => 'de', + 'expected' => 'DE Description', + ], + 'json contains single language, but another is requested' => [ + 'jsonLD' => [ + 'schema:description' => [ + '@language' => 'de', + '@value' => 'DE Description', + ], + ], + 'language' => 'en', + 'expected' => '', + ], + 'json contains single language, no language specified' => [ + 'jsonLD' => [ + 'schema:description' => [ + '@language' => 'de', + '@value' => 'DE Description', + ], + ], + 'language' => '', + 'expected' => 'DE Description', + ], + 'json contains single language, missing @language key' => [ + 'jsonLD' => [ + 'schema:description' => [ + '@value' => 'DE Description', + ], + ], + 'language' => '', + 'expected' => '', + ], + ]; + } + + /** + * @test + */ + public function returnsContainedInPlaceIds(): void + { + $openingHours = $this->prophesize(OpeningHours::class); + $subject = new Parser( + $openingHours->reveal() + ); + $result = $subject->getContainedInPlaceIds([ + 'schema:containedInPlace' => [ + ['@id' => 'https://thuecat.org/resources/043064193523-jcyt'], + ['@id' => 'https://thuecat.org/resources/349986440346-kbkf'], + ['@id' => 'https://thuecat.org/resources/794900260253-wjab'], + ['@id' => 'https://thuecat.org/resources/476888881990-xpwq'], + ['@id' => 'https://thuecat.org/resources/573211638937-gmqb'], + ], + ]); + self::assertSame([ + 'https://thuecat.org/resources/043064193523-jcyt', + 'https://thuecat.org/resources/349986440346-kbkf', + 'https://thuecat.org/resources/794900260253-wjab', + 'https://thuecat.org/resources/476888881990-xpwq', + 'https://thuecat.org/resources/573211638937-gmqb', + ], $result); + } + + /** + * @test + */ + public function returnsLanguages(): void + { + $openingHours = $this->prophesize(OpeningHours::class); + $subject = new Parser( + $openingHours->reveal() + ); + + $result = $subject->getLanguages([ + 'schema:availableLanguage' => [ + 0 => [ + '@type' => 'thuecat:Language', + '@value' => 'thuecat:German', + ], + 1 => [ + '@type' => 'thuecat:Language', + '@value' => 'thuecat:English', + ], + 2 => [ + '@type' => 'thuecat:Language', + '@value' => 'thuecat:French', + ], + ], + ]); + + self::assertSame([ + 'de', + 'en', + 'fr', + ], $result); + } + + /** + * @test + */ + public function throwsExceptionOnUnkownLanguage(): void + { + $openingHours = $this->prophesize(OpeningHours::class); + $subject = new Parser( + $openingHours->reveal() + ); + + $this->expectExceptionCode(1612367481); + $result = $subject->getLanguages([ + 'schema:availableLanguage' => [ + 0 => [ + '@type' => 'thuecat:Language', + '@value' => 'thuecat:Unkown', + ], + ], + ]); + } + + /** + * @test + */ + public function returnsNoLanguagesIfInfoIsMissing(): void + { + $openingHours = $this->prophesize(OpeningHours::class); + $subject = new Parser( + $openingHours->reveal() + ); + + $result = $subject->getLanguages([]); + + self::assertSame([], $result); + } + + /** + * @test + */ + public function returnsOpeningHours(): void + { + $jsonLD = [ + 'schema:openingHoursSpecification' => [ + '@id' => 'genid-28b33237f71b41e3ad54a99e1da769b9-b13', + 'schema:opens' => [ + '@type' => 'schema:Time', + '@value' => '10:00:00', + ], + ], + ]; + $generatedOpeningHours = [ + 'opens' => '10:00:00', + 'closes' => '', + 'from' => null, + 'through' => null, + 'daysOfWeek' => [], + ]; + + $openingHours = $this->prophesize(OpeningHours::class); + $openingHours->get($jsonLD)->willReturn($generatedOpeningHours); + + $subject = new Parser( + $openingHours->reveal() + ); + + $result = $subject->getOpeningHours($jsonLD); + + self::assertSame($generatedOpeningHours, $result); + } +} diff --git a/Tests/Unit/Domain/Import/Model/EntityCollectionTest.php b/Tests/Unit/Domain/Import/Model/EntityCollectionTest.php new file mode 100644 index 0000000..182cff4 --- /dev/null +++ b/Tests/Unit/Domain/Import/Model/EntityCollectionTest.php @@ -0,0 +1,138 @@ + + * + * 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. + */ + +use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; +use WerkraumMedia\ThueCat\Domain\Import\Model\Entity; +use WerkraumMedia\ThueCat\Domain\Import\Model\EntityCollection; + +/** + * @covers WerkraumMedia\ThueCat\Domain\Import\Model\EntityCollection + */ +class EntityCollectionTest extends TestCase +{ + use ProphecyTrait; + + /** + * @test + */ + public function canBeCreated(): void + { + $subject = new EntityCollection(); + + self::assertInstanceOf(EntityCollection::class, $subject); + } + + /** + * @test + */ + public function returnsEmptyArrayAsDefaultEntities(): void + { + $subject = new EntityCollection(); + + self::assertSame([], $subject->getEntities()); + } + + /** + * @test + */ + public function returnsFirstEntityForDefaultLanguage(): void + { + $entityWithTranslation = $this->prophesize(Entity::class); + $entityWithTranslation->isForDefaultLanguage()->willReturn(false); + + $entityWithDefaultLanguage = $this->prophesize(Entity::class); + $entityWithDefaultLanguage->isForDefaultLanguage()->willReturn(true); + + $subject = new EntityCollection(); + $subject->add($entityWithTranslation->reveal()); + $subject->add($entityWithDefaultLanguage->reveal()); + + self::assertSame( + $entityWithDefaultLanguage->reveal(), + $subject->getDefaultLanguageEntity() + ); + } + + /** + * @test + */ + public function returnsNullIfNoEntityForDefaultLanguageExists(): void + { + $entityWithTranslation = $this->prophesize(Entity::class); + $entityWithTranslation->isForDefaultLanguage()->willReturn(false); + + $subject = new EntityCollection(); + $subject->add($entityWithTranslation->reveal()); + + self::assertSame( + null, + $subject->getDefaultLanguageEntity() + ); + } + + /** + * @test + */ + public function returnsAllTranslatedEntities(): void + { + $entityWithTranslation1 = $this->prophesize(Entity::class); + $entityWithTranslation1->isTranslation()->willReturn(true); + $entityWithTranslation2 = $this->prophesize(Entity::class); + $entityWithTranslation2->isTranslation()->willReturn(true); + $entitiyWithDefaultLanguage = $this->prophesize(Entity::class); + $entitiyWithDefaultLanguage->isTranslation()->willReturn(false); + + $subject = new EntityCollection(); + $subject->add($entityWithTranslation1->reveal()); + $subject->add($entitiyWithDefaultLanguage->reveal()); + $subject->add($entityWithTranslation2->reveal()); + + self::assertSame( + [ + 0 => $entityWithTranslation1->reveal(), + 2 => $entityWithTranslation2->reveal(), + ], + $subject->getTranslatedEntities() + ); + } + + /** + * @test + */ + public function returnsEmptyArrayIfNoTranslatedEntityExists(): void + { + $entitiyWithDefaultLanguage = $this->prophesize(Entity::class); + $entitiyWithDefaultLanguage->isTranslation()->willReturn(false); + + $subject = new EntityCollection(); + $subject->add($entitiyWithDefaultLanguage->reveal()); + + self::assertSame( + [], + $subject->getTranslatedEntities() + ); + } +} diff --git a/Tests/Unit/Domain/Import/Model/GenericEntityTest.php b/Tests/Unit/Domain/Import/Model/GenericEntityTest.php index 863285a..85c31d8 100644 --- a/Tests/Unit/Domain/Import/Model/GenericEntityTest.php +++ b/Tests/Unit/Domain/Import/Model/GenericEntityTest.php @@ -39,6 +39,7 @@ class GenericEntityTest extends TestCase $subject = new GenericEntity( 0, '', + 0, '', [] ); @@ -53,6 +54,7 @@ class GenericEntityTest extends TestCase $subject = new GenericEntity( 10, '', + 0, '', [] ); @@ -67,12 +69,58 @@ class GenericEntityTest extends TestCase $subject = new GenericEntity( 0, 'tx_thuecat_entity', + 0, '', [] ); self::assertSame('tx_thuecat_entity', $subject->getTypo3DatabaseTableName()); } + /** + * @test + */ + public function returnsTypo3SystemLanguageUid(): void + { + $subject = new GenericEntity( + 0, + '', + 10, + '', + [] + ); + self::assertSame(10, $subject->getTypo3SystemLanguageUid()); + } + + /** + * @test + */ + public function claimsIsForDefaultLanguage(): void + { + $subject = new GenericEntity( + 0, + '', + 0, + '', + [] + ); + self::assertTrue($subject->isForDefaultLanguage()); + } + + /** + * @test + */ + public function claimsIsTranslation(): void + { + $subject = new GenericEntity( + 0, + '', + 10, + '', + [] + ); + self::assertTrue($subject->isTranslation()); + } + /** * @test */ @@ -81,6 +129,7 @@ class GenericEntityTest extends TestCase $subject = new GenericEntity( 0, '', + 0, 'https://example.com/resources/333039283321-xxwg', [] ); @@ -98,6 +147,7 @@ class GenericEntityTest extends TestCase $subject = new GenericEntity( 0, '', + 0, '', [ 'column_name_1' => 'value 1', @@ -121,6 +171,7 @@ class GenericEntityTest extends TestCase $subject = new GenericEntity( 0, '', + 0, '', [] ); @@ -138,6 +189,7 @@ class GenericEntityTest extends TestCase $subject = new GenericEntity( 0, '', + 0, '', [] ); @@ -155,6 +207,7 @@ class GenericEntityTest extends TestCase $subject = new GenericEntity( 0, '', + 0, '', [] ); @@ -172,6 +225,7 @@ class GenericEntityTest extends TestCase $subject = new GenericEntity( 0, '', + 0, '', [] ); diff --git a/composer.json b/composer.json index 57e187a..da0f5ad 100644 --- a/composer.json +++ b/composer.json @@ -35,7 +35,8 @@ "symfony/dependency-injection": "^5.2", "typo3/cms-backend": "^10.4", "typo3/cms-core": "^10.4", - "typo3/cms-extbase": "^10.4" + "typo3/cms-extbase": "^10.4", + "typo3/cms-frontend": "^10.4" }, "require-dev": { "friendsoftypo3/phpstan-typo3": "^0.6.0", diff --git a/dependency-checker.json b/dependency-checker.json index e961f67..304dd3a 100644 --- a/dependency-checker.json +++ b/dependency-checker.json @@ -5,6 +5,7 @@ "Configuration/SiteConfiguration/Overrides/*.php", "Configuration/TCA/*.php", "ext_emconf.php", + "ext_localconf.php", "ext_tables.php" ] } diff --git a/ext_localconf.php b/ext_localconf.php new file mode 100644 index 0000000..443e5ae --- /dev/null +++ b/ext_localconf.php @@ -0,0 +1,5 @@ +&TYPO3\\\\CMS\\\\Extbase\\\\Persistence\\\\ObjectStorage\\) does not accept TYPO3\\\\CMS\\\\Extbase\\\\Persistence\\\\ObjectStorage\\.$#" + count: 1 + path: Classes/Domain/Model/Backend/ImportLog.php + + - + message: "#^Cannot call method getFirst\\(\\) on array\\|TYPO3\\\\CMS\\\\Extbase\\\\Persistence\\\\QueryResultInterface\\.$#" + count: 1 + path: Classes/Domain/Repository/Backend/TownRepository.php + + - + message: "#^Method WerkraumMedia\\\\ThueCat\\\\Domain\\\\Repository\\\\Backend\\\\TownRepository\\:\\:findOneByRemoteIds\\(\\) should return WerkraumMedia\\\\ThueCat\\\\Domain\\\\Model\\\\Backend\\\\Town\\|null but returns object\\.$#" + count: 1 + path: Classes/Domain/Repository/Backend/TownRepository.php + + - + message: "#^Cannot call method fetchAll\\(\\) on Doctrine\\\\DBAL\\\\Driver\\\\Statement\\|int\\.$#" + count: 1 + path: Classes/Frontend/DataProcessing/ResolveEntities.php + diff --git a/phpstan.neon b/phpstan.neon index c685620..3981980 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,5 +1,6 @@ includes: - vendor/friendsoftypo3/phpstan-typo3/extension.neon + - phpstan-baseline.neon parameters: level: max paths: @@ -8,20 +9,3 @@ parameters: - Tests checkMissingIterableValueType: false reportUnmatchedIgnoredErrors: true - ignoreErrors: - - - message: "#^Cannot call method fetchColumn\\(\\) on Doctrine\\\\DBAL\\\\Driver\\\\Statement\\|int\\.$#" - count: 1 - path: Classes/Domain/Import/Importer/SaveData.php - - - message: "#^Property WerkraumMedia\\\\ThueCat\\\\Domain\\\\Model\\\\Backend\\\\ImportLog\\:\\:\\$logEntries \\(iterable\\&TYPO3\\\\CMS\\\\Extbase\\\\Persistence\\\\ObjectStorage\\) does not accept TYPO3\\\\CMS\\\\Extbase\\\\Persistence\\\\ObjectStorage\\.$#" - count: 1 - path: Classes/Domain/Model/Backend/ImportLog.php - - - message: "#^Cannot call method getFirst\\(\\) on array\\|TYPO3\\\\CMS\\\\Extbase\\\\Persistence\\\\QueryResultInterface\\.$#" - count: 1 - path: Classes/Domain/Repository/Backend/TownRepository.php - - - message: "#^Method WerkraumMedia\\\\ThueCat\\\\Domain\\\\Repository\\\\Backend\\\\TownRepository\\:\\:findOneByRemoteIds\\(\\) should return WerkraumMedia\\\\ThueCat\\\\Domain\\\\Model\\\\Backend\\\\Town\\|null but returns object\\.$#" - count: 1 - path: Classes/Domain/Repository/Backend/TownRepository.php