Browse Source

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.
pull/4/head
Daniel Siepmann 1 year ago
parent
commit
9176ba0cec
  1. 8
      Classes/Domain/Import/Converter/Converter.php
  2. 27
      Classes/Domain/Import/Converter/Organisation.php
  3. 86
      Classes/Domain/Import/Converter/TouristAttraction.php
  4. 37
      Classes/Domain/Import/Converter/TouristInformation.php
  5. 27
      Classes/Domain/Import/Converter/Town.php
  6. 73
      Classes/Domain/Import/Importer/SaveData.php
  7. 140
      Classes/Domain/Import/JsonLD/Parser.php
  8. 102
      Classes/Domain/Import/JsonLD/Parser/OpeningHours.php
  9. 6
      Classes/Domain/Import/Model/Entity.php
  10. 66
      Classes/Domain/Import/Model/EntityCollection.php
  11. 18
      Classes/Domain/Import/Model/GenericEntity.php
  12. 1
      Classes/Domain/Model/Backend/ImportLog.php
  13. 1
      Classes/Domain/Model/Backend/ImportLogEntry.php
  14. 127
      Classes/Domain/Model/Frontend/OpeningHour.php
  15. 75
      Classes/Domain/Model/Frontend/OpeningHours.php
  16. 54
      Classes/Domain/Model/Frontend/TouristAttraction.php
  17. 42
      Classes/Domain/Model/Frontend/Town.php
  18. 25
      Classes/Extension.php
  19. 85
      Classes/Frontend/DataProcessing/ResolveEntities.php
  20. 6
      Configuration/Extbase/Persistence/Classes.php
  21. 4
      Configuration/Services.yaml
  22. 2
      Configuration/SiteConfiguration/Overrides/sites.php
  23. 14
      Configuration/TCA/Overrides/sys_template.php
  24. 18
      Configuration/TCA/Overrides/tt_content.php
  25. 63
      Configuration/TCA/Overrides/tt_content_tourist_attraction.php
  26. 89
      Configuration/TCA/tx_thuecat_tourist_attraction.php
  27. 6
      Configuration/TypoScript/Rendering.typoscript
  28. 13
      Configuration/TypoScript/Rendering/TouristAttraction.typoscript
  29. 6
      Configuration/TypoScript/Rendering/_base.typoscript
  30. 1
      Configuration/TypoScript/setup.typoscript
  31. 4
      README.md
  32. 3
      Resources/Private/Language/locallang.xlf
  33. 35
      Resources/Private/Language/locallang_tca.xlf
  34. 21
      Resources/Private/Templates/Frontend/ContentElement/TouristAttraction.html
  35. 32
      Tests/Unit/Domain/Import/Converter/OrganisationTest.php
  36. 224
      Tests/Unit/Domain/Import/Converter/TouristAttractionTest.php
  37. 113
      Tests/Unit/Domain/Import/Converter/TouristInformationTest.php
  38. 81
      Tests/Unit/Domain/Import/Converter/TownTest.php
  39. 43
      Tests/Unit/Domain/Import/ImporterTest.php
  40. 310
      Tests/Unit/Domain/Import/JsonLD/Parser/OpeningHoursTest.php
  41. 457
      Tests/Unit/Domain/Import/JsonLD/ParserTest.php
  42. 138
      Tests/Unit/Domain/Import/Model/EntityCollectionTest.php
  43. 54
      Tests/Unit/Domain/Import/Model/GenericEntityTest.php
  44. 3
      composer.json
  45. 1
      dependency-checker.json
  46. 5
      ext_localconf.php
  47. 9
      ext_tables.sql
  48. 27
      phpstan-baseline.neon
  49. 18
      phpstan.neon

8
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;
}

27
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

86
Classes/Domain/Import/Converter/TouristAttraction.php

@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Import\Converter;
/*
* Copyright (C) 2021 Daniel Siepmann <coding@daniel-siepmann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
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;
}
}

37
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']);
}
}

27
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

73
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));
}
}
$log->addEntry(new ImportLogEntry(
$entity,
$dataHandler->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);
}
$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

140
Classes/Domain/Import/JsonLD/Parser.php

@ -0,0 +1,140 @@
<?php
declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Import\JsonLD;
/*
* Copyright (C) 2021 Daniel Siepmann <coding@daniel-siepmann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
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 === ''
)
;
}
}

102
Classes/Domain/Import/JsonLD/Parser/OpeningHours.php

@ -0,0 +1,102 @@
<?php
declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Import\JsonLD\Parser;
/*
* Copyright (C) 2021 Daniel Siepmann <coding@daniel-siepmann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
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
);
}
}

6
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.
*/

66
Classes/Domain/Import/Model/EntityCollection.php

@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Import\Model;
/*
* Copyright (C) 2021 Daniel Siepmann <coding@daniel-siepmann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
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();
});
}
}

18
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;

1
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);
}
}

1
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;

127
Classes/Domain/Model/Frontend/OpeningHour.php

@ -0,0 +1,127 @@
<?php
declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Model\Frontend;
/*
* Copyright (C) 2021 Daniel Siepmann <coding@daniel-siepmann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
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;
}
}

75
Classes/Domain/Model/Frontend/OpeningHours.php

@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Model\Frontend;
/*
* Copyright (C) 2021 Daniel Siepmann <coding@daniel-siepmann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
use TYPO3\CMS\Core\Type\TypeInterface;
/**
* @implements \Iterator<int, OpeningHour>
*/
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;
}
}

