Add multi language handling

Import entities in all available languages.
Provide translated records in frontend.
This commit is contained in:
Daniel Siepmann 2021-02-16 17:12:47 +01:00
parent 46144de0a7
commit 92c8bbedaa
22 changed files with 596 additions and 85 deletions

View file

@ -24,6 +24,7 @@ namespace WerkraumMedia\ThueCat\Domain\Import\Converter;
*/
use TYPO3\CMS\Core\Utility\GeneralUtility;
use WerkraumMedia\ThueCat\Domain\Import\Importer\LanguageHandling;
use WerkraumMedia\ThueCat\Domain\Import\JsonLD\Parser;
use WerkraumMedia\ThueCat\Domain\Import\Model\EntityCollection;
use WerkraumMedia\ThueCat\Domain\Import\Model\GenericEntity;
@ -33,15 +34,18 @@ use WerkraumMedia\ThueCat\Domain\Repository\Backend\TownRepository;
class TouristAttraction implements Converter
{
private Parser $parser;
private LanguageHandling $language;
private OrganisationRepository $organisationRepository;
private TownRepository $townRepository;
public function __construct(
Parser $parser,
LanguageHandling $language,
OrganisationRepository $organisationRepository,
TownRepository $townRepository
) {
$this->parser = $parser;
$this->language = $language;
$this->organisationRepository = $organisationRepository;
$this->townRepository = $townRepository;
}
@ -54,16 +58,15 @@ class TouristAttraction implements Converter
$entities = GeneralUtility::makeInstance(EntityCollection::class);
foreach ($this->parser->getLanguages($jsonLD) as $language) {
if ($language !== 'de') {
if ($this->language->isUnknown($language, $storagePid)) {
continue;
}
$systemLanguageUid = 0;
$entity = GeneralUtility::makeInstance(
GenericEntity::class,
$storagePid,
'tx_thuecat_tourist_attraction',
$systemLanguageUid,
$this->language->getSystemUid($language, $storagePid),
$this->parser->getId($jsonLD),
[
'title' => $this->parser->getTitle($jsonLD, $language),

View file

@ -0,0 +1,64 @@
<?php
namespace WerkraumMedia\ThueCat\Domain\Import\Importer;
/*
* 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\Site\Entity\SiteLanguage;
use TYPO3\CMS\Core\Site\SiteFinder;
class LanguageHandling
{
private SiteFinder $siteFinder;
public function __construct(
SiteFinder $siteFinder
) {
$this->siteFinder = $siteFinder;
}
public function isUnknown(string $languageIsoCode, int $pageUid): bool
{
$availableIsoCodes = array_map(function (SiteLanguage $language) {
return $language->getTwoLetterIsoCode();
}, $this->getLanguages($pageUid));
return in_array($languageIsoCode, $availableIsoCodes) === false;
}
public function getSystemUid(string $languageIsoCode, int $pageUid): int
{
foreach ($this->getLanguages($pageUid) as $language) {
if ($language->getTwoLetterIsoCode() === $languageIsoCode) {
return $language->getLanguageId();
}
}
return 0;
}
/**
* @return SiteLanguage[]
*/
private function getLanguages(int $pageUid): array
{
return $this->siteFinder->getSiteByPageId($pageUid)->getLanguages();
}
}

View file

@ -48,39 +48,54 @@ class SaveData
{
$this->errorLog = [];
$this->processSimpleDataHandlerDataMap($entityCollection);
// TODO: Insert update / insert of localization
$this->updateKnownData($entityCollection);
$this->createEntities($entityCollection);
$this->updateKnownData($entityCollection);
$this->updateEntities($entityCollection);
foreach ($entityCollection->getEntities() as $entity) {
$log->addEntry(new ImportLogEntry($entity, $this->errorLog));
}
}
private function processSimpleDataHandlerDataMap(EntityCollection $collection): void
private function updateKnownData(EntityCollection $entities): void
{
$dataArray = [];
$identifierMapping = [];
foreach ($collection->getEntities() as $entity) {
$identifier = $this->getIdentifier($entity);
if (strpos($identifier, 'NEW') === 0 && $entity->isTranslation()) {
foreach ($entities->getEntities() as $entity) {
if ($entity->exists()) {
continue;
}
$identifier = $this->getIdentifier($entity);
if (is_numeric($identifier)) {
$entity->setExistingTypo3Uid((int) $identifier);
} else {
$identifierMapping[spl_object_id($entity)] = $identifier;
}
$dataArray[$entity->getTypo3DatabaseTableName()][$identifier] = $this->getEntityData($entity);
}
}
private function createEntities(EntityCollection $entities): void
{
$this->createDefaultLanguageEntities($entities);
$this->createTranslationEntities($entities);
}
private function createDefaultLanguageEntities(EntityCollection $entities): void
{
$identifierMapping = [];
$entity = $entities->getDefaultLanguageEntity();
if ($entity === null) {
return;
}
$identifier = $this->getIdentifier($entity);
$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) {
foreach ($entities->getEntities() as $entity) {
if (
isset($identifierMapping[spl_object_id($entity)])
&& isset($dataHandler->substNEWwithIDs[$identifierMapping[spl_object_id($entity)]])
@ -90,6 +105,40 @@ class SaveData
}
}
private function createTranslationEntities(EntityCollection $entities): void
{
$commandMap = [];
foreach ($entities->getEntitiesToTranslate() as $entity) {
$identifier = $this->getDefaultLanguageIdentifier($entity);
if (
$entity->isForDefaultLanguage()
|| $identifier === 0
) {
continue;
}
$commandMap[$entity->getTypo3DatabaseTableName()][$identifier]['localize'] = $entity->getTypo3SystemLanguageUid();
$dataHandler = clone $this->dataHandler;
$dataHandler->start([], $commandMap);
$dataHandler->process_cmdmap();
$this->errorLog = array_merge($this->errorLog, $dataHandler->errorLog);
}
}
private function updateEntities(EntityCollection $entities): void
{
$dataArray = [];
foreach ($entities->getExistingEntities() as $entity) {
$dataArray[$entity->getTypo3DatabaseTableName()][$entity->getTypo3Uid()] = $this->getEntityData($entity);
}
$dataHandler = clone $this->dataHandler;
$dataHandler->start($dataArray, []);
$dataHandler->process_datamap();
$this->errorLog = array_merge($this->errorLog, $dataHandler->errorLog);
}
private function getIdentifier(Entity $entity): string
{
$existingUid = $this->getExistingUid($entity);
@ -113,6 +162,11 @@ class SaveData
private function getExistingUid(Entity $entity): int
{
$tableColumns = $this->connectionPool
->getConnectionForTable($entity->getTypo3DatabaseTableName())
->getSchemaManager()
->listTableColumns($entity->getTypo3DatabaseTableName());
$queryBuilder = $this->connectionPool->getQueryBuilderForTable($entity->getTypo3DatabaseTableName());
$queryBuilder->getRestrictions()->removeAll();
$queryBuilder->select('uid');
@ -121,6 +175,35 @@ class SaveData
'remote_id',
$queryBuilder->createNamedParameter($entity->getRemoteId())
));
if (isset($tableColumns['sys_language_uid'])) {
$queryBuilder->andWhere($queryBuilder->expr()->eq(
'sys_language_uid',
$queryBuilder->createNamedParameter($entity->getTypo3SystemLanguageUid())
));
}
$result = $queryBuilder->execute()->fetchColumn();
if (is_numeric($result)) {
return (int) $result;
}
return 0;
}
private function getDefaultLanguageIdentifier(Entity $entity): int
{
$queryBuilder = $this->connectionPool->getQueryBuilderForTable($entity->getTypo3DatabaseTableName());
$queryBuilder->getRestrictions()->removeAll();
$queryBuilder->select('uid');
$queryBuilder->from($entity->getTypo3DatabaseTableName());
$queryBuilder->where($queryBuilder->expr()->eq(
'remote_id',
$queryBuilder->createNamedParameter($entity->getRemoteId())
));
$queryBuilder->andWhere($queryBuilder->expr()->eq(
'sys_language_uid',
$queryBuilder->createNamedParameter(0)
));
$result = $queryBuilder->execute()->fetchColumn();
if (is_numeric($result)) {

View file

@ -64,6 +64,11 @@ interface Entity
*/
public function getTypo3Uid(): int;
/**
* Must return true in case the entitiy did exist.
*/
public function exists(): bool;
/**
* Must return true in case the entitiy did not exist.
*/

View file

@ -57,10 +57,19 @@ class EntityCollection
/**
* @return Entity[]
*/
public function getTranslatedEntities(): array
public function getEntitiesToTranslate(): array
{
return array_filter($this->entities, function (Entity $entity) {
return $entity->isTranslation();
return $entity->isTranslation()
&& $entity->exists() === false
;
});
}
public function getExistingEntities(): array
{
return array_filter($this->entities, function (Entity $entity) {
return $entity->exists();
});
}
}

View file

@ -99,6 +99,11 @@ class GenericEntity implements Entity
return $this->typo3Uid;
}
public function exists(): bool
{
return $this->getTypo3Uid() !== 0;
}
public function wasCreated(): bool
{
return $this->created;

View file

@ -29,11 +29,13 @@ 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;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
class ResolveEntities implements DataProcessorInterface
{
private ConnectionPool $connectionPool;
private DataMapper $dataMapper;
private TypoScriptFrontendController $tsfe;
public function __construct(
ConnectionPool $connectionPool,
@ -41,6 +43,7 @@ class ResolveEntities implements DataProcessorInterface
) {
$this->connectionPool = $connectionPool;
$this->dataMapper = $dataMapper;
$this->tsfe = $GLOBALS['TSFE'];
}
public function process(
@ -50,36 +53,44 @@ class ResolveEntities implements DataProcessorInterface
array $processedData
) {
$as = $cObj->stdWrapValue('as', $processorConfiguration, 'entities');
$table = $cObj->stdWrapValue('table', $processorConfiguration, '');
$tableName = $cObj->stdWrapValue('table', $processorConfiguration, '');
$uids = $cObj->stdWrapValue('uids', $processorConfiguration, '');
$uids = GeneralUtility::intExplode(',', $uids);
if ($uids === [] || $table === '') {
if ($uids === [] || $tableName === '') {
return $processedData;
}
$processedData[$as] = $this->resolveEntities($table, $uids);
$processedData[$as] = $this->resolveEntities($tableName, $uids);
return $processedData;
}
private function resolveEntities(string $table, array $uids): array
private function resolveEntities(string $tableName, array $uids): array
{
$targetType = '\WerkraumMedia\ThueCat\Domain\Model\Frontend\\' . $this->convertTableToEntity($table);
$targetType = '\WerkraumMedia\ThueCat\Domain\Model\Frontend\\' . $this->convertTableToEntity($tableName);
$queryBuilder = $this->connectionPool->getQueryBuilderForTable($table);
$queryBuilder = $this->connectionPool->getQueryBuilderForTable($tableName);
$queryBuilder->select('*');
$queryBuilder->from($table);
$queryBuilder->from($tableName);
$queryBuilder->where($queryBuilder->expr()->in(
'uid',
$queryBuilder->createNamedParameter($uids, Connection::PARAM_INT_ARRAY)
));
return $this->dataMapper->map($targetType, $queryBuilder->execute()->fetchAll());
$rows = [];
foreach ($queryBuilder->execute() as $row) {
$row = $this->tsfe->sys_page->getLanguageOverlay($tableName, $row);
if (is_array($row)) {
$rows[] = $row;
}
}
return $this->dataMapper->map($targetType, $rows);
}
private function convertTableToEntity(string $table): string
private function convertTableToEntity(string $tableName): string
{
$entityPart = str_replace('tx_thuecat_', '', $table);
$entityPart = str_replace('tx_thuecat_', '', $tableName);
return GeneralUtility::underscoredToUpperCamelCase($entityPart);
}
}

View file

@ -38,6 +38,11 @@ defined('TYPO3') or die();
'records' => [
'config' => [
'allowed' => 'tx_thuecat_tourist_attraction',
'suggestOptions' => [
'tx_thuecat_tourist_attraction' => [
'addWhere' => 'sys_language_uid in (0,-1)',
],
],
],
],
],

View file

@ -60,6 +60,15 @@ return (static function (string $extensionKey, string $tableName) {
'readOnly' => true,
],
],
'manages_tourist_attraction' => [
'label' => $languagePath . '.manages_tourist_attraction',
'config' => [
'type' => 'inline',
'foreign_table' => 'tx_thuecat_tourist_attraction',
'foreign_field' => 'managed_by',
'readOnly' => true,
],
],
'tstamp' => [
'label' => $languagePath . '.tstamp',
'config' => [
@ -74,7 +83,7 @@ return (static function (string $extensionKey, string $tableName) {
'0' => [
'showitem' => 'title, description, remote_id, tstamp'
. ',--div--;' . $languagePath . '.div.manages'
. ',manages_towns, manages_tourist_information',
. ',manages_towns, manages_tourist_information, manages_tourist_attraction',
],
],
];

View file

@ -17,10 +17,50 @@ return (static function (string $extensionKey, string $tableName) {
'disabled' => 'disable',
],
'searchFields' => 'title, description',
'transOrigPointerField' => 'l18n_parent',
'transOrigDiffSourceField' => 'l18n_diffsource',
'languageField' => 'sys_language_uid',
'translationSource' => 'l10n_source',
],
'columns' => [
'sys_language_uid' => [
'exclude' => true,
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language',
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
'special' => 'languages',
'items' => [
[
'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages',
-1,
'flags-multiple',
],
],
'default' => 0,
],
],
'l18n_parent' => [
'displayCond' => 'FIELD:sys_language_uid:>:0',
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent',
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
'items' => [['', 0]],
'foreign_table' => $tableName,
'foreign_table_where' => 'AND ' . $tableName . '.pid=###CURRENT_PID### AND ' . $tableName . '.sys_language_uid IN (-1,0)',
'default' => 0,
],
],
'l10n_source' => [
'config' => [
'type' => 'passthrough',
],
],
'title' => [
'label' => $languagePath . '.title',
'l10n_mode' => 'prefixLangTitle',
'config' => [
'type' => 'input',
'size' => 20,
@ -30,6 +70,7 @@ return (static function (string $extensionKey, string $tableName) {
],
'description' => [
'label' => $languagePath . '.description',
'l10n_mode' => 'prefixLangTitle',
'config' => [
'type' => 'text',
'readOnly' => true,
@ -37,6 +78,7 @@ return (static function (string $extensionKey, string $tableName) {
],
'opening_hours' => [
'label' => $languagePath . '.opening_hours',
'l10n_mode' => 'exclude',
'config' => [
'type' => 'text',
'readOnly' => true,
@ -44,6 +86,7 @@ return (static function (string $extensionKey, string $tableName) {
],
'address' => [
'label' => $languagePath . '.address',
'l10n_mode' => 'exclude',
'config' => [
'type' => 'text',
'readOnly' => true,
@ -51,6 +94,7 @@ return (static function (string $extensionKey, string $tableName) {
],
'media' => [
'label' => $languagePath . '.media',
'l10n_mode' => 'exclude',
'config' => [
'type' => 'text',
'readOnly' => true,
@ -58,6 +102,7 @@ return (static function (string $extensionKey, string $tableName) {
],
'remote_id' => [
'label' => $languagePath . '.remote_id',
'l10n_mode' => 'exclude',
'config' => [
'type' => 'input',
'readOnly' => true,
@ -65,6 +110,7 @@ return (static function (string $extensionKey, string $tableName) {
],
'town' => [
'label' => $languagePath . '.town',
'l10n_mode' => 'exclude',
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
@ -80,6 +126,7 @@ return (static function (string $extensionKey, string $tableName) {
],
'managed_by' => [
'label' => $languagePath . '.managed_by',
'l10n_mode' => 'exclude',
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
@ -94,9 +141,15 @@ return (static function (string $extensionKey, string $tableName) {
],
],
],
'palettes' => [
'language' => [
'label' => $languagePath . '.palette.language',
'showitem' => 'sys_language_uid,l18n_parent',
],
],
'types' => [
'0' => [
'showitem' => 'title, description, opening_hours, address, media, remote_id, town, managed_by',
'showitem' => '--palette--;;language, title, description, opening_hours, address, media, remote_id, --div--;' . $languagePath . '.tab.relations, town, managed_by',
],
],
];

View file

@ -27,6 +27,9 @@
<trans-unit id="tx_thuecat_organisation.manages_tourist_information" xml:space="preserve">
<source>Tourist Information</source>
</trans-unit>
<trans-unit id="tx_thuecat_organisation.manages_tourist_attraction" xml:space="preserve">
<source>Tourist Attraction</source>
</trans-unit>
<trans-unit id="tx_thuecat_tourist_information" xml:space="preserve">
<source>Tourist Information</source>
@ -75,6 +78,12 @@
<trans-unit id="tx_thuecat_tourist_attraction" xml:space="preserve">
<source>Tourist Attraction</source>
</trans-unit>
<trans-unit id="tx_thuecat_tourist_attraction.palette.language" xml:space="preserve">
<source>Language</source>
</trans-unit>
<trans-unit id="tx_thuecat_tourist_attraction.tab.relations" xml:space="preserve">
<source>Relations</source>
</trans-unit>
<trans-unit id="tx_thuecat_tourist_attraction.title" xml:space="preserve">
<source>Title</source>
</trans-unit>
@ -90,6 +99,9 @@
<trans-unit id="tx_thuecat_tourist_attraction.address" xml:space="preserve">
<source>Address</source>
</trans-unit>
<trans-unit id="tx_thuecat_tourist_attraction.media" xml:space="preserve">
<source>Media</source>
</trans-unit>
<trans-unit id="tx_thuecat_tourist_attraction.town" xml:space="preserve">
<source>Town</source>
</trans-unit>

View file

@ -1,11 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<dataset>
<pages>
<uid>10</uid>
<uid>1</uid>
<pid>0</pid>
<tstamp>1613400587</tstamp>
<crdate>1613400558</crdate>
<cruser_id>1</cruser_id>
<doktype>4</doktype>
<title>Rootpage</title>
<is_siteroot>1</is_siteroot>
</pages>
<pages>
<uid>10</uid>
<pid>1</pid>
<tstamp>1613400587</tstamp>
<crdate>1613400558</crdate>
<cruser_id>1</cruser_id>
<doktype>254</doktype>
<title>Storage folder</title>
</pages>

View file

@ -1,15 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<dataset>
<pages>
<uid>10</uid>
<uid>1</uid>
<pid>0</pid>
<tstamp>1613400587</tstamp>
<crdate>1613400558</crdate>
<cruser_id>1</cruser_id>
<doktype>4</doktype>
<title>Rootpage</title>
<is_siteroot>1</is_siteroot>
</pages>
<pages>
<uid>10</uid>
<pid>1</pid>
<tstamp>1613400587</tstamp>
<crdate>1613400558</crdate>
<cruser_id>1</cruser_id>
<doktype>254</doktype>
<title>Storage folder</title>
</pages>
<sys_language>
<uid>1</uid>
<pid>0</pid>
<title>English</title>
<flag>en-us-gb</flag>
<language_isocode>en</language_isocode>
</sys_language>
<sys_language>
<uid>2</uid>
<pid>0</pid>
<title>French</title>
<flag>fr</flag>
<language_isocode>fr</language_isocode>
</sys_language>
<tx_thuecat_import_configuration>
<uid>1</uid>
<pid>0</pid>

View file

@ -0,0 +1,10 @@
tx_thuecat_tourist_attraction
,"uid","pid","sys_language_uid","l18n_parent","l10n_source","l10n_state","remote_id","title","managed_by","town","address"
,1,10,0,0,0,\NULL,"https://thuecat.org/resources/835224016581-dara","Dom St. Marien",1,1,"{""street"":""Domstufen 1"",""zip"":""99084"",""city"":""Erfurt"",""email"":""dominformation@domberg-erfurt.de"",""phone"":""+49 361 6461265"",""fax"":"""",""geo"":{""latitude"":50.975955358589545,""longitude"":11.023667024961856}}"
,2,10,1,1,1,\NULL,"https://thuecat.org/resources/835224016581-dara","Cathedral of St. Mary",1,1,"{""street"":""Domstufen 1"",""zip"":""99084"",""city"":""Erfurt"",""email"":""dominformation@domberg-erfurt.de"",""phone"":""+49 361 6461265"",""fax"":"""",""geo"":{""latitude"":50.975955358589545,""longitude"":11.023667024961856}}"
,3,10,0,0,0,\NULL,"https://thuecat.org/resources/165868194223-zmqf","Alte Synagoge",1,1,"{""street"":""Waagegasse 8"",""zip"":""99084"",""city"":""Erfurt"",""email"":""altesynagoge@erfurt.de"",""phone"":""+49 361 6551520"",""fax"":""+49 361 6551669"",""geo"":{""latitude"":50.978765,""longitude"":11.029133}}"
,4,10,1,3,3,\NULL,"https://thuecat.org/resources/165868194223-zmqf","Old Synagogue",1,1,"{""street"":""Waagegasse 8"",""zip"":""99084"",""city"":""Erfurt"",""email"":""altesynagoge@erfurt.de"",""phone"":""+49 361 6551520"",""fax"":""+49 361 6551669"",""geo"":{""latitude"":50.978765,""longitude"":11.029133}}"
,5,10,2,3,3,\NULL,"https://thuecat.org/resources/165868194223-zmqf","La vieille synagogue",1,1,"{""street"":""Waagegasse 8"",""zip"":""99084"",""city"":""Erfurt"",""email"":""altesynagoge@erfurt.de"",""phone"":""+49 361 6551520"",""fax"":""+49 361 6551669"",""geo"":{""latitude"":50.978765,""longitude"":11.029133}}"
,6,10,0,0,0,\NULL,"https://thuecat.org/resources/215230952334-yyno","Krämerbrücke",1,1,"{""street"":""Benediktsplatz 1"",""zip"":""99084"",""city"":""Erfurt"",""email"":""service@erfurt-tourismus.de"",""phone"":""+49 361 66 400"",""fax"":"""",""geo"":{""latitude"":50.978772,""longitude"":11.031622}}"
,7,10,1,6,6,\NULL,"https://thuecat.org/resources/215230952334-yyno","Merchants' Bridge",1,1,"{""street"":""Benediktsplatz 1"",""zip"":""99084"",""city"":""Erfurt"",""email"":""service@erfurt-tourismus.de"",""phone"":""+49 361 66 400"",""fax"":"""",""geo"":{""latitude"":50.978772,""longitude"":11.031622}}"
,8,10,2,6,6,\NULL,"https://thuecat.org/resources/215230952334-yyno","Pont de l'épicier",1,1,"{""street"":""Benediktsplatz 1"",""zip"":""99084"",""city"":""Erfurt"",""email"":""service@erfurt-tourismus.de"",""phone"":""+49 361 66 400"",""fax"":"""",""geo"":{""latitude"":50.978772,""longitude"":11.031622}}"
Can't render this file because it has a wrong number of fields in line 2.

View file

@ -0,0 +1,47 @@
base: 'https:/example.com'
languages:
-
title: Deutsch
enabled: true
base: /
typo3Language: de
locale: de_DE.UTF-8
iso-639-1: de
navigationTitle: Deutsch
hreflang: de-DE
direction: ''
flag: de
websiteTitle: ''
languageId: 0
-
title: English
enabled: true
base: /en
typo3Language: default
locale: en_GB.UTF-8
iso-639-1: en
websiteTitle: ''
navigationTitle: English
hreflang: en-GB
direction: ''
flag: gb
languageId: 1
fallbackType: strict
fallbacks: '0'
-
title: French
enabled: true
base: /fr/
typo3Language: fr
locale: fr_FR.ytf8
iso-639-1: fr
websiteTitle: ''
navigationTitle: ''
hreflang: fr-FR
direction: ''
fallbackType: strict
fallbacks: '1,0'
flag: fr
languageId: 2
rootPageId: 1
websiteTitle: 'Example Website'

View file

@ -52,6 +52,7 @@ use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase as TestCase;
* @uses WerkraumMedia\ThueCat\Domain\Import\Converter\Town
* @uses WerkraumMedia\ThueCat\Domain\Import\Importer
* @uses WerkraumMedia\ThueCat\Domain\Import\Importer\FetchData
* @uses WerkraumMedia\ThueCat\Domain\Import\Importer\LanguageHandling
* @uses WerkraumMedia\ThueCat\Domain\Import\JsonLD\Parser
* @uses WerkraumMedia\ThueCat\Domain\Import\JsonLD\Parser\Address
* @uses WerkraumMedia\ThueCat\Domain\Import\JsonLD\Parser\Media
@ -83,6 +84,10 @@ class ImportTest extends TestCase
'typo3conf/ext/thuecat/',
];
protected $pathsToLinkInTestInstance = [
'typo3conf/ext/thuecat/Tests/Functional/Fixtures/Import/Sites/' => 'typo3conf/sites',
];
protected function setUp(): void
{
parent::setUp();
@ -201,34 +206,9 @@ class ImportTest extends TestCase
$extbaseBootstrap->handleBackendRequest($serverRequest->reveal());
$touristAttractions = $this->getAllRecords('tx_thuecat_tourist_attraction');
self::assertCount(3, $touristAttractions);
self::assertCount(8, $touristAttractions);
self::assertSame('https://thuecat.org/resources/835224016581-dara', $touristAttractions[0]['remote_id']);
self::assertSame('Dom St. Marien', $touristAttractions[0]['title']);
self::assertSame('[{"opens":"09:30:00","closes":"18:00:00","from":{"date":"2021-05-01 00:00:00.000000","timezone_type":3,"timezone":"UTC"},"through":{"date":"2021-10-31 00:00:00.000000","timezone_type":3,"timezone":"UTC"},"daysOfWeek":["Friday","Monday","Saturday","Thursday","Tuesday","Wednesday"]},{"opens":"13:00:00","closes":"18:00:00","from":{"date":"2021-05-01 00:00:00.000000","timezone_type":3,"timezone":"UTC"},"through":{"date":"2021-10-31 00:00:00.000000","timezone_type":3,"timezone":"UTC"},"daysOfWeek":["Sunday"]},{"opens":"09:30:00","closes":"17:00:00","from":{"date":"2021-11-01 00:00:00.000000","timezone_type":3,"timezone":"UTC"},"through":{"date":"2022-04-30 00:00:00.000000","timezone_type":3,"timezone":"UTC"},"daysOfWeek":["Friday","Monday","Saturday","Thursday","Tuesday","Wednesday"]},{"opens":"13:00:00","closes":"17:00:00","from":{"date":"2021-11-01 00:00:00.000000","timezone_type":3,"timezone":"UTC"},"through":{"date":"2022-04-30 00:00:00.000000","timezone_type":3,"timezone":"UTC"},"daysOfWeek":["Sunday"]}]', $touristAttractions[0]['opening_hours']);
self::assertSame('{"street":"Domstufen 1","zip":"99084","city":"Erfurt","email":"dominformation@domberg-erfurt.de","phone":"+49 361 6461265","fax":"","geo":{"latitude":50.975955358589545,"longitude":11.023667024961856}}', $touristAttractions[0]['address']);
self::assertSame('[{"mainImage":true,"type":"image","title":"Erfurt-Dom und Severikirche-beleuchtet.jpg","description":"","url":"https:\/\/cms.thuecat.org\/o\/adaptive-media\/image\/5159216\/Preview-1280x0\/image","copyrightYear":2016,"license":{"type":"https:\/\/creativecommons.org\/licenses\/by\/4.0\/","author":""}},{"mainImage":false,"type":"image","title":"Erfurt-Dom-und-Severikirche.jpg","description":"Sicht auf Dom St. Marien, St. Severikirche sowie die davor liegenden Klostergeb\u00e4ude und einem Ausschnitt des Biergartens umgeben von einem d\u00e4mmerungsverf\u00e4rten Himmel","url":"https:\/\/cms.thuecat.org\/o\/adaptive-media\/image\/5159186\/Preview-1280x0\/image","copyrightYear":2020,"license":{"type":"https:\/\/creativecommons.org\/licenses\/by\/4.0\/","author":""}},{"mainImage":false,"type":"image","title":"Erfurt-Dom und Severikirche-beleuchtet.jpg","description":"","url":"https:\/\/cms.thuecat.org\/o\/adaptive-media\/image\/5159216\/Preview-1280x0\/image","copyrightYear":2016,"license":{"type":"https:\/\/creativecommons.org\/licenses\/by\/4.0\/","author":""}}]', $touristAttractions[0]['media']);
self::assertSame('1', $touristAttractions[0]['managed_by']);
self::assertSame('1', $touristAttractions[0]['town']);
self::assertSame('1', $touristAttractions[0]['uid']);
self::assertSame('https://thuecat.org/resources/165868194223-zmqf', $touristAttractions[1]['remote_id']);
self::assertSame('Alte Synagoge', $touristAttractions[1]['title']);
self::assertSame('[{"opens":"10:00:00","closes":"18:00:00","from":{"date":"2021-03-01 00:00:00.000000","timezone_type":3,"timezone":"UTC"},"through":{"date":"2021-12-31 00:00:00.000000","timezone_type":3,"timezone":"UTC"},"daysOfWeek":["Friday","Saturday","Sunday","Thursday","Tuesday","Wednesday"]}]', $touristAttractions[1]['opening_hours']);
self::assertSame('{"street":"Waagegasse 8","zip":"99084","city":"Erfurt","email":"altesynagoge@erfurt.de","phone":"+49 361 6551520","fax":"+49 361 6551669","geo":{"latitude":50.978765,"longitude":11.029133}}', $touristAttractions[1]['address']);
self::assertSame('[{"mainImage":true,"type":"image","title":"Erfurt-Alte Synagoge","description":"Frontaler Blick auf die Hausfront\/Hausfassade im Innenhof mit Zugang \u00fcber die Waagegasse","url":"https:\/\/cms.thuecat.org\/o\/adaptive-media\/image\/5099196\/Preview-1280x0\/image","copyrightYear":2009,"license":{"type":"https:\/\/creativecommons.org\/licenses\/by\/4.0\/","author":"F:\\\\Bilddatenbank\\\\Museen und Ausstellungen\\\\Alte Synagoge"}},{"mainImage":false,"type":"image","title":"Erfurt-Alte Synagoge","description":"Frontaler Blick auf die Hausfront\/Hausfassade im Innenhof mit Zugang \u00fcber die Waagegasse","url":"https:\/\/cms.thuecat.org\/o\/adaptive-media\/image\/5099196\/Preview-1280x0\/image","copyrightYear":2009,"license":{"type":"https:\/\/creativecommons.org\/licenses\/by\/4.0\/","author":"F:\\\\Bilddatenbank\\\\Museen und Ausstellungen\\\\Alte Synagoge"}}]', $touristAttractions[1]['media']);
self::assertSame('1', $touristAttractions[1]['managed_by']);
self::assertSame('1', $touristAttractions[1]['town']);
self::assertSame('2', $touristAttractions[1]['uid']);
self::assertSame('https://thuecat.org/resources/215230952334-yyno', $touristAttractions[2]['remote_id']);
self::assertSame('Krämerbrücke', $touristAttractions[2]['title']);
self::assertSame('[]', $touristAttractions[2]['opening_hours']);
self::assertSame('{"street":"Benediktsplatz 1","zip":"99084","city":"Erfurt","email":"service@erfurt-tourismus.de","phone":"+49 361 66 400","fax":"","geo":{"latitude":50.978772,"longitude":11.031622}}', $touristAttractions[2]['address']);
self::assertSame('[{"mainImage":true,"type":"image","title":"Erfurt-Kraemerbruecke-11.jpg","description":"Kr\u00e4merbr\u00fccke in Erfurt","url":"https:\/\/cms.thuecat.org\/o\/adaptive-media\/image\/134362\/Preview-1280x0\/image","copyrightYear":2019,"license":{"type":"https:\/\/creativecommons.org\/publicdomain\/zero\/1.0\/deed.de","author":"https:\/\/home.ttgnet.de\/ttg\/projekte\/10006\/90136\/Projektdokumente\/Vergabe%20Rahmenvertrag%20Fotoproduktion"}},{"mainImage":false,"type":"image","title":"Erfurt-Kraemerbruecke.jpg","description":"Kr\u00e4merbr\u00fccke in Erfurt","url":"https:\/\/cms.thuecat.org\/o\/adaptive-media\/image\/134288\/Preview-1280x0\/image","copyrightYear":2019,"license":{"type":"https:\/\/creativecommons.org\/publicdomain\/zero\/1.0\/deed.de","author":"https:\/\/home.ttgnet.de\/ttg\/projekte\/10006\/90136\/Projektdokumente\/Vergabe%20Rahmenvertrag%20Fotoproduktion"}},{"mainImage":false,"type":"image","title":"Erfurt-Kraemerbruecke-11.jpg","description":"Kr\u00e4merbr\u00fccke in Erfurt","url":"https:\/\/cms.thuecat.org\/o\/adaptive-media\/image\/134362\/Preview-1280x0\/image","copyrightYear":2019,"license":{"type":"https:\/\/creativecommons.org\/publicdomain\/zero\/1.0\/deed.de","author":"https:\/\/home.ttgnet.de\/ttg\/projekte\/10006\/90136\/Projektdokumente\/Vergabe%20Rahmenvertrag%20Fotoproduktion"}},{"mainImage":false,"type":"image","title":"Erfurt-Kraemerbruecke-13.jpg","description":"Ansicht der Kr\u00e4merbr\u00fccke, Erfurt","url":"https:\/\/cms.thuecat.org\/o\/adaptive-media\/image\/652340\/Preview-1280x0\/image","copyrightYear":2019,"license":{"type":"https:\/\/creativecommons.org\/publicdomain\/zero\/1.0\/deed.de","author":"https:\/\/home.ttgnet.de\/ttg\/projekte\/10006\/90136\/Projektdokumente\/Vergabe%20Rahmenvertrag%20Fotoproduktion"}}]', $touristAttractions[2]['media']);
self::assertSame('1', $touristAttractions[2]['managed_by']);
self::assertSame('1', $touristAttractions[2]['town']);
self::assertSame('3', $touristAttractions[2]['uid']);
$this->assertCSVDataSet('EXT:thuecat/Tests/Functional/Fixtures/Import/ImportsTouristAttractionsWithRelationsResult.csv');
}
/**

View file

@ -26,6 +26,7 @@ namespace WerkraumMedia\ThueCat\Tests\Unit\Domain\Import\Converter;
use PHPUnit\Framework\TestCase;
use Prophecy\PhpUnit\ProphecyTrait;
use WerkraumMedia\ThueCat\Domain\Import\Converter\TouristAttraction;
use WerkraumMedia\ThueCat\Domain\Import\Importer\LanguageHandling;
use WerkraumMedia\ThueCat\Domain\Import\JsonLD\Parser;
use WerkraumMedia\ThueCat\Domain\Import\Model\EntityCollection;
use WerkraumMedia\ThueCat\Domain\Model\Backend\Organisation;
@ -48,11 +49,13 @@ class TouristAttractionTest extends TestCase
public function canBeCreated(): void
{
$parser = $this->prophesize(Parser::class);
$language = $this->prophesize(LanguageHandling::class);
$organisationRepository = $this->prophesize(OrganisationRepository::class);
$townRepository = $this->prophesize(TownRepository::class);
$subject = new TouristAttraction(
$parser->reveal(),
$language->reveal(),
$organisationRepository->reveal(),
$townRepository->reveal()
);
@ -66,11 +69,13 @@ class TouristAttractionTest extends TestCase
public function canConvert(): void
{
$parser = $this->prophesize(Parser::class);
$language = $this->prophesize(LanguageHandling::class);
$organisationRepository = $this->prophesize(OrganisationRepository::class);
$townRepository = $this->prophesize(TownRepository::class);
$subject = new TouristAttraction(
$parser->reveal(),
$language->reveal(),
$organisationRepository->reveal(),
$townRepository->reveal()
);
@ -120,11 +125,16 @@ class TouristAttractionTest extends TestCase
$parser->getAddress($jsonLD)->willReturn([]);
$parser->getMedia($jsonLD)->willReturn([]);
$language = $this->prophesize(LanguageHandling::class);
$language->isUnknown('de', 10)->willReturn(false);
$language->getSystemUid('de', 10)->willReturn(0);
$organisationRepository = $this->prophesize(OrganisationRepository::class);
$townRepository = $this->prophesize(TownRepository::class);
$subject = new TouristAttraction(
$parser->reveal(),
$language->reveal(),
$organisationRepository->reveal(),
$townRepository->reveal()
);
@ -191,6 +201,10 @@ class TouristAttractionTest extends TestCase
$parser->getAddress($jsonLD)->willReturn([]);
$parser->getMedia($jsonLD)->willReturn([]);
$language = $this->prophesize(LanguageHandling::class);
$language->isUnknown('de', 10)->willReturn(false);
$language->getSystemUid('de', 10)->willReturn(0);
$organisation = $this->prophesize(Organisation::class);
$organisation->getUid()->willReturn(10);
$organisationRepository = $this->prophesize(OrganisationRepository::class);
@ -206,6 +220,7 @@ class TouristAttractionTest extends TestCase
$subject = new TouristAttraction(
$parser->reveal(),
$language->reveal(),
$organisationRepository->reveal(),
$townRepository->reveal()
);

View file

@ -0,0 +1,144 @@
<?php
namespace WerkraumMedia\ThueCat\Tests\Unit\Domain\Import\Importer;
/*
* 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 PHPUnit\Framework\TestCase;
use Prophecy\PhpUnit\ProphecyTrait;
use TYPO3\CMS\Core\Site\Entity\Site;
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
use TYPO3\CMS\Core\Site\SiteFinder;
use WerkraumMedia\ThueCat\Domain\Import\Importer\LanguageHandling;
/**
* @covers WerkraumMedia\ThueCat\Domain\Import\Importer\LanguageHandling
*/
class LanguageHandlingTest extends TestCase
{
use ProphecyTrait;
/**
* @test
*/
public function canBeCreated(): void
{
$siteFinder = $this->prophesize(SiteFinder::class);
$subject = new LanguageHandling(
$siteFinder->reveal()
);
self::assertInstanceOf(LanguageHandling::class, $subject);
}
/**
* @test
*/
public function allowsToCheckForUnkownLanguage(): void
{
$language = $this->prophesize(SiteLanguage::class);
$language->getTwoLetterIsoCode()->willReturn('en');
$site = $this->prophesize(Site::class);
$site->getLanguages()->willReturn([$language->reveal()]);
$siteFinder = $this->prophesize(SiteFinder::class);
$siteFinder->getSiteByPageId(10)->willReturn($site->reveal());
$subject = new LanguageHandling(
$siteFinder->reveal()
);
$result = $subject->isUnknown('de', 10);
self::assertTrue($result);
}
/**
* @test
*/
public function allowsToCheckForKnownLanguage(): void
{
$language = $this->prophesize(SiteLanguage::class);
$language->getTwoLetterIsoCode()->willReturn('de');
$site = $this->prophesize(Site::class);
$site->getLanguages()->willReturn([$language->reveal()]);
$siteFinder = $this->prophesize(SiteFinder::class);
$siteFinder->getSiteByPageId(10)->willReturn($site->reveal());
$subject = new LanguageHandling(
$siteFinder->reveal()
);
$result = $subject->isUnknown('de', 10);
self::assertFalse($result);
}
/**
* @test
*/
public function providesSystemLanguageUidForLanguage(): void
{
$language = $this->prophesize(SiteLanguage::class);
$language->getTwoLetterIsoCode()->willReturn('de');
$language->getLanguageId()->willReturn(2);
$site = $this->prophesize(Site::class);
$site->getLanguages()->willReturn([$language->reveal()]);
$siteFinder = $this->prophesize(SiteFinder::class);
$siteFinder->getSiteByPageId(10)->willReturn($site->reveal());
$subject = new LanguageHandling(
$siteFinder->reveal()
);
$result = $subject->getSystemUid('de', 10);
self::assertSame(2, $result);
}
/**
* @test
*/
public function providesZeroAsFallbackSystemLanguageUidForUnkownLanguage(): void
{
$language = $this->prophesize(SiteLanguage::class);
$language->getTwoLetterIsoCode()->willReturn('fr');
$site = $this->prophesize(Site::class);
$site->getLanguages()->willReturn([$language->reveal()]);
$siteFinder = $this->prophesize(SiteFinder::class);
$siteFinder->getSiteByPageId(10)->willReturn($site->reveal());
$subject = new LanguageHandling(
$siteFinder->reveal()
);
$result = $subject->getSystemUid('de', 10);
self::assertSame(0, $result);
}
}

View file

@ -96,43 +96,39 @@ class EntityCollectionTest extends TestCase
/**
* @test
*/
public function returnsAllTranslatedEntities(): void
public function returnsEntitiesToTranslate(): 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);
$entityWithTranslation = $this->prophesize(Entity::class);
$entityWithTranslation->isTranslation()->willReturn(true);
$entityWithTranslation->exists()->willReturn(false);
$subject = new EntityCollection();
$subject->add($entityWithTranslation1->reveal());
$subject->add($entitiyWithDefaultLanguage->reveal());
$subject->add($entityWithTranslation2->reveal());
$subject->add($entityWithTranslation->reveal());
self::assertSame(
[
0 => $entityWithTranslation1->reveal(),
2 => $entityWithTranslation2->reveal(),
$entityWithTranslation->reveal(),
],
$subject->getTranslatedEntities()
$subject->getEntitiesToTranslate()
);
}
/**
* @test
*/
public function returnsEmptyArrayIfNoTranslatedEntityExists(): void
public function returnsExistingEntities(): void
{
$entitiyWithDefaultLanguage = $this->prophesize(Entity::class);
$entitiyWithDefaultLanguage->isTranslation()->willReturn(false);
$entityWithTranslation = $this->prophesize(Entity::class);
$entityWithTranslation->exists()->willReturn(true);
$subject = new EntityCollection();
$subject->add($entitiyWithDefaultLanguage->reveal());
$subject->add($entityWithTranslation->reveal());
self::assertSame(
[],
$subject->getTranslatedEntities()
[
$entityWithTranslation->reveal(),
],
$subject->getExistingEntities()
);
}
}

View file

@ -175,12 +175,28 @@ class GenericEntityTest extends TestCase
'',
[]
);
self::assertSame(
false,
self::assertFalse(
$subject->wasCreated()
);
}
/**
* @test
*/
public function returnsNotExistingByDefault(): void
{
$subject = new GenericEntity(
0,
'',
0,
'',
[]
);
self::assertFalse(
$subject->exists()
);
}
/**
* @test
*/
@ -213,7 +229,8 @@ class GenericEntityTest extends TestCase
);
$subject->setImportedTypo3Uid(10);
self::assertSame(true, $subject->wasCreated());
self::assertTrue($subject->wasCreated());
self::assertTrue($subject->exists());
self::assertSame(10, $subject->getTypo3Uid());
}
@ -231,7 +248,8 @@ class GenericEntityTest extends TestCase
);
$subject->setExistingTypo3Uid(10);
self::assertSame(false, $subject->wasCreated());
self::assertFalse($subject->wasCreated());
self::assertTrue($subject->exists());
self::assertSame(10, $subject->getTypo3Uid());
}
}

View file

@ -23,6 +23,7 @@ CREATE TABLE tx_thuecat_organisation (
description text DEFAULT '' NOT NULL,
manages_towns int(11) unsigned DEFAULT '0' NOT NULL,
manages_tourist_information int(11) unsigned DEFAULT '0' NOT NULL,
manages_tourist_attraction int(11) unsigned DEFAULT '0' NOT NULL,
);
CREATE TABLE tx_thuecat_town (

View file

@ -2,7 +2,7 @@ parameters:
ignoreErrors:
-
message: "#^Cannot call method fetchColumn\\(\\) on Doctrine\\\\DBAL\\\\Driver\\\\Statement\\|int\\.$#"
count: 1
count: 2
path: Classes/Domain/Import/Importer/SaveData.php
-
@ -21,7 +21,12 @@ parameters:
path: Classes/Domain/Repository/Backend/TownRepository.php
-
message: "#^Cannot call method fetchAll\\(\\) on Doctrine\\\\DBAL\\\\Driver\\\\Statement\\|int\\.$#"
message: "#^Argument of an invalid type Doctrine\\\\DBAL\\\\Driver\\\\Statement\\|int supplied for foreach, only iterables are supported\\.$#"
count: 1
path: Classes/Frontend/DataProcessing/ResolveEntities.php
-
message: "#^Cannot call method getLanguageOverlay\\(\\) on string\\|TYPO3\\\\CMS\\\\Core\\\\Domain\\\\Repository\\\\PageRepository\\.$#"
count: 1
path: Classes/Frontend/DataProcessing/ResolveEntities.php