Support TYPO3 v12 and PHP 8.3

Use rector, PHPUnit, Codeception and PHPStan to find and fix issues.
This commit is contained in:
Daniel Siepmann 2023-11-30 13:52:23 +01:00
parent 8a2c543ab2
commit d39053b498
Signed by: Daniel Siepmann
GPG key ID: 33D6629915560EF4
201 changed files with 4383 additions and 3663 deletions

20
.gitattributes vendored
View file

@ -1,12 +1,10 @@
Tests export-ignore
shell.nix export-ignore
.github export-ignore
.gitattributes export-ignore
.gitignore export-ignore
ecs.php export-ignore
phpstan.neon export-ignore
phpstan-baseline.neon export-ignore
phpunit.xml.dist export-ignore
codeception.dist.yml export-ignore codeception.dist.yml export-ignore
.gitattributes export-ignore
.github export-ignore
.gitignore export-ignore
.php-cs-fixer.dist.php export-ignore
phpstan-baseline.neon export-ignore
phpstan.neon export-ignore
phpunit.xml.dist export-ignore
shell.nix export-ignore
Tests export-ignore

View file

@ -15,10 +15,9 @@ jobs:
strategy: strategy:
matrix: matrix:
php-version: php-version:
- 7.4
- 8.0
- 8.1 - 8.1
- 8.2 - 8.2
- 8.3
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
@ -41,7 +40,7 @@ jobs:
- name: Install PHP - name: Install PHP
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
with: with:
php-version: "7.4" php-version: "8.2"
tools: composer:v2 tools: composer:v2
- name: Install xmllint - name: Install xmllint
@ -70,14 +69,14 @@ jobs:
- name: Install PHP - name: Install PHP
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
with: with:
php-version: "7.4" php-version: "8.2"
tools: composer:v2 tools: composer:v2
- name: Install dependencies - name: Install dependencies
run: composer install --prefer-dist --no-progress --no-suggest run: composer install --prefer-dist --no-progress --no-suggest
- name: Coding Guideline - name: Coding Guideline
run: ./vendor/bin/ecs check run: ./vendor/bin/php-cs-fixer fix --dry-run --diff
code-quality: code-quality:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -86,16 +85,12 @@ jobs:
strategy: strategy:
matrix: matrix:
include: include:
- php-version: '7.4'
typo3-version: '^10.4'
- php-version: '7.4'
typo3-version: '^11.5'
- php-version: '8.0'
typo3-version: '^11.5'
- php-version: '8.1' - php-version: '8.1'
typo3-version: '^11.5' typo3-version: '^12.4'
- php-version: '8.2' - php-version: '8.2'
typo3-version: '^11.5' typo3-version: '^12.4'
- php-version: '8.3'
typo3-version: '^12.4'
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@ -121,23 +116,14 @@ jobs:
strategy: strategy:
matrix: matrix:
include: include:
- php-version: '7.4'
typo3-version: '^10.4'
db-version: '5.6'
- php-version: '7.4'
typo3-version: '^10.4'
db-version: '8'
- php-version: '7.4'
typo3-version: '^11.5'
db-version: '8'
- php-version: '8.0'
typo3-version: '^11.5'
db-version: '8'
- php-version: '8.1' - php-version: '8.1'
typo3-version: '^11.5' typo3-version: '^12.4'
db-version: '8' db-version: '8'
- php-version: '8.2' - php-version: '8.2'
typo3-version: '^11.5' typo3-version: '^12.4'
db-version: '8'
- php-version: '8.3'
typo3-version: '^12.4'
db-version: '8' db-version: '8'
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@ -151,10 +137,16 @@ jobs:
- name: Setup MySQL - name: Setup MySQL
uses: mirromutth/mysql-action@v1.1 uses: mirromutth/mysql-action@v1.1
with: with:
mysql version: '5.7' mysql version: "${{ matrix.db-version }}"
mysql database: 'typo3' mysql database: 'typo3'
mysql root password: 'root' mysql root password: 'root'
- name: Wait for MySQL
run: |
while ! mysqladmin ping --host=127.0.0.1 --password=root --silent; do
sleep 1
done
- name: Install dependencies - name: Install dependencies
run: composer require --no-interaction --prefer-dist --no-progress "typo3/cms-backend:${{ matrix.typo3-version }}" "typo3/cms-core:${{ matrix.typo3-version }}" "typo3/cms-extbase:${{ matrix.typo3-version }}" "typo3/cms-frontend:${{ matrix.typo3-version }}" "typo3/cms-fluid-styled-content:${{ matrix.typo3-version }}" run: composer require --no-interaction --prefer-dist --no-progress "typo3/cms-backend:${{ matrix.typo3-version }}" "typo3/cms-core:${{ matrix.typo3-version }}" "typo3/cms-extbase:${{ matrix.typo3-version }}" "typo3/cms-frontend:${{ matrix.typo3-version }}" "typo3/cms-fluid-styled-content:${{ matrix.typo3-version }}"
@ -165,4 +157,16 @@ jobs:
export typo3DatabaseHost="127.0.0.1" export typo3DatabaseHost="127.0.0.1"
export typo3DatabaseUsername="root" export typo3DatabaseUsername="root"
export typo3DatabasePassword="root" export typo3DatabasePassword="root"
./vendor/bin/phpunit --testdox ./vendor/bin/phpunit
tests-acceptance:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: cachix/install-nix-action@v24
with:
nix_path: nixpkgs=channel:nixos-unstable
- name: Run Acceptance Tests
run: nix-shell --run project-test-acceptance

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
/.Build/ /.Build/
/.phpunit.cache
/composer.lock /composer.lock
/vendor/ /vendor/

73
.php-cs-fixer.dist.php Normal file
View file

@ -0,0 +1,73 @@
<?php
$finder = (new PhpCsFixer\Finder())
->ignoreVCSIgnored(true)
->in(realpath(__DIR__))
;
return (new \PhpCsFixer\Config())
->setRiskyAllowed(true)
->setRules([
'@DoctrineAnnotation' => true,
'@PSR2' => true,
'array_indentation' => true,
'array_syntax' => ['syntax' => 'short'],
'attribute_empty_parentheses' => true,
'blank_line_after_opening_tag' => true,
'braces' => ['allow_single_line_closure' => true],
'cast_spaces' => ['space' => 'none'],
'compact_nullable_typehint' => true,
'concat_space' => ['spacing' => 'one'],
'declare_equal_normalize' => ['space' => 'none'],
'declare_strict_types' => true,
'dir_constant' => true,
'fully_qualified_strict_types' => false,
'function_to_constant' => ['functions' => ['get_called_class', 'get_class', 'get_class_this', 'php_sapi_name', 'phpversion', 'pi']],
'function_typehint_space' => true,
'global_namespace_import' => ['import_classes' => true, 'import_constants' => true, 'import_functions' => true],
'lowercase_cast' => true,
'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline'],
'modernize_strpos' => true,
'modernize_types_casting' => true,
'multiline_whitespace_before_semicolons' => ['strategy' => 'new_line_for_chained_calls'],
'native_function_casing' => true,
'new_with_braces' => true,
'no_alias_functions' => true,
'no_blank_lines_after_phpdoc' => true,
'no_empty_phpdoc' => true,
'no_empty_statement' => true,
'no_extra_blank_lines' => true,
'no_leading_import_slash' => true,
'no_leading_namespace_whitespace' => true,
'no_multiline_whitespace_around_double_arrow' => true,
'no_null_property_initialization' => true,
'no_short_bool_cast' => true,
'no_singleline_whitespace_before_semicolons' => true,
'no_superfluous_elseif' => true,
'no_trailing_comma_in_singleline_array' => true,
'no_unneeded_control_parentheses' => true,
'no_unneeded_import_alias' => true,
'no_unused_imports' => true,
'no_useless_else' => true,
'no_whitespace_in_blank_line' => true,
'ordered_imports' => true,
'php_unit_construct' => ['assertions' => ['assertEquals', 'assertSame', 'assertNotEquals', 'assertNotSame']],
'php_unit_mock_short_will_return' => true,
'php_unit_test_case_static_method_calls' => ['call_type' => 'self'],
'phpdoc_no_access' => true,
'phpdoc_no_package' => true,
'phpdoc_order' => ['order' => ['test', 'dataProvider', 'param', 'throws', 'return']],
'phpdoc_scalar' => true,
'phpdoc_separation' => ['groups' => [['Extbase\\*']]],
'phpdoc_trim' => true,
'phpdoc_types' => true,
'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'],
'return_type_declaration' => ['space_before' => 'none'],
'single_line_comment_style' => ['comment_types' => ['hash']],
'single_quote' => true,
'single_trait_insert_per_statement' => true,
'trailing_comma_in_multiline' => ['elements' => ['arrays']],
'whitespace_after_comma_in_array' => true,
'yoda_style' => ['equal' => false, 'identical' => false, 'less_and_greater' => false],
])
->setFinder($finder)
;

View file

@ -34,24 +34,11 @@ use WerkraumMedia\ThueCat\Domain\Repository\Backend\ImportConfigurationRepositor
class ImportConfigurationCommand extends Command class ImportConfigurationCommand extends Command
{ {
/**
* @var ImportConfigurationRepository
*/
private $importConfigurationRepository;
/**
* @var Importer
*/
private $importer;
public function __construct( public function __construct(
ImportConfigurationRepository $importConfigurationRepository, private readonly ImportConfigurationRepository $importConfigurationRepository,
Importer $importer private readonly Importer $importer
) { ) {
parent::__construct(); parent::__construct();
$this->importConfigurationRepository = $importConfigurationRepository;
$this->importer = $importer;
} }
protected function configure(): void protected function configure(): void

View file

@ -23,22 +23,30 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Controller\Backend; namespace WerkraumMedia\ThueCat\Controller\Backend;
use TYPO3\CMS\Backend\View\BackendTemplateView; use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Backend\Template\ModuleTemplate;
use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
abstract class AbstractController extends ActionController abstract class AbstractController extends ActionController
{ {
/** private ModuleTemplateFactory $moduleTemplateFactory;
* BackendTemplateContainer
*
* @var BackendTemplateView
*/
protected $view;
/** protected ModuleTemplate $moduleTemplate;
* Backend Template Container
* public function injectModuleTemplateFactory(ModuleTemplateFactory $moduleTemplateFactory): void
* @var string {
*/ $this->moduleTemplateFactory = $moduleTemplateFactory;
protected $defaultViewObjectName = BackendTemplateView::class; }
protected function initializeView(): void
{
$this->moduleTemplate = $this->moduleTemplateFactory->create($this->request);
$this->moduleTemplate->assign('settings', $this->settings);
}
protected function htmlResponse(?string $html = null): ResponseInterface
{
return parent::htmlResponse($html ?? $this->moduleTemplate->render());
}
} }

View file

@ -23,34 +23,25 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Controller\Backend; namespace WerkraumMedia\ThueCat\Controller\Backend;
use Psr\Http\Message\ResponseInterface;
use WerkraumMedia\ThueCat\Domain\Repository\Backend\ImportConfigurationRepository; use WerkraumMedia\ThueCat\Domain\Repository\Backend\ImportConfigurationRepository;
use WerkraumMedia\ThueCat\Domain\Repository\Backend\OrganisationRepository; use WerkraumMedia\ThueCat\Domain\Repository\Backend\OrganisationRepository;
class ConfigurationController extends AbstractController class ConfigurationController extends AbstractController
{ {
/**
* @var OrganisationRepository
*/
private $organisationRepository;
/**
* @var ImportConfigurationRepository
*/
private $importConfigurationRepository;
public function __construct( public function __construct(
OrganisationRepository $organisationRepository, private readonly OrganisationRepository $organisationRepository,
ImportConfigurationRepository $importConfigurationRepository private readonly ImportConfigurationRepository $importConfigurationRepository
) { ) {
$this->organisationRepository = $organisationRepository;
$this->importConfigurationRepository = $importConfigurationRepository;
} }
public function indexAction(): void public function indexAction(): ResponseInterface
{ {
$this->view->assignMultiple([ $this->moduleTemplate->assignMultiple([
'importConfigurations' => $this->importConfigurationRepository->findAll(), 'importConfigurations' => $this->importConfigurationRepository->findAll(),
'organisations' => $this->organisationRepository->findAll(), 'organisations' => $this->organisationRepository->findAll(),
]); ]);
return $this->htmlResponse();
} }
} }

View file

@ -23,8 +23,9 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Controller\Backend; namespace WerkraumMedia\ThueCat\Controller\Backend;
use TYPO3\CMS\Core\Messaging\AbstractMessage; use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Annotation as Extbase; use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
use TYPO3\CMS\Extbase\Annotation\IgnoreValidation;
use WerkraumMedia\ThueCat\Domain\Import\Importer; use WerkraumMedia\ThueCat\Domain\Import\Importer;
use WerkraumMedia\ThueCat\Domain\Model\Backend\ImportConfiguration; use WerkraumMedia\ThueCat\Domain\Model\Backend\ImportConfiguration;
use WerkraumMedia\ThueCat\Domain\Repository\Backend\ImportLogRepository; use WerkraumMedia\ThueCat\Domain\Repository\Backend\ImportLogRepository;
@ -33,42 +34,24 @@ use WerkraumMedia\ThueCat\Typo3Wrapper\TranslationService;
class ImportController extends AbstractController class ImportController extends AbstractController
{ {
/**
* @var Importer
*/
private $importer;
/**
* @var ImportLogRepository
*/
private $repository;
/**
* @var TranslationService
*/
private $translation;
public function __construct( public function __construct(
Importer $importer, private readonly Importer $importer,
ImportLogRepository $repository, private readonly ImportLogRepository $repository,
TranslationService $translation private readonly TranslationService $translation
) { ) {
$this->importer = $importer;
$this->repository = $repository;
$this->translation = $translation;
} }
public function indexAction(): void public function indexAction(): ResponseInterface
{ {
$this->view->assignMultiple([ $this->moduleTemplate->assignMultiple([
'imports' => $this->repository->findAll(), 'imports' => $this->repository->findAll(),
]); ]);
return $this->htmlResponse();
} }
/** #[IgnoreValidation(['argumentName' => 'importConfiguration'])]
* @Extbase\IgnoreValidation("importConfiguration") public function importAction(ImportConfiguration $importConfiguration): ResponseInterface
*/
public function importAction(ImportConfiguration $importConfiguration): void
{ {
$importLog = $this->importer->importConfiguration($importConfiguration); $importLog = $this->importer->importConfiguration($importConfiguration);
@ -78,7 +61,7 @@ class ImportController extends AbstractController
$this->createImportDoneFlashMessage($importConfiguration); $this->createImportDoneFlashMessage($importConfiguration);
} }
$this->redirect('index', 'Backend\Configuration'); return $this->redirect('index', 'Backend\Configuration');
} }
private function createImportErrorFlashMessage(ImportConfiguration $importConfiguration): void private function createImportErrorFlashMessage(ImportConfiguration $importConfiguration): void
@ -93,7 +76,7 @@ class ImportController extends AbstractController
'controller.backend.import.import.error.title', 'controller.backend.import.import.error.title',
Extension::EXTENSION_NAME Extension::EXTENSION_NAME
), ),
AbstractMessage::ERROR ContextualFeedbackSeverity::ERROR
); );
} }
@ -109,7 +92,7 @@ class ImportController extends AbstractController
'controller.backend.import.import.success.title', 'controller.backend.import.import.success.title',
Extension::EXTENSION_NAME Extension::EXTENSION_NAME
), ),
AbstractMessage::OK ContextualFeedbackSeverity::OK
); );
} }
} }

View file

@ -109,6 +109,7 @@ class AccessibilityCertification implements MapsToType
/** /**
* @internal for mapping via Symfony component. * @internal for mapping via Symfony component.
*
* @param string|array $accessibilityCertificationStatus * @param string|array $accessibilityCertificationStatus
*/ */
public function setAccessibilityCertificationStatus($accessibilityCertificationStatus): void public function setAccessibilityCertificationStatus($accessibilityCertificationStatus): void
@ -121,6 +122,7 @@ class AccessibilityCertification implements MapsToType
/** /**
* @internal for mapping via Symfony component. * @internal for mapping via Symfony component.
*
* @param string|array $certificationAccessibilityDeaf * @param string|array $certificationAccessibilityDeaf
*/ */
public function setCertificationAccessibilityDeaf($certificationAccessibilityDeaf): void public function setCertificationAccessibilityDeaf($certificationAccessibilityDeaf): void
@ -133,6 +135,7 @@ class AccessibilityCertification implements MapsToType
/** /**
* @internal for mapping via Symfony component. * @internal for mapping via Symfony component.
*
* @param string|array $certificationAccessibilityMental * @param string|array $certificationAccessibilityMental
*/ */
public function setCertificationAccessibilityMental($certificationAccessibilityMental): void public function setCertificationAccessibilityMental($certificationAccessibilityMental): void
@ -145,6 +148,7 @@ class AccessibilityCertification implements MapsToType
/** /**
* @internal for mapping via Symfony component. * @internal for mapping via Symfony component.
*
* @param string|array $certificationAccessibilityPartiallyDeaf * @param string|array $certificationAccessibilityPartiallyDeaf
*/ */
public function setCertificationAccessibilityPartiallyDeaf($certificationAccessibilityPartiallyDeaf): void public function setCertificationAccessibilityPartiallyDeaf($certificationAccessibilityPartiallyDeaf): void
@ -157,6 +161,7 @@ class AccessibilityCertification implements MapsToType
/** /**
* @internal for mapping via Symfony component. * @internal for mapping via Symfony component.
*
* @param string|array $certificationAccessibilityPartiallyVisual * @param string|array $certificationAccessibilityPartiallyVisual
*/ */
public function setCertificationAccessibilityPartiallyVisual($certificationAccessibilityPartiallyVisual): void public function setCertificationAccessibilityPartiallyVisual($certificationAccessibilityPartiallyVisual): void
@ -169,6 +174,7 @@ class AccessibilityCertification implements MapsToType
/** /**
* @internal for mapping via Symfony component. * @internal for mapping via Symfony component.
*
* @param string|array $certificationAccessibilityVisual * @param string|array $certificationAccessibilityVisual
*/ */
public function setCertificationAccessibilityVisual($certificationAccessibilityVisual): void public function setCertificationAccessibilityVisual($certificationAccessibilityVisual): void
@ -181,6 +187,7 @@ class AccessibilityCertification implements MapsToType
/** /**
* @internal for mapping via Symfony component. * @internal for mapping via Symfony component.
*
* @param string|array $certificationAccessibilityWalking * @param string|array $certificationAccessibilityWalking
*/ */
public function setCertificationAccessibilityWalking($certificationAccessibilityWalking): void public function setCertificationAccessibilityWalking($certificationAccessibilityWalking): void
@ -193,6 +200,7 @@ class AccessibilityCertification implements MapsToType
/** /**
* @internal for mapping via Symfony component. * @internal for mapping via Symfony component.
*
* @param string|array $certificationAccessibilityWheelchair * @param string|array $certificationAccessibilityWheelchair
*/ */
public function setCertificationAccessibilityWheelchair($certificationAccessibilityWheelchair): void public function setCertificationAccessibilityWheelchair($certificationAccessibilityWheelchair): void

View file

@ -65,6 +65,7 @@ class Base extends Minimum
/** /**
* @internal for mapping via Symfony component. * @internal for mapping via Symfony component.
*
* @return ForeignReference[] * @return ForeignReference[]
*/ */
public function getImage(): array public function getImage(): array

View file

@ -35,6 +35,7 @@ interface MapsToType
* - thuecat:TouristInformation * - thuecat:TouristInformation
* - thuecat:Town * - thuecat:Town
* - * -
*
* @return string[] * @return string[]
*/ */
public static function getSupportedTypes(): array; public static function getSupportedTypes(): array;

View file

@ -106,6 +106,7 @@ class MediaObject extends Minimum implements MapsToType
/** /**
* @internal for mapping via Symfony component. * @internal for mapping via Symfony component.
*
* @param string|ForeignReference $author * @param string|ForeignReference $author
*/ */
public function setAuthor($author): void public function setAuthor($author): void

View file

@ -108,6 +108,7 @@ abstract class Minimum
/** /**
* @internal for mapping via Symfony component. * @internal for mapping via Symfony component.
*
* @param string|array $url * @param string|array $url
*/ */
public function setUrl($url): void public function setUrl($url): void

View file

@ -23,14 +23,15 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Import\Entity; namespace WerkraumMedia\ThueCat\Domain\Import\Entity;
use DateTimeImmutable;
use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\GeneralUtility;
use WerkraumMedia\ThueCat\Domain\Import\EntityMapper\PropertyValues;
use WerkraumMedia\ThueCat\Domain\Import\Entity\Properties\Address; use WerkraumMedia\ThueCat\Domain\Import\Entity\Properties\Address;
use WerkraumMedia\ThueCat\Domain\Import\Entity\Properties\ForeignReference; use WerkraumMedia\ThueCat\Domain\Import\Entity\Properties\ForeignReference;
use WerkraumMedia\ThueCat\Domain\Import\Entity\Properties\Geo; use WerkraumMedia\ThueCat\Domain\Import\Entity\Properties\Geo;
use WerkraumMedia\ThueCat\Domain\Import\Entity\Properties\OpeningHour; use WerkraumMedia\ThueCat\Domain\Import\Entity\Properties\OpeningHour;
use WerkraumMedia\ThueCat\Domain\Import\Entity\Shared\ContainedInPlace; use WerkraumMedia\ThueCat\Domain\Import\Entity\Shared\ContainedInPlace;
use WerkraumMedia\ThueCat\Domain\Import\Entity\Shared\Organization; use WerkraumMedia\ThueCat\Domain\Import\Entity\Shared\Organization;
use WerkraumMedia\ThueCat\Domain\Import\EntityMapper\PropertyValues;
use WerkraumMedia\ThueCat\Service\DateBasedFilter; use WerkraumMedia\ThueCat\Service\DateBasedFilter;
class Place extends Base class Place extends Base
@ -161,10 +162,11 @@ class Place extends Base
return GeneralUtility::makeInstance(DateBasedFilter::class) return GeneralUtility::makeInstance(DateBasedFilter::class)
->filterOutPreviousDates( ->filterOutPreviousDates(
$this->openingHoursSpecifications, $this->openingHoursSpecifications,
function (OpeningHour $hour): ?\DateTimeImmutable { function (OpeningHour $hour): ?DateTimeImmutable {
return $hour->getValidThrough(); return $hour->getValidThrough();
} }
); )
;
} }
/** /**
@ -175,10 +177,11 @@ class Place extends Base
return GeneralUtility::makeInstance(DateBasedFilter::class) return GeneralUtility::makeInstance(DateBasedFilter::class)
->filterOutPreviousDates( ->filterOutPreviousDates(
$this->specialOpeningHours, $this->specialOpeningHours,
function (OpeningHour $hour): ?\DateTimeImmutable { function (OpeningHour $hour): ?DateTimeImmutable {
return $hour->getValidThrough(); return $hour->getValidThrough();
} }
); )
;
} }
/** /**
@ -252,6 +255,7 @@ class Place extends Base
/** /**
* @internal for mapping via Symfony component. * @internal for mapping via Symfony component.
*
* @param string|array $sanitation * @param string|array $sanitation
*/ */
public function setSanitation($sanitation): void public function setSanitation($sanitation): void
@ -265,6 +269,7 @@ class Place extends Base
/** /**
* @internal for mapping via Symfony component. * @internal for mapping via Symfony component.
*
* @param string|array $otherService * @param string|array $otherService
*/ */
public function setOtherService($otherService): void public function setOtherService($otherService): void
@ -278,6 +283,7 @@ class Place extends Base
/** /**
* @internal for mapping via Symfony component. * @internal for mapping via Symfony component.
*
* @param string|array $trafficInfrastructure * @param string|array $trafficInfrastructure
*/ */
public function setTrafficInfrastructure($trafficInfrastructure): void public function setTrafficInfrastructure($trafficInfrastructure): void
@ -291,6 +297,7 @@ class Place extends Base
/** /**
* @internal for mapping via Symfony component. * @internal for mapping via Symfony component.
*
* @param string|array $paymentAccepted * @param string|array $paymentAccepted
*/ */
public function setPaymentAccepted($paymentAccepted): void public function setPaymentAccepted($paymentAccepted): void

View file