54
Classes/Domain/Model/Frontend/TouristAttraction.php

@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Model\Frontend;
/*
* Copyright (C) 2021 Daniel Siepmann <coding@daniel-siepmann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
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;
}
}

42
Classes/Domain/Model/Frontend/Town.php

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Model\Frontend;
/*
* Copyright (C) 2021 Daniel Siepmann <coding@daniel-siepmann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
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;
}
}

25
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
}
}
}
}
');
}
}

85
Classes/Frontend/DataProcessing/ResolveEntities.php

@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Frontend\DataProcessing;
/*
* Copyright (C) 2021 Daniel Siepmann <coding@daniel-siepmann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
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);
}
}

6
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',
],
];

4
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

2
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' => [

14
Configuration/TCA/Overrides/sys_template.php

@ -0,0 +1,14 @@
<?php
defined('TYPO3') or die();
(static function (string $extensionKey, string $tableName) {
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addStaticFile(
$extensionKey,
'Configuration/TypoScript/',
'ThüCAT'
);
})(
\WerkraumMedia\ThueCat\Extension::EXTENSION_KEY,
'sys_template'
);

18
Configuration/TCA/Overrides/tt_content.php

@ -0,0 +1,18 @@
<?php
defined('TYPO3') or die();
(static function (string $extensionKey, string $tableName) {
$languagePath = \WerkraumMedia\ThueCat\Extension::getLanguagePath()
. 'locallang_tca.xlf:' . $tableName;
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTcaSelectItemGroup(
$tableName,
'CType',
\WerkraumMedia\ThueCat\Extension::TT_CONTENT_GROUP,
$languagePath . '.group'
);
})(
\WerkraumMedia\ThueCat\Extension::EXTENSION_KEY,
'tt_content'
);

63
Configuration/TCA/Overrides/tt_content_tourist_attraction.php

@ -0,0 +1,63 @@
<?php
defined('TYPO3') or die();
(static function (string $extensionKey, string $tableName, string $cType) {
$languagePath = \WerkraumMedia\ThueCat\Extension::getLanguagePath()
. 'locallang_tca.xlf:' . $tableName . '.' . $cType;
\TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($GLOBALS['TCA'][$tableName], [
'ctrl' => [
// 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'
);

89
Configuration/TCA/tx_thuecat_tourist_attraction.php

@ -0,0 +1,89 @@
<?php
defined('TYPO3') or die();
return (static function (string $extensionKey, string $tableName) {
$languagePath = \WerkraumMedia\ThueCat\Extension::getLanguagePath() . 'locallang_tca.xlf:' . $tableName;
return [
'ctrl' => [
'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');

6
Configuration/TypoScript/Rendering.typoscript

@ -0,0 +1,6 @@
lib.thuecatContentElement =< lib.contentElement
lib.thuecatContentElement {
templateRootPaths {
9999 = EXT:thuecat/Resources/Private/Templates/Frontend/ContentElement/
}
}

13
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
}
}
}
}

6
Configuration/TypoScript/Rendering/_base.typoscript

@ -0,0 +1,6 @@
lib.thuecatContentElement =< lib.contentElement
lib.thuecatContentElement {
templateRootPaths {
9999 = EXT:thuecat/Resources/Private/Templates/Frontend/ContentElement/
}
}

1
Configuration/TypoScript/setup.typoscript

@ -0,0 +1 @@
@import 'EXT:thuecat/Configuration/TypoScript/Rendering/*.typoscript'

4
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.

3
Resources/Private/Language/locallang.xlf

@ -90,6 +90,9 @@
<trans-unit id="module.imports.summary.tableName.tx_thuecat_tourist_information" xml:space="preserve">
<source>Tourist Information</source>
</trans-unit>
<trans-unit id="module.imports.summary.tableName.tx_thuecat_tourist_attraction" xml:space="preserve">
<source>Tourist Attraction</source>
</trans-unit>
<trans-unit id="controller.backend.import.import.success.title" xml:space="preserve">
<source>Import finished</source>

35
Resources/Private/Language/locallang_tca.xlf

@ -72,6 +72,31 @@
<source>Unkown</source>
</trans-unit>
<trans-unit id="tx_thuecat_tourist_attraction" xml:space="preserve">
<source>Tourist Attraction</source>
</trans-unit>
<trans-unit id="tx_thuecat_tourist_attraction.title" xml:space="preserve">
<source>Title</source>
</trans-unit>
<trans-unit id="tx_thuecat_tourist_attraction.description" xml:space="preserve">
<source>Description</source>
</trans-unit>
<trans-unit id="tx_thuecat_tourist_attraction.remote_id" xml:space="preserve">
<source>Remote ID</source>
</trans-unit>
<trans-unit id="tx_thuecat_tourist_attraction.town" xml:space="preserve">
<source>Town</source>
</trans-unit>
<trans-unit id="tx_thuecat_tourist_attraction.town.unkown" xml:space="preserve">
<source>Unkown</source>
</trans-unit>
<trans-unit id="tx_thuecat_tourist_attraction.managed_by" xml:space="preserve">
<source>Managed by</source>
</trans-unit>
<trans-unit id="tx_thuecat_tourist_attraction.managed_by.unkown" xml:space="preserve">
<source>Unkown</source>
</trans-unit>
<trans-unit id="tx_thuecat_import_configuration" xml:space="preserve">