@ -23,8 +23,8 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Import\Entity\Properties; namespace WerkraumMedia\ThueCat\Domain\Import\Entity\Properties;
use WerkraumMedia\ThueCat\Domain\Import\EntityMapper\PropertyValues;
use WerkraumMedia\ThueCat\Domain\Import\Entity\Minimum; use WerkraumMedia\ThueCat\Domain\Import\Entity\Minimum;
use WerkraumMedia\ThueCat\Domain\Import\EntityMapper\PropertyValues;
class Offer extends Minimum class Offer extends Minimum
{ {
@ -48,6 +48,7 @@ class Offer extends Minimum
/** /**
* @internal for mapping via Symfony component. * @internal for mapping via Symfony component.
*
* @param string|array $offerType * @param string|array $offerType
*/ */
public function setOfferType($offerType): void public function setOfferType($offerType): void
@ -72,6 +73,7 @@ class Offer extends Minimum
/** /**
* @return PriceSpecification[] * @return PriceSpecification[]
*
* @internal for mapping via Symfony component. * @internal for mapping via Symfony component.
*/ */
public function getPriceSpecification(): array public function getPriceSpecification(): array

View file

@ -23,25 +23,27 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Import\Entity\Properties; namespace WerkraumMedia\ThueCat\Domain\Import\Entity\Properties;
use DateTimeImmutable;
class OpeningHour class OpeningHour
{ {
/** /**
* @var \DateTimeImmutable|null * @var DateTimeImmutable|null
*/ */
protected $validFrom = null; protected $validFrom;
/** /**
* @var \DateTimeImmutable|null * @var DateTimeImmutable|null
*/ */
protected $validThrough = null; protected $validThrough;
/** /**
* @var \DateTimeImmutable * @var DateTimeImmutable
*/ */
protected $opens; protected $opens;
/** /**
* @var \DateTimeImmutable * @var DateTimeImmutable
*/ */
protected $closes; protected $closes;
@ -50,22 +52,22 @@ class OpeningHour
*/ */
protected $daysOfWeek = []; protected $daysOfWeek = [];
public function getValidFrom(): ?\DateTimeImmutable public function getValidFrom(): ?DateTimeImmutable
{ {
return $this->validFrom; return $this->validFrom;
} }
public function getValidThrough(): ?\DateTimeImmutable public function getValidThrough(): ?DateTimeImmutable
{ {
return $this->validThrough; return $this->validThrough;
} }
public function getOpens(): \DateTimeImmutable public function getOpens(): DateTimeImmutable
{ {
return $this->opens; return $this->opens;
} }
public function getCloses(): \DateTimeImmutable public function getCloses(): DateTimeImmutable
{ {
return $this->closes; return $this->closes;
} }
@ -81,7 +83,7 @@ class OpeningHour
/** /**
* @internal for mapping via Symfony component. * @internal for mapping via Symfony component.
*/ */
public function setValidFrom(\DateTimeImmutable $validFrom): void public function setValidFrom(DateTimeImmutable $validFrom): void
{ {
$this->validFrom = $validFrom; $this->validFrom = $validFrom;
} }
@ -89,7 +91,7 @@ class OpeningHour
/** /**
* @internal for mapping via Symfony component. * @internal for mapping via Symfony component.
*/ */
public function setValidThrough(\DateTimeImmutable $validThrough): void public function setValidThrough(DateTimeImmutable $validThrough): void
{ {
$this->validThrough = $validThrough; $this->validThrough = $validThrough;
} }
@ -97,7 +99,7 @@ class OpeningHour
/** /**
* @internal for mapping via Symfony component. * @internal for mapping via Symfony component.
*/ */
public function setOpens(\DateTimeImmutable $opens): void public function setOpens(DateTimeImmutable $opens): void
{ {
$this->opens = $opens; $this->opens = $opens;
} }
@ -105,7 +107,7 @@ class OpeningHour
/** /**
* @internal for mapping via Symfony component. * @internal for mapping via Symfony component.
*/ */
public function setCloses(\DateTimeImmutable $closes): void public function setCloses(DateTimeImmutable $closes): void
{ {
$this->closes = $closes; $this->closes = $closes;
} }

View file

@ -23,8 +23,8 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Import\Entity\Properties; namespace WerkraumMedia\ThueCat\Domain\Import\Entity\Properties;
use WerkraumMedia\ThueCat\Domain\Import\EntityMapper\PropertyValues;
use WerkraumMedia\ThueCat\Domain\Import\Entity\Minimum; use WerkraumMedia\ThueCat\Domain\Import\Entity\Minimum;
use WerkraumMedia\ThueCat\Domain\Import\EntityMapper\PropertyValues;
class PriceSpecification extends Minimum class PriceSpecification extends Minimum
{ {
@ -85,6 +85,7 @@ class PriceSpecification extends Minimum
/** /**
* @internal for mapping via Symfony component. * @internal for mapping via Symfony component.
*
* @param string|array $calculationRule * @param string|array $calculationRule
*/ */
public function setCalculationRule($calculationRule): void public function setCalculationRule($calculationRule): void

View file

@ -42,6 +42,7 @@ trait ContainedInPlace
/** /**
* @return ForeignReference[] * @return ForeignReference[]
*
* @internal for mapping via Symfony component. * @internal for mapping via Symfony component.
*/ */
public function getContainedInPlace(): array public function getContainedInPlace(): array

View file

@ -46,6 +46,7 @@ trait Organization
/** /**
* @internal for mapping via Symfony component. * @internal for mapping via Symfony component.
*
* @return Offer[] * @return Offer[]
*/ */
public function getMakesOffer(): array public function getMakesOffer(): array

View file

@ -160,6 +160,7 @@ class TouristAttraction extends Place implements MapsToType
/** /**
* @internal for mapping via Symfony component. * @internal for mapping via Symfony component.
*
* @param string|array $museumService * @param string|array $museumService
*/ */
public function setMuseumService($museumService): void public function setMuseumService($museumService): void
@ -173,6 +174,7 @@ class TouristAttraction extends Place implements MapsToType
/** /**
* @internal for mapping via Symfony component. * @internal for mapping via Symfony component.
*
* @param string|array $architecturalStyle * @param string|array $architecturalStyle
*/ */
public function setArchitecturalStyle($architecturalStyle): void public function setArchitecturalStyle($architecturalStyle): void
@ -186,6 +188,7 @@ class TouristAttraction extends Place implements MapsToType
/** /**
* @internal for mapping via Symfony component. * @internal for mapping via Symfony component.
*
* @param string|array $digitalOffer * @param string|array $digitalOffer
*/ */
public function setDigitalOffer($digitalOffer): void public function setDigitalOffer($digitalOffer): void
@ -199,6 +202,7 @@ class TouristAttraction extends Place implements MapsToType
/** /**
* @internal for mapping via Symfony component. * @internal for mapping via Symfony component.
*
* @param string|array $photography * @param string|array $photography
*/ */
public function setPhotography($photography): void public function setPhotography($photography): void
@ -236,6 +240,7 @@ class TouristAttraction extends Place implements MapsToType
/** /**
* @internal for mapping via Symfony component. * @internal for mapping via Symfony component.
*
* @param string|array $availableLanguage * @param string|array $availableLanguage
*/ */
public function setAvailableLanguage($availableLanguage): void public function setAvailableLanguage($availableLanguage): void

View file

@ -30,6 +30,7 @@ use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\Serializer;
use Throwable;
use WerkraumMedia\ThueCat\Domain\Import\EntityMapper\ArrayDenormalizer; use WerkraumMedia\ThueCat\Domain\Import\EntityMapper\ArrayDenormalizer;
use WerkraumMedia\ThueCat\Domain\Import\EntityMapper\CustomAnnotationExtractor; use WerkraumMedia\ThueCat\Domain\Import\EntityMapper\CustomAnnotationExtractor;
use WerkraumMedia\ThueCat\Domain\Import\EntityMapper\JsonDecode; use WerkraumMedia\ThueCat\Domain\Import\EntityMapper\JsonDecode;
@ -55,7 +56,7 @@ class EntityMapper
'json', 'json',
$context $context
); );
} catch (\Throwable $e) { } catch (Throwable $e) {
throw new MappingException($jsonLD, $targetClassName, $e); throw new MappingException($jsonLD, $targetClassName, $e);
} }
} }

View file

@ -23,13 +23,21 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Import\EntityMapper; namespace WerkraumMedia\ThueCat\Domain\Import\EntityMapper;
use function in_array;
use InvalidArgumentException;
use LogicException; use LogicException;
use phpDocumentor\Reflection\DocBlock; use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\DocBlock\Tags\InvalidTag;
use phpDocumentor\Reflection\DocBlock\Tags\Param;
use phpDocumentor\Reflection\DocBlockFactory; use phpDocumentor\Reflection\DocBlockFactory;
use phpDocumentor\Reflection\DocBlockFactoryInterface; use phpDocumentor\Reflection\DocBlockFactoryInterface;
use phpDocumentor\Reflection\DocBlock\Tags\InvalidTag;
use phpDocumentor\Reflection\Types\Context; use phpDocumentor\Reflection\Types\Context;
use phpDocumentor\Reflection\Types\ContextFactory; use phpDocumentor\Reflection\Types\ContextFactory;
use ReflectionClass;
use ReflectionException;
use ReflectionMethod;
use ReflectionProperty;
use RuntimeException;
use Symfony\Component\PropertyInfo\Extractor\ConstructorArgumentTypeExtractorInterface; use Symfony\Component\PropertyInfo\Extractor\ConstructorArgumentTypeExtractorInterface;
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface; use Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface;
@ -75,7 +83,7 @@ class CustomAnnotationExtractor implements PropertyDescriptionExtractorInterface
public function __construct(DocBlockFactoryInterface $docBlockFactory = null, array $mutatorPrefixes = null, array $accessorPrefixes = null, array $arrayMutatorPrefixes = null) public function __construct(DocBlockFactoryInterface $docBlockFactory = null, array $mutatorPrefixes = null, array $accessorPrefixes = null, array $arrayMutatorPrefixes = null)
{ {
if (!class_exists(DocBlockFactory::class)) { if (!class_exists(DocBlockFactory::class)) {
throw new \LogicException(sprintf('Unable to use the "%s" class as the "phpdocumentor/reflection-docblock" package is not installed. Try running composer require "phpdocumentor/reflection-docblock".', __CLASS__)); throw new LogicException(sprintf('Unable to use the "%s" class as the "phpdocumentor/reflection-docblock" package is not installed. Try running composer require "phpdocumentor/reflection-docblock".', __CLASS__));
} }
$this->docBlockFactory = $docBlockFactory ?: DocBlockFactory::createInstance(); $this->docBlockFactory = $docBlockFactory ?: DocBlockFactory::createInstance();
@ -129,7 +137,7 @@ class CustomAnnotationExtractor implements PropertyDescriptionExtractorInterface
$contents = $docBlock->getDescription()->render(); $contents = $docBlock->getDescription()->render();
return '' === $contents ? null : $contents; return $contents === '' ? null : $contents;
} }
/** /**
@ -161,7 +169,7 @@ class CustomAnnotationExtractor implements PropertyDescriptionExtractorInterface
$types = []; $types = [];
/** @var DocBlock\Tags\Var_|DocBlock\Tags\Return_|DocBlock\Tags\Param $tag */ /** @var DocBlock\Tags\Var_|DocBlock\Tags\Return_|DocBlock\Tags\Param $tag */
foreach ($docBlock->getTagsByName($tag) as $tag) { foreach ($docBlock->getTagsByName($tag) as $tag) {
if ($tag && !$tag instanceof InvalidTag && null !== $tag->getType()) { if ($tag && !$tag instanceof InvalidTag && $tag->getType() !== null) {
foreach ($this->phpDocTypeHelper->getTypes($tag->getType()) as $type) { foreach ($this->phpDocTypeHelper->getTypes($tag->getType()) as $type) {
switch ($type->getClassName()) { switch ($type->getClassName()) {
case 'self': case 'self':
@ -189,7 +197,7 @@ class CustomAnnotationExtractor implements PropertyDescriptionExtractorInterface
return null; return null;
} }
if (!\in_array($prefix, $this->arrayMutatorPrefixes)) { if (!in_array($prefix, $this->arrayMutatorPrefixes)) {
return $types; return $types;
} }
@ -210,12 +218,12 @@ class CustomAnnotationExtractor implements PropertyDescriptionExtractorInterface
$types = []; $types = [];
/** @var DocBlock\Tags\Var_|DocBlock\Tags\Return_|DocBlock\Tags\Param $tag */ /** @var DocBlock\Tags\Var_|DocBlock\Tags\Return_|DocBlock\Tags\Param $tag */
foreach ($docBlock->getTagsByName('param') as $tag) { foreach ($docBlock->getTagsByName('param') as $tag) {
if ($tag && null !== $tag->getType()) { if ($tag && $tag->getType() !== null) {
$types[] = $this->phpDocTypeHelper->getTypes($tag->getType()); $types[] = $this->phpDocTypeHelper->getTypes($tag->getType());
} }
} }
if (!isset($types[0]) || [] === $types[0]) { if (!isset($types[0]) || $types[0] === []) {
return null; return null;
} }
@ -225,8 +233,8 @@ class CustomAnnotationExtractor implements PropertyDescriptionExtractorInterface
private function getDocBlockFromConstructor(string $class, string $property): ?DocBlock private function getDocBlockFromConstructor(string $class, string $property): ?DocBlock
{ {
try { try {
$reflectionClass = new \ReflectionClass($class); $reflectionClass = new ReflectionClass($class);
} catch (\ReflectionException $e) { } catch (ReflectionException $e) {
return null; return null;
} }
$reflectionConstructor = $reflectionClass->getConstructor(); $reflectionConstructor = $reflectionClass->getConstructor();
@ -238,7 +246,7 @@ class CustomAnnotationExtractor implements PropertyDescriptionExtractorInterface
$docBlock = $this->docBlockFactory->create($reflectionConstructor, $this->contextFactory->createFromReflector($reflectionConstructor)); $docBlock = $this->docBlockFactory->create($reflectionConstructor, $this->contextFactory->createFromReflector($reflectionConstructor));
return $this->filterDocBlockParams($docBlock, $property); return $this->filterDocBlockParams($docBlock, $property);
} catch (\InvalidArgumentException $e) { } catch (InvalidArgumentException $e) {
return null; return null;
} }
} }
@ -246,11 +254,18 @@ class CustomAnnotationExtractor implements PropertyDescriptionExtractorInterface
private function filterDocBlockParams(DocBlock $docBlock, string $allowedParam): DocBlock private function filterDocBlockParams(DocBlock $docBlock, string $allowedParam): DocBlock
{ {
$tags = array_values(array_filter($docBlock->getTagsByName('param'), function ($tag) use ($allowedParam) { $tags = array_values(array_filter($docBlock->getTagsByName('param'), function ($tag) use ($allowedParam) {
return $tag instanceof DocBlock\Tags\Param && $allowedParam === $tag->getVariableName(); return $tag instanceof Param && $allowedParam === $tag->getVariableName();
})); }));
return new DocBlock($docBlock->getSummary(), $docBlock->getDescription(), $tags, $docBlock->getContext(), return new DocBlock(
$docBlock->getLocation(), $docBlock->isTemplateStart(), $docBlock->isTemplateEnd()); $docBlock->getSummary(),
$docBlock->getDescription(),
$tags,
$docBlock->getContext(),
$docBlock->getLocation(),
$docBlock->isTemplateStart(),
$docBlock->isTemplateEnd()
);
} }
/** /**
@ -290,8 +305,8 @@ class CustomAnnotationExtractor implements PropertyDescriptionExtractorInterface
{ {
// Use a ReflectionProperty instead of $class to get the parent class if applicable // Use a ReflectionProperty instead of $class to get the parent class if applicable
try { try {
$reflectionProperty = new \ReflectionProperty($class, $property); $reflectionProperty = new ReflectionProperty($class, $property);
} catch (\ReflectionException $e) { } catch (ReflectionException $e) {
return null; return null;
} }
@ -305,7 +320,7 @@ class CustomAnnotationExtractor implements PropertyDescriptionExtractorInterface
try { try {
return $this->docBlockFactory->create($reflectionProperty, $this->createFromReflector($reflector)); return $this->docBlockFactory->create($reflectionProperty, $this->createFromReflector($reflector));
} catch (\InvalidArgumentException|\RuntimeException $e) { } catch (InvalidArgumentException|RuntimeException $e) {
return null; return null;
} }
} }
@ -315,25 +330,25 @@ class CustomAnnotationExtractor implements PropertyDescriptionExtractorInterface
*/ */
private function getDocBlockFromMethod(string $class, string $ucFirstProperty, int $type): ?array private function getDocBlockFromMethod(string $class, string $ucFirstProperty, int $type): ?array
{ {
$prefixes = self::ACCESSOR === $type ? $this->accessorPrefixes : $this->mutatorPrefixes; $prefixes = $type === self::ACCESSOR ? $this->accessorPrefixes : $this->mutatorPrefixes;
$prefix = null; $prefix = null;
foreach ($prefixes as $prefix) { foreach ($prefixes as $prefix) {
$methodName = $prefix . $ucFirstProperty; $methodName = $prefix . $ucFirstProperty;
try { try {
$reflectionMethod = new \ReflectionMethod($class, $methodName); $reflectionMethod = new ReflectionMethod($class, $methodName);
if ($reflectionMethod->isStatic()) { if ($reflectionMethod->isStatic()) {
continue; continue;
} }
if ( if (
(self::ACCESSOR === $type && 0 === $reflectionMethod->getNumberOfRequiredParameters()) || ($type === self::ACCESSOR && $reflectionMethod->getNumberOfRequiredParameters() === 0) ||
(self::MUTATOR === $type && $reflectionMethod->getNumberOfParameters() >= 1) ($type === self::MUTATOR && $reflectionMethod->getNumberOfParameters() >= 1)
) { ) {
break; break;
} }
} catch (\ReflectionException $e) { } catch (ReflectionException $e) {
// Try the next prefix if the method doesn't exist // Try the next prefix if the method doesn't exist
} }
} }
@ -352,7 +367,7 @@ class CustomAnnotationExtractor implements PropertyDescriptionExtractorInterface
try { try {
return [$this->docBlockFactory->create($reflectionMethod, $this->createFromReflector($reflector)), $prefix]; return [$this->docBlockFactory->create($reflectionMethod, $this->createFromReflector($reflector)), $prefix];
} catch (\InvalidArgumentException|\RuntimeException $e) { } catch (InvalidArgumentException|RuntimeException $e) {
return null; return null;
} }
} }
@ -360,7 +375,7 @@ class CustomAnnotationExtractor implements PropertyDescriptionExtractorInterface
/** /**
* Prevents a lot of redundant calls to ContextFactory::createForNamespace(). * Prevents a lot of redundant calls to ContextFactory::createForNamespace().
*/ */
private function createFromReflector(\ReflectionClass $reflector): Context private function createFromReflector(ReflectionClass $reflector): Context
{ {
$cacheKey = $reflector->getNamespaceName() . ':' . $reflector->getFileName(); $cacheKey = $reflector->getNamespaceName() . ':' . $reflector->getFileName();

View file

@ -23,9 +23,6 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Import\EntityMapper; namespace WerkraumMedia\ThueCat\Domain\Import\EntityMapper;
use TYPO3\CMS\Core\Utility\ArrayUtility;
/** /**
* Registry with supported entities and their types. * Registry with supported entities and their types.
*/ */
@ -70,10 +67,9 @@ class EntityRegistry
return ''; return '';
} }
$matches = ArrayUtility::sortArraysByKey( usort($matches, static function (array $configA, array $configB): int {
$matches, return $configA['priority'] <=> $configB['priority'];
'priority' });
);
return end($matches)['entityClassName']; return end($matches)['entityClassName'];
} }

View file

@ -23,9 +23,9 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Import\EntityMapper; namespace WerkraumMedia\ThueCat\Domain\Import\EntityMapper;
use InvalidArgumentException;
use function str_starts_with;
use Symfony\Component\Serializer\Encoder\JsonDecode as SymfonyJsonDecode; use Symfony\Component\Serializer\Encoder\JsonDecode as SymfonyJsonDecode;
use TYPO3\CMS\Core\Utility\StringUtility;
/** /**
* Used to add further necessary normalization on decoding incoming JSON structure. * Used to add further necessary normalization on decoding incoming JSON structure.
@ -50,14 +50,13 @@ class JsonDecode extends SymfonyJsonDecode
string $data, string $data,
string $format, string $format,
array $context = [] array $context = []
) { ): mixed {
$context[self::ASSOCIATIVE] = true; $context[self::ASSOCIATIVE] = true;
$result = parent::decode($data, $format, $context); $result = parent::decode($data, $format, $context);
$activeLanguage = $context[self::ACTIVE_LANGUAGE] ?? ''; $activeLanguage = $context[self::ACTIVE_LANGUAGE] ?? '';
if ($activeLanguage === '') { if ($activeLanguage === '') {
throw new \InvalidArgumentException('Provide active language: ' . self::ACTIVE_LANGUAGE); throw new InvalidArgumentException('Provide active language: ' . self::ACTIVE_LANGUAGE);
} }
return $this->process( return $this->process(
@ -95,6 +94,7 @@ class JsonDecode extends SymfonyJsonDecode
* This decode will resolve the list to a single value based on current language settings from context. * This decode will resolve the list to a single value based on current language settings from context.
* *
* @param mixed $value * @param mixed $value
*
* @return mixed * @return mixed
*/ */
private function decodeLanguageSpecificValue( private function decodeLanguageSpecificValue(
@ -156,6 +156,7 @@ class JsonDecode extends SymfonyJsonDecode
* This decode will resolve single values wrapped in array with extra info. * This decode will resolve single values wrapped in array with extra info.
* *
* @param mixed $value * @param mixed $value
*
* @return mixed * @return mixed
*/ */
private function decodeSingleValues( private function decodeSingleValues(
@ -190,6 +191,7 @@ class JsonDecode extends SymfonyJsonDecode
* Prepare data structure for PHP \DateTimeImmutable. * Prepare data structure for PHP \DateTimeImmutable.
* *
* @param mixed $value * @param mixed $value
*
* @return mixed * @return mixed
*/ */
private function decodeDateTime( private function decodeDateTime(
@ -214,6 +216,7 @@ class JsonDecode extends SymfonyJsonDecode
/** /**
* @param mixed $key * @param mixed $key
*
* @return mixed * @return mixed
*/ */
private function mapKey($key) private function mapKey($key)
@ -222,13 +225,13 @@ class JsonDecode extends SymfonyJsonDecode
return $key; return $key;
} }
if (StringUtility::beginsWith($key, '@')) { if (str_starts_with($key, '@')) {
return mb_substr($key, 1); return mb_substr($key, 1);
} }
if (StringUtility::beginsWith($key, 'schema:')) { if (str_starts_with($key, 'schema:')) {
return mb_substr($key, 7); return mb_substr($key, 7);
} }
if (StringUtility::beginsWith($key, 'thuecat:')) { if (str_starts_with($key, 'thuecat:')) {
return mb_substr($key, 8); return mb_substr($key, 8);
} }
@ -238,7 +241,7 @@ class JsonDecode extends SymfonyJsonDecode
private function doesRuleMatch(array $rule, string $type): bool private function doesRuleMatch(array $rule, string $type): bool
{ {
if ($rule['type'] === 'beginsWith') { if ($rule['type'] === 'beginsWith') {
return StringUtility::beginsWith($type, $rule['comparisonValue']); return str_starts_with($type, $rule['comparisonValue']);
} }
return false; return false;

View file

@ -23,7 +23,10 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Import\EntityMapper; namespace WerkraumMedia\ThueCat\Domain\Import\EntityMapper;
class MappingException extends \Exception use Exception;
use Throwable;
class MappingException extends Exception
{ {
/** /**
* @var array * @var array
@ -38,7 +41,7 @@ class MappingException extends \Exception
public function __construct( public function __construct(
array $jsonLD, array $jsonLD,
string $targetClassName, string $targetClassName,
\Throwable $previous Throwable $previous
) { ) {
parent::__construct( parent::__construct(
'Could not map incoming JSON-LD to target object: ' . $previous->getMessage(), 'Could not map incoming JSON-LD to target object: ' . $previous->getMessage(),

View file

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Import; namespace WerkraumMedia\ThueCat\Domain\Import;
use InvalidArgumentException;
use WerkraumMedia\ThueCat\Domain\Model\Backend\ImportConfiguration as Typo3ImportConfiguration; use WerkraumMedia\ThueCat\Domain\Model\Backend\ImportConfiguration as Typo3ImportConfiguration;
use WerkraumMedia\ThueCat\Domain\Model\Backend\ImportLog; use WerkraumMedia\ThueCat\Domain\Model\Backend\ImportLog;
@ -57,7 +58,7 @@ class Import
public function start(ImportConfiguration $configuration): void public function start(ImportConfiguration $configuration): void
{ {
if (!$configuration instanceof Typo3ImportConfiguration) { if (!$configuration instanceof Typo3ImportConfiguration) {
throw new \InvalidArgumentException('Currently only can process ImportConfiguration of TYPO3.', 1629708772); throw new InvalidArgumentException('Currently only can process ImportConfiguration of TYPO3.', 1629708772);
} }
$this->currentConfiguration = $configuration; $this->currentConfiguration = $configuration;

View file

@ -23,12 +23,13 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Import; namespace WerkraumMedia\ThueCat\Domain\Import;
use TYPO3\CMS\Core\Log\LogManager; use Exception;
use TYPO3\CMS\Core\Log\Logger; use TYPO3\CMS\Core\Log\Logger;
use TYPO3\CMS\Core\Log\LogManager;
use WerkraumMedia\ThueCat\Domain\Import\Entity\MapsToType;
use WerkraumMedia\ThueCat\Domain\Import\EntityMapper\EntityRegistry; use WerkraumMedia\ThueCat\Domain\Import\EntityMapper\EntityRegistry;
use WerkraumMedia\ThueCat\Domain\Import\EntityMapper\JsonDecode; use WerkraumMedia\ThueCat\Domain\Import\EntityMapper\JsonDecode;
use WerkraumMedia\ThueCat\Domain\Import\EntityMapper\MappingException; use WerkraumMedia\ThueCat\Domain\Import\EntityMapper\MappingException;
use WerkraumMedia\ThueCat\Domain\Import\Entity\MapsToType;
use WerkraumMedia\ThueCat\Domain\Import\Importer\Converter; use WerkraumMedia\ThueCat\Domain\Import\Importer\Converter;
use WerkraumMedia\ThueCat\Domain\Import\Importer\FetchData; use WerkraumMedia\ThueCat\Domain\Import\Importer\FetchData;
use WerkraumMedia\ThueCat\Domain\Import\Importer\Languages; use WerkraumMedia\ThueCat\Domain\Import\Importer\Languages;
@ -42,46 +43,6 @@ use WerkraumMedia\ThueCat\Domain\Repository\Backend\ImportLogRepository;
class Importer class Importer
{ {
/**
* @var UrlProviderRegistry
*/
private $urls;
/**
* @var Converter
*/
private $converter;
/**
* @var EntityRegistry
*/
private $entityRegistry;
/**
* @var EntityMapper
*/
private $entityMapper;
/**
* @var Languages
*/
private $languages;
/**
* @var FetchData
*/
private $fetchData;
/**
* @var SaveData
*/
private $saveData;
/**
* @var ImportLogRepository
*/
private $importLogRepository;
/** /**
* @var Logger * @var Logger
*/ */
@ -93,24 +54,16 @@ class Importer
private $import; private $import;
public function __construct( public function __construct(
UrlProviderRegistry $urls, private readonly UrlProviderRegistry $urls,
Converter $converter, private readonly Converter $converter,
EntityRegistry $entityRegistry, private readonly EntityRegistry $entityRegistry,
EntityMapper $entityMapper, private readonly EntityMapper $entityMapper,
Languages $languages, private readonly Languages $languages,
ImportLogRepository $importLogRepository, private readonly ImportLogRepository $importLogRepository,
FetchData $fetchData, private readonly FetchData $fetchData,
SaveData $saveData, private readonly SaveData $saveData,
LogManager $logManager LogManager $logManager
) { ) {
$this->urls = $urls;
$this->converter = $converter;
$this->entityRegistry = $entityRegistry;
$this->entityMapper = $entityMapper;
$this->languages = $languages;
$this->importLogRepository = $importLogRepository;
$this->fetchData = $fetchData;
$this->saveData = $saveData;
$this->logger = $logManager->getLogger(__CLASS__); $this->logger = $logManager->getLogger(__CLASS__);
$this->import = new Import(); $this->import = new Import();
} }
@ -139,7 +92,7 @@ class Importer
{ {
$urlProvider = $this->urls->getProviderForConfiguration($this->import->getConfiguration()); $urlProvider = $this->urls->getProviderForConfiguration($this->import->getConfiguration());
if (!$urlProvider instanceof UrlProvider) { if (!$urlProvider instanceof UrlProvider) {
throw new \Exception('No URL Provider available for given configuration.', 1629296635); throw new Exception('No URL Provider available for given configuration.', 1629296635);
} }
foreach ($urlProvider->getUrls() as $url) { foreach ($urlProvider->getUrls() as $url) {
@ -218,7 +171,8 @@ class Importer
'url' => $url, 'url' => $url,
'language' => $language, 'language' => $language,
'targetEntity' => $targetEntity, 'targetEntity' => $targetEntity,
]); ]
);
continue; continue;
} }
$entities->add($convertedEntity); $entities->add($convertedEntity);

View file

@ -32,21 +32,6 @@ use WerkraumMedia\ThueCat\Domain\Import\Importer\FetchData\InvalidResponseExcept
class FetchData class FetchData
{ {
/**
* @var RequestFactoryInterface
*/
private $requestFactory;
/**
* @var ClientInterface
*/
private $httpClient;
/**
* @var CacheFrontendInterface
*/
private $cache;
/** /**
* @var string * @var string
*/ */
@ -58,13 +43,10 @@ class FetchData
private $urlPrefix = 'https://thuecat.org'; private $urlPrefix = 'https://thuecat.org';
public function __construct( public function __construct(
RequestFactoryInterface $requestFactory, private readonly RequestFactoryInterface $requestFactory,
ClientInterface $httpClient, private readonly ClientInterface $httpClient,
CacheFrontendInterface $cache private readonly CacheFrontendInterface $cache
) { ) {
$this->requestFactory = $requestFactory;
$this->httpClient = $httpClient;
$this->cache = $cache;
} }
public function updatedNodes(string $scopeId): array public function updatedNodes(string $scopeId): array

View file

@ -23,6 +23,8 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Import\Importer\FetchData; namespace WerkraumMedia\ThueCat\Domain\Import\Importer\FetchData;
class InvalidResponseException extends \RuntimeException use RuntimeException;
class InvalidResponseException extends RuntimeException
{ {
} }

View file

@ -32,27 +32,15 @@ use WerkraumMedia\ThueCat\Domain\Model\Backend\ImportLogEntry\SavingEntity;
class SaveData class SaveData
{ {
/**
* @var DataHandler
*/
private $dataHandler;
/**
* @var ConnectionPool
*/
private $connectionPool;
/** /**
* @var mixed[] * @var mixed[]
*/ */
private $errorLog; private $errorLog;
public function __construct( public function __construct(
DataHandler $dataHandler, private readonly DataHandler $dataHandler,
ConnectionPool $connectionPool private readonly ConnectionPool $connectionPool
) { ) {
$this->dataHandler = $dataHandler;
$this->connectionPool = $connectionPool;
} }
public function import(EntityCollection $entityCollection, ImportLog $log): void public function import(EntityCollection $entityCollection, ImportLog $log): void
@ -176,7 +164,8 @@ class SaveData
$tableColumns = $this->connectionPool $tableColumns = $this->connectionPool
->getConnectionForTable($entity->getTypo3DatabaseTableName()) ->getConnectionForTable($entity->getTypo3DatabaseTableName())
->getSchemaManager() ->getSchemaManager()
->listTableColumns($entity->getTypo3DatabaseTableName()); ->listTableColumns($entity->getTypo3DatabaseTableName())
;
$queryBuilder = $this->connectionPool->getQueryBuilderForTable($entity->getTypo3DatabaseTableName()); $queryBuilder = $this->connectionPool->getQueryBuilderForTable($entity->getTypo3DatabaseTableName());
$queryBuilder->getRestrictions()->removeAll(); $queryBuilder->getRestrictions()->removeAll();
@ -193,7 +182,7 @@ class SaveData
)); ));
} }
$result = $queryBuilder->execute()->fetchColumn(); $result = $queryBuilder->executeQuery()->fetchOne();
if (is_numeric($result)) { if (is_numeric($result)) {
return (int)$result; return (int)$result;
} }
@ -216,7 +205,7 @@ class SaveData
$queryBuilder->createNamedParameter(0) $queryBuilder->createNamedParameter(0)
)); ));
$result = $queryBuilder->execute()->fetchColumn(); $result = $queryBuilder->executeQuery()->fetchOne();
if (is_numeric($result)) { if (is_numeric($result)) {
return (int)$result; return (int)$result;
} }

View file

@ -61,8 +61,7 @@ class EntityCollection
{ {
return array_filter($this->entities, function (Entity $entity) { return array_filter($this->entities, function (Entity $entity) {
return $entity->isTranslation() return $entity->isTranslation()
&& $entity->exists() === false && $entity->exists() === false;
;
}); });
} }

View file

@ -32,29 +32,11 @@ use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
class RequestFactory implements RequestFactoryInterface class RequestFactory implements RequestFactoryInterface
{ {
/**
* @var ExtensionConfiguration
*/
private $extensionConfiguration;
/**
* @var RequestFactoryInterface
*/
private $requestFactory;
/**
* @var UriFactoryInterface
*/
private $uriFactory;
public function __construct( public function __construct(
ExtensionConfiguration $extensionConfiguration, private readonly ExtensionConfiguration $extensionConfiguration,
RequestFactoryInterface $requestFactory, private readonly RequestFactoryInterface $requestFactory,
UriFactoryInterface $uriFactory private readonly UriFactoryInterface $uriFactory
) { ) {
$this->extensionConfiguration = $extensionConfiguration;
$this->requestFactory = $requestFactory;
$this->uriFactory = $uriFactory;
} }
/** /**

View file

@ -23,9 +23,9 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Import; namespace WerkraumMedia\ThueCat\Domain\Import;
use WerkraumMedia\ThueCat\Domain\Import\Entity\Properties\ForeignReference;
use WerkraumMedia\ThueCat\Domain\Import\EntityMapper\EntityRegistry; use WerkraumMedia\ThueCat\Domain\Import\EntityMapper\EntityRegistry;
use WerkraumMedia\ThueCat\Domain\Import\EntityMapper\JsonDecode; use WerkraumMedia\ThueCat\Domain\Import\EntityMapper\JsonDecode;
use WerkraumMedia\ThueCat\Domain\Import\Entity\Properties\ForeignReference;
use WerkraumMedia\ThueCat\Domain\Import\Importer\FetchData; use WerkraumMedia\ThueCat\Domain\Import\Importer\FetchData;
use WerkraumMedia\ThueCat\Domain\Import\Importer\FetchData\InvalidResponseException; use WerkraumMedia\ThueCat\Domain\Import\Importer\FetchData\InvalidResponseException;
@ -38,29 +38,11 @@ use WerkraumMedia\ThueCat\Domain\Import\Importer\FetchData\InvalidResponseExcept
*/ */
class ResolveForeignReference class ResolveForeignReference
{ {
/**
* @var FetchData
*/
private $fetchData;
/**
* @var EntityRegistry
*/
private $entityRegistry;
/**
* @var EntityMapper
*/
private $entityMapper;
public function __construct( public function __construct(
FetchData $fetchData, private readonly FetchData $fetchData,
EntityRegistry $entityRegistry, private readonly EntityRegistry $entityRegistry,
EntityMapper $entityMapper private readonly EntityMapper $entityMapper
) { ) {
$this->fetchData = $fetchData;
$this->entityRegistry = $entityRegistry;
$this->entityMapper = $entityMapper;
} }
public function resolve( public function resolve(
@ -94,6 +76,7 @@ class ResolveForeignReference
/** /**
* @param ForeignReference[] $foreignReferences * @param ForeignReference[] $foreignReferences
*
* @return string[] * @return string[]
*/ */
public static function convertToRemoteIds(array $foreignReferences): array public static function convertToRemoteIds(array $foreignReferences): array

View file

@ -23,6 +23,8 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Import; namespace WerkraumMedia\ThueCat\Domain\Import;
use Exception;
use InvalidArgumentException;
use WerkraumMedia\ThueCat\Domain\Import\Entity\MapsToType; use WerkraumMedia\ThueCat\Domain\Import\Entity\MapsToType;
use WerkraumMedia\ThueCat\Domain\Import\Importer\Converter; use WerkraumMedia\ThueCat\Domain\Import\Importer\Converter;
use WerkraumMedia\ThueCat\Domain\Import\Model\Entity; use WerkraumMedia\ThueCat\Domain\Import\Model\Entity;
@ -32,15 +34,9 @@ use WerkraumMedia\ThueCat\Domain\Model\Backend\ImportConfiguration as Typo3Impor
class Typo3Converter implements Converter class Typo3Converter implements Converter
{ {
/**
* @var Registry
*/
private $registry;
public function __construct( public function __construct(
Registry $registry private readonly Registry $registry
) { ) {
$this->registry = $registry;
} }
public function convert( public function convert(
@ -49,12 +45,12 @@ class Typo3Converter implements Converter
string $language string $language
): ?Entity { ): ?Entity {
if (!$configuration instanceof Typo3ImportConfiguration) { if (!$configuration instanceof Typo3ImportConfiguration) {
throw new \InvalidArgumentException('Only supports TYPO3 import configuration.', 1629710386); throw new InvalidArgumentException('Only supports TYPO3 import configuration.', 1629710386);
} }
$concreteConverter = $this->registry->getConverterBasedOnType($mapped); $concreteConverter = $this->registry->getConverterBasedOnType($mapped);
if (!$concreteConverter instanceof Typo3ConcreteConverter) { if (!$concreteConverter instanceof Typo3ConcreteConverter) {
throw new \Exception( throw new Exception(
'No TYPO3 Converter registered for given Entity "' . get_class($mapped) . '".', 'No TYPO3 Converter registered for given Entity "' . get_class($mapped) . '".',
1628244329 1628244329
); );

View file

@ -23,8 +23,9 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Import\Typo3Converter; namespace WerkraumMedia\ThueCat\Domain\Import\Typo3Converter;
use TYPO3\CMS\Core\Log\LogManager; use Exception;
use TYPO3\CMS\Core\Log\Logger; use TYPO3\CMS\Core\Log\Logger;
use TYPO3\CMS\Core\Log\LogManager;
use TYPO3\CMS\Extbase\Persistence\QueryResultInterface; use TYPO3\CMS\Extbase\Persistence\QueryResultInterface;
use WerkraumMedia\ThueCat\Domain\Import\Entity\AccessibilitySpecification; use WerkraumMedia\ThueCat\Domain\Import\Entity\AccessibilitySpecification;
use WerkraumMedia\ThueCat\Domain\Import\Entity\Base; use WerkraumMedia\ThueCat\Domain\Import\Entity\Base;
@ -52,41 +53,6 @@ use WerkraumMedia\ThueCat\Domain\Repository\Backend\TownRepository;
class GeneralConverter implements Converter class GeneralConverter implements Converter
{ {
/**
* @var ResolveForeignReference
*/
private $resolveForeignReference;
/**
* @var Importer
*/
private $importer;
/**
* @var LanguageHandling
*/
private $languageHandling;
/**
* @var OrganisationRepository
*/
private $organisationRepository;
/**
* @var TownRepository
*/
private $townRepository;
/**
* @var ParkingFacilityRepository
*/
private $parkingFacilityRepository;
/**
* @var NameExtractor
*/
private $nameExtractor;
/** /**
* @var Logger * @var Logger
*/ */
@ -110,22 +76,15 @@ class GeneralConverter implements Converter
]; ];
public function __construct( public function __construct(
ResolveForeignReference $resolveForeignReference, private readonly ResolveForeignReference $resolveForeignReference,
Importer $importer, private readonly Importer $importer,
LanguageHandling $languageHandling, private readonly LanguageHandling $languageHandling,
OrganisationRepository $organisationRepository, private readonly OrganisationRepository $organisationRepository,
TownRepository $townRepository, private readonly TownRepository $townRepository,
ParkingFacilityRepository $parkingFacilityRepository, private readonly ParkingFacilityRepository $parkingFacilityRepository,
NameExtractor $nameExtractor, private readonly NameExtractor $nameExtractor,
LogManager $logManager LogManager $logManager
) { ) {
$this->resolveForeignReference = $resolveForeignReference;
$this->importer = $importer;
$this->languageHandling = $languageHandling;
$this->organisationRepository = $organisationRepository;
$this->townRepository = $townRepository;
$this->parkingFacilityRepository = $parkingFacilityRepository;
$this->nameExtractor = $nameExtractor;
$this->logger = $logManager->getLogger(__CLASS__); $this->logger = $logManager->getLogger(__CLASS__);
} }
@ -214,7 +173,7 @@ class GeneralConverter implements Converter
{ {
$tableName = $this->classToTableMapping[$className] ?? ''; $tableName = $this->classToTableMapping[$className] ?? '';
if ($tableName == '') { if ($tableName == '') {
throw new \Exception('No table name configured for class ' . $className, 1629376990); throw new Exception('No table name configured for class ' . $className, 1629376990);
} }
return $tableName; return $tableName;

View file

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Import\Typo3Converter; namespace WerkraumMedia\ThueCat\Domain\Import\Typo3Converter;
use InvalidArgumentException;
use TYPO3\CMS\Core\Site\Entity\SiteLanguage; use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
use TYPO3\CMS\Core\Site\SiteFinder; use TYPO3\CMS\Core\Site\SiteFinder;
use WerkraumMedia\ThueCat\Domain\Import\ImportConfiguration; use WerkraumMedia\ThueCat\Domain\Import\ImportConfiguration;
@ -30,36 +31,30 @@ use WerkraumMedia\ThueCat\Domain\Import\Importer\Languages;
class LanguageHandling implements Languages class LanguageHandling implements Languages
{ {
/**
* @var SiteFinder
*/
private $siteFinder;
public function __construct( public function __construct(
SiteFinder $siteFinder private readonly SiteFinder $siteFinder
) { ) {
$this->siteFinder = $siteFinder;
} }
public function getAvailable(ImportConfiguration $configuration): array public function getAvailable(ImportConfiguration $configuration): array
{ {
if (method_exists($configuration, 'getStoragePid') === false) { if (method_exists($configuration, 'getStoragePid') === false) {
throw new \InvalidArgumentException('Unsupported configuration, need to retrieve storage pid.', 1629710300); throw new InvalidArgumentException('Unsupported configuration, need to retrieve storage pid.', 1629710300);
} }
return array_map(function (SiteLanguage $language) { return array_map(function (SiteLanguage $language) {
return $language->getTwoLetterIsoCode(); return $language->getLocale()->getLanguageCode();
}, $this->getLanguages($configuration->getStoragePid())); }, $this->getLanguages($configuration->getStoragePid()));
} }
public function getLanguageUidForString(int $pageUid, string $isoCode): int public function getLanguageUidForString(int $pageUid, string $isoCode): int
{ {
foreach ($this->getLanguages($pageUid) as $language) { foreach ($this->getLanguages($pageUid) as $language) {
if ($language->getTwoLetterIsoCode() === $isoCode) { if ($language->getLocale()->getLanguageCode() === $isoCode) {
return $language->getLanguageId(); return $language->getLanguageId();
} }
} }
throw new \InvalidArgumentException( throw new InvalidArgumentException(
sprintf( sprintf(
'Could not find language for combination of page "%d" and iso code "%s".', 'Could not find language for combination of page "%d" and iso code "%s".',
$pageUid, $pageUid,

View file

@ -23,25 +23,20 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Import\UrlProvider; namespace WerkraumMedia\ThueCat\Domain\Import\UrlProvider;
use InvalidArgumentException;
use WerkraumMedia\ThueCat\Domain\Import\ImportConfiguration; use WerkraumMedia\ThueCat\Domain\Import\ImportConfiguration;
use WerkraumMedia\ThueCat\Domain\Import\Importer\FetchData; use WerkraumMedia\ThueCat\Domain\Import\Importer\FetchData;
class ContainsPlaceUrlProvider implements UrlProvider class ContainsPlaceUrlProvider implements UrlProvider
{ {
/**
* @var FetchData
*/
private $fetchData;
/** /**
* @var string * @var string
*/ */
private $containsPlaceId = ''; private $containsPlaceId = '';
public function __construct( public function __construct(
FetchData $fetchData private readonly FetchData $fetchData
) { ) {
$this->fetchData = $fetchData;
} }
public function canProvideForConfiguration( public function canProvideForConfiguration(
@ -54,7 +49,7 @@ class ContainsPlaceUrlProvider implements UrlProvider
ImportConfiguration $configuration ImportConfiguration $configuration
): UrlProvider { ): UrlProvider {
if (method_exists($configuration, 'getContainsPlaceId') === false) { if (method_exists($configuration, 'getContainsPlaceId') === false) {
throw new \InvalidArgumentException('Received incompatible import configuration.', 1629709276); throw new InvalidArgumentException('Received incompatible import configuration.', 1629709276);
} }
$instance = clone $this; $instance = clone $this;
$instance->containsPlaceId = $configuration->getContainsPlaceId(); $instance->containsPlaceId = $configuration->getContainsPlaceId();

View file

@ -23,25 +23,20 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Import\UrlProvider; namespace WerkraumMedia\ThueCat\Domain\Import\UrlProvider;
use InvalidArgumentException;
use WerkraumMedia\ThueCat\Domain\Import\ImportConfiguration; use WerkraumMedia\ThueCat\Domain\Import\ImportConfiguration;
use WerkraumMedia\ThueCat\Domain\Import\Importer\FetchData; use WerkraumMedia\ThueCat\Domain\Import\Importer\FetchData;
class SyncScopeUrlProvider implements UrlProvider class SyncScopeUrlProvider implements UrlProvider
{ {
/**
* @var FetchData
*/
private $fetchData;
/** /**
* @var string * @var string
*/ */
private $syncScopeId = ''; private $syncScopeId = '';
public function __construct( public function __construct(
FetchData $fetchData private readonly FetchData $fetchData
) { ) {
$this->fetchData = $fetchData;
} }
public function canProvideForConfiguration( public function canProvideForConfiguration(
@ -54,7 +49,7 @@ class SyncScopeUrlProvider implements UrlProvider
ImportConfiguration $configuration ImportConfiguration $configuration
): UrlProvider { ): UrlProvider {
if (method_exists($configuration, 'getSyncScopeId') === false) { if (method_exists($configuration, 'getSyncScopeId') === false) {
throw new \InvalidArgumentException('Received incompatible import configuration.', 1629709276); throw new InvalidArgumentException('Received incompatible import configuration.', 1629709276);
} }
$instance = clone $this; $instance = clone $this;
$instance->syncScopeId = $configuration->getSyncScopeId(); $instance->syncScopeId = $configuration->getSyncScopeId();

View file

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Model\Backend; namespace WerkraumMedia\ThueCat\Domain\Model\Backend;
use DateTimeImmutable;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity as Typo3AbstractEntity; use TYPO3\CMS\Extbase\DomainObject\AbstractEntity as Typo3AbstractEntity;
class AbstractEntity extends Typo3AbstractEntity class AbstractEntity extends Typo3AbstractEntity
@ -43,9 +44,9 @@ class AbstractEntity extends Typo3AbstractEntity
protected $description = ''; protected $description = '';
/** /**
* @var \DateTimeImmutable|null * @var DateTimeImmutable|null
*/ */
protected $tstamp = null; protected $tstamp;
public function getRemoteId(): string public function getRemoteId(): string
{ {
@ -62,7 +63,7 @@ class AbstractEntity extends Typo3AbstractEntity
return $this->description; return $this->description;
} }
public function getLastImported(): ?\DateTimeImmutable public function getLastImported(): ?DateTimeImmutable
{ {
return $this->tstamp; return $this->tstamp;
} }

View file

@ -23,6 +23,8 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Model\Backend; namespace WerkraumMedia\ThueCat\Domain\Model\Backend;
use DateTimeImmutable;
use Exception;
use TYPO3\CMS\Core\Utility\ArrayUtility; use TYPO3\CMS\Core\Utility\ArrayUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity; use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
@ -49,9 +51,9 @@ class ImportConfiguration extends AbstractEntity implements ImportConfigurationI
protected $configuration = ''; protected $configuration = '';
/** /**
* @var \DateTimeImmutable|null * @var DateTimeImmutable|null
*/ */
protected $tstamp = null; protected $tstamp;
/** /**
* @var ObjectStorage<ImportLog> * @var ObjectStorage<ImportLog>
@ -61,7 +63,7 @@ class ImportConfiguration extends AbstractEntity implements ImportConfigurationI
/** /**
* @var string[]|null * @var string[]|null
*/ */
protected $urls = null; protected $urls;
/** /**
* @var string[] * @var string[]
@ -88,15 +90,15 @@ class ImportConfiguration extends AbstractEntity implements ImportConfigurationI
return 'tx_thuecat_import_configuration'; return 'tx_thuecat_import_configuration';
} }
public function getLastChanged(): ?\DateTimeImmutable public function getLastChanged(): ?DateTimeImmutable
{ {
return $this->tstamp; return $this->tstamp;
} }
public function getLastImported(): ?\DateTimeImmutable public function getLastImported(): ?DateTimeImmutable
{ {
$lastImport = null; $lastImport = null;
$positionOfLastLog = (string) (count($this->logs) - 1); $positionOfLastLog = (count($this->logs) - 1);
if ($this->logs->offsetExists($positionOfLastLog)) { if ($this->logs->offsetExists($positionOfLastLog)) {
$lastImport = $this->logs->offsetGet($positionOfLastLog); $lastImport = $this->logs->offsetGet($positionOfLastLog);
} }
@ -112,7 +114,7 @@ class ImportConfiguration extends AbstractEntity implements ImportConfigurationI
$storagePid = $this->getConfigurationValueFromFlexForm('storagePid'); $storagePid = $this->getConfigurationValueFromFlexForm('storagePid');
if (is_numeric($storagePid) && $storagePid > 0) { if (is_numeric($storagePid) && $storagePid > 0) {
return intval($storagePid); return (int)$storagePid;
} }
return 0; return 0;
@ -151,7 +153,7 @@ class ImportConfiguration extends AbstractEntity implements ImportConfigurationI
{ {
$containsPlaceId = $this->getConfigurationValueFromFlexForm('containsPlaceId'); $containsPlaceId = $this->getConfigurationValueFromFlexForm('containsPlaceId');
if (!is_string($containsPlaceId)) { if (!is_string($containsPlaceId)) {
throw new \Exception('Could not fetch containsPlaceId.', 1671027015); throw new Exception('Could not fetch containsPlaceId.', 1671027015);
} }
return $containsPlaceId; return $containsPlaceId;
} }

View file

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Model\Backend; namespace WerkraumMedia\ThueCat\Domain\Model\Backend;
use DateTimeImmutable;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity as Typo3AbstractEntity; use TYPO3\CMS\Extbase\DomainObject\AbstractEntity as Typo3AbstractEntity;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage; use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
use WerkraumMedia\ThueCat\Domain\Model\Backend\ImportLogEntry\SavingEntity; use WerkraumMedia\ThueCat\Domain\Model\Backend\ImportLogEntry\SavingEntity;
@ -37,10 +38,10 @@ class ImportLog extends Typo3AbstractEntity
/** /**
* @var ImportConfiguration|null * @var ImportConfiguration|null
*/ */
protected $configuration = null; protected $configuration;
/** /**
* @var \DateTimeImmutable|null * @var DateTimeImmutable|null
*/ */
protected $crdate; protected $crdate;
@ -78,7 +79,7 @@ class ImportLog extends Typo3AbstractEntity
return $this->logEntries; return $this->logEntries;
} }
public function getCreated(): ?\DateTimeImmutable public function getCreated(): ?DateTimeImmutable
{ {
return $this->crdate; return $this->crdate;
} }

View file

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Model\Backend\ImportLogEntry; namespace WerkraumMedia\ThueCat\Domain\Model\Backend\ImportLogEntry;
use Exception;
use WerkraumMedia\ThueCat\Domain\Import\EntityMapper\MappingException; use WerkraumMedia\ThueCat\Domain\Import\EntityMapper\MappingException;
use WerkraumMedia\ThueCat\Domain\Model\Backend\ImportLogEntry; use WerkraumMedia\ThueCat\Domain\Model\Backend\ImportLogEntry;
@ -54,7 +55,7 @@ class MappingError extends ImportLogEntry
{ {
$errors = json_decode($this->errors, true); $errors = json_decode($this->errors, true);
if (is_array($errors) === false) { if (is_array($errors) === false) {
throw new \Exception('Could not parse errors.', 1671097690); throw new Exception('Could not parse errors.', 1671097690);
} }
return $errors; return $errors;
} }

View file

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Model\Backend\ImportLogEntry; namespace WerkraumMedia\ThueCat\Domain\Model\Backend\ImportLogEntry;
use Exception;
use WerkraumMedia\ThueCat\Domain\Import\Model\Entity; use WerkraumMedia\ThueCat\Domain\Import\Model\Entity;
use WerkraumMedia\ThueCat\Domain\Model\Backend\ImportLogEntry; use WerkraumMedia\ThueCat\Domain\Model\Backend\ImportLogEntry;
@ -100,7 +101,7 @@ class SavingEntity extends ImportLogEntry
if ($this->errorsAsArray === [] && $this->errors !== '') { if ($this->errorsAsArray === [] && $this->errors !== '') {
$errorsAsArray = json_decode($this->errors, true); $errorsAsArray = json_decode($this->errors, true);
if (is_array($errorsAsArray) === false) { if (is_array($errorsAsArray) === false) {
throw new \Exception('Could not parse errors.', 1671097690); throw new Exception('Could not parse errors.', 1671097690);
} }
$this->errorsAsArray = array_unique($errorsAsArray); $this->errorsAsArray = array_unique($errorsAsArray);
} }

View file

@ -40,7 +40,7 @@ abstract class Base extends AbstractEntity
/** /**
* @var Media|null * @var Media|null
*/ */
protected $media = null; protected $media;
public function getTitle(): string public function getTitle(): string
{ {

View file

@ -94,6 +94,7 @@ class Media implements TypeInterface
/** /**
* @internal Only used to set the values while mapping objects. * @internal Only used to set the values while mapping objects.
*
* @see: AfterObjectThawedHandler * @see: AfterObjectThawedHandler
*/ */
public function setEditorialImages(array $images): void public function setEditorialImages(array $images): void

View file

@ -23,6 +23,8 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Model\Frontend; namespace WerkraumMedia\ThueCat\Domain\Model\Frontend;
use DateTimeImmutable;
class MergedOpeningHour class MergedOpeningHour
{ {
/** /**
@ -31,19 +33,19 @@ class MergedOpeningHour
private $weekDays = []; private $weekDays = [];
/** /**
* @var \DateTimeImmutable|null * @var DateTimeImmutable|null
*/ */
private $from; private $from;
/** /**
* @var \DateTimeImmutable|null * @var DateTimeImmutable|null
*/ */
private $through; private $through;
public function __construct( public function __construct(
array $weekDays, array $weekDays,
?\DateTimeImmutable $from, ?DateTimeImmutable $from,
?\DateTimeImmutable $through ?DateTimeImmutable $through
) { ) {
$this->weekDays = array_values($weekDays); $this->weekDays = array_values($weekDays);
$this->from = $from; $this->from = $from;
@ -72,12 +74,12 @@ class MergedOpeningHour
]); ]);
} }
public function getFrom(): ?\DateTimeImmutable public function getFrom(): ?DateTimeImmutable
{ {
return $this->from; return $this->from;
} }
public function getThrough(): ?\DateTimeImmutable public function getThrough(): ?DateTimeImmutable
{ {
return $this->through; return $this->through;
} }

View file

@ -23,7 +23,10 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Model\Frontend; namespace WerkraumMedia\ThueCat\Domain\Model\Frontend;
class MergedOpeningHours implements \Iterator, \Countable use Countable;
use Iterator;
class MergedOpeningHours implements Iterator, Countable
{ {
/** /**
* @var MergedOpeningHour[] * @var MergedOpeningHour[]

View file

@ -95,7 +95,7 @@ class Offer
public function getType(): string public function getType(): string
{ {
$offerTypes = array_filter($this->types, function (string $type) { $offerTypes = array_filter($this->types, function (string $type) {
return strpos($type, 'Offer') !== false; return str_contains($type, 'Offer');
}); });
// Ensure clean index // Ensure clean index
$offerTypes = array_values($offerTypes); $offerTypes = array_values($offerTypes);

View file

@ -23,12 +23,14 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Model\Frontend; namespace WerkraumMedia\ThueCat\Domain\Model\Frontend;
use Countable;
use Iterator;
use TYPO3\CMS\Core\Type\TypeInterface; use TYPO3\CMS\Core\Type\TypeInterface;
/** /**
* @implements \Iterator<int, Offer> * @implements \Iterator<int, Offer>
*/ */
class Offers implements TypeInterface, \Iterator, \Countable class Offers implements TypeInterface, Iterator, Countable
{ {
/** /**
* @var string * @var string

View file

@ -23,6 +23,8 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Model\Frontend; namespace WerkraumMedia\ThueCat\Domain\Model\Frontend;
use DateTimeImmutable;
use DateTimeZone;
use WerkraumMedia\ThueCat\Domain\TimingFormat; use WerkraumMedia\ThueCat\Domain\TimingFormat;
class OpeningHour class OpeningHour
@ -43,12 +45,12 @@ class OpeningHour
private $daysOfWeek; private $daysOfWeek;
/** /**
* @var \DateTimeImmutable|null * @var DateTimeImmutable|null
*/ */
private $from; private $from;
/** /**
* @var \DateTimeImmutable|null * @var DateTimeImmutable|null
*/ */
private $through; private $through;
@ -56,8 +58,8 @@ class OpeningHour
string $opens, string $opens,
string $closes, string $closes,
array $daysOfWeek, array $daysOfWeek,
?\DateTimeImmutable $from, ?DateTimeImmutable $from,
?\DateTimeImmutable $through ?DateTimeImmutable $through
) { ) {
$this->opens = $opens; $this->opens = $opens;
$this->closes = $closes; $this->closes = $closes;
@ -73,13 +75,13 @@ class OpeningHour
{ {
$from = null; $from = null;
if (isset($rawData['from'])) { if (isset($rawData['from'])) {
$timeZone = new \DateTimeZone($rawData['from']['timezone'] ?? 'Europe/Berlin'); $timeZone = new DateTimeZone($rawData['from']['timezone'] ?? 'Europe/Berlin');
$from = new \DateTimeImmutable($rawData['from']['date'], $timeZone); $from = new DateTimeImmutable($rawData['from']['date'], $timeZone);
} }
$through = null; $through = null;
if (isset($rawData['through'])) { if (isset($rawData['through'])) {
$timeZone = new \DateTimeZone($rawData['through']['timezone'] ?? 'Europe/Berlin'); $timeZone = new DateTimeZone($rawData['through']['timezone'] ?? 'Europe/Berlin');
$through = new \DateTimeImmutable($rawData['through']['date'], $timeZone); $through = new DateTimeImmutable($rawData['through']['date'], $timeZone);
} }
return new self( return new self(
@ -120,12 +122,12 @@ class OpeningHour
]); ]);
} }
public function getFrom(): ?\DateTimeImmutable public function getFrom(): ?DateTimeImmutable
{ {
return $this->from; return $this->from;
} }
public function getThrough(): ?\DateTimeImmutable public function getThrough(): ?DateTimeImmutable
{ {
return $this->through; return $this->through;
} }
@ -135,10 +137,9 @@ class OpeningHour
$from = $this->getFrom(); $from = $this->getFrom();
$through = $this->getThrough(); $through = $this->getThrough();
return $from instanceof \DateTimeImmutable return $from instanceof DateTimeImmutable
&& $through instanceof \DateTimeImmutable && $through instanceof DateTimeImmutable
&& $from->format('Ymd') === $through->format('Ymd') && $from->format('Ymd') === $through->format('Ymd');
;
} }
private function sortedDaysOfWeek(array $sorting): array private function sortedDaysOfWeek(array $sorting): array

View file

@ -23,6 +23,9 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Model\Frontend; namespace WerkraumMedia\ThueCat\Domain\Model\Frontend;
use Countable;
use DateTimeImmutable;
use Iterator;
use TYPO3\CMS\Core\Type\TypeInterface; use TYPO3\CMS\Core\Type\TypeInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\GeneralUtility;
use WerkraumMedia\ThueCat\Service\DateBasedFilter; use WerkraumMedia\ThueCat\Service\DateBasedFilter;
@ -30,7 +33,7 @@ use WerkraumMedia\ThueCat\Service\DateBasedFilter;
/** /**
* @implements \Iterator<int, OpeningHour> * @implements \Iterator<int, OpeningHour>
*/ */
class OpeningHours implements TypeInterface, \Iterator, \Countable class OpeningHours implements TypeInterface, Iterator, Countable
{ {
/** /**
* @var string * @var string
@ -63,10 +66,11 @@ class OpeningHours implements TypeInterface, \Iterator, \Countable
$array = GeneralUtility::makeInstance(DateBasedFilter::class) $array = GeneralUtility::makeInstance(DateBasedFilter::class)
->filterOutPreviousDates( ->filterOutPreviousDates(
$array, $array,
function (OpeningHour $hour): ?\DateTimeImmutable { function (OpeningHour $hour): ?DateTimeImmutable {
return $hour->getThrough(); return $hour->getThrough();
} }
); )
;
usort($array, function (OpeningHour $hourA, OpeningHour $hourB) { usort($array, function (OpeningHour $hourA, OpeningHour $hourB) {
return $hourA->getFrom() <=> $hourB->getFrom(); return $hourA->getFrom() <=> $hourB->getFrom();

View file

@ -31,7 +31,7 @@ abstract class Place extends Base
/** /**
* @var Address|null * @var Address|null
*/ */
protected $address = null; protected $address;
/** /**
* @var string * @var string
@ -41,12 +41,12 @@ abstract class Place extends Base
/** /**
* @var OpeningHours|null * @var OpeningHours|null
*/ */
protected $openingHours = null; protected $openingHours;
/** /**
* @var OpeningHours|null * @var OpeningHours|null
*/ */
protected $specialOpeningHours = null; protected $specialOpeningHours;
/** /**
* @var ObjectStorage<ParkingFacility> * @var ObjectStorage<ParkingFacility>
@ -81,7 +81,7 @@ abstract class Place extends Base
/** /**
* @var AccessiblitySpecification|null * @var AccessiblitySpecification|null
*/ */
protected $accessibilitySpecification = null; protected $accessibilitySpecification;
public function initializeObject(): void public function initializeObject(): void
{ {

View file

@ -35,12 +35,12 @@ class TouristAttraction extends Place
/** /**
* @var Offers|null * @var Offers|null
*/ */
protected $offers = null; protected $offers;
/** /**
* @var Town|null * @var Town|null
*/ */
protected $town = null; protected $town;
/** /**
* @var string * @var string

View file

@ -23,7 +23,6 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Repository\Backend; namespace WerkraumMedia\ThueCat\Domain\Repository\Backend;
use TYPO3\CMS\Extbase\Object\ObjectManagerInterface;
use TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings; use TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings;
use TYPO3\CMS\Extbase\Persistence\Repository; use TYPO3\CMS\Extbase\Persistence\Repository;
use WerkraumMedia\ThueCat\Domain\Import\ImportConfiguration; use WerkraumMedia\ThueCat\Domain\Import\ImportConfiguration;
@ -31,10 +30,9 @@ use WerkraumMedia\ThueCat\Domain\Import\ImportConfiguration;
class ImportConfigurationRepository extends Repository class ImportConfigurationRepository extends Repository
{ {
public function __construct( public function __construct(
ObjectManagerInterface $objectManager,
Typo3QuerySettings $querySettings Typo3QuerySettings $querySettings
) { ) {
parent::__construct($objectManager); parent::__construct();
$querySettings->setRespectStoragePage(false); $querySettings->setRespectStoragePage(false);

View file

@ -24,7 +24,6 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Repository\Backend; namespace WerkraumMedia\ThueCat\Domain\Repository\Backend;
use TYPO3\CMS\Core\DataHandling\DataHandler; use TYPO3\CMS\Core\DataHandling\DataHandler;
use TYPO3\CMS\Extbase\Object\ObjectManagerInterface;
use TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings; use TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings;
use TYPO3\CMS\Extbase\Persistence\QueryInterface; use TYPO3\CMS\Extbase\Persistence\QueryInterface;
use TYPO3\CMS\Extbase\Persistence\Repository; use TYPO3\CMS\Extbase\Persistence\Repository;
@ -32,19 +31,11 @@ use WerkraumMedia\ThueCat\Domain\Model\Backend\ImportLog;
class ImportLogRepository extends Repository class ImportLogRepository extends Repository
{ {
/**
* @var DataHandler
*/
private $dataHandler;
public function __construct( public function __construct(
ObjectManagerInterface $objectManager, private readonly DataHandler $dataHandler,
DataHandler $dataHandler,
Typo3QuerySettings $querySettings Typo3QuerySettings $querySettings
) { ) {
parent::__construct($objectManager); parent::__construct();
$this->dataHandler = $dataHandler;
$querySettings->setRespectStoragePage(false); $querySettings->setRespectStoragePage(false);
$this->setDefaultQuerySettings($querySettings); $this->setDefaultQuerySettings($querySettings);

View file

@ -23,7 +23,6 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Repository\Backend; namespace WerkraumMedia\ThueCat\Domain\Repository\Backend;
use TYPO3\CMS\Extbase\Object\ObjectManagerInterface;
use TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings; use TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings;
use TYPO3\CMS\Extbase\Persistence\Repository; use TYPO3\CMS\Extbase\Persistence\Repository;
use WerkraumMedia\ThueCat\Domain\Model\Backend\Organisation; use WerkraumMedia\ThueCat\Domain\Model\Backend\Organisation;
@ -34,10 +33,9 @@ use WerkraumMedia\ThueCat\Domain\Model\Backend\Organisation;
class OrganisationRepository extends Repository class OrganisationRepository extends Repository
{ {
public function __construct( public function __construct(
ObjectManagerInterface $objectManager,
Typo3QuerySettings $querySettings Typo3QuerySettings $querySettings
) { ) {
parent::__construct($objectManager); parent::__construct();
$querySettings->setRespectStoragePage(false); $querySettings->setRespectStoragePage(false);
$this->setDefaultQuerySettings($querySettings); $this->setDefaultQuerySettings($querySettings);

View file

@ -23,7 +23,6 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Repository\Backend; namespace WerkraumMedia\ThueCat\Domain\Repository\Backend;
use TYPO3\CMS\Extbase\Object\ObjectManagerInterface;
use TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings; use TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings;
use TYPO3\CMS\Extbase\Persistence\QueryResultInterface; use TYPO3\CMS\Extbase\Persistence\QueryResultInterface;
use TYPO3\CMS\Extbase\Persistence\Repository; use TYPO3\CMS\Extbase\Persistence\Repository;
@ -32,10 +31,9 @@ use WerkraumMedia\ThueCat\Domain\Import\Entity\Properties\ForeignReference;
class ParkingFacilityRepository extends Repository class ParkingFacilityRepository extends Repository
{ {
public function __construct( public function __construct(
ObjectManagerInterface $objectManager,
Typo3QuerySettings $querySettings Typo3QuerySettings $querySettings
) { ) {
parent::__construct($objectManager); parent::__construct();
$querySettings->setRespectStoragePage(false); $querySettings->setRespectStoragePage(false);

View file

@ -23,7 +23,6 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Domain\Repository\Backend; namespace WerkraumMedia\ThueCat\Domain\Repository\Backend;
use TYPO3\CMS\Extbase\Object\ObjectManagerInterface;
use TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings; use TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings;
use TYPO3\CMS\Extbase\Persistence\Repository; use TYPO3\CMS\Extbase\Persistence\Repository;
use WerkraumMedia\ThueCat\Domain\Import\ResolveForeignReference; use WerkraumMedia\ThueCat\Domain\Import\ResolveForeignReference;
@ -32,10 +31,9 @@ use WerkraumMedia\ThueCat\Domain\Model\Backend\Town;
class TownRepository extends Repository class TownRepository extends Repository
{ {
public function __construct( public function __construct(
ObjectManagerInterface $objectManager,
Typo3QuerySettings $querySettings Typo3QuerySettings $querySettings
) { ) {
parent::__construct($objectManager); parent::__construct();
$querySettings->setRespectStoragePage(false); $querySettings->setRespectStoragePage(false);

View file

@ -24,13 +24,9 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat; namespace WerkraumMedia\ThueCat;
use TYPO3\CMS\Core\Cache\Backend\TransientMemoryBackend; use TYPO3\CMS\Core\Cache\Backend\TransientMemoryBackend;
use TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider; use TYPO3\CMS\Core\DataHandling\PageDoktypeRegistry;
use TYPO3\CMS\Core\Imaging\IconRegistry;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Utility\ExtensionUtility;
use WerkraumMedia\ThueCat\Controller\Backend\ConfigurationController;
use WerkraumMedia\ThueCat\Controller\Backend\ImportController;
class Extension class Extension
{ {
@ -47,57 +43,11 @@ class Extension
return 'LLL:EXT:' . self::EXTENSION_KEY . '/Resources/Private/Language/'; return 'LLL:EXT:' . self::EXTENSION_KEY . '/Resources/Private/Language/';
} }
public static function registerBackendModules(): void
{
ExtensionUtility::registerModule(
self::EXTENSION_NAME,
'thuecat',
'',
'',
[],
[
'access' => 'user,group',
'icon' => self::getIconPath() . 'ModuleGroup.svg',
'labels' => self::getLanguagePath() . 'locallang_mod.xlf',
]
);
ExtensionUtility::registerModule(
self::EXTENSION_NAME,
'thuecat',
'configurations',
'',
[
ConfigurationController::class => 'index',
ImportController::class => 'import',
],
[
'access' => 'user,group',
'icon' => self::getIconPath() . 'ModuleConfigurations.svg',
'labels' => self::getLanguagePath() . 'locallang_mod_configurations.xlf',
]
);
ExtensionUtility::registerModule(
self::EXTENSION_NAME,
'thuecat',
'imports',
'',
[
ImportController::class => 'index,import',
],
[
'access' => 'user,group',
'icon' => self::getIconPath() . 'ModuleImports.svg',
'labels' => self::getLanguagePath() . 'locallang_mod_imports.xlf',
]
);
}
public static function registerConfig(): void public static function registerConfig(): void
{ {
self::addCaching(); self::addCaching();
self::addContentElements(); self::addContentElements();
self::addPageTypes(); self::addPageTypes();
self::addIcons();
} }
public static function getIconPath(): string public static function getIconPath(): string
@ -129,28 +79,20 @@ class Extension
private static function addPageTypes(): void private static function addPageTypes(): void
{ {
$registry = GeneralUtility::makeInstance(PageDoktypeRegistry::class);
$registry->add(
self::PAGE_DOKTYPE_TOURIST_ATTRACTION,
[
'type' => 'web',
'allowedTables' => '*',
]
);
ExtensionManagementUtility::addUserTSConfig( ExtensionManagementUtility::addUserTSConfig(
"@import 'EXT:" . self::EXTENSION_KEY . "/Configuration/TSconfig/User/All.tsconfig'" "@import 'EXT:" . self::EXTENSION_KEY . "/Configuration/TSconfig/User/All.tsconfig'"
); );
} }
private static function addIcons(): void
{
$iconFiles = GeneralUtility::getFilesInDir(GeneralUtility::getFileAbsFileName(self::getIconPath()));
if (is_array($iconFiles) === false) {
return;
}
$iconRegistry = GeneralUtility::makeInstance(IconRegistry::class);
foreach ($iconFiles as $iconFile) {
$iconRegistry->registerIcon(
str_replace('.svg', '', $iconFile),
SvgIconProvider::class,
['source' => self::getIconPath() . $iconFile]
);
}
}
private static function addCaching(): void private static function addCaching(): void
{ {
$cacheIdentifier = 'thuecat_fetchdata'; $cacheIdentifier = 'thuecat_fetchdata';

View file

@ -33,27 +33,15 @@ use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
class ResolveEntities implements DataProcessorInterface class ResolveEntities implements DataProcessorInterface
{ {
/**
* @var ConnectionPool
*/
private $connectionPool;
/**
* @var DataMapper
*/
private $dataMapper;
/** /**
* @var TypoScriptFrontendController * @var TypoScriptFrontendController
*/ */
private $tsfe; private $tsfe;
public function __construct( public function __construct(
ConnectionPool $connectionPool, private readonly ConnectionPool $connectionPool,
DataMapper $dataMapper private readonly DataMapper $dataMapper
) { ) {
$this->connectionPool = $connectionPool;
$this->dataMapper = $dataMapper;
$this->tsfe = $GLOBALS['TSFE']; $this->tsfe = $GLOBALS['TSFE'];
} }
@ -93,7 +81,7 @@ class ResolveEntities implements DataProcessorInterface
)); ));
$rows = []; $rows = [];
foreach ($queryBuilder->execute() as $row) { foreach ($queryBuilder->executeQuery()->iterateAssociative() as $row) {
$row = $this->tsfe->sys_page->getLanguageOverlay($tableName, $row); $row = $this->tsfe->sys_page->getLanguageOverlay($tableName, $row);
if (is_array($row)) { if (is_array($row)) {
$rows[] = $row; $rows[] = $row;

View file

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Service\DateBasedFilter; namespace WerkraumMedia\ThueCat\Service\DateBasedFilter;
use DateTimeImmutable;
use TYPO3\CMS\Core\Context\Context; use TYPO3\CMS\Core\Context\Context;
use WerkraumMedia\ThueCat\Service\DateBasedFilter; use WerkraumMedia\ThueCat\Service\DateBasedFilter;
@ -48,7 +49,7 @@ class FilterBasedOnTypo3Context implements DateBasedFilter
array $listToFilter, array $listToFilter,
callable $provideDate callable $provideDate
): array { ): array {
$referenceDate = $this->context->getPropertyFromAspect('date', 'full', new \DateTimeImmutable()); $referenceDate = $this->context->getPropertyFromAspect('date', 'full', new DateTimeImmutable());
return array_filter($listToFilter, function ($elementToFilter) use ($referenceDate, $provideDate) { return array_filter($listToFilter, function ($elementToFilter) use ($referenceDate, $provideDate) {
$objectDate = $provideDate($elementToFilter); $objectDate = $provideDate($elementToFilter);

View file

@ -25,19 +25,16 @@ namespace WerkraumMedia\ThueCat\Updates;
use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Install\Attribute\UpgradeWizard;
use TYPO3\CMS\Install\Updates\DatabaseUpdatedPrerequisite; use TYPO3\CMS\Install\Updates\DatabaseUpdatedPrerequisite;
use TYPO3\CMS\Install\Updates\UpgradeWizardInterface; use TYPO3\CMS\Install\Updates\UpgradeWizardInterface;
#[UpgradeWizard('thuecat_backendmoduleuserpermission_v12')]
class BackendModuleUserPermission implements UpgradeWizardInterface class BackendModuleUserPermission implements UpgradeWizardInterface
{ {
/** public function __construct(
* @var ConnectionPool private readonly ConnectionPool $connectionPool
*/ ) {
private $connectionPool;
public function __construct()
{
$this->connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
} }
public function getIdentifier(): string public function getIdentifier(): string
@ -62,8 +59,9 @@ class BackendModuleUserPermission implements UpgradeWizardInterface
$qb->count('*'); $qb->count('*');
$qb->from('be_users'); $qb->from('be_users');
$qb->where($qb->expr()->like('userMods', $qb->createNamedParameter('%site_ThuecatThuecat%'))); $qb->where($qb->expr()->like('userMods', $qb->createNamedParameter('%site_ThuecatThuecat%')));
$qb->orWhere($qb->expr()->like('userMods', $qb->createNamedParameter('%ThuecatThuecat%')));
return $qb->execute()->fetchOne() > 0; return $qb->executeQuery()->fetchOne() > 0;
} }
public function executeUpdate(): bool public function executeUpdate(): bool
@ -73,14 +71,15 @@ class BackendModuleUserPermission implements UpgradeWizardInterface
$qb->select('uid', 'userMods'); $qb->select('uid', 'userMods');
$qb->from('be_users'); $qb->from('be_users');
$qb->where($qb->expr()->like('userMods', $qb->createNamedParameter('%site_ThuecatThuecat%'))); $qb->where($qb->expr()->like('userMods', $qb->createNamedParameter('%site_ThuecatThuecat%')));
$result = $qb->execute(); $qb->orWhere($qb->expr()->like('userMods', $qb->createNamedParameter('%ThuecatThuecat%')));
$result = $qb->executeQuery()->iterateAssociative();
foreach ($result as $backendUser) { foreach ($result as $backendUser) {
$qb = $this->connectionPool->getQueryBuilderForTable('be_users'); $qb = $this->connectionPool->getQueryBuilderForTable('be_users');
$qb->update('be_users'); $qb->update('be_users');
$qb->set('userMods', $this->updateMods($backendUser['userMods'])); $qb->set('userMods', $this->updateMods($backendUser['userMods']));
$qb->where($qb->expr()->eq('uid', $qb->createNamedParameter($backendUser['uid']))); $qb->where($qb->expr()->eq('uid', $qb->createNamedParameter($backendUser['uid'])));
$qb->execute(); $qb->executeStatement();
} }
return true; return true;
@ -90,11 +89,15 @@ class BackendModuleUserPermission implements UpgradeWizardInterface
{ {
$mods = GeneralUtility::trimExplode(',', $mods, true); $mods = GeneralUtility::trimExplode(',', $mods, true);
unset($mods[array_search('site_ThuecatThuecat', $mods)]); unset(
$mods[array_search('site_ThuecatThuecat', $mods)],
$mods[array_search('ThuecatThuecat', $mods)],
$mods[array_search('ThuecatThuecat_ThuecatConfigurations', $mods)],
$mods[array_search('ThuecatThuecat_ThuecatImports', $mods)],
);
$mods[] = 'ThuecatThuecat'; $mods[] = 'thuecat_configurations';
$mods[] = 'ThuecatThuecat_ThuecatConfigurations'; $mods[] = 'thuecat_imports';
$mods[] = 'ThuecatThuecat_ThuecatImports';
return implode(',', $mods); return implode(',', $mods);
} }
@ -105,9 +108,4 @@ class BackendModuleUserPermission implements UpgradeWizardInterface
DatabaseUpdatedPrerequisite::class, DatabaseUpdatedPrerequisite::class,
]; ];
} }
public static function register(): void
{
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][self::class] = self::class;
}
} }

View file

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
use WerkraumMedia\ThueCat\Controller\Backend\ConfigurationController;
use WerkraumMedia\ThueCat\Controller\Backend\ImportController;
use WerkraumMedia\ThueCat\Extension;
return [
'thuecat_thuecat' => [
'icon' => Extension::getIconPath() . 'ModuleGroup.svg',
'position' => [
'after' => 'web',
'before' => 'file',
],
'labels' => 'LLL:EXT:thuecat/Resources/Private/Language/locallang_mod.xlf',
'extensionName' => 'Thuecat',
],
'thuecat_configurations' => [
'parent' => 'thuecat_thuecat',
'access' => 'user',
'icon' => Extension::getIconPath() . 'ModuleConfigurations.svg',
'labels' => 'LLL:EXT:thuecat/Resources/Private/Language/locallang_mod_configurations.xlf',
'extensionName' => 'Thuecat',
'controllerActions' => [
ConfigurationController::class => [
'index',
],
ImportController::class => [
'import',
],
],
],
'thuecat_imports' => [
'parent' => 'thuecat_thuecat',
'access' => 'user',
'icon' => Extension::getIconPath() . 'ModuleImports.svg',
'labels' => 'LLL:EXT:thuecat/Resources/Private/Language/locallang_mod_imports.xlf',
'extensionName' => 'Thuecat',
'controllerActions' => [
ImportController::class => [
'index',
'import',
],
],
],
];

View file

@ -1,41 +1,53 @@
<?php <?php
declare(strict_types=1);
use WerkraumMedia\ThueCat\Domain\Model\Backend\ImportConfiguration;
use WerkraumMedia\ThueCat\Domain\Model\Backend\ImportLog;
use WerkraumMedia\ThueCat\Domain\Model\Backend\ImportLogEntry;
use WerkraumMedia\ThueCat\Domain\Model\Backend\ImportLogEntry\MappingError;
use WerkraumMedia\ThueCat\Domain\Model\Backend\ImportLogEntry\SavingEntity;
use WerkraumMedia\ThueCat\Domain\Model\Backend\Organisation;
use WerkraumMedia\ThueCat\Domain\Model\Backend\ParkingFacility;
use WerkraumMedia\ThueCat\Domain\Model\Backend\TouristInformation;
use WerkraumMedia\ThueCat\Domain\Model\Backend\Town;
use WerkraumMedia\ThueCat\Domain\Model\Frontend\TouristAttraction;
return [ return [
\WerkraumMedia\ThueCat\Domain\Model\Backend\Organisation::class => [ Organisation::class => [
'tableName' => 'tx_thuecat_organisation', 'tableName' => 'tx_thuecat_organisation',
], ],
\WerkraumMedia\ThueCat\Domain\Model\Backend\Town::class => [ Town::class => [
'tableName' => 'tx_thuecat_town', 'tableName' => 'tx_thuecat_town',
], ],
\WerkraumMedia\ThueCat\Domain\Model\Backend\TouristInformation::class => [ TouristInformation::class => [
'tableName' => 'tx_thuecat_tourist_information', 'tableName' => 'tx_thuecat_tourist_information',
], ],
WerkraumMedia\ThueCat\Domain\Model\Backend\ParkingFacility::class => [ ParkingFacility::class => [
'tableName' => 'tx_thuecat_parking_facility', 'tableName' => 'tx_thuecat_parking_facility',
], ],
\WerkraumMedia\ThueCat\Domain\Model\Backend\ImportConfiguration::class => [ ImportConfiguration::class => [
'tableName' => 'tx_thuecat_import_configuration', 'tableName' => 'tx_thuecat_import_configuration',
], ],
\WerkraumMedia\ThueCat\Domain\Model\Backend\ImportLog::class => [ ImportLog::class => [
'tableName' => 'tx_thuecat_import_log', 'tableName' => 'tx_thuecat_import_log',
], ],
\WerkraumMedia\ThueCat\Domain\Model\Backend\ImportLogEntry::class => [ ImportLogEntry::class => [
'tableName' => 'tx_thuecat_import_log_entry', 'tableName' => 'tx_thuecat_import_log_entry',
'subclasses' => [ 'subclasses' => [
'savingEntity' => \WerkraumMedia\ThueCat\Domain\Model\Backend\ImportLogEntry\SavingEntity::class, 'savingEntity' => SavingEntity::class,
'mappingError' => \WerkraumMedia\ThueCat\Domain\Model\Backend\ImportLogEntry\MappingError::class, 'mappingError' => MappingError::class,
], ],
], ],
\WerkraumMedia\ThueCat\Domain\Model\Backend\ImportLogEntry\SavingEntity::class => [ SavingEntity::class => [
'tableName' => 'tx_thuecat_import_log_entry', 'tableName' => 'tx_thuecat_import_log_entry',
'recordType' => 'savingEntity', 'recordType' => 'savingEntity',
], ],
\WerkraumMedia\ThueCat\Domain\Model\Backend\ImportLogEntry\MappingError::class => [ MappingError::class => [
'tableName' => 'tx_thuecat_import_log_entry', 'tableName' => 'tx_thuecat_import_log_entry',
'recordType' => 'mappingError', 'recordType' => 'mappingError',
], ],
\WerkraumMedia\ThueCat\Domain\Model\Frontend\TouristAttraction::class => [ TouristAttraction::class => [
'tableName' => 'tx_thuecat_tourist_attraction', 'tableName' => 'tx_thuecat_tourist_attraction',
], ],
\WerkraumMedia\ThueCat\Domain\Model\Frontend\Town::class => [ \WerkraumMedia\ThueCat\Domain\Model\Frontend\Town::class => [

30
Configuration/Icons.php Normal file
View file

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
use TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use WerkraumMedia\ThueCat\Extension;
return (static function (): array {
$iconFiles = GeneralUtility::getFilesInDir(
GeneralUtility::getFileAbsFileName(
Extension::getIconPath()
)
);
if (is_array($iconFiles) === false) {
return [];
}
$icons = [];
foreach ($iconFiles as $iconFile) {
$identifier = str_replace('.svg', '', $iconFile);
$icons[$identifier] = [
'provider' => SvgIconProvider::class,
'source' => Extension::getIconPath() . $iconFile,
];
}
return $icons;
})();

View file

@ -6,17 +6,20 @@ namespace WerkraumMedia\ThueCat;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use WerkraumMedia\ThueCat\DependencyInjection\ConverterPass;
use WerkraumMedia\ThueCat\DependencyInjection\EntityPass;
use WerkraumMedia\ThueCat\DependencyInjection\UrlProvidersPass;
use WerkraumMedia\ThueCat\Domain\Import\Entity\MapsToType; use WerkraumMedia\ThueCat\Domain\Import\Entity\MapsToType;
use WerkraumMedia\ThueCat\Domain\Import\Typo3Converter\Converter; use WerkraumMedia\ThueCat\Domain\Import\Typo3Converter\Converter;
use WerkraumMedia\ThueCat\Domain\Import\UrlProvider\UrlProvider; use WerkraumMedia\ThueCat\Domain\Import\UrlProvider\UrlProvider;
return function (ContainerConfigurator $container, ContainerBuilder $containerBuilder) { return function (ContainerConfigurator $container, ContainerBuilder $containerBuilder) {
$containerBuilder->registerForAutoconfiguration(UrlProvider::class)->addTag(DependencyInjection\UrlProvidersPass::TAG); $containerBuilder->registerForAutoconfiguration(UrlProvider::class)->addTag(UrlProvidersPass::TAG);
$containerBuilder->addCompilerPass(new DependencyInjection\UrlProvidersPass()); $containerBuilder->addCompilerPass(new UrlProvidersPass());
$containerBuilder->registerForAutoconfiguration(Converter::class)->addTag(DependencyInjection\ConverterPass::TAG); $containerBuilder->registerForAutoconfiguration(Converter::class)->addTag(ConverterPass::TAG);
$containerBuilder->addCompilerPass(new DependencyInjection\ConverterPass()); $containerBuilder->addCompilerPass(new ConverterPass());
$containerBuilder->registerForAutoconfiguration(MapsToType::class)->addTag(DependencyInjection\EntityPass::TAG); $containerBuilder->registerForAutoconfiguration(MapsToType::class)->addTag(EntityPass::TAG);
$containerBuilder->addCompilerPass(new DependencyInjection\EntityPass()); $containerBuilder->addCompilerPass(new EntityPass());
}; };

View file

@ -1,12 +1,18 @@
<?php <?php
declare(strict_types=1);
use TYPO3\CMS\Core\Utility\ArrayUtility;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use WerkraumMedia\ThueCat\Extension;
defined('TYPO3') or die(); defined('TYPO3') or die();
(static function (string $extensionKey, string $tableName) { (static function (string $extensionKey, string $tableName) {
$languagePath = \WerkraumMedia\ThueCat\Extension::getLanguagePath() $languagePath = Extension::getLanguagePath()
. 'locallang_tca.xlf:' . $tableName; . 'locallang_tca.xlf:' . $tableName;
\TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($GLOBALS['TCA'][$tableName], [ ArrayUtility::mergeRecursiveWithOverrule($GLOBALS['TCA'][$tableName], [
'ctrl' => [ 'ctrl' => [
'typeicon_classes' => [ 'typeicon_classes' => [
'contains-thuecat' => 'pages_module_thuecat', 'contains-thuecat' => 'pages_module_thuecat',
@ -32,23 +38,23 @@ defined('TYPO3') or die();
], ],
]); ]);
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTcaSelectItemGroup( ExtensionManagementUtility::addTcaSelectItemGroup(
$tableName, $tableName,
'doktype', 'doktype',
\WerkraumMedia\ThueCat\Extension::TCA_SELECT_GROUP_IDENTIFIER, Extension::TCA_SELECT_GROUP_IDENTIFIER,
$languagePath . '.group' $languagePath . '.group'
); );
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTcaSelectItem( ExtensionManagementUtility::addTcaSelectItem(
$tableName, $tableName,
'module', 'module',
[ [
0 => $languagePath . '.module.thuecat', 'label' => $languagePath . '.module.thuecat',
1 => 'thuecat', 'value' => 'thuecat',
2 => 'pages_module_thuecat', 'icon' => 'pages_module_thuecat',
] ]
); );
})( })(
\WerkraumMedia\ThueCat\Extension::EXTENSION_KEY, Extension::EXTENSION_KEY,
'pages' 'pages'
); );

View file

@ -1,12 +1,18 @@
<?php <?php
declare(strict_types=1);
use TYPO3\CMS\Core\Utility\ArrayUtility;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use WerkraumMedia\ThueCat\Extension;
defined('TYPO3') or die(); defined('TYPO3') or die();
(static function (string $extensionKey, string $tableName, int $doktype, string $pageIdentifier) { (static function (string $extensionKey, string $tableName, int $doktype, string $pageIdentifier) {
$languagePath = \WerkraumMedia\ThueCat\Extension::getLanguagePath() $languagePath = Extension::getLanguagePath()
. 'locallang_tca.xlf:' . $tableName . '.' . $pageIdentifier; . 'locallang_tca.xlf:' . $tableName . '.' . $pageIdentifier;
\TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($GLOBALS['TCA'][$tableName], [ ArrayUtility::mergeRecursiveWithOverrule($GLOBALS['TCA'][$tableName], [
'ctrl' => [ 'ctrl' => [
'typeicon_classes' => [ 'typeicon_classes' => [
$doktype => $tableName . '_' . $pageIdentifier, $doktype => $tableName . '_' . $pageIdentifier,
@ -54,19 +60,19 @@ defined('TYPO3') or die();
], ],
]); ]);
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTcaSelectItem( ExtensionManagementUtility::addTcaSelectItem(
$tableName, $tableName,
'doktype', 'doktype',
[ [
$languagePath, 'label' => $languagePath,
$doktype, 'value' => $doktype,
\WerkraumMedia\ThueCat\Extension::getIconPath() . $tableName . '_' . $pageIdentifier . '.svg', 'icon' => Extension::getIconPath() . $tableName . '_' . $pageIdentifier . '.svg',
\WerkraumMedia\ThueCat\Extension::TCA_SELECT_GROUP_IDENTIFIER, 'group' => Extension::TCA_SELECT_GROUP_IDENTIFIER,
] ]
); );
})( })(
\WerkraumMedia\ThueCat\Extension::EXTENSION_KEY, Extension::EXTENSION_KEY,
'pages', 'pages',
\WerkraumMedia\ThueCat\Extension::PAGE_DOKTYPE_TOURIST_ATTRACTION, Extension::PAGE_DOKTYPE_TOURIST_ATTRACTION,
'tourist_attraction' 'tourist_attraction'
); );

View file

@ -1,19 +1,24 @@
<?php <?php
declare(strict_types=1);
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use WerkraumMedia\ThueCat\Extension;
defined('TYPO3') or die(); defined('TYPO3') or die();
(static function (string $extensionKey, string $tableName) { (static function (string $extensionKey, string $tableName) {
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addStaticFile( ExtensionManagementUtility::addStaticFile(
$extensionKey, $extensionKey,
'Configuration/TypoScript/ContentElements', 'Configuration/TypoScript/ContentElements',
'ThüCAT - Content Elements' 'ThüCAT - Content Elements'
); );
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addStaticFile( ExtensionManagementUtility::addStaticFile(
$extensionKey, $extensionKey,
'Configuration/TypoScript/PageTypes', 'Configuration/TypoScript/PageTypes',
'ThüCAT - Page Types' 'ThüCAT - Page Types'
); );
})( })(
\WerkraumMedia\ThueCat\Extension::EXTENSION_KEY, Extension::EXTENSION_KEY,
'sys_template' 'sys_template'
); );

View file

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

View file

@ -1,12 +1,18 @@
<?php <?php
declare(strict_types=1);
use TYPO3\CMS\Core\Utility\ArrayUtility;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use WerkraumMedia\ThueCat\Extension;
defined('TYPO3') or die(); defined('TYPO3') or die();
(static function (string $extensionKey, string $tableName, string $cType) { (static function (string $extensionKey, string $tableName, string $cType) {
$languagePath = \WerkraumMedia\ThueCat\Extension::getLanguagePath() $languagePath = Extension::getLanguagePath()
. 'locallang_tca.xlf:' . $tableName . '.' . $cType; . 'locallang_tca.xlf:' . $tableName . '.' . $cType;
\TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($GLOBALS['TCA'][$tableName], [ ArrayUtility::mergeRecursiveWithOverrule($GLOBALS['TCA'][$tableName], [
'ctrl' => [ 'ctrl' => [
'typeicon_classes' => [ 'typeicon_classes' => [
$cType => 'tt_content_' . $cType, $cType => 'tt_content_' . $cType,
@ -48,18 +54,18 @@ defined('TYPO3') or die();
], ],
]); ]);
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTcaSelectItem( ExtensionManagementUtility::addTcaSelectItem(
$tableName, $tableName,
'CType', 'CType',
[ [
$languagePath, 'label' => $languagePath,
$cType, 'value' => $cType,
\WerkraumMedia\ThueCat\Extension::getIconPath() . 'tt_content_' . $cType . '.svg', 'icon' => Extension::getIconPath() . 'tt_content_' . $cType . '.svg',
\WerkraumMedia\ThueCat\Extension::TCA_SELECT_GROUP_IDENTIFIER, 'group' => Extension::TCA_SELECT_GROUP_IDENTIFIER,
] ]
); );
})( })(
\WerkraumMedia\ThueCat\Extension::EXTENSION_KEY, Extension::EXTENSION_KEY,
'tt_content', 'tt_content',
'thuecat_tourist_attraction' 'thuecat_tourist_attraction'
); );

View file

@ -1,20 +1,23 @@
<?php <?php
declare(strict_types=1);
use WerkraumMedia\ThueCat\Extension;
defined('TYPO3') or die(); defined('TYPO3') or die();
return (static function (string $extensionKey, string $tableName) { return (static function (string $extensionKey, string $tableName) {
$languagePath = \WerkraumMedia\ThueCat\Extension::getLanguagePath() . 'locallang_tca.xlf:' . $tableName; $languagePath = Extension::getLanguagePath() . 'locallang_tca.xlf:' . $tableName;
$flexFormConfigurationPath = 'FILE:EXT:' . \WerkraumMedia\ThueCat\Extension::EXTENSION_KEY . '/Configuration/FlexForm/'; $flexFormConfigurationPath = 'FILE:EXT:' . Extension::EXTENSION_KEY . '/Configuration/FlexForm/';
return [ return [
'ctrl' => [ 'ctrl' => [
'label' => 'title', 'label' => 'title',
'iconfile' => \WerkraumMedia\ThueCat\Extension::getIconPath() . $tableName . '.svg', 'iconfile' => Extension::getIconPath() . $tableName . '.svg',
'type' => 'type', 'type' => 'type',
'default_sortby' => 'title', 'default_sortby' => 'title',
'tstamp' => 'tstamp', 'tstamp' => 'tstamp',
'crdate' => 'crdate', 'crdate' => 'crdate',
'cruser_id' => 'cruser_id',
'title' => $languagePath, 'title' => $languagePath,
'enablecolumns' => [ 'enablecolumns' => [
'disabled' => 'disable', 'disabled' => 'disable',
@ -28,7 +31,8 @@ return (static function (string $extensionKey, string $tableName) {
'config' => [ 'config' => [
'type' => 'input', 'type' => 'input',
'max' => 255, 'max' => 255,
'eval' => 'required,trim,unique', 'eval' => 'trim,unique',
'required' => true,
], ],
], ],
'type' => [ 'type' => [
@ -38,16 +42,16 @@ return (static function (string $extensionKey, string $tableName) {
'renderType' => 'selectSingle', 'renderType' => 'selectSingle',
'items' => [ 'items' => [
[ [
$languagePath . '.type.static', 'label' => $languagePath . '.type.static',
'static', 'value' => 'static',
], ],
[ [
$languagePath . '.type.syncScope', 'label' => $languagePath . '.type.syncScope',
'syncScope', 'value' => 'syncScope',
], ],
[ [
$languagePath . '.type.containsPlace', 'label' => $languagePath . '.type.containsPlace',
'containsPlace', 'value' => 'containsPlace',
], ],
], ],
], ],
@ -67,9 +71,8 @@ return (static function (string $extensionKey, string $tableName) {
], ],
'tstamp' => [ 'tstamp' => [
'config' => [ 'config' => [
'type' => 'input', 'type' => 'datetime',
'renderType' => 'inputDateTime', 'format' => 'datetime',
'eval' => 'datetime',
'readOnly' => true, 'readOnly' => true,
], ],
], ],
@ -89,4 +92,4 @@ return (static function (string $extensionKey, string $tableName) {
], ],
], ],
]; ];
})(\WerkraumMedia\ThueCat\Extension::EXTENSION_KEY, 'tx_thuecat_import_configuration'); })(Extension::EXTENSION_KEY, 'tx_thuecat_import_configuration');

View file

@ -1,21 +1,24 @@
<?php <?php
declare(strict_types=1);
use WerkraumMedia\ThueCat\Extension;
defined('TYPO3') or die(); defined('TYPO3') or die();
return (static function (string $extensionKey, string $tableName) { return (static function (string $extensionKey, string $tableName) {
$languagePath = \WerkraumMedia\ThueCat\Extension::getLanguagePath() . 'locallang_tca.xlf:' . $tableName; $languagePath = Extension::getLanguagePath() . 'locallang_tca.xlf:' . $tableName;
$flexFormConfigurationPath = 'FILE:EXT:' . \WerkraumMedia\ThueCat\Extension::EXTENSION_KEY . '/Configuration/FlexForm/'; $flexFormConfigurationPath = 'FILE:EXT:' . Extension::EXTENSION_KEY . '/Configuration/FlexForm/';
return [ return [
'ctrl' => [ 'ctrl' => [
'label' => 'crdate', 'label' => 'crdate',
'label_alt' => 'configuration', 'label_alt' => 'configuration',
'label_alt_force' => true, 'label_alt_force' => true,
'iconfile' => \WerkraumMedia\ThueCat\Extension::getIconPath() . $tableName . '.svg', 'iconfile' => Extension::getIconPath() . $tableName . '.svg',
'default_sortby' => 'crdate desc', 'default_sortby' => 'crdate desc',
'tstamp' => 'tstamp', 'tstamp' => 'tstamp',
'crdate' => 'crdate', 'crdate' => 'crdate',
'cruser_id' => 'cruser_id',
'title' => $languagePath, 'title' => $languagePath,
'enablecolumns' => [ 'enablecolumns' => [
'disabled' => 'disable', 'disabled' => 'disable',
@ -44,9 +47,8 @@ return (static function (string $extensionKey, string $tableName) {
'crdate' => [ 'crdate' => [
'label' => $languagePath . '.crdate', 'label' => $languagePath . '.crdate',
'config' => [ 'config' => [
'type' => 'input', 'type' => 'datetime',
'renderType' => 'inputDateTime', 'format' => 'datetime',
'eval' => 'datetime',
'readOnly' => true, 'readOnly' => true,
], ],
], ],
@ -57,4 +59,4 @@ return (static function (string $extensionKey, string $tableName) {
], ],
], ],
]; ];
})(\WerkraumMedia\ThueCat\Extension::EXTENSION_KEY, 'tx_thuecat_import_log'); })(Extension::EXTENSION_KEY, 'tx_thuecat_import_log');

View file

@ -1,22 +1,25 @@
<?php <?php
declare(strict_types=1);
use WerkraumMedia\ThueCat\Extension;
defined('TYPO3') or die(); defined('TYPO3') or die();
return (static function (string $extensionKey, string $tableName) { return (static function (string $extensionKey, string $tableName) {
$languagePath = \WerkraumMedia\ThueCat\Extension::getLanguagePath() . 'locallang_tca.xlf:' . $tableName; $languagePath = Extension::getLanguagePath() . 'locallang_tca.xlf:' . $tableName;
$flexFormConfigurationPath = 'FILE:EXT:' . \WerkraumMedia\ThueCat\Extension::EXTENSION_KEY . '/Configuration/FlexForm/'; $flexFormConfigurationPath = 'FILE:EXT:' . Extension::EXTENSION_KEY . '/Configuration/FlexForm/';
return [ return [
'ctrl' => [ 'ctrl' => [
'label' => 'type', 'label' => 'type',
'label_alt' => 'remote_id, table_name, record_uid', 'label_alt' => 'remote_id, table_name, record_uid',
'label_alt_force' => true, 'label_alt_force' => true,
'iconfile' => \WerkraumMedia\ThueCat\Extension::getIconPath() . $tableName . '.svg', 'iconfile' => Extension::getIconPath() . $tableName . '.svg',
'type' => 'type', 'type' => 'type',
'default_sortby' => 'crdate', 'default_sortby' => 'crdate',
'tstamp' => 'tstamp', 'tstamp' => 'tstamp',
'crdate' => 'crdate', 'crdate' => 'crdate',
'cruser_id' => 'cruser_id',
'title' => $languagePath, 'title' => $languagePath,
'enablecolumns' => [ 'enablecolumns' => [
'disabled' => 'disable', 'disabled' => 'disable',
@ -32,12 +35,12 @@ return (static function (string $extensionKey, string $tableName) {
'renderType' => 'selectSingle', 'renderType' => 'selectSingle',
'items' => [ 'items' => [
[ [
$languagePath . '.type.savingEntity', 'label' => $languagePath . '.type.savingEntity',
'savingEntity', 'value' => 'savingEntity',
], ],
[ [
$languagePath . '.type.mappingError', 'label' => $languagePath . '.type.mappingError',
'mappingError', 'value' => 'mappingError',
], ],
], ],
], ],
@ -56,7 +59,7 @@ return (static function (string $extensionKey, string $tableName) {
'renderType' => 'checkboxLabeledToggle', 'renderType' => 'checkboxLabeledToggle',
'items' => [ 'items' => [
[ [
0 => '', 'label' => '',
1 => '', 1 => '',
'labelChecked' => $languagePath . '.insertion.yes', 'labelChecked' => $languagePath . '.insertion.yes',
'labelUnchecked' => $languagePath . '.insertion.no', 'labelUnchecked' => $languagePath . '.insertion.no',
@ -98,9 +101,8 @@ return (static function (string $extensionKey, string $tableName) {
'crdate' => [ 'crdate' => [
'label' => $languagePath . '.crdate', 'label' => $languagePath . '.crdate',
'config' => [ 'config' => [
'type' => 'input', 'type' => 'datetime',
'renderType' => 'inputDateTime', 'format' => 'datetime',
'eval' => 'datetime',
'readOnly' => true, 'readOnly' => true,
], ],
], ],
@ -120,4 +122,4 @@ return (static function (string $extensionKey, string $tableName) {
], ],
], ],
]; ];
})(\WerkraumMedia\ThueCat\Extension::EXTENSION_KEY, 'tx_thuecat_import_log_entry'); })(Extension::EXTENSION_KEY, 'tx_thuecat_import_log_entry');

View file

@ -1,18 +1,21 @@
<?php <?php
declare(strict_types=1);
use WerkraumMedia\ThueCat\Extension;
defined('TYPO3') or die(); defined('TYPO3') or die();
return (static function (string $extensionKey, string $tableName) { return (static function (string $extensionKey, string $tableName) {
$languagePath = \WerkraumMedia\ThueCat\Extension::getLanguagePath() . 'locallang_tca.xlf:' . $tableName; $languagePath = Extension::getLanguagePath() . 'locallang_tca.xlf:' . $tableName;
return [ return [
'ctrl' => [ 'ctrl' => [
'label' => 'title', 'label' => 'title',
'iconfile' => \WerkraumMedia\ThueCat\Extension::getIconPath() . $tableName . '.svg', 'iconfile' => Extension::getIconPath() . $tableName . '.svg',
'default_sortby' => 'title', 'default_sortby' => 'title',
'tstamp' => 'tstamp', 'tstamp' => 'tstamp',
'crdate' => 'crdate', 'crdate' => 'crdate',
'cruser_id' => 'cruser_id',
'title' => $languagePath, 'title' => $languagePath,
'enablecolumns' => [ 'enablecolumns' => [
'disabled' => 'disable', 'disabled' => 'disable',
@ -73,9 +76,8 @@ return (static function (string $extensionKey, string $tableName) {
'tstamp' => [ 'tstamp' => [
'label' => $languagePath . '.tstamp', 'label' => $languagePath . '.tstamp',
'config' => [ 'config' => [
'type' => 'input', 'type' => 'datetime',
'renderType' => 'inputDateTime', 'format' => 'datetime',
'eval' => 'datetime',
'readOnly' => true, 'readOnly' => true,
], ],
], ],
@ -88,4 +90,4 @@ return (static function (string $extensionKey, string $tableName) {
], ],
], ],
]; ];
})(\WerkraumMedia\ThueCat\Extension::EXTENSION_KEY, 'tx_thuecat_organisation'); })(Extension::EXTENSION_KEY, 'tx_thuecat_organisation');

View file

@ -1,18 +1,21 @@
<?php <?php
declare(strict_types=1);
use WerkraumMedia\ThueCat\Extension;
defined('TYPO3') or die(); defined('TYPO3') or die();
return (static function (string $extensionKey, string $tableName) { return (static function (string $extensionKey, string $tableName) {
$languagePath = \WerkraumMedia\ThueCat\Extension::getLanguagePath() . 'locallang_tca.xlf:' . $tableName; $languagePath = Extension::getLanguagePath() . 'locallang_tca.xlf:' . $tableName;
return [ return [
'ctrl' => [ 'ctrl' => [
'label' => 'title', 'label' => 'title',
'iconfile' => \WerkraumMedia\ThueCat\Extension::getIconPath() . $tableName . '.svg', 'iconfile' => Extension::getIconPath() . $tableName . '.svg',
'default_sortby' => 'title', 'default_sortby' => 'title',
'tstamp' => 'tstamp', 'tstamp' => 'tstamp',
'crdate' => 'crdate', 'crdate' => 'crdate',
'cruser_id' => 'cruser_id',
'title' => $languagePath, 'title' => $languagePath,
'enablecolumns' => [ 'enablecolumns' => [
'disabled' => 'disable', 'disabled' => 'disable',
@ -27,19 +30,7 @@ return (static function (string $extensionKey, string $tableName) {
'sys_language_uid' => [ 'sys_language_uid' => [
'exclude' => true, 'exclude' => true,
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language',
'config' => [ 'config' => ['type' => 'language'],
'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' => [ 'l18n_parent' => [
'displayCond' => 'FIELD:sys_language_uid:>:0', 'displayCond' => 'FIELD:sys_language_uid:>:0',
@ -47,7 +38,7 @@ return (static function (string $extensionKey, string $tableName) {
'config' => [ 'config' => [
'type' => 'select', 'type' => 'select',
'renderType' => 'selectSingle', 'renderType' => 'selectSingle',
'items' => [['', 0]], 'items' => [['label' => '', 'value' => 0]],
'foreign_table' => $tableName, 'foreign_table' => $tableName,
'foreign_table_where' => 'AND ' . $tableName . '.pid=###CURRENT_PID### AND ' . $tableName . '.sys_language_uid IN (-1,0)', 'foreign_table_where' => 'AND ' . $tableName . '.pid=###CURRENT_PID### AND ' . $tableName . '.sys_language_uid IN (-1,0)',
'default' => 0, 'default' => 0,
@ -66,12 +57,12 @@ return (static function (string $extensionKey, string $tableName) {
'renderType' => 'checkboxToggle', 'renderType' => 'checkboxToggle',
'items' => [ 'items' => [
[ [
0 => '', 'label' => '',
1 => '', 1 => '',
'invertStateDisplay' => true 'invertStateDisplay' => true,
] ],
],
], ],
]
], ],
'title' => [ 'title' => [
@ -190,8 +181,8 @@ return (static function (string $extensionKey, string $tableName) {
'default' => '0', 'default' => '0',
'items' => [ 'items' => [
[ [
$languagePath . '.town.unkown', 'label' => $languagePath . '.town.unkown',
0, 'value' => 0,
], ],
], ],
'readOnly' => true, 'readOnly' => true,
@ -207,8 +198,8 @@ return (static function (string $extensionKey, string $tableName) {
'default' => '0', 'default' => '0',
'items' => [ 'items' => [
[ [
$languagePath . '.managed_by.unkown', 'label' => $languagePath . '.managed_by.unkown',
0, 'value' => 0,
], ],
], ],
'readOnly' => true, 'readOnly' => true,
@ -227,4 +218,4 @@ return (static function (string $extensionKey, string $tableName) {
], ],
], ],
]; ];
})(\WerkraumMedia\ThueCat\Extension::EXTENSION_KEY, 'tx_thuecat_parking_facility'); })(Extension::EXTENSION_KEY, 'tx_thuecat_parking_facility');

View file

@ -1,18 +1,21 @@
<?php <?php
declare(strict_types=1);
use WerkraumMedia\ThueCat\Extension;
defined('TYPO3') or die(); defined('TYPO3') or die();
return (static function (string $extensionKey, string $tableName) { return (static function (string $extensionKey, string $tableName) {
$languagePath = \WerkraumMedia\ThueCat\Extension::getLanguagePath() . 'locallang_tca.xlf:' . $tableName; $languagePath = Extension::getLanguagePath() . 'locallang_tca.xlf:' . $tableName;
return [ return [
'ctrl' => [ 'ctrl' => [
'label' => 'title', 'label' => 'title',
'iconfile' => \WerkraumMedia\ThueCat\Extension::getIconPath() . $tableName . '.svg', 'iconfile' => Extension::getIconPath() . $tableName . '.svg',
'default_sortby' => 'title', 'default_sortby' => 'title',
'tstamp' => 'tstamp', 'tstamp' => 'tstamp',
'crdate' => 'crdate', 'crdate' => 'crdate',
'cruser_id' => 'cruser_id',
'title' => $languagePath, 'title' => $languagePath,
'enablecolumns' => [ 'enablecolumns' => [
'disabled' => 'disable', 'disabled' => 'disable',
@ -27,19 +30,7 @@ return (static function (string $extensionKey, string $tableName) {
'sys_language_uid' => [ 'sys_language_uid' => [
'exclude' => true, 'exclude' => true,
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language',
'config' => [ 'config' => ['type' => 'language'],
'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' => [ 'l18n_parent' => [
'displayCond' => 'FIELD:sys_language_uid:>:0', 'displayCond' => 'FIELD:sys_language_uid:>:0',
@ -47,7 +38,7 @@ return (static function (string $extensionKey, string $tableName) {
'config' => [ 'config' => [
'type' => 'select', 'type' => 'select',
'renderType' => 'selectSingle', 'renderType' => 'selectSingle',
'items' => [['', 0]], 'items' => [['label' => '', 'value' => 0]],
'foreign_table' => $tableName, 'foreign_table' => $tableName,
'foreign_table_where' => 'AND ' . $tableName . '.pid=###CURRENT_PID### AND ' . $tableName . '.sys_language_uid IN (-1,0)', 'foreign_table_where' => 'AND ' . $tableName . '.pid=###CURRENT_PID### AND ' . $tableName . '.sys_language_uid IN (-1,0)',
'default' => 0, 'default' => 0,
@ -269,8 +260,8 @@ return (static function (string $extensionKey, string $tableName) {
'default' => '0', 'default' => '0',
'items' => [ 'items' => [
[ [
$languagePath . '.town.unkown', 'label' => $languagePath . '.town.unkown',
0, 'value' => 0,
], ],
], ],
'readOnly' => true, 'readOnly' => true,
@ -286,8 +277,8 @@ return (static function (string $extensionKey, string $tableName) {
'default' => '0', 'default' => '0',
'items' => [ 'items' => [
[ [
$languagePath . '.managed_by.unkown', 'label' => $languagePath . '.managed_by.unkown',
0, 'value' => 0,
], ],
], ],
'readOnly' => true, 'readOnly' => true,
@ -298,12 +289,11 @@ return (static function (string $extensionKey, string $tableName) {
'l10n_mode' => 'exclude', 'l10n_mode' => 'exclude',
'config' => [ 'config' => [
'type' => 'group', 'type' => 'group',
'internal_type' => 'db',
'allowed' => 'tx_thuecat_parking_facility', 'allowed' => 'tx_thuecat_parking_facility',
'foreign_table' => 'tx_thuecat_parking_facility', 'foreign_table' => 'tx_thuecat_parking_facility',
'suggestOptions' => [ 'suggestOptions' => [
'tx_thuecat_parking_facility' => [ 'tx_thuecat_parking_facility' => [
'searchCondition' => 'sys_language_uid IN (0,-1)' 'searchCondition' => 'sys_language_uid IN (0,-1)',
], ],
], ],
'readOnly' => true, 'readOnly' => true,
@ -313,11 +303,10 @@ return (static function (string $extensionKey, string $tableName) {
'editorial_images' => [ 'editorial_images' => [
'label' => $languagePath . '.editorial_images', 'label' => $languagePath . '.editorial_images',
'l10n_mode' => 'exclude', 'l10n_mode' => 'exclude',
'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( 'config' => [
'editorial_images', 'type' => 'file',
[], 'allowed' => 'common-image-types',
$GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] ],
),
], ],
], ],
'palettes' => [ 'palettes' => [
@ -332,4 +321,4 @@ return (static function (string $extensionKey, string $tableName) {
], ],
], ],
]; ];
})(\WerkraumMedia\ThueCat\Extension::EXTENSION_KEY, 'tx_thuecat_tourist_attraction'); })(Extension::EXTENSION_KEY, 'tx_thuecat_tourist_attraction');

View file

@ -1,18 +1,21 @@
<?php <?php
declare(strict_types=1);
use WerkraumMedia\ThueCat\Extension;
defined('TYPO3') or die(); defined('TYPO3') or die();
return (static function (string $extensionKey, string $tableName) { return (static function (string $extensionKey, string $tableName) {
$languagePath = \WerkraumMedia\ThueCat\Extension::getLanguagePath() . 'locallang_tca.xlf:' . $tableName; $languagePath = Extension::getLanguagePath() . 'locallang_tca.xlf:' . $tableName;
return [ return [
'ctrl' => [ 'ctrl' => [
'label' => 'title', 'label' => 'title',
'iconfile' => \WerkraumMedia\ThueCat\Extension::getIconPath() . $tableName . '.svg', 'iconfile' => Extension::getIconPath() . $tableName . '.svg',
'default_sortby' => 'title', 'default_sortby' => 'title',
'tstamp' => 'tstamp', 'tstamp' => 'tstamp',
'crdate' => 'crdate', 'crdate' => 'crdate',
'cruser_id' => 'cruser_id',
'title' => $languagePath, 'title' => $languagePath,
'enablecolumns' => [ 'enablecolumns' => [
'disabled' => 'disable', 'disabled' => 'disable',
@ -52,8 +55,8 @@ return (static function (string $extensionKey, string $tableName) {
'default' => '0', 'default' => '0',
'items' => [ 'items' => [
[ [
$languagePath . '.town.unkown', 'label' => $languagePath . '.town.unkown',
0, 'value' => 0,
], ],
], ],
'readOnly' => true, 'readOnly' => true,
@ -68,8 +71,8 @@ return (static function (string $extensionKey, string $tableName) {
'default' => '0', 'default' => '0',
'items' => [ 'items' => [
[ [
$languagePath . '.managed_by.unkown', 'label' => $languagePath . '.managed_by.unkown',
0, 'value' => 0,
], ],
], ],
'readOnly' => true, 'readOnly' => true,
@ -82,4 +85,4 @@ return (static function (string $extensionKey, string $tableName) {
], ],
], ],
]; ];
})(\WerkraumMedia\ThueCat\Extension::EXTENSION_KEY, 'tx_thuecat_tourist_information'); })(Extension::EXTENSION_KEY, 'tx_thuecat_tourist_information');

View file

@ -1,18 +1,21 @@
<?php <?php
declare(strict_types=1);
use WerkraumMedia\ThueCat\Extension;
defined('TYPO3') or die(); defined('TYPO3') or die();
return (static function (string $extensionKey, string $tableName) { return (static function (string $extensionKey, string $tableName) {
$languagePath = \WerkraumMedia\ThueCat\Extension::getLanguagePath() . 'locallang_tca.xlf:' . $tableName; $languagePath = Extension::getLanguagePath() . 'locallang_tca.xlf:' . $tableName;
return [ return [
'ctrl' => [ 'ctrl' => [
'label' => 'title', 'label' => 'title',
'iconfile' => \WerkraumMedia\ThueCat\Extension::getIconPath() . $tableName . '.svg', 'iconfile' => Extension::getIconPath() . $tableName . '.svg',
'default_sortby' => 'title', 'default_sortby' => 'title',
'tstamp' => 'tstamp', 'tstamp' => 'tstamp',
'crdate' => 'crdate', 'crdate' => 'crdate',
'cruser_id' => 'cruser_id',
'title' => $languagePath, 'title' => $languagePath,
'enablecolumns' => [ 'enablecolumns' => [
'disabled' => 'disable', 'disabled' => 'disable',
@ -51,8 +54,8 @@ return (static function (string $extensionKey, string $tableName) {
'foreign_table' => 'tx_thuecat_organisation', 'foreign_table' => 'tx_thuecat_organisation',
'items' => [ 'items' => [
[ [
$languagePath . '.managed_by.unkown', 'label' => $languagePath . '.managed_by.unkown',
0, 'value' => 0,
], ],
], ],
'readOnly' => true, 'readOnly' => true,
@ -74,4 +77,4 @@ return (static function (string $extensionKey, string $tableName) {
], ],
], ],
]; ];
})(\WerkraumMedia\ThueCat\Extension::EXTENSION_KEY, 'tx_thuecat_town'); })(Extension::EXTENSION_KEY, 'tx_thuecat_town');

View file

@ -20,5 +20,4 @@ Table of Contents
Configuration Configuration
Integration Integration
Changelog Changelog
Maintenance
Sitemap Sitemap

View file

@ -1,15 +0,0 @@
PHP 7.4
=======
Changes that should happen once we drop PHP 7.4.
Remove ``symfony/polyfill-php80`` dependency
--------------------------------------------
We use PHP 8.0 functions within our code base (to not add legacy code and deprecations).
One example is :file:`Classes/Domain/Import/Import.php` where we use ``str_ends_with()``.
We therefore added ``symfony/polyfill-php80`` as composer package to already make use of those functions.
We can drop that package once we are at least on PHP 8.0.

View file

@ -3,6 +3,9 @@
xmlns:f="http://typo3.org/ns/TYPO3/CMS/Backend/ViewHelpers" xmlns:f="http://typo3.org/ns/TYPO3/CMS/Backend/ViewHelpers"
data-namespace-typo3-fluid="true"> data-namespace-typo3-fluid="true">
{f:layout(name: 'Module')}
<f:section name="Content">
<h1>{f:translate(id: 'module.overview.headline')}</h1> <h1>{f:translate(id: 'module.overview.headline')}</h1>
<f:flashMessages /> <f:flashMessages />
@ -48,8 +51,10 @@
>{f:translate(id: 'module.organisations.missing.text')}</f:be.infobox> >{f:translate(id: 'module.organisations.missing.text')}</f:be.infobox>
</f:else> </f:else>
</f:if> </f:if>
</f:section>
<f:section name="ImportConfigurations"> <f:section name="ImportConfigurations">
<div class="panel panel-default">
<table class="table table-striped table-hover"> <table class="table table-striped table-hover">
<thead> <thead>
<tr> <tr>
@ -95,9 +100,11 @@
</f:for> </f:for>
</tbody> </tbody>
</table> </table>
</div>
</f:section> </f:section>
<f:section name="Organisations"> <f:section name="Organisations">
<div class="panel panel-default">
<table class="table table-striped table-hover"> <table class="table table-striped table-hover">
<thead> <thead>
<tr> <tr>
@ -127,6 +134,7 @@
</f:for> </f:for>
</tbody> </tbody>
</table> </table>
</div>
</f:section> </f:section>
<f:section name="Towns"> <f:section name="Towns">

View file

@ -1,6 +1,9 @@
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" <html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
data-namespace-typo3-fluid="true"> data-namespace-typo3-fluid="true">
{f:layout(name: 'Module')}
<f:section name="Content">
<h1>{f:translate(id: 'module.imports.headline')}</h1> <h1>{f:translate(id: 'module.imports.headline')}</h1>
<f:flashMessages /> <f:flashMessages />
@ -18,8 +21,10 @@
</f:be.infobox> </f:be.infobox>
</f:else> </f:else>
</f:if> </f:if>
</f:section>
<f:section name="Imports"> <f:section name="Imports">
<div class="panel panel-default">
<table class="table table-striped table-hover"> <table class="table table-striped table-hover">
<thead> <thead>
<tr> <tr>
@ -36,6 +41,7 @@
</f:for> </f:for>
</tbody> </tbody>
</table> </table>
</div>
</f:section> </f:section>
<f:section name="Import"> <f:section name="Import">

View file

@ -1,45 +0,0 @@
pages
,uid,pid,doktype,slug,title
,1,0,4,/,Rootpage
,2,1,255,/storage,Storage
tx_thuecat_import_configuration
,uid,pid,title,type,configuration
,1,2,"Example Configuration",static,"<?xml version=""1.0"" encoding=""utf-8"" standalone=""yes"" ?><T3FlexForms>
<data>
<sheet index=""sDEF"">
<language index=""lDEF"">
<field index=""storagePid"">
<value index=""vDEF"">2</value>
</field>
<field index=""urls"">
<el index=""el"">
<field index=""633554a57c83b383375701"">
<value index=""url"">
<el>
<field index=""url"">
<value index=""vDEF"">https://thuecat.org/resources/644315157726-jmww</value>
</field>
</el>
</value>
<value index=""_TOGGLE"">0</value>
</field>
<field index=""633551f49acee985442403"">
<value index=""url"">
<el>
<field index=""url"">
<value index=""vDEF"">https://thuecat.org/resources/072778761562-kwah</value>
</field>
</el>
</value>
<value index=""_TOGGLE"">0</value>
</field>
</el>
</field>
</language>
</sheet>
</data>
</T3FlexForms>",
"be_users"
,"uid","pid","tstamp","username","password","admin","disable","starttime","endtime","options","crdate","workspace_perms","deleted","TSconfig","lastlogin","workspace_id","db_mountpoints","usergroup","realName"
# password is "password"
,1,0,1366642540,"admin","$1$tCrlLajZ$C0sikFQQ3SWaFAZ1Me0Z/1",1,0,0,0,0,1366642540,1,0,,1371033743,0,0,0,"Klaus Admin"
Can't render this file because it has a wrong number of fields in line 2.

View file

@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
use TYPO3\CMS\Core\Domain\Repository\PageRepository;
return [
'be_users' => [
0 => [
'uid' => '1',
'pid' => '0',
'tstamp' => '1366642540',
'username' => 'admin',
'password' => '$1$tCrlLajZ$C0sikFQQ3SWaFAZ1Me0Z/1',
'admin' => '1',
'disable' => '0',
'starttime' => '0',
'endtime' => '0',
'options' => '0',
'crdate' => '1366642540',
'workspace_perms' => '1',
'deleted' => '0',
'TSconfig' => null,
'lastlogin' => '1371033743',
'workspace_id' => '0',
],
],
'pages' => [
0 => [
'uid' => '1',
'pid' => '0',
'doktype' => PageRepository::DOKTYPE_DEFAULT,
'slug' => '/',
'title' => 'Rootpage',
],
1 => [
'uid' => '2',
'pid' => '1',
'doktype' => PageRepository::DOKTYPE_SYSFOLDER,
'slug' => '/storage',
'title' => 'Storage',
],
],
'tx_thuecat_import_configuration' => [
0 => [
'uid' => '1',
'pid' => '2',
'title' => 'Example Configuration',
'type' => 'static',
'configuration' => '<?xml version="1.0" encoding="utf-8" standalone="yes" ?><T3FlexForms>
<data>
<sheet index="sDEF">
<language index="lDEF">
<field index="storagePid">
<value index="vDEF">2</value>
</field>
<field index="urls">
<el index="el">
<field index="633554a57c83b383375701">
<value index="url">
<el>
<field index="url">
<value index="vDEF">https://thuecat.org/resources/644315157726-jmww</value>
</field>
</el>
</value>
<value index="_TOGGLE">0</value>
</field>
<field index="633551f49acee985442403">
<value index="url">
<el>
<field index="url">
<value index="vDEF">https://thuecat.org/resources/072778761562-kwah</value>
</field>
</el>
</value>
<value index="_TOGGLE">0</value>
</field>
</el>
</field>
</language>
</sheet>
</data>
</T3FlexForms>',
],
],
];

View file

@ -29,6 +29,7 @@ use WerkraumMedia\ThueCat\Tests\Acceptance\Support\_generated\AcceptanceTesterAc
/** /**
* Inherited Methods * Inherited Methods
*
* @method void wantToTest($text) * @method void wantToTest($text)
* @method void wantTo($text) * @method void wantTo($text)
* @method void execute($callable) * @method void execute($callable)

View file

@ -23,6 +23,8 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Tests\Acceptance\Support; namespace WerkraumMedia\ThueCat\Tests\Acceptance\Support;
use Codappix\Typo3PhpDatasets\TestingFramework;
use Codeception\Event\SuiteEvent;
use TYPO3\TestingFramework\Core\Acceptance\Extension\BackendEnvironment; use TYPO3\TestingFramework\Core\Acceptance\Extension\BackendEnvironment;
/** /**
@ -30,6 +32,8 @@ use TYPO3\TestingFramework\Core\Acceptance\Extension\BackendEnvironment;
*/ */
class Environment extends BackendEnvironment class Environment extends BackendEnvironment
{ {
use TestingFramework;
protected $localConfig = [ protected $localConfig = [
'coreExtensionsToLoad' => [ 'coreExtensionsToLoad' => [
'install', 'install',
@ -40,13 +44,17 @@ class Environment extends BackendEnvironment
'fluid', 'fluid',
], ],
'testExtensionsToLoad' => [ 'testExtensionsToLoad' => [
'typo3conf/ext/thuecat', 'werkraummedia/thuecat',
],
'csvDatabaseFixtures' => [
__DIR__ . '/../Data/BasicDatabase.csv',
], ],
'pathsToLinkInTestInstance' => [ 'pathsToLinkInTestInstance' => [
'/../../../../../../Tests/Acceptance/Data/Sites/' => 'typo3conf/sites', '/../../../../../../Tests/Acceptance/Data/Sites/' => 'typo3conf/sites',
], ],
]; ];
public function bootstrapTypo3Environment(SuiteEvent $suiteEvent)
{
parent::bootstrapTypo3Environment($suiteEvent);
$this->importPHPDataSet(__DIR__ . '/../Data/BasicDatabase.php');
}
} }

View file

@ -23,14 +23,17 @@ declare(strict_types=1);
namespace WerkraumMedia\ThueCat\Tests\Functional; namespace WerkraumMedia\ThueCat\Tests\Functional;
use TYPO3\CMS\Core\Localization\LanguageService; use Codappix\Typo3PhpDatasets\TestingFramework;
use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; use TYPO3\CMS\Core\Localization\LanguageServiceFactory;
abstract class AbstractImportTest extends FunctionalTestCase abstract class AbstractImportTestCase extends \TYPO3\TestingFramework\Core\Functional\FunctionalTestCase
{ {
use TestingFramework;
/** /**
* Whether to expect errors to be logged. * Whether to expect errors to be logged.
* Will check for no errors if set to false. * Will check for no errors if set to false.
*
* @var bool * @var bool
*/ */
protected $expectErrors = false; protected $expectErrors = false;
@ -43,15 +46,12 @@ abstract class AbstractImportTest extends FunctionalTestCase
'extbase', 'extbase',
'frontend', 'frontend',
]); ]);
$this->testExtensionsToLoad = array_merge($this->testExtensionsToLoad, [ $this->testExtensionsToLoad = array_merge($this->testExtensionsToLoad, [
'typo3conf/ext/thuecat/', 'werkraummedia/thuecat/',
]); ]);
$this->pathsToLinkInTestInstance = array_merge($this->pathsToLinkInTestInstance, [ $this->pathsToLinkInTestInstance = array_merge($this->pathsToLinkInTestInstance, [
'typo3conf/ext/thuecat/Tests/Functional/Fixtures/Import/Sites/' => 'typo3conf/sites', 'typo3conf/ext/thuecat/Tests/Functional/Fixtures/Import/Sites/' => 'typo3conf/sites',
]); ]);
$this->configurationToUseInTestInstance = array_merge($this->configurationToUseInTestInstance, [ $this->configurationToUseInTestInstance = array_merge($this->configurationToUseInTestInstance, [
'LOG' => [ 'LOG' => [
'WerkraumMedia' => [ 'WerkraumMedia' => [
@ -72,12 +72,11 @@ abstract class AbstractImportTest extends FunctionalTestCase
]); ]);
parent::setUp(); parent::setUp();
GuzzleClientFaker::registerClient(); GuzzleClientFaker::registerClient();
$this->importPHPDataSet(__DIR__ . '/Fixtures/Import/BackendUser.php');
$this->setUpBackendUserFromFixture(1); $this->setUpBackendUser(1);
$GLOBALS['LANG'] = $this->getContainer()->get(LanguageServiceFactory::class)->create('en_US');
$GLOBALS['LANG'] = $this->getContainer()->get(LanguageService::class);
foreach ($this->getLogFiles() as $logFile) { foreach ($this->getLogFiles() as $logFile) {
file_put_contents($logFile, ''); file_put_contents($logFile, '');
} }
@ -88,13 +87,8 @@ abstract class AbstractImportTest extends FunctionalTestCase
if ($this->expectErrors === true) { if ($this->expectErrors === true) {
return; return;
} }
foreach ($this->getLogFiles() as $file) { foreach ($this->getLogFiles() as $file) {
$this->assertSame( self::assertSame('', file_get_contents($file), 'The TYPO3 log file "' . $file . '" contained content while expecting to be empty.');
'',
file_get_contents($file),
'The TYPO3 log file "' . $file . '" contained content while expecting to be empty.'
);
} }
} }
@ -103,10 +97,8 @@ abstract class AbstractImportTest extends FunctionalTestCase
$this->expectErrors = false; $this->expectErrors = false;
unset($GLOBALS['LANG']); unset($GLOBALS['LANG']);
GuzzleClientFaker::tearDown(); GuzzleClientFaker::tearDown();
parent::tearDown(); parent::tearDown();
} }
/** /**
* @return string[] * @return string[]
*/ */

View file

@ -0,0 +1,195 @@
<?php
declare(strict_types=1);
return [
'tx_thuecat_import_log' => [
0 => [
'uid' => '1',
'pid' => '0',
'configuration' => '1',
],
],
'tx_thuecat_import_log_entry' => [
0 => [
'uid' => '1',
'pid' => '0',
'import_log' => '1',
'record_uid' => '1',
'table_name' => 'tx_thuecat_organisation',
'insertion' => '1',
'errors' => '[]',
],
1 => [
'uid' => '2',
'pid' => '0',
'import_log' => '1',
'record_uid' => '1',
'table_name' => 'tx_thuecat_town',
'insertion' => '1',
'errors' => '[]',
],
2 => [
'uid' => '3',
'pid' => '0',
'import_log' => '1',
'record_uid' => '1',
'table_name' => 'tx_thuecat_parking_facility',
'insertion' => '1',
'errors' => '[]',
],
3 => [
'uid' => '4',
'pid' => '0',
'import_log' => '1',
'record_uid' => '2',
'table_name' => 'tx_thuecat_parking_facility',
'insertion' => '0',
'errors' => '[]',
],
4 => [
'uid' => '5',
'pid' => '0',
'import_log' => '1',
'record_uid' => '3',
'table_name' => 'tx_thuecat_parking_facility',
'insertion' => '0',
'errors' => '[]',
],
5 => [
'uid' => '6',
'pid' => '0',
'import_log' => '1',
'record_uid' => '1',
'table_name' => 'tx_thuecat_tourist_attraction',
'insertion' => '1',
'errors' => '[]',
],
6 => [
'uid' => '7',
'pid' => '0',
'import_log' => '1',
'record_uid' => '2',
'table_name' => 'tx_thuecat_tourist_attraction',
'insertion' => '0',
'errors' => '[]',
],
7 => [
'uid' => '8',
'pid' => '0',
'import_log' => '1',
'record_uid' => '3',
'table_name' => 'tx_thuecat_tourist_attraction',
'insertion' => '1',
'errors' => '[]',
],
8 => [
'uid' => '9',
'pid' => '0',
'import_log' => '1',
'record_uid' => '4',
'table_name' => 'tx_thuecat_tourist_attraction',
'insertion' => '0',
'errors' => '[]',
],
9 => [
'uid' => '10',
'pid' => '0',
'import_log' => '1',
'record_uid' => '5',
'table_name' => 'tx_thuecat_tourist_attraction',
'insertion' => '0',
'errors' => '[]',
],
],
'tx_thuecat_organisation' => [
0 => [
'uid' => '1',
'pid' => '10',
'remote_id' => 'https://thuecat.org/resources/018132452787-ngbe',
'title' => 'Erfurt Tourismus und Marketing GmbH',
],
],
'tx_thuecat_town' => [
0 => [
'uid' => '1',
'pid' => '10',
'remote_id' => 'https://thuecat.org/resources/043064193523-jcyt',
'title' => 'Erfurt',
'managed_by' => '1',
],
],
'tx_thuecat_parking_facility' => [
0 => [
'uid' => '1',
'pid' => '10',
'sys_language_uid' => '0',
'remote_id' => 'https://thuecat.org/resources/396420044896-drzt',
'title' => 'Parkhaus Domplatz',
'managed_by' => '1',
],
1 => [
'uid' => '2',
'pid' => '10',
'sys_language_uid' => '1',
'remote_id' => 'https://thuecat.org/resources/396420044896-drzt',
'title' => 'Car park Domplatz',
'managed_by' => '1',
],
2 => [
'uid' => '3',
'pid' => '10',
'sys_language_uid' => '2',
'remote_id' => 'https://thuecat.org/resources/396420044896-drzt',
'title' => 'Parking Domplatz',
'managed_by' => '1',
],
],
'tx_thuecat_tourist_attraction' => [
0 => [
'uid' => '1',
'pid' => '10',
'sys_language_uid' => '0',
'remote_id' => 'https://thuecat.org/resources/835224016581-dara',
'title' => 'Dom St. Marien',
'managed_by' => '1',
'town' => '1',
],
1 => [
'uid' => '2',
'pid' => '10',
'sys_language_uid' => '1',
'remote_id' => 'https://thuecat.org/resources/835224016581-dara',
'title' => 'Cathedral of St. Mary',
'managed_by' => '1',
'town' => '1',
],
2 => [
'uid' => '3',
'pid' => '10',
'sys_language_uid' => '0',
'remote_id' => 'https://thuecat.org/resources/165868194223-zmqf',
'title' => 'Alte Synagoge',
'managed_by' => '1',
'town' => '1',
],
3 => [
'uid' => '4',
'pid' => '10',
'sys_language_uid' => '1',
'remote_id' => 'https://thuecat.org/resources/165868194223-zmqf',
'title' => 'Old Synagogue',
'managed_by' => '1',
'town' => '1',
],
4 => [
'uid' => '5',
'pid' => '10',
'sys_language_uid' => '2',
'remote_id' => 'https://thuecat.org/resources/165868194223-zmqf',
'title' => 'La vieille synagogue',
'managed_by' => '1',
'town' => '1',
],
],
];

View file

@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
return [
'tx_thuecat_tourist_attraction' => [
0 => [
'uid' => '1',
'pid' => '10',
'sys_language_uid' => '0',
'remote_id' => 'https://thuecat.org/resources/835224016581-dara',
'title' => 'Dom St. Marien',
],
1 => [
'uid' => '2',
'pid' => '10',
'sys_language_uid' => '1',
'remote_id' => 'https://thuecat.org/resources/835224016581-dara',
'title' => 'Cathedral of St. Mary',
],
2 => [
'uid' => '3',
'pid' => '10',
'sys_language_uid' => '0',
'remote_id' => 'https://thuecat.org/resources/165868194223-zmqf',
'title' => 'Alte Synagoge',
],
3 => [
'uid' => '4',
'pid' => '10',
'sys_language_uid' => '1',
'remote_id' => 'https://thuecat.org/resources/165868194223-zmqf',
'title' => 'Old Synagogue',
],
4 => [
'uid' => '5',
'pid' => '10',
'sys_language_uid' => '2',
'remote_id' => 'https://thuecat.org/resources/165868194223-zmqf',
'title' => 'La vieille synagogue',
],
5 => [
'uid' => '6',
'pid' => '10',
'sys_language_uid' => '0',
'remote_id' => 'https://thuecat.org/resources/215230952334-yyno',
'title' => 'Krämerbrücke',
],
6 => [
'uid' => '7',
'pid' => '10',
'sys_language_uid' => '1',
'remote_id' => 'https://thuecat.org/resources/215230952334-yyno',
'title' => 'Merchants\' Bridge',
],
7 => [
'uid' => '8',
'pid' => '10',
'sys_language_uid' => '2',
'remote_id' => 'https://thuecat.org/resources/215230952334-yyno',
'title' => 'Pont de l\'épicier',
],
],
'tx_thuecat_organisation' => [
0 => [
'uid' => '1',
'pid' => '10',
'remote_id' => 'https://thuecat.org/resources/018132452787-ngbe',
'title' => 'Erfurt Tourismus und Marketing GmbH',
],
],
'tx_thuecat_town' => [
0 => [
'uid' => '1',
'pid' => '10',
'remote_id' => 'https://thuecat.org/resources/043064193523-jcyt',
'title' => 'Erfurt',
],
],
];

View file

@ -0,0 +1,119 @@
<?php
declare(strict_types=1);
return [
'tx_thuecat_tourist_attraction' => [
0 => [
'uid' => '1',
'pid' => '10',
'sys_language_uid' => '0',
'remote_id' => 'https://thuecat.org/resources/165868194223-zmqf',
'title' => 'Alte Synagoge',
],
1 => [
'uid' => '2',
'pid' => '10',
'sys_language_uid' => '1',
'remote_id' => 'https://thuecat.org/resources/165868194223-zmqf',
'title' => 'Old Synagogue',
],
2 => [
'uid' => '3',
'pid' => '10',
'sys_language_uid' => '2',
'remote_id' => 'https://thuecat.org/resources/165868194223-zmqf',
'title' => 'La vieille synagogue',
],
],
'tx_thuecat_import_log' => [
0 => [
'uid' => '1',
'pid' => '0',
'configuration' => '1',
'log_entries' => '0',
],
],
'tx_thuecat_import_log_entry' => [
0 => [
'uid' => '1',
'pid' => '0',
'type' => 'mappingError',
'import_log' => '1',
'record_uid' => '0',
'table_name' => '',
'insertion' => '0',
'errors' => '["Could not map incoming JSON-LD to target object: Failed to parse time string (18:00: 00) at position 5 (:): Unexpected character"]',
],
1 => [
'uid' => '2',
'pid' => '0',
'type' => 'mappingError',
'import_log' => '1',
'record_uid' => '0',
'table_name' => '',
'insertion' => '0',
'errors' => '["Could not map incoming JSON-LD to target object: Failed to parse time string (18:00: 00) at position 5 (:): Unexpected character"]',
],
2 => [
'uid' => '3',
'pid' => '0',
'type' => 'mappingError',
'import_log' => '1',
'record_uid' => '0',
'table_name' => '',
'insertion' => '0',
'errors' => '["Could not map incoming JSON-LD to target object: Failed to parse time string (18:00: 00) at position 5 (:): Unexpected character"]',
],
3 => [
'uid' => '4',
'pid' => '0',
'type' => 'savingEntity',
'import_log' => '1',
'record_uid' => '1',
'table_name' => 'tx_thuecat_organisation',
'insertion' => '1',
'errors' => '[]',
],
4 => [
'uid' => '5',
'pid' => '0',
'type' => 'savingEntity',
'import_log' => '1',
'record_uid' => '1',
'table_name' => 'tx_thuecat_town',
'insertion' => '0',
'errors' => '[]',
],
5 => [
'uid' => '6',
'pid' => '0',
'type' => 'savingEntity',
'import_log' => '1',
'record_uid' => '1',
'table_name' => 'tx_thuecat_tourist_attraction',
'insertion' => '1',
'errors' => '[]',
],
6 => [
'uid' => '7',
'pid' => '0',
'type' => 'savingEntity',
'import_log' => '1',
'record_uid' => '2',
'table_name' => 'tx_thuecat_tourist_attraction',
'insertion' => '0',
'errors' => '[]',
],
7 => [
'uid' => '8',
'pid' => '0',
'type' => 'savingEntity',
'import_log' => '1',
'record_uid' => '3',
'table_name' => 'tx_thuecat_tourist_attraction',
'insertion' => '0',
'errors' => '[]',
],
],
];

View file

@ -0,0 +1,119 @@
<?php
declare(strict_types=1);
return [
'tx_thuecat_tourist_attraction' => [
0 => [
'uid' => '1',
'pid' => '10',
'sys_language_uid' => '0',
'remote_id' => 'https://thuecat.org/resources/165868194223-zmqf',
'title' => 'Alte Synagoge',
],
1 => [
'uid' => '2',
'pid' => '10',
'sys_language_uid' => '1',
'remote_id' => 'https://thuecat.org/resources/165868194223-zmqf',
'title' => 'Old Synagogue',
],
2 => [
'uid' => '3',
'pid' => '10',
'sys_language_uid' => '2',
'remote_id' => 'https://thuecat.org/resources/165868194223-zmqf',
'title' => 'La vieille synagogue',
],
],
'tx_thuecat_import_log' => [
0 => [
'uid' => '1',
'pid' => '0',
'configuration' => '1',
'log_entries' => '0',
],
],
'tx_thuecat_import_log_entry' => [
0 => [
'uid' => '1',
'pid' => '0',
'type' => 'mappingError',
'import_log' => '1',
'record_uid' => '0',
'table_name' => '',
'insertion' => '0',
'errors' => '["Could not map incoming JSON-LD to target object: DateTimeImmutable::__construct(): Failed to parse time string (18:00: 00) at position 5 (:): Unexpected character"]',
],
1 => [
'uid' => '2',
'pid' => '0',
'type' => 'mappingError',
'import_log' => '1',
'record_uid' => '0',
'table_name' => '',
'insertion' => '0',
'errors' => '["Could not map incoming JSON-LD to target object: DateTimeImmutable::__construct(): Failed to parse time string (18:00: 00) at position 5 (:): Unexpected character"]',
],
2 => [
'uid' => '3',
'pid' => '0',
'type' => 'mappingError',
'import_log' => '1',
'record_uid' => '0',
'table_name' => '',
'insertion' => '0',
'errors' => '["Could not map incoming JSON-LD to target object: DateTimeImmutable::__construct(): Failed to parse time string (18:00: 00) at position 5 (:): Unexpected character"]',
],
3 => [
'uid' => '4',
'pid' => '0',
'type' => 'savingEntity',
'import_log' => '1',
'record_uid' => '1',
'table_name' => 'tx_thuecat_organisation',
'insertion' => '1',
'errors' => '[]',
],
4 => [
'uid' => '5',
'pid' => '0',
'type' => 'savingEntity',
'import_log' => '1',
'record_uid' => '1',
'table_name' => 'tx_thuecat_town',
'insertion' => '0',
'errors' => '[]',
],
5 => [
'uid' => '6',
'pid' => '0',
'type' => 'savingEntity',
'import_log' => '1',
'record_uid' => '1',
'table_name' => 'tx_thuecat_tourist_attraction',
'insertion' => '1',
'errors' => '[]',
],
6 => [
'uid' => '7',
'pid' => '0',
'type' => 'savingEntity',
'import_log' => '1',
'record_uid' => '2',
'table_name' => 'tx_thuecat_tourist_attraction',
'insertion' => '0',
'errors' => '[]',
],
7 => [
'uid' => '8',
'pid' => '0',
'type' => 'savingEntity',
'import_log' => '1',
'record_uid' => '3',
'table_name' => 'tx_thuecat_tourist_attraction',
'insertion' => '0',
'errors' => '[]',
],
],
];

View file

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
return [
'tx_thuecat_organisation' => [
0 => [
'uid' => '1',
'pid' => '11',
'remote_id' => 'https://thuecat.org/resources/018132452787-ngbe',
'title' => 'Erfurt Tourismus und Marketing GmbH',
],
],
'tx_thuecat_import_log' => [
0 => [
'uid' => '1',
'pid' => '0',
'configuration' => '1',
],
],
'tx_thuecat_import_log_entry' => [
0 => [
'uid' => '1',
'pid' => '0',
'import_log' => '1',
'record_uid' => '1',
'table_name' => 'tx_thuecat_organisation',
'insertion' => '1',
'errors' => '[]',
],
],
];

View file

@ -0,0 +1,120 @@
<?php
declare(strict_types=1);
return [
'tx_thuecat_tourist_attraction' => [
0 => [
'uid' => '1',
'pid' => '10',
'sys_language_uid' => '0',
'l18n_parent' => '0',
'l10n_source' => '0',
'l10n_state' => null,
'remote_id' => 'https://thuecat.org/resources/835224016581-dara',
'title' => 'Dom St. Marien',
'managed_by' => '1',
'town' => '1',
'address' => '{"street":"Domstufen 1","zip":"99084","city":"Erfurt","email":"dominformation@domberg-erfurt.de","phone":"+49 361 6461265","fax":"","geo":{"latitude":50.975955358589545,"longitude":11.023667024961856}}',
'offers' => '[]',
],
1 => [
'uid' => '2',
'pid' => '10',
'sys_language_uid' => '1',
'l18n_parent' => '1',
'l10n_source' => '1',
'l10n_state' => null,
'remote_id' => 'https://thuecat.org/resources/835224016581-dara',
'title' => 'Cathedral of St. Mary',
'managed_by' => '1',
'town' => '1',
'address' => '{"street":"Domstufen 1","zip":"99084","city":"Erfurt","email":"dominformation@domberg-erfurt.de","phone":"+49 361 6461265","fax":"","geo":{"latitude":50.975955358589545,"longitude":11.023667024961856}}',
'offers' => '[]',
],
2 => [
'uid' => '3',
'pid' => '10',
'sys_language_uid' => '0',
'l18n_parent' => '0',
'l10n_source' => '0',
'l10n_state' => null,
'remote_id' => 'https://thuecat.org/resources/165868194223-zmqf',
'title' => 'Alte Synagoge',
'managed_by' => '1',
'town' => '1',
'address' => '{"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}}',
'offers' => '[{"types":["GuidedTourOffer"],"title":"F\\u00fchrungen","description":"Immer samstags, um 11:15 Uhr findet eine \\u00f6ffentliche F\\u00fchrung durch das Museum statt. Dauer etwa 90 Minuten","prices":[{"title":"Erwachsene","description":"","price":8,"currency":"EUR","rule":"PerPerson"},{"title":"Erm\\u00e4\\u00dfigt","description":"als erm\\u00e4\\u00dfigt gelten schulpflichtige Kinder, Auszubildende, Studierende, Rentner\\/-innen, Menschen mit Behinderungen, Inhaber Sozialausweis der Landeshauptstadt Erfurt","price":5,"currency":"EUR","rule":"PerPerson"}]},{"types":["EntryOffer"],"title":"Eintritt","description":"Schulklassen und Kitagruppen im Rahmen des Unterrichts: Eintritt frei\\nAn jedem ersten Dienstag im Monat: Eintritt frei","prices":[{"title":"Erm\\u00e4\\u00dfigt","description":"als erm\\u00e4\\u00dfigt gelten schulpflichtige Kinder, Auszubildende, Studierende, Rentner\\/-innen, Menschen mit Behinderungen, Inhaber Sozialausweis der Landeshauptstadt Erfurt","price":5,"currency":"EUR","rule":"PerPerson"},{"title":"Familienkarte","description":"","price":17,"currency":"EUR","rule":"PerGroup"},{"title":"ErfurtCard","description":"","price":14.9,"currency":"EUR","rule":"PerPackage"},{"title":"Erwachsene","description":"","price":8,"currency":"EUR","rule":"PerPerson"}]}]',
],
3 => [
'uid' => '4',
'pid' => '10',
'sys_language_uid' => '1',
'l18n_parent' => '3',
'l10n_source' => '3',
'l10n_state' => null,
'remote_id' => 'https://thuecat.org/resources/165868194223-zmqf',
'title' => 'Old Synagogue',
'managed_by' => '1',
'town' => '1',
'address' => '{"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}}',
'offers' => '[{"types":["GuidedTourOffer"],"title":"","description":"","prices":[{"title":"","description":"","price":8,"currency":"EUR","rule":"PerPerson"},{"title":"","description":"","price":5,"currency":"EUR","rule":"PerPerson"}]},{"types":["EntryOffer"],"title":"","description":"","prices":[{"title":"","description":"","price":5,"currency":"EUR","rule":"PerPerson"},{"title":"","description":"","price":17,"currency":"EUR","rule":"PerGroup"},{"title":"","description":"","price":14.9,"currency":"EUR","rule":"PerPackage"},{"title":"","description":"","price":8,"currency":"EUR","rule":"PerPerson"}]}]',
],
4 => [
'uid' => '5',
'pid' => '10',
'sys_language_uid' => '2',
'l18n_parent' => '3',
'l10n_source' => '3',
'l10n_state' => null,
'remote_id' => 'https://thuecat.org/resources/165868194223-zmqf',
'title' => 'La vieille synagogue',
'managed_by' => '1',
'town' => '1',
'address' => '{"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}}',
'offers' => '[{"types":["GuidedTourOffer"],"title":"","description":"","prices":[{"title":"","description":"","price":8,"currency":"EUR","rule":"PerPerson"},{"title":"","description":"","price":5,"currency":"EUR","rule":"PerPerson"}]},{"types":["EntryOffer"],"title":"","description":"","prices":[{"title":"","description":"","price":5,"currency":"EUR","rule":"PerPerson"},{"title":"","description":"","price":17,"currency":"EUR","rule":"PerGroup"},{"title":"","description":"","price":14.9,"currency":"EUR","rule":"PerPackage"},{"title":"","description":"","price":8,"currency":"EUR","rule":"PerPerson"}]}]',
],
5 => [
'uid' => '6',
'pid' => '10',
'sys_language_uid' => '0',
'l18n_parent' => '0',
'l10n_source' => '0',
'l10n_state' => null,
'remote_id' => 'https://thuecat.org/resources/215230952334-yyno',
'title' => 'Krämerbrücke',
'managed_by' => '1',
'town' => '1',
'address' => '{"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}}',
'offers' => '[]',
],
6 => [
'uid' => '7',
'pid' => '10',
'sys_language_uid' => '1',
'l18n_parent' => '6',
'l10n_source' => '6',
'l10n_state' => null,
'remote_id' => 'https://thuecat.org/resources/215230952334-yyno',
'title' => 'Merchants\' Bridge',
'managed_by' => '1',
'town' => '1',
'address' => '{"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}}',
'offers' => '[]',
],
7 => [
'uid' => '8',
'pid' => '10',
'sys_language_uid' => '2',
'l18n_parent' => '6',
'l10n_source' => '6',
'l10n_state' => null,
'remote_id' => 'https://thuecat.org/resources/215230952334-yyno',
'title' => 'Pont de l\'épicier',
'managed_by' => '1',
'town' => '1',
'address' => '{"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}}',
'offers' => '[]',
],
],
];

View file

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
return [
'tx_thuecat_tourist_attraction' => [
0 => [
'uid' => '1',
'pid' => '10',
'sys_language_uid' => '0',
'l18n_parent' => '0',
'l10n_source' => '0',
'remote_id' => 'https://thuecat.org/resources/attraction-with-accessibility-specification',
'title' => 'Attraktion mit accessibility specification',
],
1 => [
'uid' => '2',
'pid' => '10',
'sys_language_uid' => '1',
'l18n_parent' => '1',
'l10n_source' => '1',
'remote_id' => 'https://thuecat.org/resources/attraction-with-accessibility-specification',
'title' => 'Attraction with accessibility specification',
],
],
];

View file

@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
return [
'tx_thuecat_tourist_attraction' => [
0 => [
'uid' => '1',
'pid' => '10',
'remote_id' => 'https://thuecat.org/resources/attraction-with-media',
'title' => 'Attraktion mit Bildern',
'media' => '[{"mainImage":false,"type":"image","title":"Bild mit externem Autor","description":"","url":"https:\\/\\/cms.thuecat.org\\/o\\/adaptive-media\\/image\\/5099196\\/Preview-1280x0\\/image","author":"GivenName FamilyName","copyrightYear":0,"license":{"type":"","author":""}},{"mainImage":false,"type":"image","title":"Bild mit author","description":"","url":"https:\\/\\/cms.thuecat.org\\/o\\/adaptive-media\\/image\\/5099196\\/Preview-1280x0\\/image","author":"Full Name","copyrightYear":0,"license":{"type":"https:\\/\\/creativecommons.org\\/licenses\\/by\\/4.0\\/","author":""}},{"mainImage":false,"type":"image","title":"Bild mit license author","description":"","url":"https:\\/\\/cms.thuecat.org\\/o\\/adaptive-media\\/image\\/5099196\\/Preview-1280x0\\/image","author":"","copyrightYear":0,"license":{"type":"https:\\/\\/creativecommons.org\\/licenses\\/by\\/4.0\\/","author":"Autor aus Lizenz"}},{"mainImage":false,"type":"image","title":"Bild mit author und license author","description":"","url":"https:\\/\\/cms.thuecat.org\\/o\\/adaptive-media\\/image\\/5099196\\/Preview-1280x0\\/image","author":"Full Name","copyrightYear":0,"license":{"type":"https:\\/\\/creativecommons.org\\/licenses\\/by\\/4.0\\/","author":"Autor aus Lizenz"}}]',
],
1 => [
'uid' => '2',
'pid' => '10',
'remote_id' => 'https://thuecat.org/resources/attraction-with-media',
'title' => 'Attraction with media',
'media' => '[{"mainImage":false,"type":"image","title":"Bild mit externem Autor","description":"","url":"https:\\/\\/cms.thuecat.org\\/o\\/adaptive-media\\/image\\/5099196\\/Preview-1280x0\\/image","author":"GivenName FamilyName","copyrightYear":0,"license":{"type":"","author":""}},{"mainImage":false,"type":"image","title":"Bild mit author","description":"","url":"https:\\/\\/cms.thuecat.org\\/o\\/adaptive-media\\/image\\/5099196\\/Preview-1280x0\\/image","author":"Full Name","copyrightYear":0,"license":{"type":"https:\\/\\/creativecommons.org\\/licenses\\/by\\/4.0\\/","author":""}},{"mainImage":false,"type":"image","title":"Bild mit license author","description":"","url":"https:\\/\\/cms.thuecat.org\\/o\\/adaptive-media\\/image\\/5099196\\/Preview-1280x0\\/image","author":"","copyrightYear":0,"license":{"type":"https:\\/\\/creativecommons.org\\/licenses\\/by\\/4.0\\/","author":"Autor aus Lizenz"}},{"mainImage":false,"type":"image","title":"Bild mit author und license author","description":"","url":"https:\\/\\/cms.thuecat.org\\/o\\/adaptive-media\\/image\\/5099196\\/Preview-1280x0\\/image","author":"Full Name","copyrightYear":0,"license":{"type":"https:\\/\\/creativecommons.org\\/licenses\\/by\\/4.0\\/","author":"Autor aus Lizenz"}}]',
],
],
];

View file

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
return [
'tx_thuecat_tourist_attraction' => [
0 => [
'uid' => '1',
'pid' => '10',
'sys_language_uid' => '0',
'l18n_parent' => '0',
'l10n_source' => '0',
'remote_id' => 'https://thuecat.org/resources/835224016581-dara',
'title' => 'Dom St. Marien',
'opening_hours' => '[{"opens":"13:00:00","closes":"17:00:00","from":{"date":"2050-11-01 00:00:00.000000","timezone_type":3,"timezone":"UTC"},"through":{"date":"2050-04-30 00:00:00.000000","timezone_type":3,"timezone":"UTC"},"daysOfWeek":["Sunday"]}]',
],
1 => [
'uid' => '2',
'pid' => '10',
'sys_language_uid' => '1',
'l18n_parent' => '1',
'l10n_source' => '1',
'remote_id' => 'https://thuecat.org/resources/835224016581-dara',
'title' => 'Cathedral of St. Mary',
'opening_hours' => '[{"opens":"13:00:00","closes":"17:00:00","from":{"date":"2050-11-01 00:00:00.000000","timezone_type":3,"timezone":"UTC"},"through":{"date":"2050-04-30 00:00:00.000000","timezone_type":3,"timezone":"UTC"},"daysOfWeek":["Sunday"]}]',
],
],
];

View file

@ -0,0 +1,425 @@
<?php
declare(strict_types=1);
return [
'tx_thuecat_tourist_attraction' => [
0 => [
'uid' => '1',
'pid' => '10',
'sys_language_uid' => '0',
'l18n_parent' => '0',
'l10n_source' => '0',
'l10n_state' => null,
'remote_id' => 'https://thuecat.org/resources/835224016581-dara',
'title' => 'Dom St. Marien',
'description' => 'Über eine 70-stufige Freitreppe gelangt man vom Domplatz auf den Domberg mit seinen beiden ehemaligen Stiftskirchen. Der Dom, mit hochgotischem Chor, romanischem Turmbereich und spätgotischer Westhalle, ist Nachfolger des 724 von Rom veranlassten Sakralbaus. Er war die Hauptkirche des 742 von Bischof Bonifatius gegründeten Bistums Erfurt und während des Mittelalters bis in das frühe 19. Jahrhundert Sitz des Collegiatstifts St. Marien. 1507 erhielt Martin Luther hier die Priesterweihe.
Der ursprünglich romanische Kirchenbau wurde in der Zeit der Gotik entscheidend umgebaut. Besonders sehenswert sind die gotischen Chorfenster, das umfängliche Chorgestühl (14. Jhd.) sowie die romanischen Skulpturen einer thronenden Madonna und eines monumentalen Kerzenträgers im Innenraum. Berühmt ist er auch wegen der „Gloriosa“, der mit 2,56 m Durchmesser größten freischwingenden mittelalterlichen Glocke der Welt.
Das Ensemble von Dom und Severikirche bildet eine imposante Kulisse für die jährlich im Sommer stattfindenden DomStufen-Festspiele.',
'managed_by' => '1',
'town' => '1',
'address' => '{"street":"Domstufen 1","zip":"99084","city":"Erfurt","email":"dominformation@domberg-erfurt.de","phone":"+49 361 6461265","fax":"","geo":{"latitude":50.975955358589545,"longitude":11.023667024961856}}',
'offers' => '[]',
'media' => '[{"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","author":"","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","author":"","copyrightYear":2020,"license":{"type":"https:\\/\\/creativecommons.org\\/licenses\\/by\\/4.0\\/","author":""}}]',
'slogan' => '',
'start_of_construction' => '',
'sanitation' => 'Toilets,DisabledToilets',
'other_service' => 'SeatingPossibilitiesRestArea,SouvenirShop',
'museum_service' => '',
'architectural_style' => 'GothicArt',
'traffic_infrastructure' => 'BicycleLockersEnumMem,BusParkCoachParkEnumMem',
'payment_accepted' => '',
'digital_offer' => 'AugmentedReality',
'photography' => 'Fotogenehmigung für innen',
'pets_allowed' => 'false',
'is_accessible_for_free' => 'true',
'public_access' => 'true',
'available_languages' => 'German,English',
'distance_to_public_transport' => '350:MTR:Streetcar:CityBus',
'parking_facility_near_by' => '1',
'accessibility_specification' => '{}',
'url' => 'http://www.dom-erfurt.de',
],
1 => [
'uid' => '2',
'pid' => '10',
'sys_language_uid' => '1',
'l18n_parent' => '1',
'l10n_source' => '1',
'l10n_state' => null,
'remote_id' => 'https://thuecat.org/resources/835224016581-dara',
'title' => 'Cathedral of St. Mary',
'description' => 'The late Gothic cathedral with its high-Gothic choir and Romanesque tower replaced the church built on this site for Bishop Boniface in 742. The central tower houses the "Gloriosa", the worlds largest medieval free-swinging bell.',
'managed_by' => '1',
'town' => '1',
'address' => '{"street":"Domstufen 1","zip":"99084","city":"Erfurt","email":"dominformation@domberg-erfurt.de","phone":"+49 361 6461265","fax":"","geo":{"latitude":50.975955358589545,"longitude":11.023667024961856}}',
'offers' => '[]',
'media' => '[{"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","author":"","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","author":"","copyrightYear":2020,"license":{"type":"https:\\/\\/creativecommons.org\\/licenses\\/by\\/4.0\\/","author":""}}]',
'slogan' => '',
'start_of_construction' => '',
'sanitation' => 'Toilets,DisabledToilets',
'other_service' => 'SeatingPossibilitiesRestArea,SouvenirShop',
'museum_service' => '',
'architectural_style' => 'GothicArt',
'traffic_infrastructure' => 'BicycleLockersEnumMem,BusParkCoachParkEnumMem',
'payment_accepted' => '',
'digital_offer' => 'AugmentedReality',
'photography' => '',
'pets_allowed' => 'false',
'is_accessible_for_free' => 'true',
'public_access' => 'true',
'available_languages' => 'German,English',
'distance_to_public_transport' => '350:MTR:Streetcar:CityBus',
'parking_facility_near_by' => '1',
'accessibility_specification' => '{}',
'url' => 'http://www.dom-erfurt.de',
],
2 => [
'uid' => '3',
'pid' => '10',
'sys_language_uid' => '0',
'l18n_parent' => '0',
'l10n_source' => '0',
'l10n_state' => null,
'remote_id' => 'https://thuecat.org/resources/165868194223-zmqf',
'title' => 'Alte Synagoge',
'description' => 'Beispiel Beschreibung',
'managed_by' => '1',
'town' => '1',
'address' => '{"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}}',
'offers' => '[{"types":["GuidedTourOffer"],"title":"F\\u00fchrungen","description":"Immer samstags, um 11:15 Uhr findet eine \\u00f6ffentliche F\\u00fchrung durch das Museum statt. Dauer etwa 90 Minuten","prices":[{"title":"Erwachsene","description":"","price":8,"currency":"EUR","rule":"PerPerson"},{"title":"Erm\\u00e4\\u00dfigt","description":"als erm\\u00e4\\u00dfigt gelten schulpflichtige Kinder, Auszubildende, Studierende, Rentner\\/-innen, Menschen mit Behinderungen, Inhaber Sozialausweis der Landeshauptstadt Erfurt","price":5,"currency":"EUR","rule":"PerPerson"}]},{"types":["EntryOffer"],"title":"Eintritt","description":"Schulklassen und Kitagruppen im Rahmen des Unterrichts: Eintritt frei\\nAn jedem ersten Dienstag im Monat: Eintritt frei","prices":[{"title":"Erm\\u00e4\\u00dfigt","description":"als erm\\u00e4\\u00dfigt gelten schulpflichtige Kinder, Auszubildende, Studierende, Rentner\\/-innen, Menschen mit Behinderungen, Inhaber Sozialausweis der Landeshauptstadt Erfurt","price":5,"currency":"EUR","rule":"PerPerson"},{"title":"Familienkarte","description":"","price":17,"currency":"EUR","rule":"PerGroup"},{"title":"ErfurtCard","description":"","price":14.9,"currency":"EUR","rule":"PerPackage"},{"title":"Erwachsene","description":"","price":8,"currency":"EUR","rule":"PerPerson"}]}]',
'media' => '[{"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","author":"","copyrightYear":2009,"license":{"type":"https:\\/\\/creativecommons.org\\/licenses\\/by\\/4.0\\/","author":"F:\\\\Bilddatenbank\\\\Museen und Ausstellungen\\\\Alte Synagoge"}}]',
'slogan' => 'Highlight',
'start_of_construction' => '11. Jh.',
'sanitation' => 'Toilets,DisabledToilets,NappyChangingArea,FamilyAndChildFriendly',
'other_service' => 'SeatingPossibilitiesRestArea,LockBoxes,SouvenirShop,BaggageStorage',
'museum_service' => 'MuseumShop',
'architectural_style' => 'GothicArt',
'traffic_infrastructure' => 'ZeroSpecialTrafficInfrastructure',
'payment_accepted' => 'CashPayment,EC',
'digital_offer' => 'AudioGuide,VideoGuide',
'photography' => 'ZeroPhotography',
'pets_allowed' => 'Tiere sind im Gebäude nicht gestattet, ausgenommen sind Blinden- und Blindenbegleithunde.',
'is_accessible_for_free' => 'false',
'public_access' => 'true',
'available_languages' => 'German,English,French',
'distance_to_public_transport' => '200:MTR:CityBus',
'parking_facility_near_by' => '',
'accessibility_specification' => '{"accessibilityCertificationStatus":"AccessibilityChecked","certificationAccessibilityDeaf":"None","certificationAccessibilityMental":"None","certificationAccessibilityPartiallyDeaf":"None","certificationAccessibilityPartiallyVisual":"Info","certificationAccessibilityVisual":"None","certificationAccessibilityWalking":"Info","certificationAccessibilityWheelchair":"Info","accessibilitySearchCriteria":{"facilityAccessibilityWalking":["AllRoomsStepFreeAccess","HingedGrabRailToilet","LateralAccessibleToilet","StepFreeAccess","ToiletsPeopleWithDisabilities","NinetyCMWidthPassageWays","SpecialOffersWalkingImpairment","SpecialOffersWheelchairUsers"],"facilityAccessibilityVisual":["AssistanceDogsWelcome","VisuallyContrastingStepEdges","OffersInPictoralLanguage","SpecialOffersBlindPeople","SpecialOffersVisualImpairment","TactileOffers"],"facilityAccessibilityDeaf":["AudioInductionLoop","SpecialOffersHearingImpairment"],"facilityAccessibilityMental":["InformationWithPictogramsOrPictures"]},"shortDescriptionAccessibilityDeaf":"Deutsche Beschreibung von shortDescriptionAccessibilityDeaf","shortDescriptionAccessibilityMental":"Deutsche Beschreibung von shortDescriptionAccessibilityMental","shortDescriptionAccessibilityVisual":"Deutsche Beschreibung von shortDescriptionAccessibilityVisual","shortDescriptionAccessibilityWalking":"Deutsche Beschreibung von shortDescriptionAccessibilityWalking"}',
'url' => 'http://www.alte-synagoge.erfurt.de',
],
3 => [
'uid' => '4',
'pid' => '10',
'sys_language_uid' => '1',
'l18n_parent' => '3',
'l10n_source' => '3',
'l10n_state' => null,
'remote_id' => 'https://thuecat.org/resources/165868194223-zmqf',
'title' => 'Old Synagogue',
'description' => 'The Old Synagogue is one of very few preserved medieval synagogues in Europe. Thanks to the extensive preservation of the original structure, it has a special place in the history of art and architecture and is among the most impressive and highly rated architectural monuments in Erfurt and Thuringia. The synagogue was constructed during the Middle Ages on the "via regia", one of the major European trade routes, at the heart of the historical old quarter very close to the Merchants Bridge and the town hall. Many parts of the structure still remain today, including all four thick outer walls, the Roman­esque gemel window, the Gothic rose window and the entrance to the synagogue room.',
'managed_by' => '1',
'town' => '1',
'address' => '{"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}}',
'offers' => '[{"types":["GuidedTourOffer"],"title":"","description":"","prices":[{"title":"","description":"","price":8,"currency":"EUR","rule":"PerPerson"},{"title":"","description":"","price":5,"currency":"EUR","rule":"PerPerson"}]},{"types":["EntryOffer"],"title":"","description":"","prices":[{"title":"","description":"","price":5,"currency":"EUR","rule":"PerPerson"},{"title":"","description":"","price":17,"currency":"EUR","rule":"PerGroup"},{"title":"","description":"","price":14.9,"currency":"EUR","rule":"PerPackage"},{"title":"","description":"","price":8,"currency":"EUR","rule":"PerPerson"}]}]',
'media' => '[{"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","author":"","copyrightYear":2009,"license":{"type":"https:\\/\\/creativecommons.org\\/licenses\\/by\\/4.0\\/","author":"F:\\\\Bilddatenbank\\\\Museen und Ausstellungen\\\\Alte Synagoge"}}]',
'slogan' => 'Highlight',
'start_of_construction' => '11th century',
'sanitation' => 'Toilets,DisabledToilets,NappyChangingArea,FamilyAndChildFriendly',
'other_service' => 'SeatingPossibilitiesRestArea,LockBoxes,SouvenirShop,BaggageStorage',
'museum_service' => 'MuseumShop',
'architectural_style' => 'GothicArt',
'traffic_infrastructure' => 'ZeroSpecialTrafficInfrastructure',
'payment_accepted' => 'CashPayment,EC',
'digital_offer' => 'AudioGuide,VideoGuide',
'photography' => 'ZeroPhotography',
'pets_allowed' => '',
'is_accessible_for_free' => 'false',
'public_access' => 'true',
'available_languages' => 'German,English,French',
'distance_to_public_transport' => '200:MTR:CityBus',
'parking_facility_near_by' => '',
'accessibility_specification' => '{"accessibilityCertificationStatus":"AccessibilityChecked","certificationAccessibilityDeaf":"None","certificationAccessibilityMental":"None","certificationAccessibilityPartiallyDeaf":"None","certificationAccessibilityPartiallyVisual":"Info","certificationAccessibilityVisual":"None","certificationAccessibilityWalking":"Info","certificationAccessibilityWheelchair":"Info","accessibilitySearchCriteria":{"facilityAccessibilityWalking":["AllRoomsStepFreeAccess","HingedGrabRailToilet","LateralAccessibleToilet","StepFreeAccess","ToiletsPeopleWithDisabilities","NinetyCMWidthPassageWays","SpecialOffersWalkingImpairment","SpecialOffersWheelchairUsers"],"facilityAccessibilityVisual":["AssistanceDogsWelcome","VisuallyContrastingStepEdges","OffersInPictoralLanguage","SpecialOffersBlindPeople","SpecialOffersVisualImpairment","TactileOffers"],"facilityAccessibilityDeaf":["AudioInductionLoop","SpecialOffersHearingImpairment"],"facilityAccessibilityMental":["InformationWithPictogramsOrPictures"]},"shortDescriptionAccessibilityDeaf":"English description of shortDescriptionAccessibilityDeaf","shortDescriptionAccessibilityMental":"English description of shortDescriptionAccessibilityMental","shortDescriptionAccessibilityVisual":"English description of shortDescriptionAccessibilityVisual","shortDescriptionAccessibilityWalking":"English description of shortDescriptionAccessibilityWalking"}',
'url' => 'http://www.alte-synagoge.erfurt.de',
],
4 => [
'uid' => '5',
'pid' => '10',
'sys_language_uid' => '2',
'l18n_parent' => '3',
'l10n_source' => '3',
'l10n_state' => null,
'remote_id' => 'https://thuecat.org/resources/165868194223-zmqf',
'title' => 'La vieille synagogue',
'description' => 'La vieille synagogue (datant des années 1100) est la synagogue la plus vieille dEurope totalement conservée, dans laquelle est exposé un trésor datant des 13/14èmes siècles avec une alliance juive unique et des écritures hébraïques (datant des 12ème, 13ème et 14èmes siècles). Après la redécouverte du Mikwé, Erfurt abrite des témoins uniques et fascinants dune communauté juive médiévale.',
'managed_by' => '1',
'town' => '1',
'address' => '{"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}}',
'offers' => '[{"types":["GuidedTourOffer"],"title":"","description":"","prices":[{"title":"","description":"","price":8,"currency":"EUR","rule":"PerPerson"},{"title":"","description":"","price":5,"currency":"EUR","rule":"PerPerson"}]},{"types":["EntryOffer"],"title":"","description":"","prices":[{"title":"","description":"","price":5,"currency":"EUR","rule":"PerPerson"},{"title":"","description":"","price":17,"currency":"EUR","rule":"PerGroup"},{"title":"","description":"","price":14.9,"currency":"EUR","rule":"PerPackage"},{"title":"","description":"","price":8,"currency":"EUR","rule":"PerPerson"}]}]',
'media' => '[{"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","author":"","copyrightYear":2009,"license":{"type":"https:\\/\\/creativecommons.org\\/licenses\\/by\\/4.0\\/","author":"F:\\\\Bilddatenbank\\\\Museen und Ausstellungen\\\\Alte Synagoge"}}]',
'slogan' => 'Highlight',
'start_of_construction' => '',
'sanitation' => 'Toilets,DisabledToilets,NappyChangingArea,FamilyAndChildFriendly',
'other_service' => 'SeatingPossibilitiesRestArea,LockBoxes,SouvenirShop,BaggageStorage',
'museum_service' => 'MuseumShop',
'architectural_style' => 'GothicArt',
'traffic_infrastructure' => 'ZeroSpecialTrafficInfrastructure',
'payment_accepted' => 'CashPayment,EC',
'digital_offer' => 'AudioGuide,VideoGuide',
'photography' => 'ZeroPhotography',
'pets_allowed' => '',
'is_accessible_for_free' => 'false',
'public_access' => 'true',
'available_languages' => 'German,English,French',
'distance_to_public_transport' => '200:MTR:CityBus',
'parking_facility_near_by' => '',
'accessibility_specification' => '{"accessibilityCertificationStatus":"AccessibilityChecked","certificationAccessibilityDeaf":"None","certificationAccessibilityMental":"None","certificationAccessibilityPartiallyDeaf":"None","certificationAccessibilityPartiallyVisual":"Info","certificationAccessibilityVisual":"None","certificationAccessibilityWalking":"Info","certificationAccessibilityWheelchair":"Info","accessibilitySearchCriteria":{"facilityAccessibilityWalking":["AllRoomsStepFreeAccess","HingedGrabRailToilet","LateralAccessibleToilet","StepFreeAccess","ToiletsPeopleWithDisabilities","NinetyCMWidthPassageWays","SpecialOffersWalkingImpairment","SpecialOffersWheelchairUsers"],"facilityAccessibilityVisual":["AssistanceDogsWelcome","VisuallyContrastingStepEdges","OffersInPictoralLanguage","SpecialOffersBlindPeople","SpecialOffersVisualImpairment","TactileOffers"],"facilityAccessibilityDeaf":["AudioInductionLoop","SpecialOffersHearingImpairment"],"facilityAccessibilityMental":["InformationWithPictogramsOrPictures"]}}',
'url' => 'http://www.alte-synagoge.erfurt.de',
],
5 => [
'uid' => '6',
'pid' => '10',
'sys_language_uid' => '0',
'l18n_parent' => '0',
'l10n_source' => '0',
'l10n_state' => null,
'remote_id' => 'https://thuecat.org/resources/215230952334-yyno',
'title' => 'Krämerbrücke',
'description' => 'Ein bekanntes Wahrzeichen Erfurts ist die Krämerbrücke, die längste bebaute und bewohnte Brücke Europas.Die Krämerbrücke wurde zu Beginn aus Holz und 1325 aus Stein erbaut. Zunächst war die 120 m lange Brücke mit 62 schmalen Häusern bebaut. Später wurden einige der Häuser auf nun mehr 32 zusammengefasst. An beiden Enden der Brücke standen zwei Brückenkopfkirchen. Heute existiert nur noch eine der beiden, die östlich gelegene Ägidienkirche.Auf der Krämerbrücke kann man in Galerien und Boutiquen sehr schön bummeln gehen. Man kann Thüringer Handwerksmeistern bei ihrer Arbeit über die Schulter schauen. Keramik, Porzellan und Holzschnitzereien, Blaudruck und Lauschaer Glas sind beliebte Souvenirs. Cafès, Weinhändler und Feinkostgeschäfte mit Thüringer Spezialitäten laden zum Verweilen ein.',
'managed_by' => '1',
'town' => '1',
'address' => '{"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}}',
'offers' => '[]',
'media' => '[{"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","author":"Florian Trykowski","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","author":"Florian Trykowski","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","author":"Florian Trykowski","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"}}]',
'slogan' => '',
'start_of_construction' => '',
'sanitation' => 'ZeroSanitation',
'other_service' => 'Playground,SeatingPossibilitiesRestArea,SouvenirShop,PlayCornerOrPlayArea',
'museum_service' => '',
'architectural_style' => 'ZeroInformationArchitecturalStyle',
'traffic_infrastructure' => 'BicycleLockersEnumMem',
'payment_accepted' => '',
'digital_offer' => 'ZeroDigitalOffer',
'photography' => 'TakingPicturesPermitted',
'pets_allowed' => 'true',
'is_accessible_for_free' => 'true',
'public_access' => 'true',
'available_languages' => 'German,English,French',
'distance_to_public_transport' => '250:MTR',
'parking_facility_near_by' => '1,4',
'accessibility_specification' => '{}',
'url' => 'https://www.erfurt-tourismus.de/sehenswertes/kraemerbruecke',
],
6 => [
'uid' => '7',
'pid' => '10',
'sys_language_uid' => '1',
'l18n_parent' => '6',
'l10n_source' => '6',
'l10n_state' => null,
'remote_id' => 'https://thuecat.org/resources/215230952334-yyno',
'title' => 'Merchants\' Bridge',
'description' => 'Another Erfurt landmark is the Merchants\' Bridge (Krämerbrücke), the longest series of inhabited buildings on any bridge in Europe. The Merchants\' Bridge is Erfurts most interesting secular construction, initially in wood but rebuilt in stone in 1325. There were originally 62 narrow buildings along its 120-metre length, but subsequent redevelopment left just 32 buildings. Of what was once a pair of bridgehead churches, only the Church of St. Aegidius remains at the eastern end of the bridge today. The Merchants\' Bridge is lined with galleries, cafés and boutiques offering traditional crafts, Thuringian blue printed fabrics, hand-painted ceramics, handblown glassware, jewellery, wood carvings, antiques and delicious Thuringian specialities - perfect for browsing.',
'managed_by' => '1',
'town' => '1',
'address' => '{"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}}',
'offers' => '[]',
'media' => '[{"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","author":"Florian Trykowski","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","author":"Florian Trykowski","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","author":"Florian Trykowski","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"}}]',
'slogan' => '',
'start_of_construction' => '',
'sanitation' => 'ZeroSanitation',
'other_service' => 'Playground,SeatingPossibilitiesRestArea,SouvenirShop,PlayCornerOrPlayArea',
'museum_service' => '',
'architectural_style' => 'ZeroInformationArchitecturalStyle',
'traffic_infrastructure' => 'BicycleLockersEnumMem',
'payment_accepted' => '',
'digital_offer' => 'ZeroDigitalOffer',
'photography' => 'TakingPicturesPermitted',
'pets_allowed' => 'true',
'is_accessible_for_free' => 'true',
'public_access' => 'true',
'available_languages' => 'German,English,French',
'distance_to_public_transport' => '250:MTR',
'parking_facility_near_by' => '1,4',
'accessibility_specification' => '{}',
'url' => 'https://www.erfurt-tourismus.de/sehenswertes/kraemerbruecke',
],
7 => [
'uid' => '8',
'pid' => '10',
'sys_language_uid' => '2',
'l18n_parent' => '6',
'l10n_source' => '6',
'l10n_state' => null,
'remote_id' => 'https://thuecat.org/resources/215230952334-yyno',
'title' => 'Pont de l\'épicier',
'description' => 'Le pont de lépicier est un des symboles de la ville dErfurt, le plus grand pont habité en continu dEurope. A lorigine, le pont de lépicier faisait 120 m de long et comptait 62 maisons étroites, qui furent plus tard regroupées en 32 maisons. Sur le pont de lépicier se trouvent des galeries et des petites échoppes proposant des étoffes à motifs bleu indigo de Thuringe, des céramiques peintes main, du verre de Lauscha, des bijoux et des sculptures en bois.',
'managed_by' => '1',
'town' => '1',
'address' => '{"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}}',
'offers' => '[]',
'media' => '[{"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","author":"Florian Trykowski","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","author":"Florian Trykowski","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","author":"Florian Trykowski","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"}}]',
'slogan' => '',
'start_of_construction' => '',
'sanitation' => 'ZeroSanitation',
'other_service' => 'Playground,SeatingPossibilitiesRestArea,SouvenirShop,PlayCornerOrPlayArea',
'museum_service' => '',
'architectural_style' => 'ZeroInformationArchitecturalStyle',
'traffic_infrastructure' => 'BicycleLockersEnumMem',
'payment_accepted' => '',
'digital_offer' => 'ZeroDigitalOffer',
'photography' => 'TakingPicturesPermitted',
'pets_allowed' => 'true',
'is_accessible_for_free' => 'true',
'public_access' => 'true',
'available_languages' => 'German,English,French',
'distance_to_public_transport' => '250:MTR',
'parking_facility_near_by' => '1,4',
'accessibility_specification' => '{}',
'url' => 'https://www.erfurt-tourismus.de/sehenswertes/kraemerbruecke',
],
],
'tx_thuecat_parking_facility' => [
0 => [
'uid' => '1',
'pid' => '10',
'sys_language_uid' => '0',
'l18n_parent' => '0',
'l10n_source' => '0',
'l10n_state' => null,
'remote_id' => 'https://thuecat.org/resources/396420044896-drzt',
'title' => 'Parkhaus Domplatz',
'description' => 'Das Parkhaus Domplatz befindet sich unmittelbar unterhalb der Zitadelle Petersberg am nördlichen Rand des Domplatzes. Durch die zentrale Lage ist es ein idealer Ausgangspunkt für Stadtbummel und Erkundungen des Zentrums, des Petersbergs und des Andreasviertels.',
'managed_by' => '1',
'address' => '{"street":"Bechtheimer Str. 1","zip":"99084","city":"Erfurt","email":"info@stadtwerke-erfurt.de","phone":"+49 361 5640","fax":"","geo":{"latitude":50.977648905044,"longitude":11.022127985954299}}',
'offers' => '[{"types":["ParkingFee"],"title":"","description":"","prices":[{"title":"","description":"","price":35,"currency":"EUR","rule":"PerCar"}]},{"types":["ParkingFee"],"title":"","description":"","prices":[{"title":"","description":"","price":1.5,"currency":"EUR","rule":"PerCar"}]},{"types":["ParkingFee"],"title":"","description":"","prices":[{"title":"","description":"","price":10,"currency":"EUR","rule":"PerCar"}]},{"types":["ParkingFee"],"title":"","description":"","prices":[{"title":"","description":"","price":50,"currency":"EUR","rule":"PerCar"}]}]',
'media' => '[{"mainImage":true,"type":"image","title":"Erfurt-Parkhaus-Domplatz.jpg","description":"","url":"https:\\/\\/cms.thuecat.org\\/o\\/adaptive-media\\/image\\/6486108\\/Preview-1280x0\\/image","author":"Florian Trykowski","copyrightYear":2021,"license":{"type":"https:\\/\\/creativecommons.org\\/licenses\\/by\\/4.0\\/","author":""}}]',
'sanitation' => 'ZeroSanitation',
'other_service' => 'ZeroOtherServiceEnumMem',
'traffic_infrastructure' => 'ElectricVehicleCarChargingStationEnumMem',
'payment_accepted' => '',
'distance_to_public_transport' => '240:MTR:CityBus',
],
1 => [
'uid' => '2',
'pid' => '10',
'sys_language_uid' => '1',
'l18n_parent' => '1',
'l10n_source' => '1',
'l10n_state' => null,
'remote_id' => 'https://thuecat.org/resources/396420044896-drzt',
'title' => 'Car park Domplatz',
'description' => 'The Domplatz multi-storey car park is located directly below the Petersberg Citadel on the northern edge of the Domplatz. Its central location makes it an ideal starting point for strolling through the city and exploring the centre, the Petersberg and the Andreasviertel.',
'managed_by' => '1',
'address' => '{"street":"Bechtheimer Str. 1","zip":"99084","city":"Erfurt","email":"info@stadtwerke-erfurt.de","phone":"+49 361 5640","fax":"","geo":{"latitude":50.977648905044,"longitude":11.022127985954299}}',
'offers' => '[{"types":["ParkingFee"],"title":"","description":"","prices":[{"title":"","description":"","price":35,"currency":"EUR","rule":"PerCar"}]},{"types":["ParkingFee"],"title":"","description":"","prices":[{"title":"","description":"","price":1.5,"currency":"EUR","rule":"PerCar"}]},{"types":["ParkingFee"],"title":"","description":"","prices":[{"title":"","description":"","price":10,"currency":"EUR","rule":"PerCar"}]},{"types":["ParkingFee"],"title":"","description":"","prices":[{"title":"","description":"","price":50,"currency":"EUR","rule":"PerCar"}]}]',
'media' => '[{"mainImage":true,"type":"image","title":"Erfurt-Parkhaus-Domplatz.jpg","description":"","url":"https:\\/\\/cms.thuecat.org\\/o\\/adaptive-media\\/image\\/6486108\\/Preview-1280x0\\/image","author":"Florian Trykowski","copyrightYear":2021,"license":{"type":"https:\\/\\/creativecommons.org\\/licenses\\/by\\/4.0\\/","author":""}}]',
'sanitation' => 'ZeroSanitation',
'other_service' => 'ZeroOtherServiceEnumMem',
'traffic_infrastructure' => 'ElectricVehicleCarChargingStationEnumMem',
'payment_accepted' => '',
'distance_to_public_transport' => '240:MTR:CityBus',
],
2 => [
'uid' => '3',
'pid' => '10',
'sys_language_uid' => '2',
'l18n_parent' => '1',
'l10n_source' => '1',
'l10n_state' => null,
'remote_id' => 'https://thuecat.org/resources/396420044896-drzt',
'title' => 'Parking Domplatz',
'description' => 'Le parking à étages de la Domplatz est situé juste en dessous de la citadelle de Petersberg, sur le bord nord de la Domplatz. Son emplacement central en fait un point de départ idéal pour se promener dans la ville et explorer le centre, le Petersberg et l\'Andreasviertel.',
'managed_by' => '1',
'address' => '{"street":"Bechtheimer Str. 1","zip":"99084","city":"Erfurt","email":"info@stadtwerke-erfurt.de","phone":"+49 361 5640","fax":"","geo":{"latitude":50.977648905044,"longitude":11.022127985954299}}',
'offers' => '[{"types":["ParkingFee"],"title":"","description":"","prices":[{"title":"","description":"","price":35,"currency":"EUR","rule":"PerCar"}]},{"types":["ParkingFee"],"title":"","description":"","prices":[{"title":"","description":"","price":1.5,"currency":"EUR","rule":"PerCar"}]},{"types":["ParkingFee"],"title":"","description":"","prices":[{"title":"","description":"","price":10,"currency":"EUR","rule":"PerCar"}]},{"types":["ParkingFee"],"title":"","description":"","prices":[{"title":"","description":"","price":50,"currency":"EUR","rule":"PerCar"}]}]',
'media' => '[{"mainImage":true,"type":"image","title":"Erfurt-Parkhaus-Domplatz.jpg","description":"","url":"https:\\/\\/cms.thuecat.org\\/o\\/adaptive-media\\/image\\/6486108\\/Preview-1280x0\\/image","author":"Florian Trykowski","copyrightYear":2021,"license":{"type":"https:\\/\\/creativecommons.org\\/licenses\\/by\\/4.0\\/","author":""}}]',
'sanitation' => 'ZeroSanitation',
'other_service' => 'ZeroOtherServiceEnumMem',
'traffic_infrastructure' => 'ElectricVehicleCarChargingStationEnumMem',
'payment_accepted' => '',
'distance_to_public_transport' => '240:MTR:CityBus',
],
3 => [
'uid' => '4',
'pid' => '10',
'sys_language_uid' => '0',
'l18n_parent' => '0',
'l10n_source' => '0',
'l10n_state' => null,
'remote_id' => 'https://thuecat.org/resources/440055527204-ocar',
'title' => 'Q-Park Anger 1 Parkhaus',
'description' => 'Der Q-Park liegt direkt hinter dem Kaufhaus Anger 1 im Erfurter Stadtzentrum und ist über Juri-Gagarin-Ring/Meyfartstraße zu erreichen. Durch die direkte Anbindung an den Stadtring, ist das Parkhaus gut von außerhalb über Schnellstraßen und Autobahnen zu erreichen und befindet sich gleichzeitig im unmittelbaren modernen Zentrum Erfurts.',
'managed_by' => '1',
'address' => '{"street":"Anger 1","zip":"99084","city":"Erfurt","email":"servicecenter@q-park.de","phone":"+49 218 18190290","fax":"","geo":{"latitude":50.977999330565794,"longitude":11.037503264052475}}',
'offers' => '[{"types":["ParkingFee"],"title":"","description":"","prices":[{"title":"","description":"","price":2.2,"currency":"EUR","rule":"PerCar"}]},{"types":["ParkingFee"],"title":"","description":"","prices":[{"title":"","description":"","price":1,"currency":"EUR","rule":"PerCar"}]},{"types":["ParkingFee"],"title":"","description":"","prices":[{"title":"","description":"","price":13,"currency":"EUR","rule":"PerCar"}]}]',
'media' => '[{"mainImage":true,"type":"image","title":"Q-Park-Parkhaus-Anger1-Juri-Gagarin-Ring.JPG","description":"Stra\\u00dfenansicht des Parkhauses Q-Park am Kaufhaus Anger 1, schr\\u00e4g \\u00fcber den Juri-Gagarin-Ring","url":"https:\\/\\/cms.thuecat.org\\/o\\/adaptive-media\\/image\\/5197164\\/Preview-1280x0\\/image","author":"Florian Trykowski","copyrightYear":2020,"license":{"type":"https:\\/\\/creativecommons.org\\/licenses\\/by\\/4.0\\/","author":""}}]',
'sanitation' => 'Toilets',
'other_service' => 'ZeroOtherServiceEnumMem',
'traffic_infrastructure' => '',
'payment_accepted' => '',
'distance_to_public_transport' => '120:MTR',
],
4 => [
'uid' => '5',
'pid' => '10',
'sys_language_uid' => '1',
'l18n_parent' => '4',
'l10n_source' => '4',
'l10n_state' => null,
'remote_id' => 'https://thuecat.org/resources/440055527204-ocar',
'title' => 'Q-Park Anger 1 multi-storey car park',
'description' => 'The Q-Park is located directly behind the department store Anger 1 in Erfurt\'s city centre and can be reached via Juri-Gagarin-Ring/Meyfartstraße.',
'managed_by' => '1',
'address' => '{"street":"Anger 1","zip":"99084","city":"Erfurt","email":"servicecenter@q-park.de","phone":"+49 218 18190290","fax":"","geo":{"latitude":50.977999330565794,"longitude":11.037503264052475}}',
'offers' => '[{"types":["ParkingFee"],"title":"","description":"","prices":[{"title":"","description":"","price":2.2,"currency":"EUR","rule":"PerCar"}]},{"types":["ParkingFee"],"title":"","description":"","prices":[{"title":"","description":"","price":1,"currency":"EUR","rule":"PerCar"}]},{"types":["ParkingFee"],"title":"","description":"","prices":[{"title":"","description":"","price":13,"currency":"EUR","rule":"PerCar"}]}]',
'media' => '[{"mainImage":true,"type":"image","title":"Q-Park-Parkhaus-Anger1-Juri-Gagarin-Ring.JPG","description":"Stra\\u00dfenansicht des Parkhauses Q-Park am Kaufhaus Anger 1, schr\\u00e4g \\u00fcber den Juri-Gagarin-Ring","url":"https:\\/\\/cms.thuecat.org\\/o\\/adaptive-media\\/image\\/5197164\\/Preview-1280x0\\/image","author":"Florian Trykowski","copyrightYear":2020,"license":{"type":"https:\\/\\/creativecommons.org\\/licenses\\/by\\/4.0\\/","author":""}}]',
'sanitation' => 'Toilets',
'other_service' => 'ZeroOtherServiceEnumMem',
'traffic_infrastructure' => '',
'payment_accepted' => '',
'distance_to_public_transport' => '120:MTR',
],
5 => [
'uid' => '6',
'pid' => '10',
'sys_language_uid' => '2',
'l18n_parent' => '4',
'l10n_source' => '4',
'l10n_state' => null,
'remote_id' => 'https://thuecat.org/resources/440055527204-ocar',
'title' => 'Q-Park Anger 1 parking à étages',
'description' => 'Le Q-Park est situé directement derrière le grand magasin Anger 1 dans le centre-ville d\'Erfurt et peut être atteint par la Juri-Gagarin-Ring/Meyfartstraße.',
'managed_by' => '1',
'address' => '{"street":"Anger 1","zip":"99084","city":"Erfurt","email":"servicecenter@q-park.de","phone":"+49 218 18190290","fax":"","geo":{"latitude":50.977999330565794,"longitude":11.037503264052475}}',
'offers' => '[{"types":["ParkingFee"],"title":"","description":"","prices":[{"title":"","description":"","price":2.2,"currency":"EUR","rule":"PerCar"}]},{"types":["ParkingFee"],"title":"","description":"","prices":[{"title":"","description":"","price":1,"currency":"EUR","rule":"PerCar"}]},{"types":["ParkingFee"],"title":"","description":"","prices":[{"title":"","description":"","price":13,"currency":"EUR","rule":"PerCar"}]}]',
'media' => '[{"mainImage":true,"type":"image","title":"Q-Park-Parkhaus-Anger1-Juri-Gagarin-Ring.JPG","description":"Stra\\u00dfenansicht des Parkhauses Q-Park am Kaufhaus Anger 1, schr\\u00e4g \\u00fcber den Juri-Gagarin-Ring","url":"https:\\/\\/cms.thuecat.org\\/o\\/adaptive-media\\/image\\/5197164\\/Preview-1280x0\\/image","author":"Florian Trykowski","copyrightYear":2020,"license":{"type":"https:\\/\\/creativecommons.org\\/licenses\\/by\\/4.0\\/","author":""}}]',
'sanitation' => 'Toilets',
'other_service' => 'ZeroOtherServiceEnumMem',
'traffic_infrastructure' => '',
'payment_accepted' => '',
'distance_to_public_transport' => '120:MTR',
],
],
'tx_thuecat_organisation' => [
0 => [
'uid' => '1',
'pid' => '10',
'remote_id' => 'https://thuecat.org/resources/018132452787-ngbe',
'title' => 'Erfurt Tourismus und Marketing GmbH',
'description' => 'Die Erfurt Tourismus & Marketing GmbH (ETMG) wurde 1997 als offizielle Organisation zur Tourismusförderung in der Landeshauptstadt Erfurt gegründet und nahm am 01.01.1998 die Geschäftstätigkeit auf.
Zu den Aufgaben zählen die kommunale Tourismusförderung als wesentliches Instrument der Wirtschafts- und Stadtentwicklung der Landeshauptstadt Erfurt, die Positionierung der Stadt Erfurt auf dem nationalen und internationalen Tourismusmarkt als dynamische und sympathische Landeshauptstadt, attraktives Städtereiseziel und Tagungsstandort, die Vervollkommnung des touristischen Serviceangebotes entsprechend den Bedürfnissen der individuellen Gäste und der Tourismuswirtschaft und das Betreiben der Erfurt Tourist Information.
Im Januar 2009 wurde das Marketing für die Landeshauptstadt Erfurt an die ETMG übertragen und neu geordnet. Die Hauptaufgaben im Stadtmarketing liegen darin, die Wahrnehmung der Stadt in folgenden Bereichen zu verstärken: traditionsreicher und innovativer Wirtschaftsstandort, lebendiger und kreativer Wissenschaftsstandort, attraktiver Wohnstandort mit Flair und Sportstandort mit exzellenten Bedingungen für Nachwuchs- und Spitzensportler.
Gesellschafter: Stadt Erfurt
Geschäftsführerin: Frau Dr. Carmen Hildebrandt
Aufsichtsratsvorsitzender: Herr Dominik Kordon
Mitarbeiter: ca. 30 Angestellte, 4 Auszubildende',
],
],
];

View file

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
return [
'tx_thuecat_tourist_attraction' => [
0 => [
'uid' => '1',
'pid' => '10',
'sys_language_uid' => '0',
'l18n_parent' => '0',
'l10n_source' => '0',
'remote_id' => 'https://thuecat.org/resources/835224016581-dara',
'title' => 'Dom St. Marien',
'special_opening_hours' => '[{"opens":"10:00:00","closes":"14:00:00","from":{"date":"2050-12-31 00:00:00.000000","timezone_type":3,"timezone":"UTC"},"through":{"date":"2050-12-31 00:00:00.000000","timezone_type":3,"timezone":"UTC"},"daysOfWeek":["Saturday"]}]',
],
1 => [
'uid' => '2',
'pid' => '10',
'sys_language_uid' => '1',
'l18n_parent' => '1',
'l10n_source' => '1',
'remote_id' => 'https://thuecat.org/resources/835224016581-dara',
'title' => 'Cathedral of St. Mary',
'special_opening_hours' => '[{"opens":"10:00:00","closes":"14:00:00","from":{"date":"2050-12-31 00:00:00.000000","timezone_type":3,"timezone":"UTC"},"through":{"date":"2050-12-31 00:00:00.000000","timezone_type":3,"timezone":"UTC"},"daysOfWeek":["Saturday"]}]',
],
],
];

View file

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
return [
'tx_thuecat_tourist_information' => [
0 => [
'uid' => '1',
'pid' => '10',
'remote_id' => 'https://thuecat.org/resources/333039283321-xxwg',
'title' => 'Erfurt Tourist Information',
'description' => 'Direkt an der Krämerbrücke liegt die Erfurter Tourist Information. Nach einer Modernisierung im Frühjahr 2017 erstrahlt diese in neuem Glanz und ist auch technisch auf dem neuesten Stand. Hier erhalten Sie Stadtpläne, Broschüren zu Erfurt und originelle Souvenirs. Zudem bietet die Tourist Information vielfältige Stadtführungen und Rundfahrten mit Straßenbahn oder Bus sowie kompetente Beratung zu Hotels, Pensionen und Privatunterkünften.',
'managed_by' => '1',
'town' => '1',
],
],
'tx_thuecat_town' => [
0 => [
'uid' => '1',
'pid' => '10',
'remote_id' => 'https://thuecat.org/resources/043064193523-jcyt',
'title' => 'Erfurt',
'description' => 'Krämerbrücke, Dom, Alte Synagoge die Thüringer Landeshauptstadt Erfurt hat viele Kulturschätze. Und ein wunderbar junges, studentisches Flair.Eine gute Mischung für alle, die beim Schlendern und Bummeln gerne Städte entdecken: Denn in Erfurt findet man einen wunderbaren mittelalterlichen Stadtkern mit vielen netten Läden, Cafès und Restaurants. Urlauber wie Einheimische bummeln durch die Gassen der Altstadt, aus allen Ecken wispern Geschichte und alte Geschichten. Stolze historische Bügerhäuser bilden eine der schönsten Altstädte Europas, mittendrin das neugotische Rathaus aus den 1870er-Jahren am Fischmarkt, die spitztürmige St. Severikirche und der mächtige Dom, 1117 erstmals urkundlich erwähnt auf seiner schier endlosen, kaskadenförmigen Freitreppe chillen Jung und Alt gern in der Abendsonne. Ehe sie weiter ziehen zum Tagesausklang in eine der coolen Kneipen und Bars (Tipp: Oma Lilo oder Cafè Hilgenfeld), in die Wein-Destille am benachbarten Petersberg oder in eins der lässigen Restaurants (Tipp: Mathilda oder Ballenberger), wo zum freundlichen Miteinander eine frische und moderne Küche serviert wird.In Erfurt pulsiert das Leben, lassen Sie sich einfach treiben. Von Ihrer Neugierde ...',
'managed_by' => '1',
],
],
'tx_thuecat_organisation' => [
0 => [
'uid' => '1',
'pid' => '10',
'remote_id' => 'https://thuecat.org/resources/018132452787-ngbe',
'title' => 'Erfurt Tourismus und Marketing GmbH',
],
],
];

Some files were not shown because too many files have changed in this diff Show more